summaryrefslogtreecommitdiff
path: root/browser/base
diff options
context:
space:
mode:
Diffstat (limited to 'browser/base')
-rw-r--r--browser/base/Makefile.in43
-rw-r--r--browser/base/content/aboutDialog.css76
-rw-r--r--browser/base/content/aboutDialog.js608
-rw-r--r--browser/base/content/aboutDialog.xul135
-rw-r--r--browser/base/content/aboutRobots-icon.pngbin0 -> 9817 bytes
-rw-r--r--browser/base/content/aboutRobots-widget-left.pngbin0 -> 2224 bytes
-rw-r--r--browser/base/content/aboutRobots.xhtml108
-rw-r--r--browser/base/content/aboutSocialError.xhtml124
-rw-r--r--browser/base/content/abouthealthreport/abouthealth.css15
-rw-r--r--browser/base/content/abouthealthreport/abouthealth.js141
-rw-r--r--browser/base/content/abouthealthreport/abouthealth.xhtml32
-rw-r--r--browser/base/content/abouthome/aboutHome.css431
-rw-r--r--browser/base/content/abouthome/aboutHome.js565
-rw-r--r--browser/base/content/abouthome/aboutHome.xhtml72
-rw-r--r--browser/base/content/abouthome/addons.pngbin0 -> 1444 bytes
-rw-r--r--browser/base/content/abouthome/addons@2x.pngbin0 -> 3783 bytes
-rw-r--r--browser/base/content/abouthome/apps.pngbin0 -> 961 bytes
-rw-r--r--browser/base/content/abouthome/apps@2x.pngbin0 -> 2562 bytes
-rw-r--r--browser/base/content/abouthome/bookmarks.pngbin0 -> 1276 bytes
-rw-r--r--browser/base/content/abouthome/bookmarks@2x.pngbin0 -> 2946 bytes
-rw-r--r--browser/base/content/abouthome/downloads.pngbin0 -> 898 bytes
-rw-r--r--browser/base/content/abouthome/downloads@2x.pngbin0 -> 2018 bytes
-rw-r--r--browser/base/content/abouthome/history.pngbin0 -> 1654 bytes
-rw-r--r--browser/base/content/abouthome/history@2x.pngbin0 -> 4629 bytes
-rw-r--r--browser/base/content/abouthome/mozilla.pngbin0 -> 2684 bytes
-rw-r--r--browser/base/content/abouthome/mozilla@2x.pngbin0 -> 5647 bytes
-rw-r--r--browser/base/content/abouthome/noise.pngbin0 -> 4025 bytes
-rw-r--r--browser/base/content/abouthome/restore-large.pngbin0 -> 2841 bytes
-rw-r--r--browser/base/content/abouthome/restore-large@2x.pngbin0 -> 7267 bytes
-rw-r--r--browser/base/content/abouthome/restore.pngbin0 -> 1796 bytes
-rw-r--r--browser/base/content/abouthome/restore@2x.pngbin0 -> 4810 bytes
-rw-r--r--browser/base/content/abouthome/settings.pngbin0 -> 1557 bytes
-rw-r--r--browser/base/content/abouthome/settings@2x.pngbin0 -> 3836 bytes
-rw-r--r--browser/base/content/abouthome/snippet1.pngbin0 -> 1470 bytes
-rw-r--r--browser/base/content/abouthome/snippet1@2x.pngbin0 -> 3243 bytes
-rw-r--r--browser/base/content/abouthome/snippet2.pngbin0 -> 3287 bytes
-rw-r--r--browser/base/content/abouthome/snippet2@2x.pngbin0 -> 11027 bytes
-rw-r--r--browser/base/content/abouthome/sync.pngbin0 -> 1879 bytes
-rw-r--r--browser/base/content/abouthome/sync@2x.pngbin0 -> 4615 bytes
-rw-r--r--browser/base/content/baseMenuOverlay.xul107
-rw-r--r--browser/base/content/blockedSite.xhtml193
-rw-r--r--browser/base/content/browser-addons.js417
-rw-r--r--browser/base/content/browser-appmenu.inc400
-rw-r--r--browser/base/content/browser-charsetmenu.inc145
-rw-r--r--browser/base/content/browser-context.inc403
-rw-r--r--browser/base/content/browser-data-submission-info-bar.js136
-rw-r--r--browser/base/content/browser-doctype.inc25
-rw-r--r--browser/base/content/browser-feeds.js224
-rw-r--r--browser/base/content/browser-fullScreen.js605
-rw-r--r--browser/base/content/browser-fullZoom.js557
-rw-r--r--browser/base/content/browser-gestureSupport.js1059
-rw-r--r--browser/base/content/browser-menubar.inc623
-rw-r--r--browser/base/content/browser-places.js1286
-rw-r--r--browser/base/content/browser-plugins.js1053
-rw-r--r--browser/base/content/browser-safebrowsing.js53
-rw-r--r--browser/base/content/browser-sets.inc420
-rw-r--r--browser/base/content/browser-social.js1406
-rw-r--r--browser/base/content/browser-syncui.js467
-rw-r--r--browser/base/content/browser-tabPreviews.js1051
-rw-r--r--browser/base/content/browser-tabPreviews.xml75
-rw-r--r--browser/base/content/browser-thumbnails.js198
-rw-r--r--browser/base/content/browser-title.css181
-rw-r--r--browser/base/content/browser-webrtcUI.js55
-rw-r--r--browser/base/content/browser.css723
-rw-r--r--browser/base/content/browser.js7265
-rw-r--r--browser/base/content/browser.xul1116
-rw-r--r--browser/base/content/browserMountPoints.inc12
-rw-r--r--browser/base/content/chatWindow.xul109
-rw-r--r--browser/base/content/content.js45
-rw-r--r--browser/base/content/downloadManagerOverlay.xul32
-rw-r--r--browser/base/content/global-scripts.inc13
-rw-r--r--browser/base/content/hiddenWindow.xul19
-rw-r--r--browser/base/content/highlighter.css105
-rw-r--r--browser/base/content/imagedocument.pngbin0 -> 2185 bytes
-rw-r--r--browser/base/content/jsConsoleOverlay.xul18
-rw-r--r--browser/base/content/macBrowserOverlay.xul64
-rw-r--r--browser/base/content/newtab/cells.js126
-rw-r--r--browser/base/content/newtab/drag.js151
-rw-r--r--browser/base/content/newtab/dragDataHelper.js22
-rw-r--r--browser/base/content/newtab/drop.js150
-rw-r--r--browser/base/content/newtab/dropPreview.js222
-rw-r--r--browser/base/content/newtab/dropTargetShim.js188
-rw-r--r--browser/base/content/newtab/grid.js171
-rw-r--r--browser/base/content/newtab/newTab.css209
-rw-r--r--browser/base/content/newtab/newTab.js57
-rw-r--r--browser/base/content/newtab/newTab.xul54
-rw-r--r--browser/base/content/newtab/page.js135
-rw-r--r--browser/base/content/newtab/sites.js192
-rw-r--r--browser/base/content/newtab/transformations.js265
-rw-r--r--browser/base/content/newtab/undo.js116
-rw-r--r--browser/base/content/newtab/updater.js186
-rw-r--r--browser/base/content/nsContextMenu.js1623
-rw-r--r--browser/base/content/openLocation.js133
-rw-r--r--browser/base/content/openLocation.xul57
-rw-r--r--browser/base/content/overrides/app-license.html6
-rw-r--r--browser/base/content/padlock.css193
-rw-r--r--browser/base/content/padlock.js193
-rw-r--r--browser/base/content/padlock.xul63
-rw-r--r--browser/base/content/padlock_classic_broken.pngbin0 -> 726 bytes
-rw-r--r--browser/base/content/padlock_classic_ev.pngbin0 -> 566 bytes
-rw-r--r--browser/base/content/padlock_classic_https.pngbin0 -> 589 bytes
-rw-r--r--browser/base/content/padlock_mod_broken.pngbin0 -> 728 bytes
-rw-r--r--browser/base/content/padlock_mod_ev.pngbin0 -> 290 bytes
-rw-r--r--browser/base/content/padlock_mod_https.pngbin0 -> 338 bytes
-rw-r--r--browser/base/content/padlock_mod_low.pngbin0 -> 342 bytes
-rw-r--r--browser/base/content/pageinfo/feeds.js59
-rw-r--r--browser/base/content/pageinfo/feeds.xml40
-rw-r--r--browser/base/content/pageinfo/pageInfo.css26
-rw-r--r--browser/base/content/pageinfo/pageInfo.js1276
-rw-r--r--browser/base/content/pageinfo/pageInfo.xml29
-rw-r--r--browser/base/content/pageinfo/pageInfo.xul558
-rw-r--r--browser/base/content/pageinfo/permissions.js357
-rw-r--r--browser/base/content/pageinfo/security.js345
-rw-r--r--browser/base/content/popup-notifications.inc108
-rw-r--r--browser/base/content/report-phishing-overlay.xul35
-rw-r--r--browser/base/content/safeMode.css8
-rw-r--r--browser/base/content/safeMode.js152
-rw-r--r--browser/base/content/safeMode.xul87
-rw-r--r--browser/base/content/sanitize.js518
-rw-r--r--browser/base/content/sanitize.xul183
-rw-r--r--browser/base/content/sanitizeDialog.css23
-rw-r--r--browser/base/content/sanitizeDialog.js910
-rw-r--r--browser/base/content/socialchat.xml747
-rw-r--r--browser/base/content/softwareUpdateOverlay.xul18
-rw-r--r--browser/base/content/sync/aboutSyncTabs-bindings.xml46
-rw-r--r--browser/base/content/sync/aboutSyncTabs.css11
-rw-r--r--browser/base/content/sync/aboutSyncTabs.js251
-rw-r--r--browser/base/content/sync/aboutSyncTabs.xul68
-rw-r--r--browser/base/content/sync/addDevice.js157
-rw-r--r--browser/base/content/sync/addDevice.xul129
-rw-r--r--browser/base/content/sync/genericChange.js234
-rw-r--r--browser/base/content/sync/genericChange.xul123
-rw-r--r--browser/base/content/sync/key.xhtml54
-rw-r--r--browser/base/content/sync/notification.xml129
-rw-r--r--browser/base/content/sync/progress.js71
-rw-r--r--browser/base/content/sync/progress.xhtml55
-rw-r--r--browser/base/content/sync/quota.js268
-rw-r--r--browser/base/content/sync/quota.xul65
-rw-r--r--browser/base/content/sync/setup.js1088
-rw-r--r--browser/base/content/sync/setup.xul504
-rw-r--r--browser/base/content/sync/utils.js218
-rw-r--r--browser/base/content/tabbrowser.css65
-rw-r--r--browser/base/content/tabbrowser.xml4801
-rw-r--r--browser/base/content/test/Makefile.in372
-rw-r--r--browser/base/content/test/POSTSearchEngine.xml6
-rw-r--r--browser/base/content/test/alltabslistener.html8
-rw-r--r--browser/base/content/test/app_bug575561.html19
-rw-r--r--browser/base/content/test/app_subframe_bug575561.html12
-rw-r--r--browser/base/content/test/audio.oggbin0 -> 47411 bytes
-rw-r--r--browser/base/content/test/authenticate.sjs220
-rw-r--r--browser/base/content/test/blockNoPlugins.xml7
-rw-r--r--browser/base/content/test/blockPluginHard.xml11
-rw-r--r--browser/base/content/test/blockPluginVulnerableNoUpdate.xml11
-rw-r--r--browser/base/content/test/blockPluginVulnerableUpdatable.xml11
-rw-r--r--browser/base/content/test/browser_CTP_context_menu.js103
-rw-r--r--browser/base/content/test/browser_CTP_data_urls.js256
-rw-r--r--browser/base/content/test/browser_CTP_drag_drop.js104
-rw-r--r--browser/base/content/test/browser_URLBarSetURI.js82
-rw-r--r--browser/base/content/test/browser_aboutHealthReport.js105
-rw-r--r--browser/base/content/test/browser_aboutHome.js520
-rw-r--r--browser/base/content/test/browser_aboutSyncProgress.js102
-rw-r--r--browser/base/content/test/browser_addon_bar.js63
-rw-r--r--browser/base/content/test/browser_addon_bar_aomlistener.js67
-rw-r--r--browser/base/content/test/browser_addon_bar_close_button.js19
-rw-r--r--browser/base/content/test/browser_addon_bar_shortcut.js18
-rw-r--r--browser/base/content/test/browser_allTabsPanel.js162
-rw-r--r--browser/base/content/test/browser_alltabslistener.js204
-rw-r--r--browser/base/content/test/browser_blob-channelname.js11
-rw-r--r--browser/base/content/test/browser_bookmark_titles.js86
-rw-r--r--browser/base/content/test/browser_bug304198.js125
-rw-r--r--browser/base/content/test/browser_bug321000.js80
-rw-r--r--browser/base/content/test/browser_bug329212.js43
-rw-r--r--browser/base/content/test/browser_bug356571.js91
-rw-r--r--browser/base/content/test/browser_bug380960.js91
-rw-r--r--browser/base/content/test/browser_bug386835.js89
-rw-r--r--browser/base/content/test/browser_bug405137.js5
-rw-r--r--browser/base/content/test/browser_bug406216.js54
-rw-r--r--browser/base/content/test/browser_bug409481.js83
-rw-r--r--browser/base/content/test/browser_bug409624.js73
-rw-r--r--browser/base/content/test/browser_bug413915.js59
-rw-r--r--browser/base/content/test/browser_bug416661.js43
-rw-r--r--browser/base/content/test/browser_bug417483.js26
-rw-r--r--browser/base/content/test/browser_bug419612.js32
-rw-r--r--browser/base/content/test/browser_bug422590.js50
-rw-r--r--browser/base/content/test/browser_bug423833.js138
-rw-r--r--browser/base/content/test/browser_bug424101.js53
-rw-r--r--browser/base/content/test/browser_bug427559.js40
-rw-r--r--browser/base/content/test/browser_bug432599.js127
-rw-r--r--browser/base/content/test/browser_bug435035.js16
-rw-r--r--browser/base/content/test/browser_bug435325.js72
-rw-r--r--browser/base/content/test/browser_bug441778.js46
-rw-r--r--browser/base/content/test/browser_bug455852.js16
-rw-r--r--browser/base/content/test/browser_bug460146.js51
-rw-r--r--browser/base/content/test/browser_bug462289.js85
-rw-r--r--browser/base/content/test/browser_bug462673.js53
-rw-r--r--browser/base/content/test/browser_bug477014.js55
-rw-r--r--browser/base/content/test/browser_bug479408.js17
-rw-r--r--browser/base/content/test/browser_bug479408_sample.html4
-rw-r--r--browser/base/content/test/browser_bug481560.js27
-rw-r--r--browser/base/content/test/browser_bug484315.js23
-rw-r--r--browser/base/content/test/browser_bug491431.js34
-rw-r--r--browser/base/content/test/browser_bug495058.js43
-rw-r--r--browser/base/content/test/browser_bug517902.js41
-rw-r--r--browser/base/content/test/browser_bug519216.js50
-rw-r--r--browser/base/content/test/browser_bug520538.js15
-rw-r--r--browser/base/content/test/browser_bug521216.js47
-rw-r--r--browser/base/content/test/browser_bug533232.js36
-rw-r--r--browser/base/content/test/browser_bug537474.js8
-rw-r--r--browser/base/content/test/browser_bug550565.js21
-rw-r--r--browser/base/content/test/browser_bug553455.js903
-rw-r--r--browser/base/content/test/browser_bug555224.js40
-rw-r--r--browser/base/content/test/browser_bug555767.js68
-rw-r--r--browser/base/content/test/browser_bug556061.js96
-rw-r--r--browser/base/content/test/browser_bug559991.js42
-rw-r--r--browser/base/content/test/browser_bug561623.js29
-rw-r--r--browser/base/content/test/browser_bug561636.js474
-rw-r--r--browser/base/content/test/browser_bug562649.js27
-rw-r--r--browser/base/content/test/browser_bug563588.js30
-rw-r--r--browser/base/content/test/browser_bug565575.js13
-rw-r--r--browser/base/content/test/browser_bug565667.js59
-rw-r--r--browser/base/content/test/browser_bug567306.js44
-rw-r--r--browser/base/content/test/browser_bug575561.js90
-rw-r--r--browser/base/content/test/browser_bug575830.js33
-rw-r--r--browser/base/content/test/browser_bug577121.js24
-rw-r--r--browser/base/content/test/browser_bug578534.js29
-rw-r--r--browser/base/content/test/browser_bug579872.js29
-rw-r--r--browser/base/content/test/browser_bug580638.js58
-rw-r--r--browser/base/content/test/browser_bug580956.js29
-rw-r--r--browser/base/content/test/browser_bug581242.js21
-rw-r--r--browser/base/content/test/browser_bug581253.js102
-rw-r--r--browser/base/content/test/browser_bug581947.js90
-rw-r--r--browser/base/content/test/browser_bug585558.js152
-rw-r--r--browser/base/content/test/browser_bug585785.js35
-rw-r--r--browser/base/content/test/browser_bug585830.js25
-rw-r--r--browser/base/content/test/browser_bug590206.js136
-rw-r--r--browser/base/content/test/browser_bug592338.js136
-rw-r--r--browser/base/content/test/browser_bug594131.js23
-rw-r--r--browser/base/content/test/browser_bug595507.js39
-rw-r--r--browser/base/content/test/browser_bug596687.js26
-rw-r--r--browser/base/content/test/browser_bug597218.js38
-rw-r--r--browser/base/content/test/browser_bug598923.js33
-rw-r--r--browser/base/content/test/browser_bug599325.js21
-rw-r--r--browser/base/content/test/browser_bug609700.js20
-rw-r--r--browser/base/content/test/browser_bug616836.js4
-rw-r--r--browser/base/content/test/browser_bug623155.js136
-rw-r--r--browser/base/content/test/browser_bug623893.js46
-rw-r--r--browser/base/content/test/browser_bug624734.js23
-rw-r--r--browser/base/content/test/browser_bug647886.js36
-rw-r--r--browser/base/content/test/browser_bug655584.js23
-rw-r--r--browser/base/content/test/browser_bug664672.js19
-rw-r--r--browser/base/content/test/browser_bug676619.js121
-rw-r--r--browser/base/content/test/browser_bug678392-1.html12
-rw-r--r--browser/base/content/test/browser_bug678392-2.html12
-rw-r--r--browser/base/content/test/browser_bug678392.js192
-rw-r--r--browser/base/content/test/browser_bug710878.js29
-rw-r--r--browser/base/content/test/browser_bug719271.js89
-rw-r--r--browser/base/content/test/browser_bug724239.js33
-rw-r--r--browser/base/content/test/browser_bug734076.js107
-rw-r--r--browser/base/content/test/browser_bug735471.js59
-rw-r--r--browser/base/content/test/browser_bug743421.js118
-rw-r--r--browser/base/content/test/browser_bug744745.js46
-rw-r--r--browser/base/content/test/browser_bug749738.js36
-rw-r--r--browser/base/content/test/browser_bug752516.js48
-rw-r--r--browser/base/content/test/browser_bug763468_perwindowpb.js64
-rw-r--r--browser/base/content/test/browser_bug767836_perwindowpb.js83
-rw-r--r--browser/base/content/test/browser_bug771331.js82
-rw-r--r--browser/base/content/test/browser_bug783614.js13
-rw-r--r--browser/base/content/test/browser_bug787619.js52
-rw-r--r--browser/base/content/test/browser_bug797677.js47
-rw-r--r--browser/base/content/test/browser_bug812562.js96
-rw-r--r--browser/base/content/test/browser_bug816527.js122
-rw-r--r--browser/base/content/test/browser_bug817947.js56
-rw-r--r--browser/base/content/test/browser_bug818118.js48
-rw-r--r--browser/base/content/test/browser_bug820497.js61
-rw-r--r--browser/base/content/test/browser_bug822367.js196
-rw-r--r--browser/base/content/test/browser_bug832435.js23
-rw-r--r--browser/base/content/test/browser_bug839103.js159
-rw-r--r--browser/base/content/test/browser_bug880101.js50
-rw-r--r--browser/base/content/test/browser_bug902156.js213
-rw-r--r--browser/base/content/test/browser_canonizeURL.js54
-rw-r--r--browser/base/content/test/browser_clearplugindata.html32
-rw-r--r--browser/base/content/test/browser_clearplugindata.js140
-rw-r--r--browser/base/content/test/browser_clearplugindata_noage.html32
-rw-r--r--browser/base/content/test/browser_contentAreaClick.js307
-rw-r--r--browser/base/content/test/browser_contextSearchTabPosition.js68
-rw-r--r--browser/base/content/test/browser_ctrlTab.js151
-rw-r--r--browser/base/content/test/browser_customize.js24
-rw-r--r--browser/base/content/test/browser_customize_popupNotification.js27
-rw-r--r--browser/base/content/test/browser_datareporting_notification.js198
-rw-r--r--browser/base/content/test/browser_disablechrome.js216
-rw-r--r--browser/base/content/test/browser_discovery.js159
-rw-r--r--browser/base/content/test/browser_drag.js45
-rw-r--r--browser/base/content/test/browser_duplicateIDs.js8
-rw-r--r--browser/base/content/test/browser_findbarClose.js40
-rw-r--r--browser/base/content/test/browser_fullscreen-window-open.js394
-rw-r--r--browser/base/content/test/browser_gestureSupport.js671
-rw-r--r--browser/base/content/test/browser_getshortcutoruri.js146
-rw-r--r--browser/base/content/test/browser_hide_removing.js35
-rw-r--r--browser/base/content/test/browser_homeDrop.js91
-rw-r--r--browser/base/content/test/browser_identity_UI.js119
-rw-r--r--browser/base/content/test/browser_keywordBookmarklets.js38
-rw-r--r--browser/base/content/test/browser_keywordSearch.js86
-rw-r--r--browser/base/content/test/browser_keywordSearch_postData.js94
-rw-r--r--browser/base/content/test/browser_lastAccessedTab.js26
-rw-r--r--browser/base/content/test/browser_locationBarCommand.js213
-rw-r--r--browser/base/content/test/browser_locationBarExternalLoad.js65
-rw-r--r--browser/base/content/test/browser_middleMouse_inherit.js55
-rw-r--r--browser/base/content/test/browser_minimize.js37
-rw-r--r--browser/base/content/test/browser_offlineQuotaNotification.js74
-rw-r--r--browser/base/content/test/browser_overflowScroll.js87
-rw-r--r--browser/base/content/test/browser_pageInfo.js38
-rw-r--r--browser/base/content/test/browser_pageInfo_plugins.js187
-rw-r--r--browser/base/content/test/browser_page_style_menu.js67
-rw-r--r--browser/base/content/test/browser_pinnedTabs.js73
-rw-r--r--browser/base/content/test/browser_plainTextLinks.js115
-rw-r--r--browser/base/content/test/browser_pluginCrashCommentAndURL.js154
-rw-r--r--browser/base/content/test/browser_pluginnotification.js841
-rw-r--r--browser/base/content/test/browser_pluginplaypreview.js317
-rw-r--r--browser/base/content/test/browser_pluginplaypreview2.js179
-rw-r--r--browser/base/content/test/browser_plugins_added_dynamically.js179
-rw-r--r--browser/base/content/test/browser_popupNotification.js991
-rw-r--r--browser/base/content/test/browser_popupUI.js57
-rw-r--r--browser/base/content/test/browser_private_browsing_window.js65
-rw-r--r--browser/base/content/test/browser_private_no_prompt.js13
-rw-r--r--browser/base/content/test/browser_relatedTabs.js49
-rw-r--r--browser/base/content/test/browser_removeTabsToTheEnd.js24
-rw-r--r--browser/base/content/test/browser_sanitize-download-history.js142
-rw-r--r--browser/base/content/test/browser_sanitize-passwordDisabledHosts.js41
-rw-r--r--browser/base/content/test/browser_sanitize-sitepermissions.js37
-rw-r--r--browser/base/content/test/browser_sanitize-timespans.js810
-rw-r--r--browser/base/content/test/browser_sanitizeDialog.js1099
-rw-r--r--browser/base/content/test/browser_sanitizeDialog_treeView.js632
-rw-r--r--browser/base/content/test/browser_save_link-perwindowpb.js167
-rw-r--r--browser/base/content/test/browser_save_private_link_perwindowpb.js131
-rw-r--r--browser/base/content/test/browser_save_video.js91
-rw-r--r--browser/base/content/test/browser_scope.js4
-rw-r--r--browser/base/content/test/browser_selectTabAtIndex.js19
-rw-r--r--browser/base/content/test/browser_tabDrop.js71
-rw-r--r--browser/base/content/test/browser_tabMatchesInAwesomebar_perwindowpb.js249
-rw-r--r--browser/base/content/test/browser_tab_drag_drop_perwindow.js50
-rw-r--r--browser/base/content/test/browser_tab_dragdrop.js118
-rw-r--r--browser/base/content/test/browser_tab_dragdrop2.js52
-rw-r--r--browser/base/content/test/browser_tab_dragdrop2_frame1.xul167
-rw-r--r--browser/base/content/test/browser_tabfocus.js278
-rw-r--r--browser/base/content/test/browser_tabopen_reflows.js126
-rw-r--r--browser/base/content/test/browser_tabs_isActive.js29
-rw-r--r--browser/base/content/test/browser_tabs_owner.js32
-rw-r--r--browser/base/content/test/browser_typeAheadFind.js28
-rw-r--r--browser/base/content/test/browser_unloaddialogs.js134
-rw-r--r--browser/base/content/test/browser_urlHighlight.js112
-rw-r--r--browser/base/content/test/browser_urlbarAutoFillTrimURLs.js85
-rw-r--r--browser/base/content/test/browser_urlbarCopying.js204
-rw-r--r--browser/base/content/test/browser_urlbarEnter.js69
-rw-r--r--browser/base/content/test/browser_urlbarRevert.js29
-rw-r--r--browser/base/content/test/browser_urlbarStop.js40
-rw-r--r--browser/base/content/test/browser_urlbarTrimURLs.js89
-rw-r--r--browser/base/content/test/browser_urlbar_search_healthreport.js65
-rw-r--r--browser/base/content/test/browser_utilityOverlay.js62
-rw-r--r--browser/base/content/test/browser_visibleFindSelection.js39
-rw-r--r--browser/base/content/test/browser_visibleTabs.js93
-rw-r--r--browser/base/content/test/browser_visibleTabs_bookmarkAllPages.js34
-rw-r--r--browser/base/content/test/browser_visibleTabs_bookmarkAllTabs.js66
-rw-r--r--browser/base/content/test/browser_visibleTabs_contextMenu.js54
-rw-r--r--browser/base/content/test/browser_visibleTabs_tabPreview.js41
-rw-r--r--browser/base/content/test/browser_wyciwyg_urlbarCopying.js39
-rw-r--r--browser/base/content/test/browser_zbug569342.js78
-rw-r--r--browser/base/content/test/bug364677-data.xml5
-rw-r--r--browser/base/content/test/bug364677-data.xml^headers^1
-rw-r--r--browser/base/content/test/bug395533-data.txt6
-rw-r--r--browser/base/content/test/bug564387.html11
-rw-r--r--browser/base/content/test/bug564387_video1.ogvbin0 -> 28942 bytes
-rw-r--r--browser/base/content/test/bug564387_video1.ogv^headers^3
-rw-r--r--browser/base/content/test/bug592338.html24
-rw-r--r--browser/base/content/test/bug792517-2.html5
-rw-r--r--browser/base/content/test/bug792517.html5
-rw-r--r--browser/base/content/test/bug792517.sjs13
-rw-r--r--browser/base/content/test/bug839103.css1
-rw-r--r--browser/base/content/test/ctxmenu-image.pngbin0 -> 5401 bytes
-rw-r--r--browser/base/content/test/disablechrome.html4
-rw-r--r--browser/base/content/test/discovery.html8
-rw-r--r--browser/base/content/test/domplate_test.js51
-rw-r--r--browser/base/content/test/download_page.html47
-rw-r--r--browser/base/content/test/dummy_page.html8
-rw-r--r--browser/base/content/test/feed_discovery.html113
-rw-r--r--browser/base/content/test/feed_tab.html17
-rw-r--r--browser/base/content/test/file_bug550565_favicon.icobin0 -> 1406 bytes
-rw-r--r--browser/base/content/test/file_bug550565_popup.html12
-rw-r--r--browser/base/content/test/file_bug822367_1.html18
-rw-r--r--browser/base/content/test/file_bug822367_1.js1
-rw-r--r--browser/base/content/test/file_bug822367_2.html16
-rw-r--r--browser/base/content/test/file_bug822367_3.html27
-rw-r--r--browser/base/content/test/file_bug822367_4.html18
-rw-r--r--browser/base/content/test/file_bug822367_4.js1
-rw-r--r--browser/base/content/test/file_bug822367_4B.html18
-rw-r--r--browser/base/content/test/file_bug822367_5.html24
-rw-r--r--browser/base/content/test/file_bug822367_6.html16
-rw-r--r--browser/base/content/test/file_bug902156.js5
-rw-r--r--browser/base/content/test/file_bug902156_1.html15
-rw-r--r--browser/base/content/test/file_bug902156_2.html17
-rw-r--r--browser/base/content/test/file_bug902156_3.html15
-rw-r--r--browser/base/content/test/file_fullscreen-window-open.html24
-rw-r--r--browser/base/content/test/gZipOfflineChild.cacheManifest2
-rw-r--r--browser/base/content/test/gZipOfflineChild.cacheManifest^headers^1
-rw-r--r--browser/base/content/test/gZipOfflineChild.htmlbin0 -> 298 bytes
-rw-r--r--browser/base/content/test/gZipOfflineChild.html^headers^2
-rw-r--r--browser/base/content/test/gZipOfflineChild_uncompressed.html21
-rw-r--r--browser/base/content/test/head.js400
-rw-r--r--browser/base/content/test/head_plain.js15
-rw-r--r--browser/base/content/test/healthreport_testRemoteCommands.html128
-rw-r--r--browser/base/content/test/moz.build8
-rw-r--r--browser/base/content/test/moz.pngbin0 -> 580 bytes
-rw-r--r--browser/base/content/test/newtab/Makefile.in38
-rw-r--r--browser/base/content/test/newtab/browser_newtab_block.js61
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug721442.js23
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug722273.js68
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug723102.js19
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug723121.js30
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug725996.js23
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug734043.js27
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug735987.js26
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug752841.js53
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug765628.js27
-rw-r--r--browser/base/content/test/newtab/browser_newtab_bug876313.js24
-rw-r--r--browser/base/content/test/newtab/browser_newtab_disable.js34
-rw-r--r--browser/base/content/test/newtab/browser_newtab_drag_drop.js74
-rw-r--r--browser/base/content/test/newtab/browser_newtab_drag_drop_ext.js55
-rw-r--r--browser/base/content/test/newtab/browser_newtab_drop_preview.js22
-rw-r--r--browser/base/content/test/newtab/browser_newtab_focus.js57
-rw-r--r--browser/base/content/test/newtab/browser_newtab_perwindow_private_browsing.js68
-rw-r--r--browser/base/content/test/newtab/browser_newtab_reset.js28
-rw-r--r--browser/base/content/test/newtab/browser_newtab_tabsync.js61
-rw-r--r--browser/base/content/test/newtab/browser_newtab_undo.js49
-rw-r--r--browser/base/content/test/newtab/browser_newtab_unpin.js56
-rw-r--r--browser/base/content/test/newtab/head.js390
-rw-r--r--browser/base/content/test/newtab/moz.build6
-rw-r--r--browser/base/content/test/offlineChild.cacheManifest2
-rw-r--r--browser/base/content/test/offlineChild.cacheManifest^headers^1
-rw-r--r--browser/base/content/test/offlineChild.html20
-rw-r--r--browser/base/content/test/offlineChild2.cacheManifest2
-rw-r--r--browser/base/content/test/offlineChild2.cacheManifest^headers^1
-rw-r--r--browser/base/content/test/offlineChild2.html20
-rw-r--r--browser/base/content/test/offlineEvent.cacheManifest2
-rw-r--r--browser/base/content/test/offlineEvent.cacheManifest^headers^1
-rw-r--r--browser/base/content/test/offlineEvent.html9
-rw-r--r--browser/base/content/test/offlineQuotaNotification.cacheManifest7
-rw-r--r--browser/base/content/test/offlineQuotaNotification.html9
-rw-r--r--browser/base/content/test/page_style_sample.html40
-rw-r--r--browser/base/content/test/pluginCrashCommentAndURL.html27
-rw-r--r--browser/base/content/test/plugin_add_dynamically.html17
-rw-r--r--browser/base/content/test/plugin_alternate_content.html9
-rw-r--r--browser/base/content/test/plugin_both.html10
-rw-r--r--browser/base/content/test/plugin_both2.html10
-rw-r--r--browser/base/content/test/plugin_bug744745.html12
-rw-r--r--browser/base/content/test/plugin_bug749455.html8
-rw-r--r--browser/base/content/test/plugin_bug752516.html24
-rw-r--r--browser/base/content/test/plugin_bug787619.html9
-rw-r--r--browser/base/content/test/plugin_bug797677.html5
-rw-r--r--browser/base/content/test/plugin_bug820497.html17
-rw-r--r--browser/base/content/test/plugin_clickToPlayAllow.html9
-rw-r--r--browser/base/content/test/plugin_clickToPlayDeny.html9
-rw-r--r--browser/base/content/test/plugin_data_url.html11
-rw-r--r--browser/base/content/test/plugin_hidden_to_visible.html11
-rw-r--r--browser/base/content/test/plugin_test.html9
-rw-r--r--browser/base/content/test/plugin_test2.html10
-rw-r--r--browser/base/content/test/plugin_test3.html9
-rw-r--r--browser/base/content/test/plugin_two_types.html9
-rw-r--r--browser/base/content/test/plugin_unknown.html9
-rw-r--r--browser/base/content/test/print_postdata.sjs22
-rw-r--r--browser/base/content/test/privateBrowsingMode.js3
-rw-r--r--browser/base/content/test/redirect_bug623155.sjs16
-rw-r--r--browser/base/content/test/social/Makefile.in47
-rw-r--r--browser/base/content/test/social/blocklist.xml6
-rw-r--r--browser/base/content/test/social/browser_addons.js327
-rw-r--r--browser/base/content/test/social/browser_blocklist.js179
-rw-r--r--browser/base/content/test/social/browser_chat_tearoff.js121
-rw-r--r--browser/base/content/test/social/browser_defaults.js14
-rw-r--r--browser/base/content/test/social/browser_share.js140
-rw-r--r--browser/base/content/test/social/browser_social_activation.js348
-rw-r--r--browser/base/content/test/social/browser_social_chatwindow.js472
-rw-r--r--browser/base/content/test/social/browser_social_chatwindow_resize.js90
-rw-r--r--browser/base/content/test/social/browser_social_chatwindowfocus.js360
-rw-r--r--browser/base/content/test/social/browser_social_errorPage.js183
-rw-r--r--browser/base/content/test/social/browser_social_flyout.js164
-rw-r--r--browser/base/content/test/social/browser_social_isVisible.js67
-rw-r--r--browser/base/content/test/social/browser_social_markButton.js187
-rw-r--r--browser/base/content/test/social/browser_social_mozSocial_API.js81
-rw-r--r--browser/base/content/test/social/browser_social_multiprovider.js111
-rw-r--r--browser/base/content/test/social/browser_social_perwindowPB.js82
-rw-r--r--browser/base/content/test/social/browser_social_sidebar.js105
-rw-r--r--browser/base/content/test/social/browser_social_toolbar.js195
-rw-r--r--browser/base/content/test/social/browser_social_window.js145
-rw-r--r--browser/base/content/test/social/head.js517
-rw-r--r--browser/base/content/test/social/moz.build7
-rw-r--r--browser/base/content/test/social/moz.pngbin0 -> 580 bytes
-rw-r--r--browser/base/content/test/social/opengraph/Makefile.in21
-rw-r--r--browser/base/content/test/social/opengraph/moz.build4
-rw-r--r--browser/base/content/test/social/opengraph/og_invalid_url.html11
-rw-r--r--browser/base/content/test/social/opengraph/opengraph.html13
-rw-r--r--browser/base/content/test/social/opengraph/shortlink_linkrel.html10
-rw-r--r--browser/base/content/test/social/opengraph/shorturl_link.html10
-rw-r--r--browser/base/content/test/social/opengraph/shorturl_linkrel.html25
-rw-r--r--browser/base/content/test/social/share.html18
-rw-r--r--browser/base/content/test/social/social_activate.html45
-rw-r--r--browser/base/content/test/social/social_activate_iframe.html11
-rw-r--r--browser/base/content/test/social/social_chat.html32
-rw-r--r--browser/base/content/test/social/social_flyout.html37
-rw-r--r--browser/base/content/test/social/social_mark_image.pngbin0 -> 934 bytes
-rw-r--r--browser/base/content/test/social/social_panel.html24
-rw-r--r--browser/base/content/test/social/social_sidebar.html47
-rw-r--r--browser/base/content/test/social/social_window.html17
-rw-r--r--browser/base/content/test/social/social_worker.js149
-rw-r--r--browser/base/content/test/subtst_contextmenu.html71
-rw-r--r--browser/base/content/test/test_bug364677.html32
-rw-r--r--browser/base/content/test/test_bug395533.html39
-rw-r--r--browser/base/content/test/test_bug435035.html1
-rw-r--r--browser/base/content/test/test_bug452451.html96
-rw-r--r--browser/base/content/test/test_bug462673.html18
-rw-r--r--browser/base/content/test/test_bug628179.html10
-rw-r--r--browser/base/content/test/test_bug839103.html10
-rw-r--r--browser/base/content/test/test_contextmenu.html1088
-rw-r--r--browser/base/content/test/test_feed_discovery.html54
-rw-r--r--browser/base/content/test/test_offlineNotification.html126
-rw-r--r--browser/base/content/test/test_offline_gzip.html112
-rw-r--r--browser/base/content/test/test_wyciwyg_copying.html13
-rw-r--r--browser/base/content/test/title_test.svg59
-rw-r--r--browser/base/content/test/video.oggbin0 -> 285310 bytes
-rw-r--r--browser/base/content/test/zoom_test.html14
-rw-r--r--browser/base/content/urlbarBindings.xml2198
-rw-r--r--browser/base/content/utilityOverlay.js686
-rw-r--r--browser/base/content/viewSourceOverlay.xul26
-rw-r--r--browser/base/content/web-panels.js103
-rw-r--r--browser/base/content/web-panels.xul71
-rw-r--r--browser/base/content/win6BrowserOverlay.xul12
-rw-r--r--browser/base/jar.mn143
-rw-r--r--browser/base/moz.build7
535 files changed, 76579 insertions, 0 deletions
diff --git a/browser/base/Makefile.in b/browser/base/Makefile.in
new file mode 100644
index 000000000..285ef15f0
--- /dev/null
+++ b/browser/base/Makefile.in
@@ -0,0 +1,43 @@
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEPTH = @DEPTH@
+topsrcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+include $(topsrcdir)/config/config.mk
+
+abs_srcdir = $(call core_abspath,$(srcdir))
+
+CHROME_DEPS += $(abs_srcdir)/content/overrides/app-license.html
+
+include $(topsrcdir)/config/rules.mk
+
+PRE_RELEASE_SUFFIX := ""
+
+DEFINES += \
+ -DMOZ_APP_VERSION=$(MOZ_APP_VERSION) \
+ -DAPP_LICENSE_BLOCK=$(abs_srcdir)/content/overrides/app-license.html \
+ -DPRE_RELEASE_SUFFIX="$(PRE_RELEASE_SUFFIX)" \
+ $(NULL)
+
+ifneq (,$(filter windows gtk2 gtk3 cocoa, $(MOZ_WIDGET_TOOLKIT)))
+DEFINES += -DHAVE_SHELL_SERVICE=1
+endif
+
+ifneq (,$(filter windows cocoa gtk2 gtk3, $(MOZ_WIDGET_TOOLKIT)))
+DEFINES += -DCONTEXT_COPY_IMAGE_CONTENTS=1
+endif
+
+ifneq (,$(filter windows cocoa, $(MOZ_WIDGET_TOOLKIT)))
+DEFINES += -DCAN_DRAW_IN_TITLEBAR=1
+endif
+
+ifneq (,$(filter windows gtk2 gtk3, $(MOZ_WIDGET_TOOLKIT)))
+DEFINES += -DMENUBAR_CAN_AUTOHIDE=1
+endif
diff --git a/browser/base/content/aboutDialog.css b/browser/base/content/aboutDialog.css
new file mode 100644
index 000000000..7145ef1c7
--- /dev/null
+++ b/browser/base/content/aboutDialog.css
@@ -0,0 +1,76 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#PMaboutDialog {
+ width: 620px;
+}
+
+#PMrightBox {
+ background-image: url("chrome://branding/content/about-wordmark.png");
+ background-repeat: no-repeat;
+ /* padding-top creates room for the wordmark */
+ padding-top: 38px;
+ margin-top:20px;
+}
+
+#PMrightBox:-moz-locale-dir(rtl) {
+ background-position: 100% 0;
+}
+
+#PMbottomBox {
+ padding: 15px 10px 0;
+}
+
+#PMversion {
+ margin-top: 10px;
+ -moz-margin-start: 0;
+ -moz-user-select: text;
+ -moz-user-focus: normal;
+ cursor: text;
+}
+
+#distribution,
+#distributionId {
+ font-weight: bold;
+ display: none;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+.text-blurb {
+ margin-bottom: 10px;
+ -moz-margin-start: 0;
+ -moz-padding-start: 0;
+}
+
+#updateButton,
+#updateDeck > hbox > label {
+ -moz-margin-start: 0;
+ -moz-padding-start: 0;
+}
+
+.update-throbber {
+ width: 16px;
+ min-height: 16px;
+ -moz-margin-end: 3px;
+ list-style-image: url("chrome://global/skin/icons/loading_16.png");
+}
+
+.text-link,
+.text-link:focus {
+ margin: 0px;
+ padding: 0px;
+}
+
+.bottom-link,
+.bottom-link:focus {
+ text-align: center;
+ margin: 0 40px;
+}
+
+#currentChannel {
+ margin: 0;
+ padding: 0;
+ font-weight: bold;
+}
diff --git a/browser/base/content/aboutDialog.js b/browser/base/content/aboutDialog.js
new file mode 100644
index 000000000..ea8350cbb
--- /dev/null
+++ b/browser/base/content/aboutDialog.js
@@ -0,0 +1,608 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// Services = object with smart getters for common XPCOM services
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+const PREF_EM_HOTFIX_ID = "extensions.hotfix.id";
+
+function init(aEvent)
+{
+ if (aEvent.target != document)
+ return;
+
+ try {
+ var distroId = Services.prefs.getCharPref("distribution.id");
+ if (distroId) {
+ var distroVersion = Services.prefs.getCharPref("distribution.version");
+
+ var distroIdField = document.getElementById("distributionId");
+ distroIdField.value = distroId + " - " + distroVersion;
+ distroIdField.style.display = "block";
+
+ // This must be set last because it might not exist due to bug 895473.
+ var distroAbout = Services.prefs.getComplexValue("distribution.about",
+ Components.interfaces.nsISupportsString);
+ var distroField = document.getElementById("distribution");
+ distroField.value = distroAbout;
+ distroField.style.display = "block";
+ }
+ }
+ catch (e) {
+ // Pref is unset
+ Components.utils.reportError(e);
+ }
+
+ // Include the build ID and display warning if this is an "a#" (nightly or aurora) build
+ let version = Services.appinfo.version;
+ if (/a\d+$/.test(version)) {
+ let buildID = Services.appinfo.appBuildID;
+ let buildDate = buildID.slice(0,4) + "-" + buildID.slice(4,6) + "-" + buildID.slice(6,8);
+ document.getElementById("version").textContent += " (" + buildDate + ")";
+ document.getElementById("experimental").hidden = false;
+ document.getElementById("communityDesc").hidden = true;
+ }
+
+#ifdef MOZ_UPDATER
+ gAppUpdater = new appUpdater();
+
+#if MOZ_UPDATE_CHANNEL != release
+ let defaults = Services.prefs.getDefaultBranch("");
+ let channelLabel = document.getElementById("currentChannel");
+ channelLabel.value = defaults.getCharPref("app.update.channel");
+#endif
+#endif
+
+#ifdef XP_MACOSX
+ // it may not be sized at this point, and we need its width to calculate its position
+ window.sizeToContent();
+ window.moveTo((screen.availWidth / 2) - (window.outerWidth / 2), screen.availHeight / 5);
+#endif
+}
+
+#ifdef MOZ_UPDATER
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
+Components.utils.import("resource://gre/modules/AddonManager.jsm");
+
+var gAppUpdater;
+
+function onUnload(aEvent) {
+ if (gAppUpdater.isChecking)
+ gAppUpdater.checker.stopChecking(Components.interfaces.nsIUpdateChecker.CURRENT_CHECK);
+ // Safe to call even when there isn't a download in progress.
+ gAppUpdater.removeDownloadListener();
+ gAppUpdater = null;
+}
+
+
+function appUpdater()
+{
+ this.updateDeck = document.getElementById("updateDeck");
+
+ // Hide the update deck when there is already an update window open to avoid
+ // syncing issues between them.
+ if (Services.wm.getMostRecentWindow("Update:Wizard")) {
+ this.updateDeck.hidden = true;
+ return;
+ }
+
+ XPCOMUtils.defineLazyServiceGetter(this, "aus",
+ "@mozilla.org/updates/update-service;1",
+ "nsIApplicationUpdateService");
+ XPCOMUtils.defineLazyServiceGetter(this, "checker",
+ "@mozilla.org/updates/update-checker;1",
+ "nsIUpdateChecker");
+ XPCOMUtils.defineLazyServiceGetter(this, "um",
+ "@mozilla.org/updates/update-manager;1",
+ "nsIUpdateManager");
+
+ this.bundle = Services.strings.
+ createBundle("chrome://browser/locale/browser.properties");
+
+ this.updateBtn = document.getElementById("updateButton");
+
+ // The button label value must be set so its height is correct.
+ this.setupUpdateButton("update.checkInsideButton");
+
+ let manualURL = Services.urlFormatter.formatURLPref("app.update.url.manual");
+ let manualLink = document.getElementById("manualLink");
+ manualLink.value = manualURL;
+ manualLink.href = manualURL;
+ document.getElementById("failedLink").href = manualURL;
+
+ if (this.updateDisabledAndLocked) {
+ this.selectPanel("adminDisabled");
+ return;
+ }
+
+ if (this.isPending || this.isApplied) {
+ this.setupUpdateButton("update.restart." +
+ (this.isMajor ? "upgradeButton" : "updateButton"));
+ return;
+ }
+
+ if (this.aus.isOtherInstanceHandlingUpdates) {
+ this.selectPanel("otherInstanceHandlingUpdates");
+ return;
+ }
+
+ if (this.isDownloading) {
+ this.startDownload();
+ return;
+ }
+
+ if (this.updateEnabled && this.updateAuto) {
+ this.selectPanel("checkingForUpdates");
+ this.isChecking = true;
+ this.checker.checkForUpdates(this.updateCheckListener, true);
+ return;
+ }
+}
+
+appUpdater.prototype =
+{
+ // true when there is an update check in progress.
+ isChecking: false,
+
+ // true when there is an update already staged / ready to be applied.
+ get isPending() {
+ if (this.update) {
+ return this.update.state == "pending" ||
+ this.update.state == "pending-service";
+ }
+ return this.um.activeUpdate &&
+ (this.um.activeUpdate.state == "pending" ||
+ this.um.activeUpdate.state == "pending-service");
+ },
+
+ // true when there is an update already installed in the background.
+ get isApplied() {
+ if (this.update)
+ return this.update.state == "applied" ||
+ this.update.state == "applied-service";
+ return this.um.activeUpdate &&
+ (this.um.activeUpdate.state == "applied" ||
+ this.um.activeUpdate.state == "applied-service");
+ },
+
+ // true when there is an update download in progress.
+ get isDownloading() {
+ if (this.update)
+ return this.update.state == "downloading";
+ return this.um.activeUpdate &&
+ this.um.activeUpdate.state == "downloading";
+ },
+
+ // true when the update type is major.
+ get isMajor() {
+ if (this.update)
+ return this.update.type == "major";
+ return this.um.activeUpdate.type == "major";
+ },
+
+ // true when updating is disabled by an administrator.
+ get updateDisabledAndLocked() {
+ return !this.updateEnabled &&
+ Services.prefs.prefIsLocked("app.update.enabled");
+ },
+
+ // true when updating is enabled.
+ get updateEnabled() {
+ try {
+ return Services.prefs.getBoolPref("app.update.enabled");
+ }
+ catch (e) { }
+ return true; // Firefox default is true
+ },
+
+ // true when updating in background is enabled.
+ get backgroundUpdateEnabled() {
+ return this.updateEnabled &&
+ gAppUpdater.aus.canStageUpdates;
+ },
+
+ // true when updating is automatic.
+ get updateAuto() {
+ try {
+ return Services.prefs.getBoolPref("app.update.auto");
+ }
+ catch (e) { }
+ return true; // Firefox default is true
+ },
+
+ /**
+ * Sets the deck's selected panel.
+ *
+ * @param aChildID
+ * The id of the deck's child to select.
+ */
+ selectPanel: function(aChildID) {
+ this.updateDeck.selectedPanel = document.getElementById(aChildID);
+ this.updateBtn.disabled = (aChildID != "updateButtonBox");
+ },
+
+ /**
+ * Sets the update button's label and accesskey.
+ *
+ * @param aKeyPrefix
+ * The prefix for the properties file entry to use for setting the
+ * label and accesskey.
+ */
+ setupUpdateButton: function(aKeyPrefix) {
+ this.updateBtn.label = this.bundle.GetStringFromName(aKeyPrefix + ".label");
+ this.updateBtn.accessKey = this.bundle.GetStringFromName(aKeyPrefix + ".accesskey");
+ if (!document.commandDispatcher.focusedElement ||
+ document.commandDispatcher.focusedElement == this.updateBtn)
+ this.updateBtn.focus();
+ },
+
+ /**
+ * Handles oncommand for the update button.
+ */
+ buttonOnCommand: function() {
+ if (this.isPending || this.isApplied) {
+ // Notify all windows that an application quit has been requested.
+ let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"].
+ createInstance(Components.interfaces.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
+
+ // Something aborted the quit process.
+ if (cancelQuit.data)
+ return;
+
+ let appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"].
+ getService(Components.interfaces.nsIAppStartup);
+
+ // If already in safe mode restart in safe mode (bug 327119)
+ if (Services.appinfo.inSafeMode) {
+ appStartup.restartInSafeMode(Components.interfaces.nsIAppStartup.eAttemptQuit);
+ return;
+ }
+
+ appStartup.quit(Components.interfaces.nsIAppStartup.eAttemptQuit |
+ Components.interfaces.nsIAppStartup.eRestart);
+ return;
+ }
+
+ const URI_UPDATE_PROMPT_DIALOG = "chrome://mozapps/content/update/updates.xul";
+ // Firefox no longer displays a license for updates and the licenseURL check
+ // is just in case a distibution does.
+ if (this.update && (this.update.billboardURL || this.update.licenseURL ||
+ this.addons.length != 0)) {
+ var ary = null;
+ ary = Components.classes["@mozilla.org/supports-array;1"].
+ createInstance(Components.interfaces.nsISupportsArray);
+ ary.AppendElement(this.update);
+ var openFeatures = "chrome,centerscreen,dialog=no,resizable=no,titlebar,toolbar=no";
+ Services.ww.openWindow(null, URI_UPDATE_PROMPT_DIALOG, "", openFeatures, ary);
+ window.close();
+ return;
+ }
+
+ this.selectPanel("checkingForUpdates");
+ this.isChecking = true;
+ this.checker.checkForUpdates(this.updateCheckListener, true);
+ },
+
+ /**
+ * Implements nsIUpdateCheckListener. The methods implemented by
+ * nsIUpdateCheckListener are in a different scope from nsIIncrementalDownload
+ * to make it clear which are used by each interface.
+ */
+ updateCheckListener: {
+ /**
+ * See nsIUpdateService.idl
+ */
+ onCheckComplete: function(aRequest, aUpdates, aUpdateCount) {
+ gAppUpdater.isChecking = false;
+ gAppUpdater.update = gAppUpdater.aus.
+ selectUpdate(aUpdates, aUpdates.length);
+ if (!gAppUpdater.update) {
+ gAppUpdater.selectPanel("noUpdatesFound");
+ return;
+ }
+
+ if (gAppUpdater.update.unsupported) {
+ if (gAppUpdater.update.detailsURL) {
+ let unsupportedLink = document.getElementById("unsupportedLink");
+ unsupportedLink.href = gAppUpdater.update.detailsURL;
+ }
+ gAppUpdater.selectPanel("unsupportedSystem");
+ return;
+ }
+
+ if (!gAppUpdater.aus.canApplyUpdates) {
+ gAppUpdater.selectPanel("manualUpdate");
+ return;
+ }
+
+ // Firefox no longer displays a license for updates and the licenseURL
+ // check is just in case a distibution does.
+ if (gAppUpdater.update.billboardURL || gAppUpdater.update.licenseURL) {
+ gAppUpdater.selectPanel("updateButtonBox");
+ gAppUpdater.setupUpdateButton("update.openUpdateUI." +
+ (this.isMajor ? "upgradeButton"
+ : "applyButton"));
+ return;
+ }
+
+ if (!gAppUpdater.update.appVersion ||
+ Services.vc.compare(gAppUpdater.update.appVersion,
+ Services.appinfo.version) == 0) {
+ gAppUpdater.startDownload();
+ return;
+ }
+
+ gAppUpdater.checkAddonCompatibility();
+ },
+
+ /**
+ * See nsIUpdateService.idl
+ */
+ onError: function(aRequest, aUpdate) {
+ // Errors in the update check are treated as no updates found. If the
+ // update check fails repeatedly without a success the user will be
+ // notified with the normal app update user interface so this is safe.
+ gAppUpdater.isChecking = false;
+ gAppUpdater.selectPanel("noUpdatesFound");
+ },
+
+ /**
+ * See nsISupports.idl
+ */
+ QueryInterface: function(aIID) {
+ if (!aIID.equals(Components.interfaces.nsIUpdateCheckListener) &&
+ !aIID.equals(Components.interfaces.nsISupports))
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ return this;
+ }
+ },
+
+ /**
+ * Checks the compatibility of add-ons for the application update.
+ */
+ checkAddonCompatibility: function() {
+ try {
+ var hotfixID = Services.prefs.getCharPref(PREF_EM_HOTFIX_ID);
+ }
+ catch (e) { }
+
+ var self = this;
+ AddonManager.getAllAddons(function(aAddons) {
+ self.addons = [];
+ self.addonsCheckedCount = 0;
+ aAddons.forEach(function(aAddon) {
+ // Protect against code that overrides the add-ons manager and doesn't
+ // implement the isCompatibleWith or the findUpdates method.
+ if (!("isCompatibleWith" in aAddon) || !("findUpdates" in aAddon)) {
+ let errMsg = "Add-on doesn't implement either the isCompatibleWith " +
+ "or the findUpdates method!";
+ if (aAddon.id)
+ errMsg += " Add-on ID: " + aAddon.id;
+ Components.utils.reportError(errMsg);
+ return;
+ }
+
+ // If an add-on isn't appDisabled and isn't userDisabled then it is
+ // either active now or the user expects it to be active after the
+ // restart. If that is the case and the add-on is not installed by the
+ // application and is not compatible with the new application version
+ // then the user should be warned that the add-on will become
+ // incompatible. If an addon's type equals plugin it is skipped since
+ // checking plugins compatibility information isn't supported and
+ // getting the scope property of a plugin breaks in some environments
+ // (see bug 566787). The hotfix add-on is also ignored as it shouldn't
+ // block the user from upgrading.
+ try {
+ if (aAddon.type != "plugin" && aAddon.id != hotfixID &&
+ !aAddon.appDisabled && !aAddon.userDisabled &&
+ aAddon.scope != AddonManager.SCOPE_APPLICATION &&
+ aAddon.isCompatible &&
+ !aAddon.isCompatibleWith(self.update.appVersion,
+ self.update.platformVersion))
+ self.addons.push(aAddon);
+ }
+ catch (e) {
+ Components.utils.reportError(e);
+ }
+ });
+ self.addonsTotalCount = self.addons.length;
+ if (self.addonsTotalCount == 0) {
+ self.startDownload();
+ return;
+ }
+
+ self.checkAddonsForUpdates();
+ });
+ },
+
+ /**
+ * Checks if there are updates for add-ons that are incompatible with the
+ * application update.
+ */
+ checkAddonsForUpdates: function() {
+ this.addons.forEach(function(aAddon) {
+ aAddon.findUpdates(this, AddonManager.UPDATE_WHEN_NEW_APP_DETECTED,
+ this.update.appVersion,
+ this.update.platformVersion);
+ }, this);
+ },
+
+ /**
+ * See XPIProvider.jsm
+ */
+ onCompatibilityUpdateAvailable: function(aAddon) {
+ for (var i = 0; i < this.addons.length; ++i) {
+ if (this.addons[i].id == aAddon.id) {
+ this.addons.splice(i, 1);
+ break;
+ }
+ }
+ },
+
+ /**
+ * See XPIProvider.jsm
+ */
+ onUpdateAvailable: function(aAddon, aInstall) {
+ if (!Services.blocklist.isAddonBlocklisted(aAddon.id, aInstall.version,
+ this.update.appVersion,
+ this.update.platformVersion)) {
+ // Compatibility or new version updates mean the same thing here.
+ this.onCompatibilityUpdateAvailable(aAddon);
+ }
+ },
+
+ /**
+ * See XPIProvider.jsm
+ */
+ onUpdateFinished: function(aAddon) {
+ ++this.addonsCheckedCount;
+
+ if (this.addonsCheckedCount < this.addonsTotalCount)
+ return;
+
+ if (this.addons.length == 0) {
+ // Compatibility updates or new version updates were found for all add-ons
+ this.startDownload();
+ return;
+ }
+
+ this.selectPanel("updateButtonBox");
+ this.setupUpdateButton("update.openUpdateUI." +
+ (this.isMajor ? "upgradeButton" : "applyButton"));
+ },
+
+ /**
+ * Starts the download of an update mar.
+ */
+ startDownload: function() {
+ if (!this.update)
+ this.update = this.um.activeUpdate;
+ this.update.QueryInterface(Components.interfaces.nsIWritablePropertyBag);
+ this.update.setProperty("foregroundDownload", "true");
+
+ this.aus.pauseDownload();
+ let state = this.aus.downloadUpdate(this.update, false);
+ if (state == "failed") {
+ this.selectPanel("downloadFailed");
+ return;
+ }
+
+ this.setupDownloadingUI();
+ },
+
+ /**
+ * Switches to the UI responsible for tracking the download.
+ */
+ setupDownloadingUI: function() {
+ this.downloadStatus = document.getElementById("downloadStatus");
+ this.downloadStatus.value =
+ DownloadUtils.getTransferTotal(0, this.update.selectedPatch.size);
+ this.selectPanel("downloading");
+ this.aus.addDownloadListener(this);
+ },
+
+ removeDownloadListener: function() {
+ if (this.aus) {
+ this.aus.removeDownloadListener(this);
+ }
+ },
+
+ /**
+ * See nsIRequestObserver.idl
+ */
+ onStartRequest: function(aRequest, aContext) {
+ },
+
+ /**
+ * See nsIRequestObserver.idl
+ */
+ onStopRequest: function(aRequest, aContext, aStatusCode) {
+ switch (aStatusCode) {
+ case Components.results.NS_ERROR_UNEXPECTED:
+ if (this.update.selectedPatch.state == "download-failed" &&
+ (this.update.isCompleteUpdate || this.update.patchCount != 2)) {
+ // Verification error of complete patch, informational text is held in
+ // the update object.
+ this.removeDownloadListener();
+ this.selectPanel("downloadFailed");
+ break;
+ }
+ // Verification failed for a partial patch, complete patch is now
+ // downloading so return early and do NOT remove the download listener!
+ break;
+ case Components.results.NS_BINDING_ABORTED:
+ // Do not remove UI listener since the user may resume downloading again.
+ break;
+ case Components.results.NS_OK:
+ this.removeDownloadListener();
+ if (this.backgroundUpdateEnabled) {
+ this.selectPanel("applying");
+ let update = this.um.activeUpdate;
+ let self = this;
+ Services.obs.addObserver(function (aSubject, aTopic, aData) {
+ // Update the UI when the background updater is finished
+ let status = aData;
+ if (status == "applied" || status == "applied-service" ||
+ status == "pending" || status == "pending-service") {
+ // If the update is successfully applied, or if the updater has
+ // fallen back to non-staged updates, show the Restart to Update
+ // button.
+ self.selectPanel("updateButtonBox");
+ self.setupUpdateButton("update.restart." +
+ (self.isMajor ? "upgradeButton" : "updateButton"));
+ } else if (status == "failed") {
+ // Background update has failed, let's show the UI responsible for
+ // prompting the user to update manually.
+ self.selectPanel("downloadFailed");
+ } else if (status == "downloading") {
+ // We've fallen back to downloading the full update because the
+ // partial update failed to get staged in the background.
+ // Therefore we need to keep our observer.
+ self.setupDownloadingUI();
+ return;
+ }
+ Services.obs.removeObserver(arguments.callee, "update-staged");
+ }, "update-staged", false);
+ } else {
+ this.selectPanel("updateButtonBox");
+ this.setupUpdateButton("update.restart." +
+ (this.isMajor ? "upgradeButton" : "updateButton"));
+ }
+ break;
+ default:
+ this.removeDownloadListener();
+ this.selectPanel("downloadFailed");
+ break;
+ }
+
+ },
+
+ /**
+ * See nsIProgressEventSink.idl
+ */
+ onStatus: function(aRequest, aContext, aStatus, aStatusArg) {
+ },
+
+ /**
+ * See nsIProgressEventSink.idl
+ */
+ onProgress: function(aRequest, aContext, aProgress, aProgressMax) {
+ this.downloadStatus.value =
+ DownloadUtils.getTransferTotal(aProgress, aProgressMax);
+ },
+
+ /**
+ * See nsISupports.idl
+ */
+ QueryInterface: function(aIID) {
+ if (!aIID.equals(Components.interfaces.nsIProgressEventSink) &&
+ !aIID.equals(Components.interfaces.nsIRequestObserver) &&
+ !aIID.equals(Components.interfaces.nsISupports))
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ return this;
+ }
+};
+#endif
diff --git a/browser/base/content/aboutDialog.xul b/browser/base/content/aboutDialog.xul
new file mode 100644
index 000000000..5ce8212f4
--- /dev/null
+++ b/browser/base/content/aboutDialog.xul
@@ -0,0 +1,135 @@
+<?xml version="1.0"?> <!-- -*- Mode: HTML -*- -->
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/aboutDialog.css" type="text/css"?>
+<?xml-stylesheet href="chrome://branding/content/aboutDialog.css" type="text/css"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % aboutDialogDTD SYSTEM "chrome://browser/locale/aboutDialog.dtd" >
+%aboutDialogDTD;
+]>
+
+#ifdef XP_MACOSX
+<?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?>
+#endif
+
+<window xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="PMaboutDialog"
+ windowtype="Browser:About"
+ onload="init(event);"
+#ifdef MOZ_UPDATER
+ onunload="onUnload(event);"
+#endif
+#ifdef XP_MACOSX
+ inwindowmenu="false"
+#else
+ title="&aboutDialog.title;"
+#endif
+ role="dialog"
+ aria-describedby="version distribution distributionId communityDesc contributeDesc trademark"
+ >
+
+ <script type="application/javascript" src="chrome://browser/content/aboutDialog.js"/>
+
+ <vbox id="aboutPMDialogContainer">
+ <hbox id="PMclientBox">
+ <vbox id="PMleftBox" flex="1"/>
+ <vbox id="PMrightBox" flex="1">
+#ifdef HAVE_64BIT_OS
+#expand <label id="PMversion">Version: __MOZ_APP_VERSION__ (x64)</label>
+#else
+#expand <label id="PMversion">Version: __MOZ_APP_VERSION__ (x86)</label>
+#endif
+ <label id="distribution" class="text-blurb"/>
+ <label id="distributionId" class="text-blurb"/>
+
+ <vbox id="detailsBox">
+ <vbox id="updateBox">
+#ifdef MOZ_UPDATER
+ <deck id="updateDeck" orient="vertical">
+ <hbox id="updateButtonBox" align="center">
+ <button id="updateButton" align="start"
+ oncommand="gAppUpdater.buttonOnCommand();"/>
+ <spacer flex="1"/>
+ </hbox>
+ <hbox id="checkingForUpdates" align="center">
+ <image class="update-throbber"/><label>&update.checkingForUpdates;</label>
+ </hbox>
+ <hbox id="checkingAddonCompat" align="center">
+ <image class="update-throbber"/><label>&update.checkingAddonCompat;</label>
+ </hbox>
+ <hbox id="downloading" align="center">
+ <image class="update-throbber"/><label>&update.downloading.start;</label><label id="downloadStatus"/><label>&update.downloading.end;</label>
+ </hbox>
+ <hbox id="applying" align="center">
+ <image class="update-throbber"/><label>&update.applying;</label>
+ </hbox>
+ <hbox id="downloadFailed" align="center">
+ <label>&update.failed.start;</label><label id="failedLink" class="text-link">&update.failed.linkText;</label><label>&update.failed.end;</label>
+ </hbox>
+ <hbox id="adminDisabled" align="center">
+ <label>&update.adminDisabled;</label>
+ </hbox>
+ <hbox id="noUpdatesFound" align="center">
+ <label>&update.noUpdatesFound;</label>
+ </hbox>
+ <hbox id="manualUpdate" align="center">
+ <label>&update.manual.start;</label><label id="manualLink" class="text-link"/><label>&update.manual.end;</label>
+ </hbox>
+ </deck>
+#endif
+ </vbox>
+
+#ifdef MOZ_UPDATER
+# <description class="text-blurb" id="currentChannelText">
+# &channel.description.start;<label id="currentChannel"/>&channel.description.end;
+# </description>
+#endif
+# <vbox id="experimental" hidden="true">
+# <description class="text-blurb" id="warningDesc">
+# &warningDesc.version;
+#ifdef MOZ_TELEMETRY_ON_BY_DEFAULT
+# &warningDesc.telemetryDesc;
+#endif
+# </description>
+# </vbox>
+ <description class="text-pmcreds">
+ Pale Moon is released by <label class="text-link" href="http://www.moonchildproductions.info">Moonchild Productions</label>.
+ </description>
+ <description class="text-pmcreds">
+ Testers and moral support: Jose Arellano, Cole Hughes, Tobias Olsson, Duco Quanjer, Gerardo Rubio, Daryl Sprint, Jason Sullivan.
+ </description>
+ <description class="text-pmcreds">
+ Special thanks to: Lee Brown, Jacob M. Ross, Phil Chan, Colin Moran, and all other supporters!
+ </description>
+ <description class="text-blurb">
+ If you wish to contribute, please consider helping out by providing support to other users or becoming a beta tester on the <label class="text-link" href="http://forum.palemoon.org/">Pale Moon forum</label>
+ </description>
+ </vbox>
+ </vbox>
+ </hbox>
+ <vbox id="PMbottomBox">
+ <hbox pack="center">
+ <label class="text-link bottom-link" href="about:license">Licensing information</label>
+ <label class="text-link bottom-link" href="about:rights">End-user rights</label>
+ <label class="text-link bottom-link" href="http://www.palemoon.org/releasenotes-ng.shtml">Release notes</label>
+ </hbox>
+ <description id="PMtrademark">&trademarkInfo.part1;</description>
+ </vbox>
+ </vbox>
+
+ <keyset>
+ <key keycode="VK_ESCAPE" oncommand="window.close();"/>
+ </keyset>
+
+#ifdef XP_MACOSX
+#include browserMountPoints.inc
+#endif
+</window>
diff --git a/browser/base/content/aboutRobots-icon.png b/browser/base/content/aboutRobots-icon.png
new file mode 100644
index 000000000..1c4899aaf
--- /dev/null
+++ b/browser/base/content/aboutRobots-icon.png
Binary files differ
diff --git a/browser/base/content/aboutRobots-widget-left.png b/browser/base/content/aboutRobots-widget-left.png
new file mode 100644
index 000000000..3a1e48d5f
--- /dev/null
+++ b/browser/base/content/aboutRobots-widget-left.png
Binary files differ
diff --git a/browser/base/content/aboutRobots.xhtml b/browser/base/content/aboutRobots.xhtml
new file mode 100644
index 000000000..23fe3ba17
--- /dev/null
+++ b/browser/base/content/aboutRobots.xhtml
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD
+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+ <!ENTITY % netErrorDTD
+ SYSTEM "chrome://global/locale/netError.dtd">
+ %netErrorDTD;
+ <!ENTITY % globalDTD
+ SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+ <!ENTITY % aboutrobotsDTD
+ SYSTEM "chrome://browser/locale/aboutRobots.dtd">
+ %aboutrobotsDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>&robots.pagetitle;</title>
+ <link rel="stylesheet" href="chrome://global/skin/netError.css" type="text/css" media="all" />
+ <link rel="icon" type="image/png" id="favicon" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8%2F9hAAAACGFjVEwAAAASAAAAAJNtBPIAAAAaZmNUTAAAAAAAAAAQAAAAEAAAAAAAAAAALuAD6AABhIDeugAAALhJREFUOI2Nk8sNxCAMRDlGohauXFOMpfTiAlxICqAELltHLqlgctg1InzMRhpFAc%2BLGWTnmoeZYamt78zXdZmaQtQMADlnU0OIAlbmJUBEcO4bRKQY2rUXIPmAGnDuG%2FBx3%2FfvOPVaDUg%2BoAPUf1PArIMCSD5glMEsUGaG%2BkyAFWIBaCsKuA%2BHGCNijLgP133XgOEtaPFMy2vUolEGJoCIzBmoRUR9%2B7rxj16DZaW%2FmgtmxnJ8V3oAnApQwNS5zpcAAAAaZmNUTAAAAAEAAAAQAAAAEAAAAAAAAAAAAB4D6AIB52fclgAAACpmZEFUAAAAAjiNY2AYBVhBc3Pzf2LEcGreqcbwH1kDNjHauWAUjAJyAADymxf9WF%2Bu8QAAABpmY1RMAAAAAwAAABAAAAAQAAAAAAAAAAAAHgPoAgEK8Q9%2FAAAAFmZkQVQAAAAEOI1jYBgFo2AUjAIIAAAEEAAB0xIn4wAAABpmY1RMAAAABQAAABAAAAAQAAAAAAAAAAAAHgPoAgHnO30FAAAAQGZkQVQAAAAGOI1jYBieYKcaw39ixHCC%2F6cwFWMTw2rz%2F1MM%2F6Vu%2Ff%2F%2F%2FxTD%2F51qEIwuRjsXILuEGLFRMApgAADhNCsVfozYcAAAABpmY1RMAAAABwAAABAAAAAQAAAAAAAAAAAAHgPoAgEKra7sAAAAFmZkQVQAAAAIOI1jYBgFo2AUjAIIAAAEEAABM9s3hAAAABpmY1RMAAAACQAAABAAAAAQAAAAAAAAAAAAHgPoAgHn3p%2BwAAAAKmZkQVQAAAAKOI1jYBgFWEFzc%2FN%2FYsRwat6pxvAfWQM2Mdq5YBSMAnIAAPKbF%2F1BhPl6AAAAGmZjVEwAAAALAAAAEAAAABAAAAAAAAAAAAAeA%2BgCAQpITFkAAAAWZmRBVAAAAAw4jWNgGAWjYBSMAggAAAQQAAHaszpmAAAAGmZjVEwAAAANAAAAEAAAABAAAAAAAAAAAAAeA%2BgCAeeCPiMAAABAZmRBVAAAAA44jWNgGJ5gpxrDf2LEcIL%2FpzAVYxPDavP%2FUwz%2FpW79%2F%2F%2F%2FFMP%2FnWoQjC5GOxcgu4QYsVEwCmAAAOE0KxUmBL0KAAAAGmZjVEwAAAAPAAAAEAAAABAAAAAAAAAAAAAeA%2BgCAQoU7coAAAAWZmRBVAAAABA4jWNgGAWjYBSMAggAAAQQAAEpOBELAAAAGmZjVEwAAAARAAAAEAAAABAAAAAAAAAAAAAeA%2BgCAeYVWtoAAAAqZmRBVAAAABI4jWNgGAVYQXNz839ixHBq3qnG8B9ZAzYx2rlgFIwCcgAA8psX%2FWvpAecAAAAaZmNUTAAAABMAAAAQAAAAEAAAAAAAAAAAAB4D6AIBC4OJMwAAABZmZEFUAAAAFDiNY2AYBaNgFIwCCAAABBAAAcBQHOkAAAAaZmNUTAAAABUAAAAQAAAAEAAAAAAAAAAAAB4D6AIB5kn7SQAAAEBmZEFUAAAAFjiNY2AYnmCnGsN%2FYsRwgv%2BnMBVjE8Nq8%2F9TDP%2Blbv3%2F%2F%2F8Uw%2F%2BdahCMLkY7FyC7hBixUTAKYAAA4TQrFc%2BcEoQAAAAaZmNUTAAAABcAAAAQAAAAEAAAAAAAAAAAAB4D6AIBC98ooAAAABZmZEFUAAAAGDiNY2AYBaNgFIwCCAAABBAAASCZDI4AAAAaZmNUTAAAABkAAAAQAAAAEAAAAAAAAAAAAB4D6AIB5qwZ%2FAAAACpmZEFUAAAAGjiNY2AYBVhBc3Pzf2LEcGreqcbwH1kDNjHauWAUjAJyAADymxf9cjJWbAAAABpmY1RMAAAAGwAAABAAAAAQAAAAAAAAAAAAHgPoAgELOsoVAAAAFmZkQVQAAAAcOI1jYBgFo2AUjAIIAAAEEAAByfEBbAAAABpmY1RMAAAAHQAAABAAAAAQAAAAAAAAAAAAHgPoAgHm8LhvAAAAQGZkQVQAAAAeOI1jYBieYKcaw39ixHCC%2F6cwFWMTw2rz%2F1MM%2F6Vu%2Ff%2F%2F%2FxTD%2F51qEIwuRjsXILuEGLFRMApgAADhNCsVlxR3%2FgAAABpmY1RMAAAAHwAAABAAAAAQAAAAAAAAAAAAHgPoAgELZmuGAAAAFmZkQVQAAAAgOI1jYBgFo2AUjAIIAAAEEAABHP5cFQAAABpmY1RMAAAAIQAAABAAAAAQAAAAAAAAAAAAHgPoAgHlgtAOAAAAKmZkQVQAAAAiOI1jYBgFWEFzc%2FN%2FYsRwat6pxvAfWQM2Mdq5YBSMAnIAAPKbF%2F0%2FMvDdAAAAAElFTkSuQmCC"/>
+
+ <script type="application/javascript"><![CDATA[
+ var buttonClicked = false;
+ function robotButton()
+ {
+ var button = document.getElementById('errorTryAgain');
+ if (buttonClicked) {
+ button.style.visibility = "hidden";
+ } else {
+ var newLabel = button.getAttribute("label2");
+ button.textContent = newLabel;
+ buttonClicked = true;
+ }
+ }
+ ]]></script>
+
+ <style type="text/css"><![CDATA[
+ #errorPageContainer {
+ background-image: none;
+ }
+
+ #errorPageContainer:before {
+ content: url('chrome://browser/content/aboutRobots-icon.png');
+ position: absolute;
+ }
+
+ body[dir=rtl] #icon,
+ body[dir=rtl] #errorPageContainer:before {
+ transform: scaleX(-1);
+ }
+ ]]></style>
+ </head>
+
+ <body dir="&locale.dir;">
+
+ <!-- PAGE CONTAINER (for styling purposes only) -->
+ <div id="errorPageContainer">
+
+ <!-- Error Title -->
+ <div id="errorTitle">
+ <h1 id="errorTitleText">&robots.errorTitleText;</h1>
+ </div>
+
+ <!-- LONG CONTENT (the section most likely to require scrolling) -->
+ <div id="errorLongContent">
+
+ <!-- Short Description -->
+ <div id="errorShortDesc">
+ <p id="errorShortDescText">&robots.errorShortDescText;</p>
+ </div>
+
+ <!-- Long Description (Note: See netError.dtd for used XHTML tags) -->
+ <div id="errorLongDesc">
+ <ul>
+ <li>&robots.errorLongDesc1;</li>
+ <li>&robots.errorLongDesc2;</li>
+ <li>&robots.errorLongDesc3;</li>
+ <li>&robots.errorLongDesc4;</li>
+ </ul>
+ </div>
+
+ <!-- Short Description -->
+ <div id="errorTrailerDesc">
+ <p id="errorTrailerDescText">&robots.errorTrailerDescText;</p>
+ </div>
+
+ </div>
+
+ <!-- Button -->
+ <button id="errorTryAgain"
+ label2="&robots.dontpress;"
+ onclick="robotButton();">&retry.label;</button>
+
+ <img src="chrome://browser/content/aboutRobots-widget-left.png"
+ style="position: absolute; bottom: -12px; left: -10px;"/>
+ <img src="chrome://browser/content/aboutRobots-widget-left.png"
+ style="position: absolute; bottom: -12px; right: -10px; transform: scaleX(-1);"/>
+ </div>
+
+ </body>
+</html>
diff --git a/browser/base/content/aboutSocialError.xhtml b/browser/base/content/aboutSocialError.xhtml
new file mode 100644
index 000000000..6bef2d7bd
--- /dev/null
+++ b/browser/base/content/aboutSocialError.xhtml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD
+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+ <!ENTITY % netErrorDTD SYSTEM "chrome://global/locale/netError.dtd">
+ %netErrorDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>&loadError.label;</title>
+ <link rel="stylesheet" type="text/css" media="all"
+ href="chrome://browser/skin/aboutSocialError.css"/>
+ </head>
+
+ <body>
+ <div id="error-box">
+ <p id="main-error-msg"></p>
+ <p id="helper-error-msg"></p>
+ </div>
+ <div id="button-box">
+ <button id="btnTryAgain" onclick="tryAgainButton()"/>
+ <button id="btnCloseSidebar" onclick="closeSidebarButton()"/>
+ </div>
+ </body>
+
+ <script type="text/javascript;version=1.8"><![CDATA[
+ const Cu = Components.utils;
+
+ Cu.import("resource://gre/modules/Services.jsm");
+ Cu.import("resource:///modules/Social.jsm");
+
+ let config = {
+ tryAgainCallback: reloadProvider
+ }
+
+ function parseQueryString() {
+ let url = document.documentURI;
+ let queryString = url.replace(/^about:socialerror\??/, "");
+
+ let modeMatch = queryString.match(/mode=([^&]+)/);
+ let mode = modeMatch && modeMatch[1] ? modeMatch[1] : "";
+ let originMatch = queryString.match(/origin=([^&]+)/);
+ config.origin = originMatch && originMatch[1] ? decodeURIComponent(originMatch[1]) : "";
+
+ switch (mode) {
+ case "compactInfo":
+ document.getElementById("btnTryAgain").style.display = 'none';
+ document.getElementById("btnCloseSidebar").style.display = 'none';
+ break;
+ case "tryAgainOnly":
+ document.getElementById("btnCloseSidebar").style.display = 'none';
+ //intentional fall-through
+ case "tryAgain":
+ let urlMatch = queryString.match(/url=([^&]+)/);
+ let encodedURL = urlMatch && urlMatch[1] ? urlMatch[1] : "";
+ let url = decodeURIComponent(encodedURL);
+
+ config.tryAgainCallback = loadQueryURL;
+ config.queryURL = url;
+ break;
+ case "workerFailure":
+ config.tryAgainCallback = reloadProvider;
+ break;
+ default:
+ break;
+ }
+ }
+
+ function setUpStrings() {
+ let brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties");
+ let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
+
+ let productName = brandBundle.GetStringFromName("brandShortName");
+ let provider = Social && Social.provider;
+ if (config.origin) {
+ provider = Social && Social._getProviderFromOrigin(config.origin);
+ }
+ let providerName = provider && provider.name;
+
+ // Sets up the error message
+ let msg = browserBundle.formatStringFromName("social.error.message", [productName, providerName], 2);
+ document.getElementById("main-error-msg").textContent = msg;
+
+ // Sets up the buttons' labels and accesskeys
+ let btnTryAgain = document.getElementById("btnTryAgain");
+ btnTryAgain.textContent = browserBundle.GetStringFromName("social.error.tryAgain.label");
+ btnTryAgain.accessKey = browserBundle.GetStringFromName("social.error.tryAgain.accesskey");
+
+ let btnCloseSidebar = document.getElementById("btnCloseSidebar");
+ btnCloseSidebar.textContent = browserBundle.GetStringFromName("social.error.closeSidebar.label");
+ btnCloseSidebar.accessKey = browserBundle.GetStringFromName("social.error.closeSidebar.accesskey");
+ }
+
+ function closeSidebarButton() {
+ Social.toggleSidebar();
+ }
+
+ function tryAgainButton() {
+ config.tryAgainCallback();
+ }
+
+ function loadQueryURL() {
+ window.location.href = config.queryURL;
+ }
+
+ function reloadProvider() {
+ Social.enabled = false;
+ Services.tm.mainThread.dispatch(function() {
+ Social.enabled = true;
+ }, Components.interfaces.nsIThread.DISPATCH_NORMAL);
+ }
+
+ parseQueryString();
+ setUpStrings();
+ ]]></script>
+</html>
diff --git a/browser/base/content/abouthealthreport/abouthealth.css b/browser/base/content/abouthealthreport/abouthealth.css
new file mode 100644
index 000000000..3dd40fc24
--- /dev/null
+++ b/browser/base/content/abouthealthreport/abouthealth.css
@@ -0,0 +1,15 @@
+* {
+ margin: 0;
+ padding: 0;
+}
+
+html, body {
+ height: 100%;
+}
+
+#remote-report {
+ width: 100%;
+ height: 100%;
+ border: 0;
+ display: flex;
+}
diff --git a/browser/base/content/abouthealthreport/abouthealth.js b/browser/base/content/abouthealthreport/abouthealth.js
new file mode 100644
index 000000000..84c054bca
--- /dev/null
+++ b/browser/base/content/abouthealthreport/abouthealth.js
@@ -0,0 +1,141 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Preferences.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+const reporter = Cc["@mozilla.org/datareporting/service;1"]
+ .getService(Ci.nsISupports)
+ .wrappedJSObject
+ .healthReporter;
+
+const policy = Cc["@mozilla.org/datareporting/service;1"]
+ .getService(Ci.nsISupports)
+ .wrappedJSObject
+ .policy;
+
+const prefs = new Preferences("datareporting.healthreport.");
+
+
+let healthReportWrapper = {
+ init: function () {
+ reporter.onInit().then(healthReportWrapper.refreshPayload,
+ healthReportWrapper.handleInitFailure);
+
+ let iframe = document.getElementById("remote-report");
+ iframe.addEventListener("load", healthReportWrapper.initRemotePage, false);
+ let report = this._getReportURI();
+ iframe.src = report.spec;
+ prefs.observe("uploadEnabled", this.updatePrefState, healthReportWrapper);
+ },
+
+ uninit: function () {
+ prefs.ignore("uploadEnabled", this.updatePrefState, healthReportWrapper);
+ },
+
+ _getReportURI: function () {
+ let url = Services.urlFormatter.formatURLPref("datareporting.healthreport.about.reportUrl");
+ return Services.io.newURI(url, null, null);
+ },
+
+ onOptIn: function () {
+ policy.recordHealthReportUploadEnabled(true,
+ "Health report page sent opt-in command.");
+ this.updatePrefState();
+ },
+
+ onOptOut: function () {
+ policy.recordHealthReportUploadEnabled(false,
+ "Health report page sent opt-out command.");
+ this.updatePrefState();
+ },
+
+ updatePrefState: function () {
+ try {
+ let prefs = {
+ enabled: policy.healthReportUploadEnabled,
+ }
+ this.injectData("prefs", prefs);
+ } catch (e) {
+ this.reportFailure(this.ERROR_PREFS_FAILED);
+ }
+ },
+
+ refreshPayload: function () {
+ reporter.collectAndObtainJSONPayload().then(healthReportWrapper.updatePayload,
+ healthReportWrapper.handlePayloadFailure);
+ },
+
+ updatePayload: function (data) {
+ healthReportWrapper.injectData("payload", data);
+ },
+
+ injectData: function (type, content) {
+ let report = this._getReportURI();
+
+ // file URIs can't be used for targetOrigin, so we use "*" for this special case
+ // in all other cases, pass in the URL to the report so we properly restrict the message dispatch
+ let reportUrl = report.scheme == "file" ? "*" : report.spec;
+
+ let data = {
+ type: type,
+ content: content
+ }
+
+ let iframe = document.getElementById("remote-report");
+ iframe.contentWindow.postMessage(data, reportUrl);
+ },
+
+ handleRemoteCommand: function (evt) {
+ switch (evt.detail.command) {
+ case "DisableDataSubmission":
+ this.onOptOut();
+ break;
+ case "EnableDataSubmission":
+ this.onOptIn();
+ break;
+ case "RequestCurrentPrefs":
+ this.updatePrefState();
+ break;
+ case "RequestCurrentPayload":
+ this.refreshPayload();
+ break;
+ default:
+ Cu.reportError("Unexpected remote command received: " + evt.detail.command + ". Ignoring command.");
+ break;
+ }
+ },
+
+ initRemotePage: function () {
+ let iframe = document.getElementById("remote-report").contentDocument;
+ iframe.addEventListener("RemoteHealthReportCommand",
+ function onCommand(e) {healthReportWrapper.handleRemoteCommand(e);},
+ false);
+ healthReportWrapper.updatePrefState();
+ },
+
+ // error handling
+ ERROR_INIT_FAILED: 1,
+ ERROR_PAYLOAD_FAILED: 2,
+ ERROR_PREFS_FAILED: 3,
+
+ reportFailure: function (error) {
+ let details = {
+ errorType: error,
+ }
+ healthReportWrapper.injectData("error", details);
+ },
+
+ handleInitFailure: function () {
+ healthReportWrapper.reportFailure(healthReportWrapper.ERROR_INIT_FAILED);
+ },
+
+ handlePayloadFailure: function () {
+ healthReportWrapper.reportFailure(healthReportWrapper.ERROR_PAYLOAD_FAILED);
+ },
+}
diff --git a/browser/base/content/abouthealthreport/abouthealth.xhtml b/browser/base/content/abouthealthreport/abouthealth.xhtml
new file mode 100644
index 000000000..62b27e266
--- /dev/null
+++ b/browser/base/content/abouthealthreport/abouthealth.xhtml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+ %brandDTD;
+ <!ENTITY % securityPrefsDTD SYSTEM "chrome://browser/locale/preferences/security.dtd">
+ %securityPrefsDTD;
+ <!ENTITY % aboutHealthReportDTD SYSTEM "chrome://browser/locale/aboutHealthReport.dtd">
+ %aboutHealthReportDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>&abouthealth.pagetitle;</title>
+ <link rel="icon" type="image/png" id="favicon"
+ href="chrome://branding/content/icon32.png"/>
+ <link rel="stylesheet"
+ href="chrome://browser/content/abouthealthreport/abouthealth.css"
+ type="text/css" />
+ <script type="text/javascript;version=1.8"
+ src="chrome://browser/content/abouthealthreport/abouthealth.js" />
+ </head>
+ <body onload="healthReportWrapper.init();"
+ onunload="healthReportWrapper.uninit();">
+ <iframe id="remote-report"/>
+ </body>
+</html>
+
diff --git a/browser/base/content/abouthome/aboutHome.css b/browser/base/content/abouthome/aboutHome.css
new file mode 100644
index 000000000..ce8db3cce
--- /dev/null
+++ b/browser/base/content/abouthome/aboutHome.css
@@ -0,0 +1,431 @@
+%if 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+%endif
+
+html {
+ font: message-box;
+ font-size: 100%;
+ background-color: hsl(0,0%,90%);
+ color: #000;
+ height: 100%;
+}
+
+body {
+ margin: 0;
+ display: -moz-box;
+ -moz-box-orient: vertical;
+ width: 100%;
+ height: 100%;
+ background-image: url(chrome://browser/content/abouthome/noise.png),
+ linear-gradient(hsla(0,0%,100%,.7), hsla(0,0%,100%,.4));
+}
+
+input,
+button {
+ font-size: inherit;
+ font-family: inherit;
+}
+
+a {
+ color: -moz-nativehyperlinktext;
+ text-decoration: none;
+}
+
+.spacer {
+ -moz-box-flex: 1;
+}
+
+#topSection {
+ text-align: center;
+}
+
+#brandLogo {
+ height: 192px;
+ width: 192px;
+ margin: 22px auto 31px;
+ background-image: url("chrome://branding/content/about-logo.png");
+ background-size: 192px auto;
+ background-position: center center;
+ background-repeat: no-repeat;
+}
+
+#searchForm,
+#snippets {
+ width: 470px;
+}
+
+#searchForm {
+ display: -moz-box;
+}
+
+#searchLogoContainer {
+ display: -moz-box;
+ -moz-box-align: center;
+ padding-top: 2px;
+ -moz-padding-end: 8px;
+}
+
+#searchLogoContainer[hidden] {
+ display: none;
+}
+
+#searchEngineLogo {
+ display: inline-block;
+ height: 28px;
+ width: 70px;
+ min-width: 70px;
+}
+
+#searchText {
+ -moz-box-flex: 1;
+ padding: 6px 8px;
+ background: hsla(0,0%,100%,.9) padding-box;
+ border: 1px solid;
+ border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
+ box-shadow: 0 1px 0 hsla(210,65%,9%,.02) inset,
+ 0 0 2px hsla(210,65%,9%,.1) inset,
+ 0 1px 0 hsla(0,0%,100%,.2);
+ border-radius: 2.5px 0 0 2.5px;
+}
+
+#searchText:-moz-dir(rtl) {
+ border-radius: 0 2.5px 2.5px 0;
+}
+
+#searchText:focus,
+#searchText[autofocus] {
+ border-color: hsla(206,100%,60%,.6) hsla(206,76%,52%,.6) hsla(204,100%,40%,.6);
+}
+
+#searchSubmit {
+ -moz-margin-start: -1px;
+ background: linear-gradient(hsla(0,0%,100%,.8), hsla(0,0%,100%,.1)) padding-box;
+ padding: 0 9px;
+ border: 1px solid;
+ border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
+ -moz-border-start: 1px solid transparent;
+ border-radius: 0 2.5px 2.5px 0;
+ box-shadow: 0 0 2px hsla(0,0%,100%,.5) inset,
+ 0 1px 0 hsla(0,0%,100%,.2);
+ cursor: pointer;
+ transition-property: background-color, border-color, box-shadow;
+ transition-duration: 150ms;
+}
+
+#searchSubmit:-moz-dir(rtl) {
+ border-radius: 2.5px 0 0 2.5px;
+}
+
+#searchText:focus + #searchSubmit,
+#searchText + #searchSubmit:hover,
+#searchText[autofocus] + #searchSubmit {
+ border-color: #59b5fc #45a3e7 #3294d5;
+ color: white;
+}
+
+#searchText:focus + #searchSubmit,
+#searchText[autofocus] + #searchSubmit {
+ background-image: linear-gradient(#4cb1ff, #1793e5);
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,
+ 0 0 0 1px hsla(0,0%,100%,.1) inset,
+ 0 1px 0 hsla(210,54%,20%,.03);
+}
+
+#searchText + #searchSubmit:hover {
+ background-image: linear-gradient(#66bdff, #0d9eff);
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.2) inset,
+ 0 0 0 1px hsla(0,0%,100%,.1) inset,
+ 0 1px 0 hsla(210,54%,20%,.03),
+ 0 0 4px hsla(206,100%,20%,.2);
+}
+
+#searchText + #searchSubmit:hover:active {
+ box-shadow: 0 1px 1px hsla(211,79%,6%,.1) inset,
+ 0 0 1px hsla(211,79%,6%,.2) inset;
+ transition-duration: 0ms;
+}
+
+#defaultSnippet1,
+#defaultSnippet2,
+#rightsSnippet {
+ display: block;
+ min-height: 38px;
+ background: 30px center no-repeat;
+ padding: 6px 0;
+ -moz-padding-start: 79px;
+}
+
+#rightsSnippet[hidden] {
+ display: none;
+}
+
+#defaultSnippet1:-moz-dir(rtl),
+#defaultSnippet2:-moz-dir(rtl),
+#rightsSnippet:-moz-dir(rtl) {
+ background-position: right 30px center;
+}
+
+#defaultSnippet1 {
+ background-image: url("chrome://browser/content/abouthome/snippet1.png");
+}
+
+#defaultSnippet2 {
+ background-image: url("chrome://browser/content/abouthome/snippet2.png");
+}
+
+#snippets {
+ display: inline-block;
+ text-align: start;
+ margin: 12px 0;
+ color: #3c3c3c;
+ font-size: 75%;
+ /* 12px is the computed font size, 15px the computed line height of the snippets
+ with Segoe UI on a default Windows 7 setup. The 15/12 multiplier approximately
+ converts em from units of font-size to units of line-height. The goal is to
+ preset the height of a three-line snippet to avoid visual moving/flickering as
+ the snippets load. */
+ min-height: calc(15/12 * 3em);
+}
+
+#launcher {
+ display: -moz-box;
+ -moz-box-align: center;
+ -moz-box-pack: center;
+ width: 100%;
+ background-color: hsla(0,0%,0%,.03);
+ border-top: 1px solid hsla(0,0%,0%,.03);
+ box-shadow: 0 1px 2px hsla(0,0%,0%,.02) inset,
+ 0 -1px 0 hsla(0,0%,100%,.25);
+}
+
+#launcher:not([session]),
+body[narrow] #launcher[session] {
+ display: block; /* display separator and restore button on separate lines */
+ text-align: center;
+ white-space: nowrap; /* prevent navigational buttons from wrapping */
+}
+
+.launchButton {
+ display: -moz-box;
+ -moz-box-orient: vertical;
+ margin: 16px 1px;
+ padding: 14px 6px;
+ min-width: 88px;
+ max-width: 176px;
+ max-height: 85px;
+ vertical-align: top;
+ white-space: normal;
+ background: transparent padding-box;
+ border: 1px solid transparent;
+ border-radius: 2.5px;
+ color: #525c66;
+ font-size: 75%;
+ cursor: pointer;
+ transition-property: background-color, border-color, box-shadow;
+ transition-duration: 150ms;
+}
+
+body[narrow] #launcher[session] > .launchButton {
+ margin: 4px 1px;
+}
+
+.launchButton:hover {
+ background-color: hsla(211,79%,6%,.03);
+ border-color: hsla(210,54%,20%,.15) hsla(210,54%,20%,.17) hsla(210,54%,20%,.2);
+}
+
+.launchButton:hover:active {
+ background-image: linear-gradient(hsla(211,79%,6%,.02), hsla(211,79%,6%,.05));
+ border-color: hsla(210,54%,20%,.2) hsla(210,54%,20%,.23) hsla(210,54%,20%,.25);
+ box-shadow: 0 1px 1px hsla(211,79%,6%,.05) inset,
+ 0 0 1px hsla(211,79%,6%,.1) inset;
+ transition-duration: 0ms;
+}
+
+.launchButton[hidden],
+#launcher:not([session]) > #restorePreviousSessionSeparator,
+#launcher:not([session]) > #restorePreviousSession {
+ display: none;
+}
+
+#restorePreviousSessionSeparator {
+ width: 3px;
+ height: 116px;
+ margin: 0 10px;
+ background-image: linear-gradient(hsla(0,0%,100%,0), hsla(0,0%,100%,.35), hsla(0,0%,100%,0)),
+ linear-gradient(hsla(211,79%,6%,0), hsla(211,79%,6%,.2), hsla(211,79%,6%,0)),
+ linear-gradient(hsla(0,0%,100%,0), hsla(0,0%,100%,.35), hsla(0,0%,100%,0));
+ background-position: left top, center, right bottom;
+ background-size: 1px auto;
+ background-repeat: no-repeat;
+}
+
+body[narrow] #restorePreviousSessionSeparator {
+ margin: 0 auto;
+ width: 512px;
+ height: 3px;
+ background-image: linear-gradient(to right, hsla(0,0%,100%,0), hsla(0,0%,100%,.35), hsla(0,0%,100%,0)),
+ linear-gradient(to right, hsla(211,79%,6%,0), hsla(211,79%,6%,.2), hsla(211,79%,6%,0)),
+ linear-gradient(to right, hsla(0,0%,100%,0), hsla(0,0%,100%,.35), hsla(0,0%,100%,0));
+ background-size: auto 1px;
+}
+
+#restorePreviousSession {
+ max-width: none;
+ font-size: 90%;
+}
+
+body[narrow] #restorePreviousSession {
+ font-size: 80%;
+}
+
+.launchButton::before {
+ display: block;
+ width: 32px;
+ height: 32px;
+ margin: 0 auto 6px;
+ line-height: 0; /* remove extra vertical space due to non-zero font-size */
+}
+
+#downloads::before {
+ content: url("chrome://browser/content/abouthome/downloads.png");
+}
+
+#bookmarks::before {
+ content: url("chrome://browser/content/abouthome/bookmarks.png");
+}
+
+#history::before {
+ content: url("chrome://browser/content/abouthome/history.png");
+}
+
+#apps::before {
+ content: url("chrome://browser/content/abouthome/apps.png");
+}
+
+#addons::before {
+ content: url("chrome://browser/content/abouthome/addons.png");
+}
+
+#sync::before {
+ content: url("chrome://browser/content/abouthome/sync.png");
+}
+
+#settings::before {
+ content: url("chrome://browser/content/abouthome/settings.png");
+}
+
+#restorePreviousSession::before {
+ content: url("chrome://browser/content/abouthome/restore-large.png");
+ height: 48px;
+ width: 48px;
+ display: inline-block; /* display on same line as text label */
+ vertical-align: middle;
+ margin-bottom: 0;
+ -moz-margin-end: 8px;
+}
+
+#restorePreviousSession:-moz-dir(rtl)::before {
+ transform: scaleX(-1);
+}
+
+body[narrow] #restorePreviousSession::before {
+ content: url("chrome://browser/content/abouthome/restore.png");
+ height: 32px;
+ width: 32px;
+}
+
+#aboutMozilla {
+ display: block;
+ position: relative; /* pin wordmark to edge of document, not of viewport */
+ -moz-box-ordinal-group: 0;
+ opacity: .5;
+ transition: opacity 150ms;
+}
+
+#aboutMozilla:hover {
+ opacity: 1;
+}
+
+#aboutMozilla::before {
+ content: url("chrome://browser/content/abouthome/mozilla.png");
+ display: block;
+ position: absolute;
+ top: 12px;
+ right: 12px;
+ width: 69px;
+ height: 19px;
+}
+
+/* [HiDPI]
+ * At resolutions above 1dppx, prefer downscaling the 2x Retina graphics
+ * rather than upscaling the original-size ones (bug 818940).
+ */
+@media not all and (max-resolution: 1dppx) {
+ #brandLogo {
+ background-image: url("chrome://branding/content/about-logo@2x.png");
+ }
+
+ #defaultSnippet1,
+ #defaultSnippet2,
+ #rightsSnippet {
+ background-size: 40px;
+ }
+
+ #defaultSnippet1 {
+ background-image: url("chrome://browser/content/abouthome/snippet1@2x.png");
+ }
+
+ #defaultSnippet2 {
+ background-image: url("chrome://browser/content/abouthome/snippet2@2x.png");
+ }
+
+ .launchButton::before,
+ #aboutMozilla::before {
+ transform: scale(.5);
+ transform-origin: 0 0;
+ }
+
+ #downloads::before {
+ content: url("chrome://browser/content/abouthome/downloads@2x.png");
+ }
+
+ #bookmarks::before {
+ content: url("chrome://browser/content/abouthome/bookmarks@2x.png");
+ }
+
+ #history::before {
+ content: url("chrome://browser/content/abouthome/history@2x.png");
+ }
+
+ #apps::before {
+ content: url("chrome://browser/content/abouthome/apps@2x.png");
+ }
+
+ #addons::before {
+ content: url("chrome://browser/content/abouthome/addons@2x.png");
+ }
+
+ #sync::before {
+ content: url("chrome://browser/content/abouthome/sync@2x.png");
+ }
+
+ #settings::before {
+ content: url("chrome://browser/content/abouthome/settings@2x.png");
+ }
+
+ #restorePreviousSession::before {
+ content: url("chrome://browser/content/abouthome/restore-large@2x.png");
+ }
+
+ body[narrow] #restorePreviousSession::before {
+ content: url("chrome://browser/content/abouthome/restore@2x.png");
+ }
+
+ #aboutMozilla::before {
+ content: url("chrome://browser/content/abouthome/mozilla@2x.png");
+ }
+}
+
diff --git a/browser/base/content/abouthome/aboutHome.js b/browser/base/content/abouthome/aboutHome.js
new file mode 100644
index 000000000..003755d22
--- /dev/null
+++ b/browser/base/content/abouthome/aboutHome.js
@@ -0,0 +1,565 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const SEARCH_ENGINES = {
+ "Google": {
+ // This is the "2x" image designed for OS X retina resolution, Windows at 192dpi, etc.;
+ // it will be scaled down as necessary on lower-dpi displays.
+ image: "data:image/png;base64," +
+ "iVBORw0KGgoAAAANSUhEUgAAAIwAAAA4CAYAAAAvmxBdAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJ" +
+ "bWFnZVJlYWR5ccllPAAAGrFJREFUeNrtfHt4VdW172+utZOASLJ5+BaIFrUeXkFsa0Fl++gDnznV" +
+ "VlvFxt7aqvUUarXtse3Bau35ak/rZ9XT26NtfOvV6wFET+FYCQEKWqsQIT5RCAgSXnlnrzXneNw/" +
+ "1lphJSSQ8BB7bub3zW+LO3uN+fiNMcf4jTEX0N/6W3/rb/2tv30smtnXB3zmRi2FQakxQNKX3WkW" +
+ "9S/tgW3HLpmQM543A0BWVSHMYGIwOTDxzxrOf3/RQQfMZ2/SLAvKhTFVBGUqKFONH2QAzwOMF38a" +
+ "wHhYZAxWAqhe/iszp3+b970d/sInc57vz/J8L2eMB2MAEYkBQ6DQ3dRw4dq7AUjcP3rAfPZmLWXC" +
+ "LHKoIAcQAUxaB5EaEfc6AEBhjDEwmcx43/fO9HxT4vkReBIAAZgjgodW3NcPnn1sHgD/iHknn+0d" +
+ "6s8XEUhsXXac/34WAAGw8afuT8GZ3X055YeSJcIsG+pMZwFn0UihezRofPt3G54f/0E8cNMN+Myo" +
+ "8jVTCgYd823PLzrPeIBnABiUQ1F+UoWsVOYb33mkoKp/7/dKyT0AGc47X4s0sjBEoLxbBqAQAMfW" +
+ "Rfe38B4BM+VHUkYOs8mi1FrABbK4dcvK73zwp1M3xYPOxANKBqbpCdXNGb0UwPKRF74xpfDQ0t+K" +
+ "54+IvlKoahmAhaO/mv/ZmicG3tqPgT61ZM2dZMQJOYhIdByRM/F3dCCOox4Bc3oEliqyyNoQCPPu" +
+ "sXceKZqRsigu7pwaWBowiRb46+f9Q1V2wl1nDx09/R7jF30x9adNlN8yPx4DHwht+B/cBIBoRqeI" +
+ "E4hE/oshTcB0wNbT6/o/zrhFyohR5ZxmrVWE+fDxdx4puhGAH4OkPe5B6pykeJAc/7cDEMZ/095Y" +
+ "870P339m+BXs2v4kbCFsm9u2vnpJ3bzR7wAo2B/R2v+PjSnyXcRxtOLUSXFxwAFz5i2SZUIVO82S" +
+ "BWye/vLOIwNvjL8OYqCEfXCmJAZPHkC7sK1REbj2+lmbq86qTVmmfuuyN2cTiREWKCvACgml9kDL" +
+ "7HQksehsZmSdA6yVpsa6P38v3swg7m4vN1dGXrThKGP8yS5fP33j/LEvxKDbl2f2A0YFCtkZQDOa" +
+ "PjLAnP4jrmBGjh1AVhG2ttxfX33++vjY2eeNXf/siLUAzgEwMJZrY2vF/Vu/t4BRqCqgCmj07wMV" +
+ "HXUCzJQfUlZE72ICnANcqNj21h8eiK1AX46gXh29KT9H+rd9XxBjYGCgig7QHOgjPgMAKigXQZYp" +
+ "si4uCOc3v35zY2wF9ufGSgxA7fdd9g8ho9ol4P4ojiQWnSUMMANECrJNy1NWYH8eGfsEvJbLv1IK" +
+ "1XIAUwEtA0xplJMwjcaYlTDeShg8dOgjj6/cJxNYfWIWkHJoh5yyjkSZ8RbB89YBZq4/pXafGeuz" +
+ "b9WciXJxo2B2houqgAjABJCLOwFMqFv57+bBxMIAJm1det3avnl1OYCLAeSgWhofaY1QXQSRuYc+" +
+ "/OiD3QLmUzNdqTBKhRVMADsF5beuToXJB90KtFz+lVIVniXOVUAUqjpXVB4WwPjGTPB8/0zjeTnj" +
+ "ezl43szmKy6vNkDF4MeeXNc3oJyUhfAMkJsJkSxUVrLos6o6z/O8Ucb3phrPzyHKeVTwkpPXseg3" +
+ "Cqe+1SfG+swfaw6KGTAoJ5eyGF3IBeEIJB2AcXxb0FI/L45uFQBMGiu6Z3ai9eqrclBUClFWVatV" +
+ "5GERNT5wEVQnQLUcIuVNX75kFjn60rA5c1d0AoywlkcxfdwZ2LSgbOmBZAv70povu7RcyFUqcZYd" +
+ "Pbxix44fnLv8pbYUOWh+P3ZM9uJRo34xoLDgq8b3YTxvqhqsaPzyJTdmn36msjdyqPqkMhWqBFGZ" +
+ "MtV8uDX4zMjp2zemyEoPgGn4zyOvGzy48A54GcD3Sz1jFrqqE+4uOOvdmb0ASlYEs5mQE9afUdhy" +
+ "0yv3lHzwya/8ZcjgI0+5yssU3QKYkgQ4Ivp60LL1n8kBQfOWuvdnj6uLldgHQKoKxU7HV/eg2y1X" +
+ "XXmXEs1U0ZVb29o//4k5c5P5eQB+s+68aVeUFBTcCxUoS6kRWfjhueecc9SfX3ytA9QTr7eVACqY" +
+ "FDYEwnbB2qcHHg6gLY6ODhpomi77coUyVaojhKH9+ZHzF/wqXiztEg34APxNX/jCvQOLCi83fpy8" +
+ "UsCJXHLYnGdn785S0uKTyyBUBXJZcW5x4bSN56ciyLQcD4Bf/+ThVwwbUvRb+JkoswqAWX5b9Lm1" +
+ "M3uSM/UnUiaCKiZk2blvvnxX0ePxuBNAmpMur51wyLBPzjVeBBoVwIXBk6vuP+SG+LkcuwkWAA96" +
+ "/JjZKnKxkACkkFb5Nztz220xX9bJlWi+6opKFalQlpqlmzZNu6B6SaJ0knKJ/DW5qd8p8TO3x6AB" +
+ "qza1EE06cdmy9wDAY5LjmBTMkQnUnZ42H0ywNF52aU6FK4UY5NySI+cv+E3MCnMM5HyqtwFoO3rB" +
+ "gmuDMFjGjiCOIEQwzH9c+7lzju+JTaYlJ2ehUqXMWWFqeurFxqsAFMVf25Ss9kTOEZdvebClJbxT" +
+ "yUGZoEzwlL/b9tzRX+pOztSfSBZApSqyIrL45buKnkaUJEzLCN5+csxr+ab6fyILkI2OIZYBlx9/" +
+ "2bYvpLgw2+EqKLKdwoceVKJp+tfuEpYKZcaW1tZbLqheEsbj3GV+oxdV3x0GwQZrHUIiWKIST3Vm" +
+ "DG54zFrKrBBWiGgSyx9Uv6Xh0n/MKlGlOII4h80trQ+kuJt8HGklZHg6FZF/Y/uOb7O1YOvAzkGt" +
+ "Kxmoehe6SYNEpkErwZIFC4I2fuLKf2tLtDOPzumPhA6wAPJDLt1yuzjaAEcAMUCMApXfvPP7IcO6" +
+ "gkYFs4RRpgy49qanUsAPu/T8W48e/YwL6S/kYtBYwM8U/yu6KVlQUShr9CkKyK7b1vDVy0qVeaYy" +
+ "gaxbdeK85/8a/z7sYR3zgXM1gXUInEPoCEw8PR6z8YQxaidQPh6RrgrPEOZS4chKjFuydEEKFD1x" +
+ "QgrAnfO3V98Jw/B5dhFgmByU+MK/nnrq6K6gcQtPyqlIubJAibCxPv/fsVVNgCI9yGEAQdBq71NH" +
+ "UEdQIoBo5PBBeklazuQfSpYFM0UAFsDmd2yMf9+1XkUT3otc8AiRwpFChCBCI0detGbSLtYr5uw6" +
+ "tk26XctZwgxhRt65ZSmr1t389M1Jk85wzKcHRAiJkCfasDnI/0sMGN+jlLMrAigMhp0+f+TBBIw4" +
+ "milEYOcQBHZZAoZeEIgKgIIgeJbD2MqEFhxaDAFmdAWMisxQFigzlAUnX9e4rA9yeHuTna3koBQB" +
+ "RogxwOPvxNbQAAA7VHQEFKSQKEFIu4lA5d3HiiuFNB4XQZlhUHBK11QO0oRdD7ouROVCkeJZG7ak" +
+ "/KBOYHlz4sTy1WVlVY5oYego2+bs82+3tFw6YcVrp01dteqpxNfyhKQuGlxCMSsKBh570ABT/8XP" +
+ "5dhRVpyDWAd2Ns0O9yrhWdfcMpvCEByEoNCCwhBgvgBdM+PM5TH5FPW+1ZLo8de2viehe12dhVoH" +
+ "OAtDPO61O4o+kYCTnE5wVuGsxlzKHul7BUDKdomKgwpB2QHAyNiP2Dl+0Z2WRXZ9YP0F55WJczvX" +
+ "0jp09U3fLiurWD1+/NqQaHZIVNbu3O1vt7aM+fSqVRWXvPvu0pRldwAkQ5brjO+NMh0kgMIvGjYZ" +
+ "wIKETPxIrYt1U5M8iThKJil9yZGc++ab298dP36Jb8wZohqhQHRErKEeAA6fG5FT5yIlYYI6tzfO" +
+ "vtiQni3MYDw0ChqEgUMyejyAdwGwDeW4ZI9FAGQOmwzgv/cERmZbDXhnKBNUGMJkUhGVduSSJJ1P" +
+ "6rw8HIalJo7ilBkchgCgL48fVzLceDc4kZnWUdap1AQi10x+660n4jXyk1M7ZXEZgHhMUkMO4Njp" +
+ "hQGMf8h56Fx++ZE1a+1xZC2Szjs3sk9uUEhUbSMvP3LeyOGZ0tKJiearo1J1DHVRPYmS7JUcG2g1" +
+ "pxxUsooBnpmQWAOb10YbKGygcKFCZOC0XqxrRKokCBQG5euX77In2k1P+2hhWEZBAAoCuCCEcW7E" +
+ "2xMn/m6oYo0jyjnmuc3Off6UN96YMvmtt5LILSmQ61r3xAA0I+xqPBiIejAd1f7e2MPPfvm4LQs/" +
+ "89a+bP6nZuSzfsaU+T7g+UBixYQVRFGS01kFO22srRy0EgA4CEvFRHS3MANMY/fGbybmlQqAFSBV" +
+ "sCp8kWwCGA5dqefFShnnRV77ecHYU37iXuqLoB0tsuIo34v3NfJR1GlJsrnOuiXGy1y8k+rwxh57" +
+ "3srSD/6rbLdra7yMqgjUCGAULR8uWr0LJPYAGApCeCbKNygLPKIxJ65YOSU+YpLUUCYGiqBzQVy3" +
+ "Ft1zbevnJl60UARqACgcVDo9ZZr63Mqua68QxlpmrWJC1FmrmLSKCFVktcpZrbKhzg4D26E5Lgjg" +
+ "8vnoMwwh1hU/dvTRo/qcDyJqcESw5Dp6o3XNHVrqLDSubAdFjuXwwWZcX+Wc9APboKxQUoiLurXa" +
+ "IYfCpjlCDsoxZ6OCouLRt+xpbY3nA8aDMR6E2+9vffOWxl02cQ+Bbdjevt7l83D5ABRaKNHYO484" +
+ "YmgMkoJ4jElCOL8Lz9NN87YumrRDxc2DElQZKgIVhZcZcO1hZ74wtK/H0thvtuXGXdM2S0S/ziQ1" +
+ "FPJiG7pHwvbgDhtKnQ0VNhCEeUHQLmiuf2fymieGvJGY8DCfX+yCEC5xWIlwtO+P6+s4VESJGS4+" +
+ "liwxKjZ/2FGRZvPhYgktxEZdHWOAr2P34ihWIQWTgJ2CnWJbo9Ymz1g/5+h1QsF9wgKJ19Z4hV87" +
+ "4fKNE3cnx8v4V8H4UOjqhvce+zW6qdWVlOvSjQsDlw/WUT4A5QNQGIJDizMPHXR+CiRBb4GSzlYr" +
+ "26Z7vYKSC42nUOPBqA9VU1I0ZOJPEYWj1NvVW/3AoEUAFgO4IzZ1hYk2jf9WUw7IjCIXHUVhXrFp" +
+ "/sQtKZPIoXXr/PjoSkZeoHo6gP/bFyeciECqcHG3IrXp37a2SF3xQNPxRAXgq5nS1bHsDWCYALYA" +
+ "u+h0W/impI8Pad9ec/vAoWVTjV84Nsn5FAwcvmDMN5rOqf1jyatdHzjuGjvThloKYH3b5qVXt775" +
+ "44ZuN1QEKknF3a6ImfDee4tWjBrV6R5Qoeq1AP6Avaxx8gDolhdPXAh2qzQmZFQ4ZhALrj/mvLpT" +
+ "+qhxya0BP5VVZQBkA6jNR0AJ2xUUcjKGjsx4k3PVYUwaJU6rJ3reLiHlHppjBjF3fLYSzU/noEZ8" +
+ "3611VusoVJBVsFWAdezim/3jemSFe+SNIsvCpAhCXf7TBZI+PnTr4nO2t2xcME3ZroYKIouEEqDo" +
+ "xfHfav/GxOttFgBOucGWll0XVqrqXYDWNLz3aG7bsovWp4i2TvkhScLqNBezq/M/zxLBxV2Yx/75" +
+ "yCPP6usc04CJ+B3bcLMwQTiK+0UIwgz1ip8+4pyaYX0x0SnWMkjnYGygkm9nBO0MGzoI2TTDyQBw" +
+ "7ubNawPmeZYZNt5wZhrxX8OHX9yXSTJzGcVgIWasbs8/hc7XRzXM670cg0Vs5H+MHm6u74ucrb/K" +
+ "lAlFPoySoqFFn+rm+OCGV762df2cYWe4fP0M5qDWhoowRIm1/h+s1YZx3wrVOV1LDhXMaGzfXntF" +
+ "46vXtMQRS/clsqRRT9SNd0GMBo6edRStZbKeg4D//ciQIcP2CTDbqsdVKQePq1JMFkXxv4qO9AaM" +
+ "fPGoaeuG9kXp0LkU0wGgMFC1gYAdAeyg0m3IrE3W3mtTvodjRpHq9X3xL4h5Qsq63P/z9ra6LqSc" +
+ "vvmBPkwOTex2lnf4wNee/47fa99NGGVJ8Zl1qP3UPfwkdr15mDDV+Y3Pf+Kh9c9kz9pee89J7dve" +
+ "vaRt+7qLbVv47y5UUKggp3BB/okNz0/aHI8332OaIgELxWDpptQtt6X+Qcu03nVYGQYxjxzl+7/e" +
+ "GyvjdYrCtv31JiW7QTjy6qWj83jF4AeP/MLaodiHRtZBXAihEEIWkq4eSgGmvKGhqpX5d1YEVhiW" +
+ "BaI6Zf6QITN7s5ELhw4tZZavkwhIZMOC1rZfo5s64nPv4+1NzXot2/hYiqKckglH4/7eRojCOosp" +
+ "St6u2ijfS1Hv3I0SdVy5aam9ecumBeOqN8w7aRkxSlMVdRDmRHa4m5xWPKPEusUA6maIrcy/cCKw" +
+ "InASKaCoXrlo2LAH+xpMpAEjLauu2ObaNnxVmZqUHaI8SaR+KnIhTPHCo6ZtOn6vk4qUPNNGnV2P" +
+ "J0ptENweMq92zHBMcMwwIrfMLS6etKdJEnMlCYOZm9YE4dUPkWvsIUckJ/+SZwd5PCEOEBc5rh7j" +
+ "grqf+VfvSc7mO/xZSihVAra3YMY/PqqrUhZVe7C8yRHTBqAVQJuQN5idgJ2ASQAz4PJjptWevKc0" +
+ "RZQ0TQATRWDd/dmFDQ2VeaLH0z4dRVTK9EXZ7IqFJSXH7W6eLw0blntp2NAydGOSqPGVs/5mW9Zc" +
+ "JGKbRSxELIRDCFuIuAmiBa8eMW37rcdc1JDtM+3PYdSp43k9/ulPgmDrsnz+vFBktRWBZYEVKSlU" +
+ "feH5wYPP7u5Hfy4uzi4oLq50IjkSaXrf2vIfBPnV6PlKiwKg0XfyNe2BPkmJ8+oUGeh/bLjNu7En" +
+ "0Gy+w5sppLcyKRra9IZJ98hTvciop9MPSSFUwGTnEjHICsgpyKHYHzjquWMvrJ+wewUENPFjCIAx" +
+ "k3uStyIMbw5FVieWJvJpBE5kgqq+X1VcPGdRcfHMxSUluSUlJbmlUZ+1tKRkLRGVnrZ9Rw12rSLt" +
+ "sDpFg8vmfbpw0HH3wcuMMSaiao2XAbwMjPFhPL/ReN6DfsY8tHHekN0WXR929vqsCpWruFshPEqF" +
+ "o3IyADuWTxgea1rYTbRVeEMmc+SnCwp+OcB4l3kmLq0D4BnzkA/MMUBjvDMXC1DBqlkCFr9N9E//" +
+ "HIZpPyDsQVuTFwsMfP273k8GFeLbvo9izwe8DGA8VMPgIc/D2piALlPFDGWUMqNuazOun/RbeQU7" +
+ "L/zl0cfC+SPOXjG84NBRawCvJNoSE7PiBgr5Xx/MKf7jLnzIbUPKlHVF5C11KgJfD9+shY8Vxjd3" +
+ "0780rEvP8bFDDvnVQGO+lU5MeTDwzM5aTbOzNyrw/XNbWx9JFLknk+sjqjobUHJq9XS/cNj3jZcZ" +
+ "Ac9PwBIDyAeMD2O8RhhvpTFYqYpGqMQOM2UhlFOhsvjfgNJ6ofxyoZaXbHPt8mDNjDU9ACYBbyGA" +
+ "AT/KZEZ/MpO5qciYyRlgROeJGSh0nQCL21Ufmx4EL8dMpqScRt4DFVAAYMCtORx+0Rhz7aFF+GJB" +
+ "BmNM/JKklGo1KlBtHZ474U79P9hZOZcQYb0unD/mwu05qADCZwE4C8Y7I3kTk4kFx+mUuzfMKf5e" +
+ "+rn+rUMq4PR4hFII0gw0xpdvGAWGoDqHf9m8IuV8m2Qtf1pQMPok37+50JhpHlC8EzwRcAzwOqs+" +
+ "Vkv06I+da04nInd3RvuxgCIAhcUTF5zvFQ79oucP+Cy8zIjE6qQnt5Pviu5IqAogVKNCNSrBUte6" +
+ "blnrqi/Vo3O9rI3Pc7cbP6sgGQcAf7rvl3zK908uBKjAGK5jrrmNKKHj/RS3E6L3V2USLUzkZAB4" +
+ "i75pTivwwQMyoKYQ685+QOtScvzUHPbIlJ54ZVsuDPTrZDmnQqUQggo1qkoNRDyFeJ6XGQfjF0fW" +
+ "3O9YWxW6adNzw36Dzm/JKEJ0k7QgtfiSygd1vSrkdZ3jlb6fneT7Y+MN1xrmVX9gbkw9q1MdsemF" +
+ "U5wkpwqSRSw49gfZAcPPHOsVlIww/sBjjPEVnqfGZEQlWKVCjWK31TW/dv56pCruU126TGxPl+US" +
+ "IrAgNQ7TQ+pNukQqfalLNimApvMt6CZMTvsiu3VOJ17XnrNWZ9m85oK8Qmz4sFB+CeXrF29dfOqG" +
+ "1PwKs6fOKyvKjrnb8wrHGD8TWfCOEoX85zb96dgXY9leN2NM+y3SJZG4u7XsSldIykFPz09NHxbR" +
+ "T2U3M11AsKf8aRqtnBqQoG91oWkGOS0/XaQo2Pf3u5mUDK9LukD7Mv5Tv9teSQ4VzipsINUtW9Zc" +
+ "t/mFiRu7WbcOuQNP+MXQ4hGX3mEKBl1mjB9bbwAqSz6cf+TZ8Qaabta/u6hM92ItpZs5dvyor5R/" +
+ "dwvp9QAa6eFzfxRlpVMk2mXh93czeyPn1Bn5ShWtYAJsyEve+OPgC7Hzmgx3USDtejQedlbtDX7h" +
+ "0Ns6HChV5LcvP7rpb1+qx/690dHrtewL05c2c7ZLtrM91fOpDGjXyvT9+WYBPQAg3NPcey1n4vVt" +
+ "FUJSIfGNjJZNy2ekkqzpazIJOefSoTaA9q1VY+5Wbvs9NAoYVBkFh5Sesi9lJ/u6lt5+WETpoi2M" +
+ "PpZU/k9szmKGtVGRWBjQ6g3zP78pxfSGKb+tJ4LPAsi31S/+uXCUlVZmCIc+DlI15L4Cpr/1FA1d" +
+ "0VLqAilzgcCGChdQc5eoTXqpkNS66hv1YLsUElURiG1sOZj7lunf3v3fwlBKjRfX9EjEHKcscV98" +
+ "D40zRKIqgEpz4yvTVnfjU/VbmL/r4yhwTTbPCNsZNi8g50/OnvbCsXu5wQqVURCBuOb7seu98n7A" +
+ "/L23Tc8NX8mW6pL73UoOhYPH/GJv/I7Dzlqbg5pRUG1q++A//+Ng+4f9gDlATVzLHfErZiHioKrn" +
+ "H37uhgeG597sdYnIYeeszypQqQawre9dHNbd0Yj9/5KnfsB8DJpuXXj8Q+ryj3dUZglD1Uz3MsWv" +
+ "HX7uh1fv6QGHn7upAmrWQpEV2zSt+bVptamw+6C9VaP/hcoHrvkABgydUjPLywy6Oboh6HW6PgLj" +
+ "LYqStqYRQHKDMQflMhXOQrnata27tvGvufrEn8ZBfmdPP2AO7NpmAAw85B8qTyjKlt1svAHTjPGL" +
+ "k4w0jAcTAyllnBoh9Kxw/tEdS8cuT0WyH4vX1PYD5qMBzQDE2eFDxz09zsscWuwVHX6a8YwaFAiM" +
+ "NAkHr4vdUdf82rQN6JwnSl4N4vAxeKdxP2A+mjXuKTvcXcY9TdOnyxPk4zKZ/vbRAqe75C3QfZZY" +
+ "0P/y6/7299z+H4QrdGsoib8JAAAAAElFTkSuQmCC"
+ },
+ "DuckDuckGo": {
+ image: "data:image/png;base64," +
+ "iVBORw0KGgoAAAANSUhEUgAAAIwAAAA4CAYAAAAvmxBdAAAVhUlEQVR4Xu3dd5SU1d3A8e/vPs/0" +
+ "2crussBSdkHEAgoomEQSUTAW3hRbfMUeUwgSj9FoorGXqDGxBHvMazRGE0KsBQuiEVRUEEEM0pfO" +
+ "1tndmZ32PPf3knDCUZAlIYsxOfM553f2v91/vnufOzP33BFV5TOnQFQ1snFN/YCVb88Z6S2dd1B8" +
+ "3Qf7lTSv6R9PNle4uXQEVNRxvUy4qL29pPeGRNXA5d6g4fOLhoyYN2C/oe8Vl5QmAoFAnm72GQqm" +
+ "oKO9vXj5e/NHtr48/fjq92eOq2xYOsixvuMpKFuhfJywjQMYI5oKF7evrR09t/LE7z3Ze9TYZyPx" +
+ "+FpjjPdfEkxBY0ND9ftP//7EkpceOLNm/cJh+J6rylYWcIwSiCHhuEo4ggRdMCLq+UomK5pJq2Y7" +
+ "BD8HqoIAAmKhPdKjuX7EMc9WnfCde/YZOfot13Xz/6HBFKi1pdmlCya23Dz5PPeDN/eygCqAqIn3" +
+ "ULduiAb2Ha3BfUYgJeUgBhxHRAwgoupbfF/wPcXL461bRX7xm5Jb8q7Yhno0lzUYMIANx9Lh0y99" +
+ "svjEc292YkXzAfufE0yBse0tX+qY+uNrOp/+9SGo5yggTlADQw72I4efQGDf4Wg6RW7xO5Jf8g7+" +
+ "ulVi21rRXAr8HKpWRBzFCSGRIpyKSnX6701wv0PU7Vunms2RmfO0ZGc/Z/zWjSKiAqJOdV1LyUVT" +
+ "7wkdcuQvENP8mQ+mQGPZt2ZelLj2nCl+Q30ZAqijoVFH+rGTJiHROJnXniE75znxN64yms8AKghd" +
+ "062DEZVIqQbq9tHwYcdpcL+DNDvvFUlNv1dsywYHA0jAjx512lslF956vkSL5n5Wgymwfq+O/7vx" +
+ "jvZfX/0/+FkXC27N3n7xlOvVlFdp8pFfSnbuC0bTbYKqIOw+BcSoKeut0WNPtZEjjtPOx++X1FMP" +
+ "GPysAXD777epxy1PXuj2qXsEsJ+hYArUy9e2Xn7GtPTLj44AFVVHY1/7tld0+g8l+cht2vnE/Y7N" +
+ "p0S2htJ9FEDUlPWxxZOusE5VjSRunIK3YbkrAhIpzlRMfeGy4P6jbwH8z0AwBZrPDWqacvQzmfkv" +
+ "D0ZETbxCS3/wC9/t1ZeWq78t3oZlDqiwp6nRyJiveMXnXEL7fdeTef1JV9UKKlp118wrQgeNvX5X" +
+ "0Rj2uMJjqOmik/6UmbclFkSdylrb4/qHfU0naTzvK463fqkLKijo1oGt0/3ESudrT7jNPznTxL8x" +
+ "iehXvuUhroJKw6RxV+aWzJ8MyL9vhSmIJm778fT2h244CiPqVg+0Pa64TzPzZtv2X18XUD8jAIiB" +
+ "3nWEK6rBDaHZTmyiCb+lGe1MoGpB6FZOWR+/7KJbbXb+n0lOv8tV64mJlnX2mr74ZKei11PshMue" +
+ "UmA6X3nyqrbf/uxIAKe4l5ZdcqdNz5vNllhc9TKCAIAaQ6puNLEzzqN86EhQRTs78BvWkX3/bTpf" +
+ "mkZm3p/RbAoM3cJrWe+03PB9yn881drOlJd85gHXT7VGG77/1TvK7n1pRThe/MGnuMIU+M2bj91w" +
+ "wrBHbUdDnEDUVlx2n29TbbT8/AIXLy18hAQiFJ8wmdD44wnvPwoxZvs9ENlFb9D2qxvIzH0BxNId" +
+ "VMGtGuBXXPNrm7j7OskueNkBKDnjkudKp1x7ItD5KQRToNavaLzgGy91vjr9ABAtPuUCL/LFo2m8" +
+ "8ETHJlsMwsek9zqEztMvRbw8TjBMqLSU4spKiquqicVjiAgANtVBx8O3kbjvOtTPgPCvUwjufZBX" +
+ "ftEt2njBScZv2+gYN5KvfvCN84N7H3DHpxBMQerNmZc3nHvU5ajnBGqHedW3Psam848jv+I9F2FH" +
+ "4qA4gIJvkHgZgeGHEvzSUZSMP4FQccnHVpvk0w+Seu73ZN57Hc11guFfo6JFX/+uFzpgNE1XnOUi" +
+ "KpEDvriy4p4XxrrB0Jo9GExB0+bNtanvjX/VX7mor6jR6rtmeOk3ZpJ46CZXRKWrx4MTK6fkrB8S" +
+ "n3AqTnkVuAFEgO0qU1Xw8ngbVpO462o6ZjyCGMu/RB3tOfUZr+03t5B5+/kAIhq7/g8/rTrqhEv3" +
+ "YDAFCx+889qiWyZfahVihx2fL598haw7ebRRmzbshCgEBgyj+rY/Eui/F/8UVVp+eTmt918HRvlX" +
+ "hOqGexWX3q4bvn2kg582nZW1awc9vuhL4Whs1R4IpqC1ubnXhm8d/mp45cK9cEK29/0v+22P3Elq" +
+ "xsMBhJ3Ssj7U/OYVwv0GsTvU99h03nGkXnsKEXabqqNVV96b75z9vCRf+kPAEWi5+P4fjvzfs2/e" +
+ "Ay+rC96f9fzYPqsX11mF2EGH+yYal9TMJ4wCKJ9ILAQmXbXbsWSyeVLpPGUX3ULm3Tfxk43sNrG0" +
+ "/eE+Uz7pMk29/Li1Nmeyj917QsexJ9xbVFzcDmDoFgWe5wWysx7/mvq+o1Y0NuEUOp6bpjaXEgV2" +
+ "Nuke/Sg6+n8B8H3LklWNzJq7gtXrW7BW6UpzopN7fj+X+6bNZdqCNuKnnof6oOzmqEr2w/cc9fMa" +
+ "2OsAtQoVq947YPVfFu/XzStMQWtTU1WPJXNHWwWnR28bHjZKWu+9AUVFlE+mkDxoPEXxCNYq055f" +
+ "yKamJGNHD0REUFVA2JlgwOGbJxxMLBKkrSNDONWTjkfvxG/dwO6yXobO2TMl+sVjNPPBO+pmM+FV" +
+ "s18cP3T0597oxmAKNqxYtm9R07oaayG0/0HqNW4mt26Vg4LyycSD7N6jcIFM3iMWDTH5lKEEXId/" +
+ "RFEsxN+VFkfQWDXxcceReHQqGHaPqnS+NctUXnyzlUBIfS8jzvzXxnieF3ZdN+PSLQo6PlhwcMxa" +
+ "Y30IH/h5Mu+/o9bLsCu58l4AhIMuR4/ZG9cx/LNS6RwbGzuorSkjfuTxtP7hLsBntwjkNq0T9TxM" +
+ "RV/1Ni2jdPUH+3q5XNFfgzF0hwLHXfmXA3wFcRwN7zuC9HvviKqC0uXkjYsCIrItlpa2TmbM/pCV" +
+ "a5tR1a5DTWWZ+MNHuPTWGbwwZxnBQfvi9hwAym6PptvFb20kWDsQtRBNbO6ZSyX7dNcjqUA1HG9a" +
+ "308VJF6qblVvydUvQa2KCjtlFGwqScazRAMOAIn2NOdc9kfqN7Ry8jEHcvyRQ6mrKWdn1m5KsHJd" +
+ "C9Fw4G97oKMO+SrBQUPIbVgBwu5RJbP8Qwn03UvVn4FR39H21kFUVi0wdIeCYDjRWKkKpqiHqlr1" +
+ "WpsEdvGfDLgNa2nPeADbVpctEeD7lufnLGXpqka6MnhAJRMnDKdf7zLO/NpIxA0QqKlF7XZ/a+uA" +
+ "bB0UdGcrjKrkN9QT6N0fFVEVcFJt3bXCFKiq6zdtKlYFJxoDL49NZ1GlawLRVYtozfhUFwFA76pi" +
+ "vvyFvXnpjWVUlcU4aP8auuI6hovPOQxVRUQAMOE4WFC2MmEI9YaiUUJ0X0F9yKyGxIuW3AZA+DgF" +
+ "v61ZnPJKRQEFL9FS3k3BFAjq4uWCqkAoiFormvdF6ZoKRFcupjnt8XfhUIDLJx3BN48/mMqyGPFY" +
+ "iF1jWyyqis21E6iGyF5CdD8hMkQI9gYJCFgAiB6oaN7Q8LAFYQeay6iJRFQFVMHx8+HuC6ZAsCoA" +
+ "iICqKICyS6H1S9mcaEf7Fm1bIYJBl9qacrqm4DWguTWgafDbIL8O0u9R/qWn6HGEgxMTAFC2soAB" +
+ "P6G0zrS0PKEggPIxqqBWQURQUO3mE3cF4uG6nirYnAeOYzGOURB2wSTb8NavJrNPLyIBh11jayTN" +
+ "v0TbHgevETQHeKAWALcYQEDZSkBEyayDtlmWttlKvpGthE8WDInN5nRbLMZ43RdMgS/hWEqh3E+m" +
+ "RNygEgqqtrNrCsFlC2g79OBdB6OKpl5G10+C7CpAQYRtRPgYB/x2JTlfScxSUksUzW4XirIDtWDi" +
+ "ZeolWrEWACQUaeuuYApEck5JeTNKX789gRhHnJJS8pvXIkKX1ED0w3m0ZM+muoguaXYxWj8R/CYQ" +
+ "AQSskmsCJw5OVEDA71BSi5S217b+9FOg2/ekXUcc6NmX/MZ1YFUQcGJFm7ormAIh41b1Wm+VAzXZ" +
+ "gteR0GDNYNJL39cthF0IL1tIUzIPFXStcy74jSAGAFWl/lpLxzuKBMCJAgb8JKgHOHyMKv8QMUZD" +
+ "g4aQnPMiKoCIOqU9VnZbMAWSD9UN+QDlWJvJSeYv7xMeOpzEzD8h7Fpw43Kam5rw+xXjGGGnIsPB" +
+ "REHTgGDTkF6tqANY8JJsgwEUAJSPPL0EULoWjGmgujfp5R8KgImVtG0JZhWAoVsUlIz/2jtqRUGl" +
+ "8903NDb8EMSEUNjlmM40/pplpHIeXZHwUKTHZMAFwIkJ1acZghWAgNqPjAIGnDhE66DHl4Wacw0D" +
+ "LjGE+8FOP7VQcCur1cSKNbe+XhSIjfjCMhONd+cepiBYO/hdU1TW6idbyjvemWuqzv2JBqr62OzG" +
+ "FQ67oh7BD9+l/YjDKA4H2CkJID0vJ1OfQJvvI1QjlI8zFB0sZJYr2U3gd4I44JZAsEoI9gS3FCQo" +
+ "CEpmDXgZ2PnLftkS+xc0/eH7+Ml2wUB05Ji54jipbgymwEQi6yNDhi1Mvv3KYdk1SyW3ZqUWjz3G" +
+ "Njw81QgqdEFVCS9ZQFPGUlNC10yUxBt9aLjXEttHKB4txIcKsf3lb+GgoApYthLAQm6j0vqK0vSs" +
+ "Jd8CIjuPsnjcMdoy7TeiqBjj+LERh7wIaDcGUyCO27klkGc7tgSDlzctT/7eVpx8Ng2/uwfVHLsS" +
+ "Wv0+ifYUWhVBROiKWh8vBe3v6t/GhCHYE6IDhUidEKoGEwIvCZl6SP1F6Vyh+B2AbB1lRyiEB+zl" +
+ "B/v0p+PtOQaBQJ8BqyN77/c2QDcHU1AybsLTm35184Vec0NVYsbjUn3uj6Ro9OFe++szAghdcho3" +
+ "0LlpI7naHoRcoStueSXKNvgZSK+GzlWKiO74ASMg0vV7LwCqRstPPlsTzz2Gl2wTMVB82DHPumXl" +
+ "mwvXfewB6vvO6h+c/mDLE787Ra1or8mXeMWHHcmHJx3uiPiGLqgE2XTlg3z+xK9THg3SlbZZM1h+" +
+ "1gTApzsFq+u8QQ8+ydKTxomX2OSYaFHH4N++OD42YvTcPbDCFIjj+JWnn3tX2ysvTMgnmoo3P3CH" +
+ "6XHyWfT46kS/6YmHBFTYCdEcgSXvksh+lfIoXQrVDsKUVOIlNrGdrhaRrlmjvS66yjb+7n7JNW9y" +
+ "cUR7njFlRmz4qPl78H6YgtiBo96s/t4lz6iKesmEs/6Gy2yvC66QQGU/q12djbEQWrqI5lSOXa8E" +
+ "fQgP2ptP+n1N8SCpoPPPnbBT0dIj/icfrhssmx+611GBQGXftupvnX8bIvk9G0xhlfGqTv/2jZEB" +
+ "+zQAND89zU0teFv7Xn6TlUDUdtEMwbVLaG9N4FslmW+gKbOGjN+5wzFNE45QPGY8WFAAC4niEHdM" +
+ "GMjJU0bw4Ji+GPsP9qIQqq6zfS6+Rtb85HzRXMqAY/v+6PpH3PKKN9mOc+WVV9K9CiQQ3Bzdd1iw" +
+ "afrDX1LNO8m359LzrO+pW1yh7W+/blAr7AjJWzoOPZaaAX2Yu/lWHls1ldc2z2VjOklJsILiQBwR" +
+ "wVefXDRAy1N/gnyWv4yu4s4zhzCztox2DAIctaABlF1y4mW29md32y2bdJqfneYCUnzI4cv6XnrD" +
+ "d8SYxKd1e0OBaqz+yose23j/z8cBFA3/gjfw9l/Lxjt+rg2P/soFX9iBQ+OP7mTUWWeyoOkaXtv0" +
+ "KqtTsDxpSfoVfLn34YzoU8bsxnksb23EeWMxxwRyvDGigqVJWJ5U2vLQvznNA3cuIJLz6YqEiuyA" +
+ "a27x1fOov+J8x+bTxo2Xdw6btfDUYK8+j32aN1AViKT6/eS6ye1zXn45tWR+Tce7r7v1V/zQ73/N" +
+ "L0R9z2+Y9oCzQzTWx/1wEa1pH8SwlWDE0JBp5oHVv2eB+jQnhdaUoWNQnIE1LmQUUP4uHzDkHEOY" +
+ "nQSjYCJFtt9lN/kmFmflxZMdm0sbxbGDpj50+5ZYngT49IMpPJqW7TP9pVPf/fy+T3qJTcUtM59y" +
+ "FPEGXHuLOOUV3oZ7fuGieeEjgsvfo7WjE9cN8FECOI5gEEQEgJyFVF7ZnhXBIqiyA1UIlFb5tdff" +
+ "ZlFY+aMpjt/ebFSh/yU/nV467pgrAf/fdItmgVtS9uqwF98620TK0mCl5aUn3OWTT6dq4tky8Of3" +
+ "eSZSZlXZJrC+nmRTC0aibE/4OFVFAWv4GMcqxirbUysaG3yAN+S3T2i+sYHlF37H8doajSr0Ovv7" +
+ "s/qce+E5QPbffO1qQah33+kH/nnhaYHKfq2qKm3vvOYu/to43LIKhr0415aOOTpvNaBWwSSayNav" +
+ "QrR0hzhcP86g6H4MjNUyuuJArjrwO9w06hGOesWl3+oOgr5iBEpSecJZH2vZOiqKG7N9Jl3k7f2b" +
+ "P7Hp/+7RlZed7/rpdqM4ts+5lz5be+2txyHS/hm62Lkg39x05AenfOWejoVv9hdUkIBWTzzHqznv" +
+ "YumYN1fX//JnJvXBItNy7k8lftpgZm28iRVJZXM2yoiKcXx3yERqi3qxvaY/Pcqyb09kc0WQRf3i" +
+ "lKY8Rq5IYBF1wnFKDxtva6ZcaHONTdRffZF0Ll/iYsAEI/m6a29/qPq0b56/LZbPVjAFNpMeuvrK" +
+ "i2/f+ODdY9TmHXwI1dT6vSedpz3GHyvJhfN1VUMSjhljFrb/UuLBfeRzPY+hX7w/O2PzORYePYbk" +
+ "orcQFRXXJVBdo+Vjj7QVx5+MuAHZcPdt2vTsYw54gkKopq55yN2/vano4M/dBmQBPqvBFKiWtc56" +
+ "4YJlF3x3Unb96nIEUKOR2sG28usnafmErxOoHUwwGkLEiCDCNgg70paXnmPNjVdr0fCRWjJmLOEB" +
+ "daRXraDxj7+j9dUXjc2kBFTEuH7VSWfOrbvqpkvc0rI/Awrw2Q+mwPgdHaPX3X3rj9dNvfEom0kF" +
+ "VAEVdYvLtGjoAVo85ggtGf05CfcbqMGqKjGhMB9pRwEBUN/Ha23R9OrlZFatlMRrL2v73NclXb/C" +
+ "qJ8XMQCyJaZD1g687hdTi0aMvh+Rlv/AL9gq0Hw+3PbWnMPX3n7jlLY5s8baXDYEgIIiagIh3NIe" +
+ "Gqqq1EBVb9zyCtxoXDFGbT5n/PaE5ho2mtzmjeSbW/A720R9X8SwTbimf33Pb5zxUO9vTv5VoKKq" +
+ "/r/gK/wKbDYTTi1eNHTzH393SvPzT0/IrF5Zp2KNCFtpF8cqBba/ndVEYqmKCcfP6Xn8xEeLRx78" +
+ "rFtS2oCIAvx3BVMgms/H8q3N+zc9/cTYphlPf/6vIWU3ru+jnufySUTULSpujwzca9mWPcy8skMP" +
+ "e6Xkc4fODlb32iyOk6cb/T/N+faHj8AX2gAAAABJRU5ErkJggg=="
+ }
+};
+
+// The process of adding a new default snippet involves:
+// * add a new entity to aboutHome.dtd
+// * add a <span/> for it in aboutHome.xhtml
+// * add an entry here in the proper ordering (based on spans)
+// The <a/> part of the snippet will be linked to the corresponding url.
+const DEFAULT_SNIPPETS_URLS = [
+ "https://www.mozilla.org/firefox/features/?utm_source=snippet&utm_medium=snippet&utm_campaign=default+feature+snippet"
+, "https://addons.mozilla.org/firefox/?utm_source=snippet&utm_medium=snippet&utm_campaign=addons"
+];
+
+const SNIPPETS_UPDATE_INTERVAL_MS = 86400000; // 1 Day.
+
+// This global tracks if the page has been set up before, to prevent double inits
+let gInitialized = false;
+let gObserver = new MutationObserver(function (mutations) {
+ for (let mutation of mutations) {
+ if (mutation.attributeName == "searchEngineURL") {
+ setupSearchEngine();
+ if (!gInitialized) {
+ ensureSnippetsMapThen(loadSnippets);
+ gInitialized = true;
+ }
+ return;
+ }
+ }
+});
+
+window.addEventListener("pageshow", function () {
+ // Delay search engine setup, cause browser.js::BrowserOnAboutPageLoad runs
+ // later and may use asynchronous getters.
+ window.gObserver.observe(document.documentElement, { attributes: true });
+ fitToWidth();
+ window.addEventListener("resize", fitToWidth);
+});
+
+window.addEventListener("pagehide", function() {
+ window.gObserver.disconnect();
+ window.removeEventListener("resize", fitToWidth);
+});
+
+// This object has the same interface as Map and is used to store and retrieve
+// the snippets data. It is lazily initialized by ensureSnippetsMapThen(), so
+// be sure its callback returned before trying to use it.
+let gSnippetsMap;
+let gSnippetsMapCallbacks = [];
+
+/**
+ * Ensure the snippets map is properly initialized.
+ *
+ * @param aCallback
+ * Invoked once the map has been initialized, gets the map as argument.
+ * @note Snippets should never directly manage the underlying storage, since
+ * it may change inadvertently.
+ */
+function ensureSnippetsMapThen(aCallback)
+{
+ if (gSnippetsMap) {
+ aCallback(gSnippetsMap);
+ return;
+ }
+
+ // Handle multiple requests during the async initialization.
+ gSnippetsMapCallbacks.push(aCallback);
+ if (gSnippetsMapCallbacks.length > 1) {
+ // We are already updating, the callbacks will be invoked when done.
+ return;
+ }
+
+ // TODO (bug 789348): use a real asynchronous storage here. This setTimeout
+ // is done just to catch bugs with the asynchronous behavior.
+ setTimeout(function() {
+ // Populate the cache from the persistent storage.
+ let cache = new Map();
+ for (let key of [ "snippets-last-update",
+ "snippets-cached-version",
+ "snippets" ]) {
+ cache.set(key, localStorage[key]);
+ }
+
+ gSnippetsMap = Object.freeze({
+ get: function (aKey) cache.get(aKey),
+ set: function (aKey, aValue) {
+ localStorage[aKey] = aValue;
+ return cache.set(aKey, aValue);
+ },
+ has: function(aKey) cache.has(aKey),
+ delete: function(aKey) {
+ delete localStorage[aKey];
+ return cache.delete(aKey);
+ },
+ clear: function() {
+ localStorage.clear();
+ return cache.clear();
+ },
+ get size() cache.size
+ });
+
+ for (let callback of gSnippetsMapCallbacks) {
+ callback(gSnippetsMap);
+ }
+ gSnippetsMapCallbacks.length = 0;
+ }, 0);
+}
+
+function onSearchSubmit(aEvent)
+{
+ let searchTerms = document.getElementById("searchText").value;
+ let searchURL = document.documentElement.getAttribute("searchEngineURL");
+
+ if (searchURL && searchTerms.length > 0) {
+ // Send an event that a search was performed. This was originally
+ // added so Firefox Health Report could record that a search from
+ // about:home had occurred.
+ let engineName = document.documentElement.getAttribute("searchEngineName");
+ let event = new CustomEvent("AboutHomeSearchEvent", {detail: engineName});
+ document.dispatchEvent(event);
+
+ const SEARCH_TOKEN = "_searchTerms_";
+ let searchPostData = document.documentElement.getAttribute("searchEnginePostData");
+ if (searchPostData) {
+ // Check if a post form already exists. If so, remove it.
+ const POST_FORM_NAME = "searchFormPost";
+ let form = document.forms[POST_FORM_NAME];
+ if (form) {
+ form.parentNode.removeChild(form);
+ }
+
+ // Create a new post form.
+ form = document.body.appendChild(document.createElement("form"));
+ form.setAttribute("name", POST_FORM_NAME);
+ // Set the URL to submit the form to.
+ form.setAttribute("action", searchURL.replace(SEARCH_TOKEN, searchTerms));
+ form.setAttribute("method", "post");
+
+ // Create new <input type=hidden> elements for search param.
+ searchPostData = searchPostData.split("&");
+ for (let postVar of searchPostData) {
+ let [name, value] = postVar.split("=");
+ if (value == SEARCH_TOKEN) {
+ value = searchTerms;
+ }
+ let input = document.createElement("input");
+ input.setAttribute("type", "hidden");
+ input.setAttribute("name", name);
+ input.setAttribute("value", value);
+ form.appendChild(input);
+ }
+ // Submit the form.
+ form.submit();
+ } else {
+ searchURL = searchURL.replace(SEARCH_TOKEN, encodeURIComponent(searchTerms));
+ window.location.href = searchURL;
+ }
+ }
+
+ aEvent.preventDefault();
+}
+
+
+function setupSearchEngine()
+{
+ // The "autofocus" attribute doesn't focus the form element
+ // immediately when the element is first drawn, so the
+ // attribute is also used for styling when the page first loads.
+ let searchText = document.getElementById("searchText");
+ searchText.addEventListener("blur", function searchText_onBlur() {
+ searchText.removeEventListener("blur", searchText_onBlur);
+ searchText.removeAttribute("autofocus");
+ });
+
+ let searchEngineName = document.documentElement.getAttribute("searchEngineName");
+ let searchEngineInfo = SEARCH_ENGINES[searchEngineName];
+ let logoElt = document.getElementById("searchEngineLogo");
+
+ // Add search engine logo.
+ if (searchEngineInfo && searchEngineInfo.image) {
+ logoElt.parentNode.hidden = false;
+ logoElt.src = searchEngineInfo.image;
+ logoElt.alt = searchEngineName;
+ searchText.placeholder = "";
+ }
+ else {
+ logoElt.parentNode.hidden = true;
+ searchText.placeholder = searchEngineName;
+ }
+
+}
+
+/**
+ * Update the local snippets from the remote storage, then show them through
+ * showSnippets.
+ */
+function loadSnippets()
+{
+ if (!gSnippetsMap)
+ throw new Error("Snippets map has not properly been initialized");
+
+ // Check cached snippets version.
+ let cachedVersion = gSnippetsMap.get("snippets-cached-version") || 0;
+ let currentVersion = document.documentElement.getAttribute("snippetsVersion");
+ if (cachedVersion < currentVersion) {
+ // The cached snippets are old and unsupported, restart from scratch.
+ gSnippetsMap.clear();
+ }
+
+ // Check last snippets update.
+ let lastUpdate = gSnippetsMap.get("snippets-last-update");
+ let updateURL = document.documentElement.getAttribute("snippetsURL");
+ let shouldUpdate = !lastUpdate ||
+ Date.now() - lastUpdate > SNIPPETS_UPDATE_INTERVAL_MS;
+ if (updateURL && shouldUpdate) {
+ // Try to update from network.
+ let xhr = new XMLHttpRequest();
+ try {
+ xhr.open("GET", updateURL, true);
+ } catch (ex) {
+ showSnippets();
+ return;
+ }
+ // Even if fetching should fail we don't want to spam the server, thus
+ // set the last update time regardless its results. Will retry tomorrow.
+ gSnippetsMap.set("snippets-last-update", Date.now());
+ xhr.onerror = function (event) {
+ showSnippets();
+ };
+ xhr.onload = function (event)
+ {
+ if (xhr.status == 200) {
+ gSnippetsMap.set("snippets", xhr.responseText);
+ gSnippetsMap.set("snippets-cached-version", currentVersion);
+ }
+ showSnippets();
+ };
+ xhr.send(null);
+ } else {
+ showSnippets();
+ }
+}
+
+/**
+ * Shows locally cached remote snippets, or default ones when not available.
+ *
+ * @note: snippets should never invoke showSnippets(), or they may cause
+ * a "too much recursion" exception.
+ */
+let _snippetsShown = false;
+function showSnippets()
+{
+ let snippetsElt = document.getElementById("snippets");
+
+ // Show about:rights notification, if needed.
+ let showRights = document.documentElement.getAttribute("showKnowYourRights");
+ if (showRights) {
+ let rightsElt = document.getElementById("rightsSnippet");
+ let anchor = rightsElt.getElementsByTagName("a")[0];
+ anchor.href = "about:rights";
+ snippetsElt.appendChild(rightsElt);
+ rightsElt.removeAttribute("hidden");
+ return;
+ }
+
+ if (!gSnippetsMap)
+ throw new Error("Snippets map has not properly been initialized");
+ if (_snippetsShown) {
+ // There's something wrong with the remote snippets, just in case fall back
+ // to the default snippets.
+ showDefaultSnippets();
+ throw new Error("showSnippets should never be invoked multiple times");
+ }
+ _snippetsShown = true;
+
+ let snippets = gSnippetsMap.get("snippets");
+ // If there are remotely fetched snippets, try to to show them.
+ if (snippets) {
+ // Injecting snippets can throw if they're invalid XML.
+ try {
+ snippetsElt.innerHTML = snippets;
+ // Scripts injected by innerHTML are inactive, so we have to relocate them
+ // through DOM manipulation to activate their contents.
+ Array.forEach(snippetsElt.getElementsByTagName("script"), function(elt) {
+ let relocatedScript = document.createElement("script");
+ relocatedScript.type = "text/javascript;version=1.8";
+ relocatedScript.text = elt.text;
+ elt.parentNode.replaceChild(relocatedScript, elt);
+ });
+ return;
+ } catch (ex) {
+ // Bad content, continue to show default snippets.
+ }
+ }
+
+ showDefaultSnippets();
+}
+
+/**
+ * Clear snippets element contents and show default snippets.
+ */
+function showDefaultSnippets()
+{
+ // Clear eventual contents...
+ let snippetsElt = document.getElementById("snippets");
+ snippetsElt.innerHTML = "";
+
+ // ...then show default snippets.
+ let defaultSnippetsElt = document.getElementById("defaultSnippets");
+ let entries = defaultSnippetsElt.querySelectorAll("span");
+ // Choose a random snippet. Assume there is always at least one.
+ let randIndex = Math.floor(Math.random() * entries.length);
+ let entry = entries[randIndex];
+ // Inject url in the eventual link.
+ if (DEFAULT_SNIPPETS_URLS[randIndex]) {
+ let links = entry.getElementsByTagName("a");
+ // Default snippets can have only one link, otherwise something is messed
+ // up in the translation.
+ if (links.length == 1) {
+ links[0].href = DEFAULT_SNIPPETS_URLS[randIndex];
+ }
+ }
+ // Move the default snippet to the snippets element.
+ snippetsElt.appendChild(entry);
+}
+
+function fitToWidth() {
+ if (window.scrollMaxX) {
+ document.body.setAttribute("narrow", "true");
+ } else if (document.body.hasAttribute("narrow")) {
+ document.body.removeAttribute("narrow");
+ fitToWidth();
+ }
+}
diff --git a/browser/base/content/abouthome/aboutHome.xhtml b/browser/base/content/abouthome/aboutHome.xhtml
new file mode 100644
index 000000000..17ff83945
--- /dev/null
+++ b/browser/base/content/abouthome/aboutHome.xhtml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD
+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+ <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+ <!ENTITY % aboutHomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd">
+ %aboutHomeDTD;
+ <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd" >
+ %browserDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>&abouthome.pageTitle;</title>
+
+ <link rel="icon" type="image/png" id="favicon"
+ href="chrome://branding/content/icon32.png"/>
+ <link rel="stylesheet" type="text/css" media="all"
+ href="chrome://browser/content/abouthome/aboutHome.css"/>
+
+ <script type="text/javascript;version=1.8"
+ src="chrome://browser/content/abouthome/aboutHome.js"/>
+ </head>
+
+ <body dir="&locale.dir;">
+ <div class="spacer"/>
+ <div id="topSection">
+ <div id="brandLogo"></div>
+
+ <div id="searchContainer">
+ <form name="searchForm" id="searchForm" onsubmit="onSearchSubmit(event)">
+ <div id="searchLogoContainer"><img id="searchEngineLogo"/></div>
+ <input type="text" name="q" value="" id="searchText" maxlength="256"
+ autofocus="autofocus"/>
+ <input id="searchSubmit" type="submit" value="&abouthome.searchEngineButton.label;"/>
+ </form>
+ </div>
+
+ <div id="snippetContainer">
+<!-- <div id="defaultSnippets" hidden="true">
+ <span id="defaultSnippet1">&abouthome.defaultSnippet1.v1;</span>
+ <span id="defaultSnippet2">&abouthome.defaultSnippet2.v1;</span>
+ </div>
+ <span id="rightsSnippet" hidden="true">&abouthome.rightsSnippet;</span>
+ <div id="snippets"/> -->
+ </div>
+ </div>
+ <div class="spacer"/>
+
+ <div id="launcher">
+ <button class="launchButton" id="downloads">&abouthome.downloadsButton.label;</button>
+ <button class="launchButton" id="bookmarks">&abouthome.bookmarksButton.label;</button>
+ <button class="launchButton" id="history">&abouthome.historyButton.label;</button>
+ <button class="launchButton" id="apps" hidden="true">&abouthome.appsButton.label;</button>
+ <button class="launchButton" id="addons">&abouthome.addonsButton.label;</button>
+ <button class="launchButton" id="sync">&abouthome.syncButton.label;</button>
+ <button class="launchButton" id="settings">&abouthome.settingsButton.label;</button>
+ <div id="restorePreviousSessionSeparator"/>
+ <button class="launchButton" id="restorePreviousSession">&historyRestoreLastSession.label;</button>
+ </div>
+
+<!-- <a id="aboutMozilla" href="http://www.mozilla.org/about/"/> -->
+ </body>
+</html>
diff --git a/browser/base/content/abouthome/addons.png b/browser/base/content/abouthome/addons.png
new file mode 100644
index 000000000..41519ce49
--- /dev/null
+++ b/browser/base/content/abouthome/addons.png
Binary files differ
diff --git a/browser/base/content/abouthome/addons@2x.png b/browser/base/content/abouthome/addons@2x.png
new file mode 100644
index 000000000..d4d04ee8c
--- /dev/null
+++ b/browser/base/content/abouthome/addons@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/apps.png b/browser/base/content/abouthome/apps.png
new file mode 100644
index 000000000..79fc95d49
--- /dev/null
+++ b/browser/base/content/abouthome/apps.png
Binary files differ
diff --git a/browser/base/content/abouthome/apps@2x.png b/browser/base/content/abouthome/apps@2x.png
new file mode 100644
index 000000000..cbe7a6d53
--- /dev/null
+++ b/browser/base/content/abouthome/apps@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/bookmarks.png b/browser/base/content/abouthome/bookmarks.png
new file mode 100644
index 000000000..5c7e194a6
--- /dev/null
+++ b/browser/base/content/abouthome/bookmarks.png
Binary files differ
diff --git a/browser/base/content/abouthome/bookmarks@2x.png b/browser/base/content/abouthome/bookmarks@2x.png
new file mode 100644
index 000000000..7ede00744
--- /dev/null
+++ b/browser/base/content/abouthome/bookmarks@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/downloads.png b/browser/base/content/abouthome/downloads.png
new file mode 100644
index 000000000..3d4d10e7a
--- /dev/null
+++ b/browser/base/content/abouthome/downloads.png
Binary files differ
diff --git a/browser/base/content/abouthome/downloads@2x.png b/browser/base/content/abouthome/downloads@2x.png
new file mode 100644
index 000000000..d384a22c6
--- /dev/null
+++ b/browser/base/content/abouthome/downloads@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/history.png b/browser/base/content/abouthome/history.png
new file mode 100644
index 000000000..ae742b1aa
--- /dev/null
+++ b/browser/base/content/abouthome/history.png
Binary files differ
diff --git a/browser/base/content/abouthome/history@2x.png b/browser/base/content/abouthome/history@2x.png
new file mode 100644
index 000000000..696902e7c
--- /dev/null
+++ b/browser/base/content/abouthome/history@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/mozilla.png b/browser/base/content/abouthome/mozilla.png
new file mode 100644
index 000000000..f2c348d13
--- /dev/null
+++ b/browser/base/content/abouthome/mozilla.png
Binary files differ
diff --git a/browser/base/content/abouthome/mozilla@2x.png b/browser/base/content/abouthome/mozilla@2x.png
new file mode 100644
index 000000000..f8fc622d0
--- /dev/null
+++ b/browser/base/content/abouthome/mozilla@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/noise.png b/browser/base/content/abouthome/noise.png
new file mode 100644
index 000000000..3467cf4d4
--- /dev/null
+++ b/browser/base/content/abouthome/noise.png
Binary files differ
diff --git a/browser/base/content/abouthome/restore-large.png b/browser/base/content/abouthome/restore-large.png
new file mode 100644
index 000000000..ef593e6e1
--- /dev/null
+++ b/browser/base/content/abouthome/restore-large.png
Binary files differ
diff --git a/browser/base/content/abouthome/restore-large@2x.png b/browser/base/content/abouthome/restore-large@2x.png
new file mode 100644
index 000000000..d5c71d0b0
--- /dev/null
+++ b/browser/base/content/abouthome/restore-large@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/restore.png b/browser/base/content/abouthome/restore.png
new file mode 100644
index 000000000..5c3d6f437
--- /dev/null
+++ b/browser/base/content/abouthome/restore.png
Binary files differ
diff --git a/browser/base/content/abouthome/restore@2x.png b/browser/base/content/abouthome/restore@2x.png
new file mode 100644
index 000000000..5acb63052
--- /dev/null
+++ b/browser/base/content/abouthome/restore@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/settings.png b/browser/base/content/abouthome/settings.png
new file mode 100644
index 000000000..4b0c30990
--- /dev/null
+++ b/browser/base/content/abouthome/settings.png
Binary files differ
diff --git a/browser/base/content/abouthome/settings@2x.png b/browser/base/content/abouthome/settings@2x.png
new file mode 100644
index 000000000..c77cb9a92
--- /dev/null
+++ b/browser/base/content/abouthome/settings@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/snippet1.png b/browser/base/content/abouthome/snippet1.png
new file mode 100644
index 000000000..ce2ec55c2
--- /dev/null
+++ b/browser/base/content/abouthome/snippet1.png
Binary files differ
diff --git a/browser/base/content/abouthome/snippet1@2x.png b/browser/base/content/abouthome/snippet1@2x.png
new file mode 100644
index 000000000..f57cd0a82
--- /dev/null
+++ b/browser/base/content/abouthome/snippet1@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/snippet2.png b/browser/base/content/abouthome/snippet2.png
new file mode 100644
index 000000000..e0724fb6d
--- /dev/null
+++ b/browser/base/content/abouthome/snippet2.png
Binary files differ
diff --git a/browser/base/content/abouthome/snippet2@2x.png b/browser/base/content/abouthome/snippet2@2x.png
new file mode 100644
index 000000000..40577f52f
--- /dev/null
+++ b/browser/base/content/abouthome/snippet2@2x.png
Binary files differ
diff --git a/browser/base/content/abouthome/sync.png b/browser/base/content/abouthome/sync.png
new file mode 100644
index 000000000..11e40cc93
--- /dev/null
+++ b/browser/base/content/abouthome/sync.png
Binary files differ
diff --git a/browser/base/content/abouthome/sync@2x.png b/browser/base/content/abouthome/sync@2x.png
new file mode 100644
index 000000000..6354f5bf9
--- /dev/null
+++ b/browser/base/content/abouthome/sync@2x.png
Binary files differ
diff --git a/browser/base/content/baseMenuOverlay.xul b/browser/base/content/baseMenuOverlay.xul
new file mode 100644
index 000000000..4b8a7fd11
--- /dev/null
+++ b/browser/base/content/baseMenuOverlay.xul
@@ -0,0 +1,107 @@
+<?xml version="1.0"?>
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<!DOCTYPE overlay [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % baseMenuOverlayDTD SYSTEM "chrome://browser/locale/baseMenuOverlay.dtd">
+%baseMenuOverlayDTD;
+]>
+<overlay id="baseMenuOverlay"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
+
+#ifdef XP_MACOSX
+<!-- nsMenuBarX hides these and uses them to build the Application menu.
+ When using Carbon widgets for Mac OS X widgets, some of these are not
+ used as they only apply to Cocoa widget builds. All version of Firefox
+ through Firefox 2 will use Carbon widgets. -->
+ <menupopup id="menu_ToolsPopup">
+ <menuitem id="menu_preferences" label="&preferencesCmdMac.label;" key="key_preferencesCmdMac" oncommand="openPreferences();"/>
+ <menuitem id="menu_mac_services" label="&servicesMenuMac.label;"/>
+ <menuitem id="menu_mac_hide_app" label="&hideThisAppCmdMac.label;" key="key_hideThisAppCmdMac"/>
+ <menuitem id="menu_mac_hide_others" label="&hideOtherAppsCmdMac.label;" key="key_hideOtherAppsCmdMac"/>
+ <menuitem id="menu_mac_show_all" label="&showAllAppsCmdMac.label;"/>
+ </menupopup>
+<!-- Mac window menu -->
+#include ../../../toolkit/content/macWindowMenu.inc
+#endif
+
+#ifdef XP_WIN
+ <menu id="helpMenu"
+ label="&helpMenuWin.label;"
+ accesskey="&helpMenuWin.accesskey;">
+#else
+ <menu id="helpMenu"
+ label="&helpMenu.label;"
+ accesskey="&helpMenu.accesskey;">
+#endif
+ <menupopup id="menu_HelpPopup" onpopupshowing="buildHelpMenu();">
+ <menuitem id="menu_openHelp"
+ oncommand="openHelpLink('firefox-help')"
+ onclick="checkForMiddleClick(this, event);"
+ label="&productHelp.label;"
+ accesskey="&productHelp.accesskey;"
+#ifdef XP_MACOSX
+ key="key_openHelpMac"/>
+#else
+ />
+#endif
+#ifdef MOZ_SERVICES_HEALTHREPORT
+ <menuitem id="healthReport"
+ label="&healthReport.label;"
+ accesskey="&healthReport.accesskey;"
+ oncommand="openHealthReport()"
+ onclick="checkForMiddleClick(this, event);"/>
+#endif
+ <menuitem id="troubleShooting"
+ accesskey="&helpTroubleshootingInfo.accesskey;"
+ label="&helpTroubleshootingInfo.label;"
+ oncommand="openTroubleshootingPage()"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="feedbackPage"
+ accesskey="&helpFeedbackPage.accesskey;"
+ label="&helpFeedbackPage.label;"
+ oncommand="openFeedbackPage()"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="helpSafeMode"
+ accesskey="&helpSafeMode.accesskey;"
+ label="&helpSafeMode.label;"
+ oncommand="safeModeRestart();"/>
+ <menuseparator id="aboutSeparator"/>
+ <menuitem id="aboutName"
+ accesskey="&aboutProduct.accesskey;"
+ label="&aboutProduct.label;"
+ oncommand="openAboutDialog();"/>
+ </menupopup>
+ </menu>
+
+ <keyset id="baseMenuKeyset">
+#ifdef XP_MACOSX
+ <key id="key_openHelpMac"
+ oncommand="openHelpLink('firefox-osxkey');"
+ key="&helpMac.commandkey;"
+ modifiers="accel"/>
+<!-- These are used to build the Application menu under Cocoa widgets -->
+ <key id="key_preferencesCmdMac"
+ key="&preferencesCmdMac.commandkey;"
+ modifiers="accel"/>
+ <key id="key_hideThisAppCmdMac"
+ key="&hideThisAppCmdMac.commandkey;"
+ modifiers="accel"/>
+ <key id="key_hideOtherAppsCmdMac"
+ key="&hideOtherAppsCmdMac.commandkey;"
+ modifiers="accel,alt"/>
+#endif
+ </keyset>
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_browser" src="chrome://browser/locale/browser.properties"/>
+ <stringbundle id="bundle_browser_region" src="chrome://browser-region/locale/region.properties"/>
+ </stringbundleset>
+</overlay>
diff --git a/browser/base/content/blockedSite.xhtml b/browser/base/content/blockedSite.xhtml
new file mode 100644
index 000000000..b56875eb6
--- /dev/null
+++ b/browser/base/content/blockedSite.xhtml
@@ -0,0 +1,193 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+ <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+ %brandDTD;
+ <!ENTITY % blockedSiteDTD SYSTEM "chrome://browser/locale/safebrowsing/phishing-afterload-warning-message.dtd">
+ %blockedSiteDTD;
+]>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<html xmlns="http://www.w3.org/1999/xhtml" class="blacklist">
+ <head>
+ <link rel="stylesheet" href="chrome://global/skin/netError.css" type="text/css" media="all" />
+ <link rel="icon" type="image/png" id="favicon" href="chrome://global/skin/icons/blacklist_favicon.png"/>
+
+ <script type="application/javascript"><![CDATA[
+ // Error url MUST be formatted like this:
+ // about:blocked?e=error_code&u=url
+
+ // Note that this file uses document.documentURI to get
+ // the URL (with the format from above). This is because
+ // document.location.href gets the current URI off the docshell,
+ // which is the URL displayed in the location bar, i.e.
+ // the URI that the user attempted to load.
+
+ function getErrorCode()
+ {
+ var url = document.documentURI;
+ var error = url.search(/e\=/);
+ var duffUrl = url.search(/\&u\=/);
+ return decodeURIComponent(url.slice(error + 2, duffUrl));
+ }
+
+ function getURL()
+ {
+ var url = document.documentURI;
+ var match = url.match(/&u=([^&]+)&/);
+
+ // match == null if not found; if so, return an empty string
+ // instead of what would turn out to be portions of the URI
+ if (!match)
+ return "";
+
+ url = decodeURIComponent(match[1]);
+
+ // If this is a view-source page, then get then real URI of the page
+ if (url.startsWith("view-source:"))
+ url = url.slice(12);
+ return url;
+ }
+
+ /**
+ * Attempt to get the hostname via document.location. Fail back
+ * to getURL so that we always return something meaningful.
+ */
+ function getHostString()
+ {
+ try {
+ return document.location.hostname;
+ } catch (e) {
+ return getURL();
+ }
+ }
+
+ function initPage()
+ {
+ // Handoff to the appropriate initializer, based on error code
+ switch (getErrorCode()) {
+ case "malwareBlocked" :
+ initPage_malware();
+ break;
+ case "phishingBlocked" :
+ initPage_phishing();
+ break;
+ }
+ }
+
+ /**
+ * Initialize custom strings and functionality for blocked malware case
+ */
+ function initPage_malware()
+ {
+ // Remove phishing strings
+ var el = document.getElementById("errorTitleText_phishing");
+ el.parentNode.removeChild(el);
+
+ el = document.getElementById("errorShortDescText_phishing");
+ el.parentNode.removeChild(el);
+
+ el = document.getElementById("errorLongDescText_phishing");
+ el.parentNode.removeChild(el);
+
+ // Set sitename
+ document.getElementById("malware_sitename").textContent = getHostString();
+ document.title = document.getElementById("errorTitleText_malware")
+ .innerHTML;
+ }
+
+ /**
+ * Initialize custom strings and functionality for blocked phishing case
+ */
+ function initPage_phishing()
+ {
+ // Remove malware strings
+ var el = document.getElementById("errorTitleText_malware");
+ el.parentNode.removeChild(el);
+
+ el = document.getElementById("errorShortDescText_malware");
+ el.parentNode.removeChild(el);
+
+ el = document.getElementById("errorLongDescText_malware");
+ el.parentNode.removeChild(el);
+
+ // Set sitename
+ document.getElementById("phishing_sitename").textContent = getHostString();
+ document.title = document.getElementById("errorTitleText_phishing")
+ .innerHTML;
+ }
+ ]]></script>
+ <style type="text/css">
+ /* Style warning button to look like a small text link in the
+ bottom right. This is preferable to just using a text link
+ since there is already a mechanism in browser.js for trapping
+ oncommand events from unprivileged chrome pages (BrowserOnCommand).*/
+ #ignoreWarningButton {
+ -moz-appearance: none;
+ background: transparent;
+ border: none;
+ color: white; /* Hard coded because netError.css forces this page's background to dark red */
+ text-decoration: underline;
+ margin: 0;
+ padding: 0;
+ position: relative;
+ top: 23px;
+ left: 20px;
+ font-size: smaller;
+ }
+
+ #ignoreWarning {
+ text-align: right;
+ }
+ </style>
+ </head>
+
+ <body dir="&locale.dir;">
+ <div id="errorPageContainer">
+
+ <!-- Error Title -->
+ <div id="errorTitle">
+ <h1 id="errorTitleText_phishing">&safeb.blocked.phishingPage.title;</h1>
+ <h1 id="errorTitleText_malware">&safeb.blocked.malwarePage.title;</h1>
+ </div>
+
+ <div id="errorLongContent">
+
+ <!-- Short Description -->
+ <div id="errorShortDesc">
+ <p id="errorShortDescText_phishing">&safeb.blocked.phishingPage.shortDesc;</p>
+ <p id="errorShortDescText_malware">&safeb.blocked.malwarePage.shortDesc;</p>
+ </div>
+
+ <!-- Long Description -->
+ <div id="errorLongDesc">
+ <p id="errorLongDescText_phishing">&safeb.blocked.phishingPage.longDesc;</p>
+ <p id="errorLongDescText_malware">&safeb.blocked.malwarePage.longDesc;</p>
+ </div>
+
+ <!-- Action buttons -->
+ <div id="buttons">
+ <!-- Commands handled in browser.js -->
+ <button id="getMeOutButton">&safeb.palm.accept.label;</button>
+ <button id="reportButton">&safeb.palm.reportPage.label;</button>
+ </div>
+ </div>
+ <div id="ignoreWarning">
+ <button id="ignoreWarningButton">&safeb.palm.decline.label;</button>
+ </div>
+ </div>
+ <!--
+ - Note: It is important to run the script this way, instead of using
+ - an onload handler. This is because error pages are loaded as
+ - LOAD_BACKGROUND, which means that onload handlers will not be executed.
+ -->
+ <script type="application/javascript">initPage();</script>
+ </body>
+</html>
diff --git a/browser/base/content/browser-addons.js b/browser/base/content/browser-addons.js
new file mode 100644
index 000000000..b638f31f9
--- /dev/null
+++ b/browser/base/content/browser-addons.js
@@ -0,0 +1,417 @@
+# -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+const gXPInstallObserver = {
+ _findChildShell: function (aDocShell, aSoughtShell)
+ {
+ if (aDocShell == aSoughtShell)
+ return aDocShell;
+
+ var node = aDocShell.QueryInterface(Components.interfaces.nsIDocShellTreeNode);
+ for (var i = 0; i < node.childCount; ++i) {
+ var docShell = node.getChildAt(i);
+ docShell = this._findChildShell(docShell, aSoughtShell);
+ if (docShell == aSoughtShell)
+ return docShell;
+ }
+ return null;
+ },
+
+ _getBrowser: function (aDocShell)
+ {
+ for (let browser of gBrowser.browsers) {
+ if (this._findChildShell(browser.docShell, aDocShell))
+ return browser;
+ }
+ return null;
+ },
+
+ observe: function (aSubject, aTopic, aData)
+ {
+ var brandBundle = document.getElementById("bundle_brand");
+ var installInfo = aSubject.QueryInterface(Components.interfaces.amIWebInstallInfo);
+ var win = installInfo.originatingWindow;
+ var shell = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebNavigation)
+ .QueryInterface(Components.interfaces.nsIDocShell);
+ var browser = this._getBrowser(shell);
+ if (!browser)
+ return;
+ const anchorID = "addons-notification-icon";
+ var messageString, action;
+ var brandShortName = brandBundle.getString("brandShortName");
+
+ var notificationID = aTopic;
+ // Make notifications persist a minimum of 30 seconds
+ var options = {
+ timeout: Date.now() + 30000
+ };
+
+ switch (aTopic) {
+ case "addon-install-disabled":
+ notificationID = "xpinstall-disabled"
+
+ if (gPrefService.prefIsLocked("xpinstall.enabled")) {
+ messageString = gNavigatorBundle.getString("xpinstallDisabledMessageLocked");
+ buttons = [];
+ }
+ else {
+ messageString = gNavigatorBundle.getString("xpinstallDisabledMessage");
+
+ action = {
+ label: gNavigatorBundle.getString("xpinstallDisabledButton"),
+ accessKey: gNavigatorBundle.getString("xpinstallDisabledButton.accesskey"),
+ callback: function editPrefs() {
+ gPrefService.setBoolPref("xpinstall.enabled", true);
+ }
+ };
+ }
+
+ PopupNotifications.show(browser, notificationID, messageString, anchorID,
+ action, null, options);
+ break;
+ case "addon-install-blocked":
+ messageString = gNavigatorBundle.getFormattedString("xpinstallPromptWarning",
+ [brandShortName, installInfo.originatingURI.host]);
+
+ let secHistogram = Components.classes["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry).getHistogramById("SECURITY_UI");
+ action = {
+ label: gNavigatorBundle.getString("xpinstallPromptAllowButton"),
+ accessKey: gNavigatorBundle.getString("xpinstallPromptAllowButton.accesskey"),
+ callback: function() {
+ secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED_CLICK_THROUGH);
+ installInfo.install();
+ }
+ };
+
+ secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED);
+ PopupNotifications.show(browser, notificationID, messageString, anchorID,
+ action, null, options);
+ break;
+ case "addon-install-started":
+ var needsDownload = function needsDownload(aInstall) {
+ return aInstall.state != AddonManager.STATE_DOWNLOADED;
+ }
+ // If all installs have already been downloaded then there is no need to
+ // show the download progress
+ if (!installInfo.installs.some(needsDownload))
+ return;
+ notificationID = "addon-progress";
+ messageString = gNavigatorBundle.getString("addonDownloading");
+ messageString = PluralForm.get(installInfo.installs.length, messageString);
+ options.installs = installInfo.installs;
+ options.contentWindow = browser.contentWindow;
+ options.sourceURI = browser.currentURI;
+ options.eventCallback = function(aEvent) {
+ if (aEvent != "removed")
+ return;
+ options.contentWindow = null;
+ options.sourceURI = null;
+ };
+ PopupNotifications.show(browser, notificationID, messageString, anchorID,
+ null, null, options);
+ break;
+ case "addon-install-failed":
+ // TODO This isn't terribly ideal for the multiple failure case
+ for (let install of installInfo.installs) {
+ let host = (installInfo.originatingURI instanceof Ci.nsIStandardURL) &&
+ installInfo.originatingURI.host;
+ if (!host)
+ host = (install.sourceURI instanceof Ci.nsIStandardURL) &&
+ install.sourceURI.host;
+
+ let error = (host || install.error == 0) ? "addonError" : "addonLocalError";
+ if (install.error != 0)
+ error += install.error;
+ else if (install.addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED)
+ error += "Blocklisted";
+ else
+ error += "Incompatible";
+
+ messageString = gNavigatorBundle.getString(error);
+ messageString = messageString.replace("#1", install.name);
+ if (host)
+ messageString = messageString.replace("#2", host);
+ messageString = messageString.replace("#3", brandShortName);
+ messageString = messageString.replace("#4", Services.appinfo.version);
+
+ PopupNotifications.show(browser, notificationID, messageString, anchorID,
+ action, null, options);
+ }
+ break;
+ case "addon-install-complete":
+ var needsRestart = installInfo.installs.some(function(i) {
+ return i.addon.pendingOperations != AddonManager.PENDING_NONE;
+ });
+
+ if (needsRestart) {
+ messageString = gNavigatorBundle.getString("addonsInstalledNeedsRestart");
+ action = {
+ label: gNavigatorBundle.getString("addonInstallRestartButton"),
+ accessKey: gNavigatorBundle.getString("addonInstallRestartButton.accesskey"),
+ callback: function() {
+ Application.restart();
+ }
+ };
+ }
+ else {
+ messageString = gNavigatorBundle.getString("addonsInstalled");
+ action = null;
+ }
+
+ messageString = PluralForm.get(installInfo.installs.length, messageString);
+ messageString = messageString.replace("#1", installInfo.installs[0].name);
+ messageString = messageString.replace("#2", installInfo.installs.length);
+ messageString = messageString.replace("#3", brandShortName);
+
+ // Remove notificaion on dismissal, since it's possible to cancel the
+ // install through the addons manager UI, making the "restart" prompt
+ // irrelevant.
+ options.removeOnDismissal = true;
+
+ PopupNotifications.show(browser, notificationID, messageString, anchorID,
+ action, null, options);
+ break;
+ }
+ }
+};
+
+/*
+ * When addons are installed/uninstalled, check and see if the number of items
+ * on the add-on bar changed:
+ * - If an add-on was installed, incrementing the count, show the bar.
+ * - If an add-on was uninstalled, and no more items are left, hide the bar.
+ */
+let AddonsMgrListener = {
+ get addonBar() document.getElementById("addon-bar"),
+ get statusBar() document.getElementById("status-bar"),
+ getAddonBarItemCount: function() {
+ // Take into account the contents of the status bar shim for the count.
+ var itemCount = this.statusBar.childNodes.length;
+
+ var defaultOrNoninteractive = this.addonBar.getAttribute("defaultset")
+ .split(",")
+ .concat(["separator", "spacer", "spring"]);
+ for (let item of this.addonBar.currentSet.split(",")) {
+ if (defaultOrNoninteractive.indexOf(item) == -1)
+ itemCount++;
+ }
+
+ return itemCount;
+ },
+ onInstalling: function(aAddon) {
+ this.lastAddonBarCount = this.getAddonBarItemCount();
+ },
+ onInstalled: function(aAddon) {
+ if (this.getAddonBarItemCount() > this.lastAddonBarCount)
+ setToolbarVisibility(this.addonBar, true);
+ },
+ onUninstalling: function(aAddon) {
+ this.lastAddonBarCount = this.getAddonBarItemCount();
+ },
+ onUninstalled: function(aAddon) {
+ if (this.getAddonBarItemCount() == 0)
+ setToolbarVisibility(this.addonBar, false);
+ },
+ onEnabling: function(aAddon) this.onInstalling(),
+ onEnabled: function(aAddon) this.onInstalled(),
+ onDisabling: function(aAddon) this.onUninstalling(),
+ onDisabled: function(aAddon) this.onUninstalled(),
+};
+
+
+var LightWeightThemeWebInstaller = {
+ handleEvent: function (event) {
+ switch (event.type) {
+ case "InstallBrowserTheme":
+ case "PreviewBrowserTheme":
+ case "ResetBrowserThemePreview":
+ // ignore requests from background tabs
+ if (event.target.ownerDocument.defaultView.top != content)
+ return;
+ }
+ switch (event.type) {
+ case "InstallBrowserTheme":
+ this._installRequest(event);
+ break;
+ case "PreviewBrowserTheme":
+ this._preview(event);
+ break;
+ case "ResetBrowserThemePreview":
+ this._resetPreview(event);
+ break;
+ case "pagehide":
+ case "TabSelect":
+ this._resetPreview();
+ break;
+ }
+ },
+
+ get _manager () {
+ var temp = {};
+ Cu.import("resource://gre/modules/LightweightThemeManager.jsm", temp);
+ delete this._manager;
+ return this._manager = temp.LightweightThemeManager;
+ },
+
+ _installRequest: function (event) {
+ var node = event.target;
+ var data = this._getThemeFromNode(node);
+ if (!data)
+ return;
+
+ if (this._isAllowed(node)) {
+ this._install(data);
+ return;
+ }
+
+ var allowButtonText =
+ gNavigatorBundle.getString("lwthemeInstallRequest.allowButton");
+ var allowButtonAccesskey =
+ gNavigatorBundle.getString("lwthemeInstallRequest.allowButton.accesskey");
+ var message =
+ gNavigatorBundle.getFormattedString("lwthemeInstallRequest.message",
+ [node.ownerDocument.location.host]);
+ var buttons = [{
+ label: allowButtonText,
+ accessKey: allowButtonAccesskey,
+ callback: function () {
+ LightWeightThemeWebInstaller._install(data);
+ }
+ }];
+
+ this._removePreviousNotifications();
+
+ var notificationBox = gBrowser.getNotificationBox();
+ var notificationBar =
+ notificationBox.appendNotification(message, "lwtheme-install-request", "",
+ notificationBox.PRIORITY_INFO_MEDIUM,
+ buttons);
+ notificationBar.persistence = 1;
+ },
+
+ _install: function (newLWTheme) {
+ var previousLWTheme = this._manager.currentTheme;
+
+ var listener = {
+ onEnabling: function(aAddon, aRequiresRestart) {
+ if (!aRequiresRestart)
+ return;
+
+ let messageString = gNavigatorBundle.getFormattedString("lwthemeNeedsRestart.message",
+ [aAddon.name], 1);
+
+ let action = {
+ label: gNavigatorBundle.getString("lwthemeNeedsRestart.button"),
+ accessKey: gNavigatorBundle.getString("lwthemeNeedsRestart.accesskey"),
+ callback: function () {
+ Application.restart();
+ }
+ };
+
+ let options = {
+ timeout: Date.now() + 30000
+ };
+
+ PopupNotifications.show(gBrowser.selectedBrowser, "addon-theme-change",
+ messageString, "addons-notification-icon",
+ action, null, options);
+ },
+
+ onEnabled: function(aAddon) {
+ LightWeightThemeWebInstaller._postInstallNotification(newLWTheme, previousLWTheme);
+ }
+ };
+
+ AddonManager.addAddonListener(listener);
+ this._manager.currentTheme = newLWTheme;
+ AddonManager.removeAddonListener(listener);
+ },
+
+ _postInstallNotification: function (newTheme, previousTheme) {
+ function text(id) {
+ return gNavigatorBundle.getString("lwthemePostInstallNotification." + id);
+ }
+
+ var buttons = [{
+ label: text("undoButton"),
+ accessKey: text("undoButton.accesskey"),
+ callback: function () {
+ LightWeightThemeWebInstaller._manager.forgetUsedTheme(newTheme.id);
+ LightWeightThemeWebInstaller._manager.currentTheme = previousTheme;
+ }
+ }, {
+ label: text("manageButton"),
+ accessKey: text("manageButton.accesskey"),
+ callback: function () {
+ BrowserOpenAddonsMgr("addons://list/theme");
+ }
+ }];
+
+ this._removePreviousNotifications();
+
+ var notificationBox = gBrowser.getNotificationBox();
+ var notificationBar =
+ notificationBox.appendNotification(text("message"),
+ "lwtheme-install-notification", "",
+ notificationBox.PRIORITY_INFO_MEDIUM,
+ buttons);
+ notificationBar.persistence = 1;
+ notificationBar.timeout = Date.now() + 20000; // 20 seconds
+ },
+
+ _removePreviousNotifications: function () {
+ var box = gBrowser.getNotificationBox();
+
+ ["lwtheme-install-request",
+ "lwtheme-install-notification"].forEach(function (value) {
+ var notification = box.getNotificationWithValue(value);
+ if (notification)
+ box.removeNotification(notification);
+ });
+ },
+
+ _previewWindow: null,
+ _preview: function (event) {
+ if (!this._isAllowed(event.target))
+ return;
+
+ var data = this._getThemeFromNode(event.target);
+ if (!data)
+ return;
+
+ this._resetPreview();
+
+ this._previewWindow = event.target.ownerDocument.defaultView;
+ this._previewWindow.addEventListener("pagehide", this, true);
+ gBrowser.tabContainer.addEventListener("TabSelect", this, false);
+
+ this._manager.previewTheme(data);
+ },
+
+ _resetPreview: function (event) {
+ if (!this._previewWindow ||
+ event && !this._isAllowed(event.target))
+ return;
+
+ this._previewWindow.removeEventListener("pagehide", this, true);
+ this._previewWindow = null;
+ gBrowser.tabContainer.removeEventListener("TabSelect", this, false);
+
+ this._manager.resetPreview();
+ },
+
+ _isAllowed: function (node) {
+ var pm = Services.perms;
+
+ var uri = node.ownerDocument.documentURIObject;
+ return pm.testPermission(uri, "install") == pm.ALLOW_ACTION;
+ },
+
+ _getThemeFromNode: function (node) {
+ return this._manager.parseTheme(node.getAttribute("data-browsertheme"),
+ node.baseURI);
+ }
+}
diff --git a/browser/base/content/browser-appmenu.inc b/browser/base/content/browser-appmenu.inc
new file mode 100644
index 000000000..87276a537
--- /dev/null
+++ b/browser/base/content/browser-appmenu.inc
@@ -0,0 +1,400 @@
+# -*- Mode: HTML -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<menupopup id="appmenu-popup"
+ onpopupshowing="if (event.target == this) {
+ updateEditUIVisibility();
+#ifdef MOZ_SERVICES_SYNC
+ gSyncUI.updateUI();
+#endif
+ return;
+ }
+ updateCharacterEncodingMenuState();
+ if (event.target.parentNode.parentNode.parentNode.parentNode == this)
+ this._currentPopup = event.target;">
+ <hbox>
+ <vbox id="appmenuPrimaryPane">
+ <splitmenu id="appmenu_newTab"
+ label="&tabCmd.label;"
+ command="cmd_newNavigatorTab">
+ <menupopup>
+ <menuitem id="appmenu_newTab_popup"
+ label="&tabCmd.label;"
+ command="cmd_newNavigatorTab"
+ key="key_newNavigatorTab"/>
+ <menuitem id="appmenu_newNavigator"
+ label="&newNavigatorCmd.label;"
+ command="cmd_newNavigator"
+ key="key_newNavigator"/>
+ <menuseparator/>
+ <menuitem id="appmenu_openFile"
+ label="&openFileCmd.label;"
+ command="Browser:OpenFile"
+ key="openFileKb"/>
+ </menupopup>
+ </splitmenu>
+ <menuitem id="appmenu_newPrivateWindow"
+ class="menuitem-iconic menuitem-iconic-tooltip"
+ label="&newPrivateWindow.label;"
+ command="Tools:PrivateBrowsing"
+ key="key_privatebrowsing"/>
+ <menuitem label="&goOfflineCmd.label;"
+ id="appmenu_offlineModeRecovery"
+ type="checkbox"
+ observes="workOfflineMenuitemState"
+ oncommand="BrowserOffline.toggleOfflineStatus();"/>
+ <menuseparator class="appmenu-menuseparator"/>
+ <hbox>
+ <menuitem id="appmenu-edit-label"
+ label="&appMenuEdit.label;"
+ disabled="true"/>
+ <toolbarbutton id="appmenu-cut"
+ class="appmenu-edit-button"
+ command="cmd_cut"
+ onclick="if (!this.disabled) hidePopup();"
+ tooltiptext="&cutButton.tooltip;"/>
+ <toolbarbutton id="appmenu-copy"
+ class="appmenu-edit-button"
+ command="cmd_copy"
+ onclick="if (!this.disabled) hidePopup();"
+ tooltiptext="&copyButton.tooltip;"/>
+ <toolbarbutton id="appmenu-paste"
+ class="appmenu-edit-button"
+ command="cmd_paste"
+ onclick="if (!this.disabled) hidePopup();"
+ tooltiptext="&pasteButton.tooltip;"/>
+ <spacer flex="1"/>
+ <menu id="appmenu-editmenu">
+ <menupopup id="appmenu-editmenu-menupopup">
+ <menuitem id="appmenu-editmenu-cut"
+ class="menuitem-iconic"
+ label="&cutCmd.label;"
+ key="key_cut"
+ command="cmd_cut"/>
+ <menuitem id="appmenu-editmenu-copy"
+ class="menuitem-iconic"
+ label="&copyCmd.label;"
+ key="key_copy"
+ command="cmd_copy"/>
+ <menuitem id="appmenu-editmenu-paste"
+ class="menuitem-iconic"
+ label="&pasteCmd.label;"
+ key="key_paste"
+ command="cmd_paste"/>
+ <menuseparator/>
+ <menuitem id="appmenu-editmenu-undo"
+ label="&undoCmd.label;"
+ key="key_undo"
+ command="cmd_undo"/>
+ <menuitem id="appmenu-editmenu-redo"
+ label="&redoCmd.label;"
+ key="key_redo"
+ command="cmd_redo"/>
+ <menuseparator/>
+ <menuitem id="appmenu-editmenu-selectAll"
+ label="&selectAllCmd.label;"
+ key="key_selectAll"
+ command="cmd_selectAll"/>
+ <menuseparator/>
+ <menuitem id="appmenu-editmenu-delete"
+ label="&deleteCmd.label;"
+ key="key_delete"
+ command="cmd_delete"/>
+ </menupopup>
+ </menu>
+ </hbox>
+ <menuitem id="appmenu_find"
+ class="menuitem-tooltip"
+ label="&appMenuFind.label;"
+ command="cmd_find"
+ key="key_find"/>
+ <menuseparator class="appmenu-menuseparator"/>
+ <menuitem id="appmenu_savePage"
+ class="menuitem-tooltip"
+ label="&savePageCmd.label;"
+ command="Browser:SavePage"
+ key="key_savePage"/>
+ <menuitem id="appmenu_sendLink"
+ label="&emailPageCmd.label;"
+ command="Browser:SendLink"/>
+ <splitmenu id="appmenu_print"
+ iconic="true"
+ label="&printCmd.label;"
+ command="cmd_print">
+ <menupopup>
+ <menuitem id="appmenu_print_popup"
+ class="menuitem-iconic"
+ label="&printCmd.label;"
+ command="cmd_print"
+ key="printKb"/>
+ <menuitem id="appmenu_printPreview"
+ label="&printPreviewCmd.label;"
+ command="cmd_printPreview"/>
+ <menuitem id="appmenu_printSetup"
+ label="&printSetupCmd.label;"
+ command="cmd_pageSetup"/>
+ </menupopup>
+ </splitmenu>
+ <menuseparator class="appmenu-menuseparator"/>
+ <splitmenu id="appmenu_webDeveloper"
+ command="Tools:DevToolbox"
+ label="&appMenuWebDeveloper.label;">
+ <menupopup id="appmenu_webDeveloper_popup">
+ <menuitem id="appmenu_devToolbox"
+ observes="devtoolsMenuBroadcaster_DevToolbox"/>
+ <menuseparator id="appmenu_devtools_separator"/>
+ <menuitem id="appmenu_devToolbar"
+ observes="devtoolsMenuBroadcaster_DevToolbar"/>
+ <menuitem id="appmenu_chromeDebugger"
+ observes="devtoolsMenuBroadcaster_ChromeDebugger"/>
+ <menuitem id="appmenu_browserConsole"
+ observes="devtoolsMenuBroadcaster_BrowserConsole"/>
+ <menuitem id="appmenu_responsiveUI"
+ observes="devtoolsMenuBroadcaster_ResponsiveUI"/>
+ <menuitem id="appmenu_scratchpad"
+ observes="devtoolsMenuBroadcaster_Scratchpad"/>
+ <menuitem id="appmenu_pageSource"
+ observes="devtoolsMenuBroadcaster_PageSource"/>
+ <menuitem id="appmenu_errorConsole"
+ observes="devtoolsMenuBroadcaster_ErrorConsole"/>
+ <menuitem id="appmenu_devtools_connect"
+ observes="devtoolsMenuBroadcaster_connect"/>
+ <menuseparator id="appmenu_devToolsEndSeparator"/>
+ <menuitem id="appmenu_getMoreDevtools"
+ observes="devtoolsMenuBroadcaster_GetMoreTools"/>
+ <menuseparator/>
+#define ID_PREFIX appmenu_developer_
+#define OMIT_ACCESSKEYS
+#include browser-charsetmenu.inc
+#undef ID_PREFIX
+#undef OMIT_ACCESSKEYS
+ <menuitem label="&goOfflineCmd.label;"
+ type="checkbox"
+ observes="workOfflineMenuitemState"
+ oncommand="BrowserOffline.toggleOfflineStatus();"/>
+ </menupopup>
+ </splitmenu>
+ <menuseparator class="appmenu-menuseparator"/>
+#define ID_PREFIX appmenu_
+#define OMIT_ACCESSKEYS
+#include browser-charsetmenu.inc
+#undef ID_PREFIX
+#undef OMIT_ACCESSKEYS
+ <menuitem id="appmenu_fullScreen"
+ class="menuitem-tooltip"
+ label="&fullScreenCmd.label;"
+ type="checkbox"
+ observes="View:FullScreen"
+ key="key_fullScreen"/>
+#ifdef MOZ_SERVICES_SYNC
+ <!-- only one of sync-setup or sync-syncnow will be showing at once -->
+ <menuitem id="sync-setup-appmenu"
+ label="&syncSetup.label;"
+ observes="sync-setup-state"
+ oncommand="gSyncUI.openSetup()"/>
+ <menuitem id="sync-syncnowitem-appmenu"
+ label="&syncSyncNowItem.label;"
+ observes="sync-syncnow-state"
+ oncommand="gSyncUI.doSync(event);"/>
+#endif
+ <menuitem id="appmenu-quit"
+ class="menuitem-iconic"
+#ifdef XP_WIN
+ label="&quitApplicationCmdWin.label;"
+#else
+ label="&quitApplicationCmd.label;"
+#endif
+ command="cmd_quitApplication"/>
+ </vbox>
+ <vbox id="appmenuSecondaryPane">
+ <splitmenu id="appmenu_bookmarks"
+ iconic="true"
+ label="&bookmarksMenu.label;"
+ command="Browser:ShowAllBookmarks">
+ <menupopup id="appmenu_bookmarksPopup"
+ placespopup="true"
+ context="placesContext"
+ openInTabs="children"
+ oncommand="BookmarksEventHandler.onCommand(event, this.parentNode._placesView);"
+ onclick="BookmarksEventHandler.onClick(event, this.parentNode._placesView);"
+ onpopupshowing="BookmarkingUI.onPopupShowing(event);
+ if (!this.parentNode._placesView)
+ new PlacesMenu(event, 'place:folder=BOOKMARKS_MENU');"
+ tooltip="bhTooltip"
+ popupsinherittooltip="true">
+ <menuitem id="appmenu_showAllBookmarks"
+ label="&palemoon.menu.allBookmarks.label;"
+ command="Browser:ShowAllBookmarks"
+ context=""
+ key="manBookmarkKb"/>
+ <menuseparator/>
+ <menuitem id="appmenu_bookmarkThisPage"
+ class="menuitem-iconic"
+ label="&bookmarkThisPageCmd.label;"
+ command="Browser:AddBookmarkAs"
+ key="addBookmarkAsKb"/>
+ <menuitem id="appmenu_subscribeToPage"
+ class="menuitem-iconic"
+ label="&subscribeToPageMenuitem.label;"
+ oncommand="return FeedHandler.subscribeToFeed(null, event);"
+ onclick="checkForMiddleClick(this, event);"
+ observes="singleFeedMenuitemState"/>
+ <menu id="appmenu_subscribeToPageMenu"
+ class="menu-iconic"
+ label="&subscribeToPageMenupopup.label;"
+ observes="multipleFeedsMenuState">
+ <menupopup id="appmenu_subscribeToPageMenupopup"
+ onpopupshowing="return FeedHandler.buildFeedList(event.target);"
+ oncommand="return FeedHandler.subscribeToFeed(null, event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ </menu>
+ <menuseparator/>
+ <menu id="appmenu_bookmarksToolbar"
+ placesanonid="toolbar-autohide"
+ class="menu-iconic bookmark-item"
+ label="&personalbarCmd.label;"
+ container="true">
+ <menupopup id="appmenu_bookmarksToolbarPopup"
+ placespopup="true"
+ context="placesContext"
+ onpopupshowing="if (!this.parentNode._placesView)
+ new PlacesMenu(event, 'place:folder=TOOLBAR');"/>
+ </menu>
+ <menuseparator/>
+ <!-- Bookmarks menu items -->
+ <menuseparator builder="end"
+ class="hide-if-empty-places-result"/>
+ <menuitem id="appmenu_unsortedBookmarks"
+ label="&appMenuUnsorted.label;"
+ oncommand="PlacesCommandHook.showPlacesOrganizer('UnfiledBookmarks');"
+ class="menuitem-iconic"/>
+ </menupopup>
+ </splitmenu>
+ <splitmenu id="appmenu_history"
+ iconic="true"
+ label="&historyMenu.label;"
+ command="Browser:ShowAllHistory">
+ <menupopup id="appmenu_historyMenupopup"
+ placespopup="true"
+ oncommand="this.parentNode._placesView._onCommand(event);"
+ onclick="checkForMiddleClick(this, event);"
+ onpopupshowing="if (!this.parentNode._placesView)
+ new HistoryMenu(event);"
+ tooltip="bhTooltip"
+ popupsinherittooltip="true">
+ <menuitem id="appmenu_showAllHistory"
+ label="&showAllHistoryCmd2.label;"
+ command="Browser:ShowAllHistory"
+ key="showAllHistoryKb"/>
+ <menuseparator/>
+ <menuitem id="appmenu_sanitizeHistory"
+ label="&clearRecentHistory.label;"
+ key="key_sanitize"
+ command="Tools:Sanitize"/>
+ <menuseparator class="hide-if-empty-places-result"/>
+#ifdef MOZ_SERVICES_SYNC
+ <menuitem id="appmenu_sync-tabs"
+ class="syncTabsMenuItem"
+ label="&syncTabsMenu2.label;"
+ oncommand="BrowserOpenSyncTabs();"
+ disabled="true"/>
+#endif
+ <menuitem id="appmenu_restoreLastSession"
+ label="&historyRestoreLastSession.label;"
+ command="Browser:RestoreLastSession"/>
+ <menu id="appmenu_recentlyClosedTabsMenu"
+ class="recentlyClosedTabsMenu"
+ label="&historyUndoMenu.label;"
+ disabled="true">
+ <menupopup id="appmenu_recentlyClosedTabsMenupopup"
+ onpopupshowing="document.getElementById('appmenu_history')._placesView.populateUndoSubmenu();"/>
+ </menu>
+ <menu id="appmenu_recentlyClosedWindowsMenu"
+ class="recentlyClosedWindowsMenu"
+ label="&historyUndoWindowMenu.label;"
+ disabled="true">
+ <menupopup id="appmenu_recentlyClosedWindowsMenupopup"
+ onpopupshowing="document.getElementById('appmenu_history')._placesView.populateUndoWindowSubmenu();"/>
+ </menu>
+ <menuseparator/>
+ </menupopup>
+ </splitmenu>
+ <menuitem id="appmenu_downloads"
+ class="menuitem-tooltip"
+ label="&downloads.label;"
+ command="Tools:Downloads"
+ key="key_openDownloads"/>
+ <spacer id="appmenuSecondaryPane-spacer"/>
+ <menuitem id="appmenu_addons"
+ class="menuitem-iconic menuitem-iconic-tooltip"
+ label="&addons.label;"
+ command="Tools:Addons"
+ key="key_openAddons"/>
+ <splitmenu id="appmenu_customize"
+#ifdef XP_UNIX
+ label="&preferencesCmdUnix.label;"
+#else
+ label="&preferencesCmd2.label;"
+#endif
+ oncommand="openPreferences();">
+ <menupopup id="appmenu_customizeMenu"
+ onpopupshowing="onViewToolbarsPopupShowing(event, document.getElementById('appmenu_toggleToolbarsSeparator'));">
+ <menuitem id="appmenu_preferences"
+#ifdef XP_UNIX
+ label="&preferencesCmdUnix.label;"
+#else
+ label="&preferencesCmd2.label;"
+#endif
+ oncommand="openPreferences();"/>
+ <menuseparator/>
+ <menuseparator id="appmenu_toggleToolbarsSeparator"/>
+ <menuitem id="appmenu_toggleTabsOnTop"
+ label="&viewTabsOnTop.label;"
+ type="checkbox"
+ command="cmd_ToggleTabsOnTop"/>
+ <menuitem id="appmenu_toolbarLayout"
+ label="&appMenuToolbarLayout.label;"
+ command="cmd_CustomizeToolbars"/>
+ </menupopup>
+ </splitmenu>
+ <splitmenu id="appmenu_help"
+ label="&helpMenu.label;"
+ oncommand="openHelpLink('firefox-help')">
+ <menupopup id="appmenu_helpMenupopup">
+ <menuitem id="appmenu_openHelp"
+ label="&helpMenu.label;"
+ oncommand="openHelpLink('firefox-help')"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="appmenu_gettingStarted"
+ label="&appMenuGettingStarted.label;"
+ oncommand="gBrowser.loadOneTab('https://www.mozilla.org/firefox/central/', {inBackground: false});"
+ onclick="checkForMiddleClick(this, event);"/>
+#ifdef MOZ_SERVICES_HEALTHREPORT
+ <menuitem id="appmenu_healthReport"
+ label="&healthReport.label;"
+ oncommand="openHealthReport()"
+ onclick="checkForMiddleClick(this, event);"/>
+#endif
+ <menuitem id="appmenu_troubleshootingInfo"
+ label="&helpTroubleshootingInfo.label;"
+ oncommand="openTroubleshootingPage()"
+ onclick="checkForMiddleClick(this,event);"/>
+ <menuitem id="appmenu_feedbackPage"
+ label="&helpFeedbackPage.label;"
+ oncommand="openFeedbackPage()"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuseparator/>
+ <menuitem id="appmenu_safeMode"
+ label="&appMenuSafeMode.label;"
+ oncommand="safeModeRestart();"/>
+ <menuseparator/>
+ <menuitem id="appmenu_about"
+ label="&aboutProduct.label;"
+ oncommand="openAboutDialog();"/>
+ </menupopup>
+ </splitmenu>
+ </vbox>
+ </hbox>
+</menupopup>
diff --git a/browser/base/content/browser-charsetmenu.inc b/browser/base/content/browser-charsetmenu.inc
new file mode 100644
index 000000000..c6ac63e3b
--- /dev/null
+++ b/browser/base/content/browser-charsetmenu.inc
@@ -0,0 +1,145 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#filter substitution
+
+#expand <menu id="__ID_PREFIX__charsetMenu"
+ label="&charsetMenu.label;"
+#ifndef OMIT_ACCESSKEYS
+ accesskey="&charsetMenu.accesskey;"
+#endif
+ datasources="rdf:charset-menu"
+ ref="NC:BrowserCharsetMenuRoot"
+ oncommand="MultiplexHandler(event)"
+ onpopupshowing="CreateMenu('browser'); CreateMenu('more-menu');"
+ onpopupshown="UpdateMenus(event);"
+ observes="isImage">
+ <template>
+ <rule rdf:type="http://home.netscape.com/NC-rdf#BookmarkSeparator">
+ <menupopup>
+ <menuseparator uri="..." />
+ </menupopup>
+ </rule>
+ <rule>
+ <menupopup>
+ <menuitem type="radio" name="charsetGroup" checked="rdf:http://home.netscape.com/NC-rdf#Checked" uri="..." label="rdf:http://home.netscape.com/NC-rdf#Name"/>
+ </menupopup>
+ </rule>
+ </template>
+
+ <menupopup>
+ <menu label="&charsetMenuAutodet.label;"
+#ifndef OMIT_ACCESSKEYS
+ accesskey="&charsetMenuAutodet.accesskey;"
+#endif
+ datasources="rdf:charset-menu" ref="NC:BrowserAutodetMenuRoot">
+ <template>
+ <rule rdf:type="http://home.netscape.com/NC-rdf#CharsetDetector">
+ <menupopup>
+ <menuitem type="radio" name="detectorGroup" checked="rdf:http://home.netscape.com/NC-rdf#Checked" uri="..." label="rdf:http://home.netscape.com/NC-rdf#Name"/>
+ </menupopup>
+ </rule>
+ </template>
+ <menupopup>
+ </menupopup>
+ </menu>
+ <menu label="&charsetMenuMore.label;"
+#ifndef OMIT_ACCESSKEYS
+ accesskey="&charsetMenuMore.accesskey;"
+#endif
+ datasources="rdf:charset-menu" ref="NC:BrowserMoreCharsetMenuRoot">
+ <template>
+ <rule>
+ <menupopup>
+ <menuitem uri="..." label="rdf:http://home.netscape.com/NC-rdf#Name"/>
+ </menupopup>
+ </rule>
+ </template>
+ <menupopup>
+ <menu label="&charsetMenuMore1.label;"
+#ifndef OMIT_ACCESSKEYS
+ accesskey="&charsetMenuMore1.accesskey;"
+#endif
+ datasources="rdf:charset-menu" ref="NC:BrowserMore1CharsetMenuRoot">
+ <template>
+ <rule>
+ <menupopup>
+ <menuitem uri="..." label="rdf:http://home.netscape.com/NC-rdf#Name"/>
+ </menupopup>
+ </rule>
+ </template>
+ <menupopup>
+ </menupopup>
+ </menu>
+ <menu label="&charsetMenuMore2.label;"
+#ifndef OMIT_ACCESSKEYS
+ accesskey="&charsetMenuMore2.accesskey;"
+#endif
+ datasources="rdf:charset-menu" ref="NC:BrowserMore2CharsetMenuRoot">
+ <template>
+ <rule>
+ <menupopup>
+ <menuitem uri="..." label="rdf:http://home.netscape.com/NC-rdf#Name"/>
+ </menupopup>
+ </rule>
+ </template>
+ <menupopup>
+ </menupopup>
+ </menu>
+ <menu label="&charsetMenuMore3.label;"
+#ifndef OMIT_ACCESSKEYS
+ accesskey="&charsetMenuMore3.accesskey;"
+#endif
+ datasources="rdf:charset-menu" ref="NC:BrowserMore3CharsetMenuRoot">
+ <template>
+ <rule>
+ <menupopup>
+ <menuitem uri="..." label="rdf:http://home.netscape.com/NC-rdf#Name"/>
+ </menupopup>
+ </rule>
+ </template>
+ <menupopup>
+ </menupopup>
+ </menu>
+ <menu label="&charsetMenuMore4.label;"
+#ifndef OMIT_ACCESSKEYS
+ accesskey="&charsetMenuMore4.accesskey;"
+#endif
+ datasources="rdf:charset-menu" ref="NC:BrowserMore4CharsetMenuRoot">
+ <template>
+ <rule>
+ <menupopup>
+ <menuitem uri="..." label="rdf:http://home.netscape.com/NC-rdf#Name"/>
+ </menupopup>
+ </rule>
+ </template>
+ <menupopup>
+ </menupopup>
+ </menu>
+ <menu label="&charsetMenuMore5.label;"
+#ifndef OMIT_ACCESSKEYS
+ accesskey="&charsetMenuMore5.accesskey;"
+#endif
+ datasources="rdf:charset-menu" ref="NC:BrowserMore5CharsetMenuRoot">
+ <template>
+ <rule>
+ <menupopup>
+ <menuitem uri="..." label="rdf:http://home.netscape.com/NC-rdf#Name"/>
+ </menupopup>
+ </rule>
+ </template>
+ <menupopup>
+ </menupopup>
+ </menu>
+ <menuseparator />
+ </menupopup>
+ </menu>
+ <menuitem name="charsetCustomize"
+#ifndef OMIT_ACCESSKEYS
+ accesskey="&charsetCustomize.accesskey;"
+#endif
+ label="&charsetCustomize.label;"
+ oncommand="window.openDialog('chrome://global/content/customizeCharset.xul', 'PrefWindow', 'chrome,modal=yes,resizable=yes', 'browser');"/>
+ </menupopup>
+</menu>
diff --git a/browser/base/content/browser-context.inc b/browser/base/content/browser-context.inc
new file mode 100644
index 000000000..90f045ead
--- /dev/null
+++ b/browser/base/content/browser-context.inc
@@ -0,0 +1,403 @@
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+ <menuseparator id="page-menu-separator"/>
+ <menuitem id="spell-no-suggestions"
+ disabled="true"
+ label="&spellNoSuggestions.label;"/>
+ <menuitem id="spell-add-to-dictionary"
+ label="&spellAddToDictionary.label;"
+ accesskey="&spellAddToDictionary.accesskey;"
+ oncommand="InlineSpellCheckerUI.addToDictionary();"/>
+ <menuitem id="spell-undo-add-to-dictionary"
+ label="&spellUndoAddToDictionary.label;"
+ accesskey="&spellUndoAddToDictionary.accesskey;"
+ oncommand="InlineSpellCheckerUI.undoAddToDictionary();" />
+ <menuseparator id="spell-suggestions-separator"/>
+ <menuitem id="context-openlinkincurrent"
+ label="&openLinkCmdInCurrent.label;"
+ accesskey="&openLinkCmdInCurrent.accesskey;"
+ oncommand="gContextMenu.openLinkInCurrent();"/>
+ <menuitem id="context-openlinkintab"
+ label="&openLinkCmdInTab.label;"
+ accesskey="&openLinkCmdInTab.accesskey;"
+ oncommand="gContextMenu.openLinkInTab();"/>
+ <menuitem id="context-openlink"
+ label="&openLinkCmd.label;"
+ accesskey="&openLinkCmd.accesskey;"
+ oncommand="gContextMenu.openLink();"/>
+ <menuitem id="context-openlinkprivate"
+ label="&openLinkInPrivateWindowCmd.label;"
+ accesskey="&openLinkInPrivateWindowCmd.accesskey;"
+ oncommand="gContextMenu.openLinkInPrivateWindow();"/>
+ <menuseparator id="context-sep-open"/>
+ <menuitem id="context-bookmarklink"
+ label="&bookmarkThisLinkCmd.label;"
+ accesskey="&bookmarkThisLinkCmd.accesskey;"
+ oncommand="gContextMenu.bookmarkLink();"/>
+ <menuitem id="context-marklink"
+ accesskey="&social.marklink.accesskey;"
+ oncommand="gContextMenu.markLink();"/>
+ <menuitem id="context-sharelink"
+ label="&shareLinkCmd.label;"
+ accesskey="&shareLinkCmd.accesskey;"
+ oncommand="gContextMenu.shareLink();"/>
+ <menuitem id="context-savelink"
+ label="&saveLinkCmd.label;"
+ accesskey="&saveLinkCmd.accesskey;"
+ oncommand="gContextMenu.saveLink();"/>
+ <menuitem id="context-sendlink"
+ label="&palemoon.sendLinkCmd.label;"
+ accesskey="&palemoon.sendLinkCmd.accesskey;"
+ oncommand="gContextMenu.sendLink();"/>
+ <menuitem id="context-copyemail"
+ label="&copyEmailCmd.label;"
+ accesskey="&copyEmailCmd.accesskey;"
+ oncommand="gContextMenu.copyEmail();"/>
+ <menuitem id="context-copylink"
+ label="&copyLinkCmd.label;"
+ accesskey="&copyLinkCmd.accesskey;"
+ oncommand="goDoCommand('cmd_copyLink');"/>
+ <menuseparator id="context-sep-copylink"/>
+ <menuitem id="context-media-play"
+ label="&mediaPlay.label;"
+ accesskey="&mediaPlay.accesskey;"
+ oncommand="gContextMenu.mediaCommand('play');"/>
+ <menuitem id="context-media-pause"
+ label="&mediaPause.label;"
+ accesskey="&mediaPause.accesskey;"
+ oncommand="gContextMenu.mediaCommand('pause');"/>
+ <menuitem id="context-media-mute"
+ label="&mediaMute.label;"
+ accesskey="&mediaMute.accesskey;"
+ oncommand="gContextMenu.mediaCommand('mute');"/>
+ <menuitem id="context-media-unmute"
+ label="&mediaUnmute.label;"
+ accesskey="&mediaUnmute.accesskey;"
+ oncommand="gContextMenu.mediaCommand('unmute');"/>
+ <menu id="context-media-playbackrate" label="&mediaPlaybackRate.label;" accesskey="&mediaPlaybackRate.accesskey;">
+ <menupopup>
+ <menuitem id="context-media-playbackrate-050x"
+ label="&mediaPlaybackRate050x.label;"
+ accesskey="&mediaPlaybackRate050x.accesskey;"
+ type="radio"
+ name="playbackrate"
+ oncommand="gContextMenu.mediaCommand('playbackRate', 0.5);"/>
+ <menuitem id="context-media-playbackrate-100x"
+ label="&mediaPlaybackRate100x.label;"
+ accesskey="&mediaPlaybackRate100x.accesskey;"
+ type="radio"
+ name="playbackrate"
+ checked="true"
+ oncommand="gContextMenu.mediaCommand('playbackRate', 1.0);"/>
+ <menuitem id="context-media-playbackrate-150x"
+ label="&mediaPlaybackRate150x.label;"
+ accesskey="&mediaPlaybackRate150x.accesskey;"
+ type="radio"
+ name="playbackrate"
+ oncommand="gContextMenu.mediaCommand('playbackRate', 1.5);"/>
+ <menuitem id="context-media-playbackrate-200x"
+ label="&mediaPlaybackRate200x.label;"
+ accesskey="&mediaPlaybackRate200x.accesskey;"
+ type="radio"
+ name="playbackrate"
+ oncommand="gContextMenu.mediaCommand('playbackRate', 2.0);"/>
+ </menupopup>
+ </menu>
+ <menuitem id="context-media-showcontrols"
+ label="&mediaShowControls.label;"
+ accesskey="&mediaShowControls.accesskey;"
+ oncommand="gContextMenu.mediaCommand('showcontrols');"/>
+ <menuitem id="context-media-hidecontrols"
+ label="&mediaHideControls.label;"
+ accesskey="&mediaHideControls.accesskey;"
+ oncommand="gContextMenu.mediaCommand('hidecontrols');"/>
+ <menuitem id="context-video-showstats"
+ accesskey="&videoShowStats.accesskey;"
+ label="&videoShowStats.label;"
+ oncommand="gContextMenu.mediaCommand('showstats');"/>
+ <menuitem id="context-video-hidestats"
+ accesskey="&videoHideStats.accesskey;"
+ label="&videoHideStats.label;"
+ oncommand="gContextMenu.mediaCommand('hidestats');"/>
+ <menuitem id="context-video-fullscreen"
+ accesskey="&videoFullScreen.accesskey;"
+ label="&videoFullScreen.label;"
+ oncommand="gContextMenu.fullScreenVideo();"/>
+ <menuitem id="context-leave-dom-fullscreen"
+ accesskey="&leaveDOMFullScreen.accesskey;"
+ label="&leaveDOMFullScreen.label;"
+ oncommand="gContextMenu.leaveDOMFullScreen();"/>
+ <menuseparator id="context-media-sep-commands"/>
+ <menuitem id="context-reloadimage"
+ label="&reloadImageCmd.label;"
+ accesskey="&reloadImageCmd.accesskey;"
+ oncommand="gContextMenu.reloadImage();"/>
+ <menuitem id="context-viewimage"
+ label="&viewImageCmd.label;"
+ accesskey="&viewImageCmd.accesskey;"
+ oncommand="gContextMenu.viewMedia(event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="context-viewvideo"
+ label="&viewVideoCmd.label;"
+ accesskey="&viewVideoCmd.accesskey;"
+ oncommand="gContextMenu.viewMedia(event);"
+ onclick="checkForMiddleClick(this, event);"/>
+#ifdef CONTEXT_COPY_IMAGE_CONTENTS
+ <menuitem id="context-copyimage-contents"
+ label="&copyImageContentsCmd.label;"
+ accesskey="&copyImageContentsCmd.accesskey;"
+ oncommand="goDoCommand('cmd_copyImage');"/>
+#endif
+ <menuitem id="context-copyimage"
+ label="&copyImageCmd.label;"
+ accesskey="&copyImageCmd.accesskey;"
+ oncommand="gContextMenu.copyMediaLocation();"/>
+ <menuitem id="context-copyvideourl"
+ label="&copyVideoURLCmd.label;"
+ accesskey="&copyVideoURLCmd.accesskey;"
+ oncommand="gContextMenu.copyMediaLocation();"/>
+ <menuitem id="context-copyaudiourl"
+ label="&copyAudioURLCmd.label;"
+ accesskey="&copyAudioURLCmd.accesskey;"
+ oncommand="gContextMenu.copyMediaLocation();"/>
+ <menuseparator id="context-sep-copyimage"/>
+ <menuitem id="context-saveimage"
+ label="&saveImageCmd.label;"
+ accesskey="&saveImageCmd.accesskey;"
+ oncommand="gContextMenu.saveMedia();"/>
+ <menuitem id="context-shareimage"
+ label="&shareImageCmd.label;"
+ accesskey="&shareImageCmd.accesskey;"
+ oncommand="gContextMenu.shareImage();"/>
+ <menuitem id="context-sendimage"
+ label="&emailImageCmd.label;"
+ accesskey="&emailImageCmd.accesskey;"
+ oncommand="gContextMenu.sendMedia();"/>
+ <menuitem id="context-setDesktopBackground"
+ label="&setDesktopBackgroundCmd.label;"
+ accesskey="&setDesktopBackgroundCmd.accesskey;"
+ oncommand="gContextMenu.setDesktopBackground();"/>
+ <menuitem id="context-viewimageinfo"
+ label="&viewImageInfoCmd.label;"
+ accesskey="&viewImageInfoCmd.accesskey;"
+ oncommand="gContextMenu.viewImageInfo();"/>
+ <menuitem id="context-savevideo"
+ label="&saveVideoCmd.label;"
+ accesskey="&saveVideoCmd.accesskey;"
+ oncommand="gContextMenu.saveMedia();"/>
+ <menuitem id="context-sharevideo"
+ label="&shareVideoCmd.label;"
+ accesskey="&shareVideoCmd.accesskey;"
+ oncommand="gContextMenu.shareVideo();"/>
+ <menuitem id="context-saveaudio"
+ label="&saveAudioCmd.label;"
+ accesskey="&saveAudioCmd.accesskey;"
+ oncommand="gContextMenu.saveMedia();"/>
+ <menuitem id="context-video-saveimage"
+ accesskey="&videoSaveImage.accesskey;"
+ label="&videoSaveImage.label;"
+ oncommand="gContextMenu.saveVideoFrameAsImage();"/>
+ <menuitem id="context-sendvideo"
+ label="&emailVideoCmd.label;"
+ accesskey="&emailVideoCmd.accesskey;"
+ oncommand="gContextMenu.sendMedia();"/>
+ <menuitem id="context-sendaudio"
+ label="&emailAudioCmd.label;"
+ accesskey="&emailAudioCmd.accesskey;"
+ oncommand="gContextMenu.sendMedia();"/>
+ <menuitem id="context-ctp-play"
+ label="&playPluginCmd.label;"
+ accesskey="&playPluginCmd.accesskey;"
+ oncommand="gContextMenu.playPlugin();"/>
+ <menuitem id="context-ctp-hide"
+ label="&hidePluginCmd.label;"
+ accesskey="&hidePluginCmd.accesskey;"
+ oncommand="gContextMenu.hidePlugin();"/>
+ <menuseparator id="context-sep-ctp"/>
+ <menuitem id="context-back"
+ label="&backCmd.label;"
+ accesskey="&backCmd.accesskey;"
+ command="Browser:BackOrBackDuplicate"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="context-forward"
+ label="&forwardCmd.label;"
+ accesskey="&forwardCmd.accesskey;"
+ command="Browser:ForwardOrForwardDuplicate"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="context-reload"
+ label="&reloadCmd.label;"
+ accesskey="&reloadCmd.accesskey;"
+ oncommand="gContextMenu.reload(event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="context-stop"
+ label="&stopCmd.label;"
+ accesskey="&stopCmd.accesskey;"
+ command="Browser:Stop"/>
+ <menuseparator id="context-sep-stop"/>
+ <menuitem id="context-bookmarkpage"
+ label="&bookmarkPageCmd2.label;"
+ accesskey="&bookmarkPageCmd2.accesskey;"
+ oncommand="gContextMenu.bookmarkThisPage();"/>
+ <menuitem id="context-markpage"
+ accesskey="&social.markpage.accesskey;"
+ command="Social:TogglePageMark"/>
+ <menuitem id="context-sharepage"
+ label="&sharePageCmd.label;"
+ accesskey="&sharePageCmd.accesskey;"
+ oncommand="SocialShare.sharePage();"/>
+ <menuitem id="context-savepage"
+ label="&savePageCmd.label;"
+ accesskey="&savePageCmd.accesskey2;"
+ oncommand="gContextMenu.savePageAs();"/>
+ <menuitem id="context-sendpage"
+ label="&palemoon.sendPageCmd.label;"
+ accesskey="&palemoon.sendPageCmd.accesskey;"
+ oncommand="gContextMenu.sendPage();"/>
+ <menuseparator id="context-sep-viewbgimage"/>
+ <menuitem id="context-viewbgimage"
+ label="&viewBGImageCmd.label;"
+ accesskey="&viewBGImageCmd.accesskey;"
+ oncommand="gContextMenu.viewBGImage(event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="context-undo"
+ label="&undoCmd.label;"
+ accesskey="&undoCmd.accesskey;"
+ command="cmd_undo"/>
+ <menuseparator id="context-sep-undo"/>
+ <menuitem id="context-cut"
+ label="&cutCmd.label;"
+ accesskey="&cutCmd.accesskey;"
+ command="cmd_cut"/>
+ <menuitem id="context-copy"
+ label="&copyCmd.label;"
+ accesskey="&copyCmd.accesskey;"
+ command="cmd_copy"/>
+ <menuitem id="context-paste"
+ label="&pasteCmd.label;"
+ accesskey="&pasteCmd.accesskey;"
+ command="cmd_paste"/>
+ <menuitem id="context-delete"
+ label="&deleteCmd.label;"
+ accesskey="&deleteCmd.accesskey;"
+ command="cmd_delete"/>
+ <menuseparator id="context-sep-paste"/>
+ <menuitem id="context-selectall"
+ label="&selectAllCmd.label;"
+ accesskey="&selectAllCmd.accesskey;"
+ command="cmd_selectAll"/>
+ <menuseparator id="context-sep-selectall"/>
+ <menuitem id="context-keywordfield"
+ label="&keywordfield.label;"
+ accesskey="&keywordfield.accesskey;"
+ oncommand="AddKeywordForSearchField();"/>
+ <menuitem id="context-searchselect"
+ oncommand="BrowserSearch.loadSearchFromContext(getBrowserSelection());"/>
+ <menuitem id="context-shareselect"
+ label="&shareSelectCmd.label;"
+ accesskey="&shareSelectCmd.accesskey;"
+ oncommand="gContextMenu.shareSelect(getBrowserSelection());"/>
+ <menuseparator id="frame-sep"/>
+ <menu id="frame" label="&thisFrameMenu.label;" accesskey="&thisFrameMenu.accesskey;">
+ <menupopup>
+ <menuitem id="context-showonlythisframe"
+ label="&showOnlyThisFrameCmd.label;"
+ accesskey="&showOnlyThisFrameCmd.accesskey;"
+ oncommand="gContextMenu.showOnlyThisFrame();"/>
+ <menuitem id="context-openframeintab"
+ label="&openFrameCmdInTab.label;"
+ accesskey="&openFrameCmdInTab.accesskey;"
+ oncommand="gContextMenu.openFrameInTab();"/>
+ <menuitem id="context-openframe"
+ label="&openFrameCmd.label;"
+ accesskey="&openFrameCmd.accesskey;"
+ oncommand="gContextMenu.openFrame();"/>
+ <menuseparator/>
+ <menuitem id="context-reloadframe"
+ label="&reloadFrameCmd.label;"
+ accesskey="&reloadFrameCmd.accesskey;"
+ oncommand="gContextMenu.reloadFrame();"/>
+ <menuseparator/>
+ <menuitem id="context-bookmarkframe"
+ label="&bookmarkThisFrameCmd.label;"
+ accesskey="&bookmarkThisFrameCmd.accesskey;"
+ oncommand="gContextMenu.addBookmarkForFrame();"/>
+ <menuitem id="context-saveframe"
+ label="&saveFrameCmd.label;"
+ accesskey="&saveFrameCmd.accesskey;"
+ oncommand="gContextMenu.saveFrame();"/>
+ <menuseparator/>
+ <menuitem id="context-printframe"
+ label="&printFrameCmd.label;"
+ accesskey="&printFrameCmd.accesskey;"
+ oncommand="gContextMenu.printFrame();"/>
+ <menuseparator/>
+ <menuitem id="context-viewframesource"
+ label="&viewFrameSourceCmd.label;"
+ accesskey="&viewFrameSourceCmd.accesskey;"
+ oncommand="gContextMenu.viewFrameSource();"
+ observes="isFrameImage"/>
+ <menuitem id="context-viewframeinfo"
+ label="&viewFrameInfoCmd.label;"
+ accesskey="&viewFrameInfoCmd.accesskey;"
+ oncommand="gContextMenu.viewFrameInfo();"/>
+ </menupopup>
+ </menu>
+ <menuitem id="context-viewpartialsource-selection"
+ label="&viewPartialSourceForSelectionCmd.label;"
+ accesskey="&viewPartialSourceCmd.accesskey;"
+ oncommand="gContextMenu.viewPartialSource('selection');"
+ observes="isImage"/>
+ <menuitem id="context-viewpartialsource-mathml"
+ label="&viewPartialSourceForMathMLCmd.label;"
+ accesskey="&viewPartialSourceCmd.accesskey;"
+ oncommand="gContextMenu.viewPartialSource('mathml');"
+ observes="isImage"/>
+ <menuseparator id="context-sep-viewsource"/>
+ <menuitem id="context-viewsource"
+ label="&viewPageSourceCmd.label;"
+ accesskey="&viewPageSourceCmd.accesskey;"
+ oncommand="BrowserViewSourceOfDocument(gContextMenu.browser.contentDocument);"
+ observes="isImage"/>
+ <menuitem id="context-viewinfo"
+ label="&viewPageInfoCmd.label;"
+ accesskey="&viewPageInfoCmd.accesskey;"
+ oncommand="gContextMenu.viewInfo();"/>
+ <menuseparator id="spell-separator"/>
+ <menuitem id="spell-check-enabled"
+ label="&spellCheckToggle.label;"
+ type="checkbox"
+ accesskey="&spellCheckToggle.accesskey;"
+ oncommand="InlineSpellCheckerUI.toggleEnabled();"/>
+ <menuitem id="spell-add-dictionaries-main"
+ label="&spellAddDictionaries.label;"
+ accesskey="&spellAddDictionaries.accesskey;"
+ oncommand="gContextMenu.addDictionaries();"/>
+ <menu id="spell-dictionaries"
+ label="&spellDictionaries.label;"
+ accesskey="&spellDictionaries.accesskey;">
+ <menupopup id="spell-dictionaries-menu">
+ <menuseparator id="spell-language-separator"/>
+ <menuitem id="spell-add-dictionaries"
+ label="&spellAddDictionaries.label;"
+ accesskey="&spellAddDictionaries.accesskey;"
+ oncommand="gContextMenu.addDictionaries();"/>
+ </menupopup>
+ </menu>
+ <menuseparator hidden="true" id="context-sep-bidi"/>
+ <menuitem hidden="true" id="context-bidi-text-direction-toggle"
+ label="&bidiSwitchTextDirectionItem.label;"
+ accesskey="&bidiSwitchTextDirectionItem.accesskey;"
+ command="cmd_switchTextDirection"/>
+ <menuitem hidden="true" id="context-bidi-page-direction-toggle"
+ label="&bidiSwitchPageDirectionItem.label;"
+ accesskey="&bidiSwitchPageDirectionItem.accesskey;"
+ oncommand="gContextMenu.switchPageDirection();"/>
+ <menuseparator id="inspect-separator" hidden="true"/>
+ <menuitem id="context-inspect"
+ hidden="true"
+ label="&inspectContextMenu.label;"
+ accesskey="&inspectContextMenu.accesskey;"
+ oncommand="gContextMenu.inspectNode();"/>
diff --git a/browser/base/content/browser-data-submission-info-bar.js b/browser/base/content/browser-data-submission-info-bar.js
new file mode 100644
index 000000000..f58334e76
--- /dev/null
+++ b/browser/base/content/browser-data-submission-info-bar.js
@@ -0,0 +1,136 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/**
+ * Represents an info bar that shows a data submission notification.
+ */
+let gDataNotificationInfoBar = {
+ _OBSERVERS: [
+ "datareporting:notify-data-policy:request",
+ "datareporting:notify-data-policy:close",
+ ],
+
+ _DATA_REPORTING_NOTIFICATION: "data-reporting",
+
+ get _notificationBox() {
+ delete this._notificationBox;
+ return this._notificationBox = document.getElementById("global-notificationbox");
+ },
+
+ get _log() {
+ let log4moz = Cu.import("resource://services-common/log4moz.js", {}).Log4Moz;
+ delete this._log;
+ return this._log = log4moz.repository.getLogger("Services.DataReporting.InfoBar");
+ },
+
+ init: function() {
+ window.addEventListener("unload", function onUnload() {
+ window.removeEventListener("unload", onUnload, false);
+
+ for (let o of this._OBSERVERS) {
+ Services.obs.removeObserver(this, o);
+ }
+ }.bind(this), false);
+
+ for (let o of this._OBSERVERS) {
+ Services.obs.addObserver(this, o, true);
+ }
+ },
+
+ _getDataReportingNotification: function (name=this._DATA_REPORTING_NOTIFICATION) {
+ return this._notificationBox.getNotificationWithValue(name);
+ },
+
+ _displayDataPolicyInfoBar: function (request) {
+ if (this._getDataReportingNotification()) {
+ return;
+ }
+
+ let brandBundle = document.getElementById("bundle_brand");
+ let appName = brandBundle.getString("brandShortName");
+ let vendorName = brandBundle.getString("vendorShortName");
+
+ let message = gNavigatorBundle.getFormattedString(
+ "dataReportingNotification.message",
+ [appName, vendorName]);
+
+ this._actionTaken = false;
+
+ let buttons = [{
+ label: gNavigatorBundle.getString("dataReportingNotification.button.label"),
+ accessKey: gNavigatorBundle.getString("dataReportingNotification.button.accessKey"),
+ popup: null,
+ callback: function () {
+ // Clicking the button to go to the preferences tab constitutes
+ // acceptance of the data upload policy for Firefox Health Report.
+ // This will ensure the checkbox is checked. The user has the option of
+ // unchecking it.
+ request.onUserAccept("info-bar-button-pressed");
+ this._actionTaken = true;
+ window.openAdvancedPreferences("dataChoicesTab");
+ }.bind(this),
+ }];
+
+ this._log.info("Creating data reporting policy notification.");
+ let notification = this._notificationBox.appendNotification(
+ message,
+ this._DATA_REPORTING_NOTIFICATION,
+ null,
+ this._notificationBox.PRIORITY_INFO_HIGH,
+ buttons,
+ function onEvent(event) {
+ if (event == "removed") {
+ if (!this._actionTaken) {
+ request.onUserAccept("info-bar-dismissed");
+ }
+
+ Services.obs.notifyObservers(null, "datareporting:notify-data-policy:close", null);
+ }
+ }.bind(this)
+ );
+
+ // Tell the notification request we have displayed the notification.
+ request.onUserNotifyComplete();
+ },
+
+ _clearPolicyNotification: function () {
+ let notification = this._getDataReportingNotification();
+ if (notification) {
+ this._log.debug("Closing notification.");
+ notification.close();
+ }
+ },
+
+ onNotifyDataPolicy: function (request) {
+ try {
+ this._displayDataPolicyInfoBar(request);
+ } catch (ex) {
+ request.onUserNotifyFailed(ex);
+ }
+ },
+
+ observe: function(subject, topic, data) {
+ switch (topic) {
+ case "datareporting:notify-data-policy:request":
+ this.onNotifyDataPolicy(subject.wrappedJSObject.object);
+ break;
+
+ case "datareporting:notify-data-policy:close":
+ // If this observer fires, it means something else took care of
+ // responding. Therefore, we don't need to do anything. So, we
+ // act like we took action and clear state.
+ this._actionTaken = true;
+ this._clearPolicyNotification();
+ break;
+
+ default:
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIObserver,
+ Ci.nsISupportsWeakReference,
+ ]),
+};
+
diff --git a/browser/base/content/browser-doctype.inc b/browser/base/content/browser-doctype.inc
new file mode 100644
index 000000000..ddfe0dcef
--- /dev/null
+++ b/browser/base/content/browser-doctype.inc
@@ -0,0 +1,25 @@
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd" >
+%browserDTD;
+<!ENTITY % palemoonDTD SYSTEM "chrome://browser/locale/palemoon.dtd" >
+%palemoonDTD;
+<!ENTITY % baseMenuDTD SYSTEM "chrome://browser/locale/baseMenuOverlay.dtd" >
+%baseMenuDTD;
+<!ENTITY % charsetDTD SYSTEM "chrome://global/locale/charsetOverlay.dtd" >
+%charsetDTD;
+<!ENTITY % textcontextDTD SYSTEM "chrome://global/locale/textcontext.dtd" >
+%textcontextDTD;
+<!ENTITY % customizeToolbarDTD SYSTEM "chrome://global/locale/customizeToolbar.dtd">
+ %customizeToolbarDTD;
+<!ENTITY % placesDTD SYSTEM "chrome://browser/locale/places/places.dtd">
+%placesDTD;
+#ifdef MOZ_SAFE_BROWSING
+<!ENTITY % safebrowsingDTD SYSTEM "chrome://browser/locale/safebrowsing/phishing-afterload-warning-message.dtd">
+%safebrowsingDTD;
+#endif
+<!ENTITY % aboutHomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd">
+%aboutHomeDTD;
+]>
+
diff --git a/browser/base/content/browser-feeds.js b/browser/base/content/browser-feeds.js
new file mode 100644
index 000000000..c67877269
--- /dev/null
+++ b/browser/base/content/browser-feeds.js
@@ -0,0 +1,224 @@
+# -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+/**
+ * The Feed Handler object manages discovery of RSS/ATOM feeds in web pages
+ * and shows UI when they are discovered.
+ */
+var FeedHandler = {
+
+ /* Pale Moon: Address Bar: Feeds
+ * The click handler for the Feed icon in the location bar. Opens the
+ * subscription page if user is not given a choice of feeds.
+ * (Otherwise the list of available feeds will be presented to the
+ * user in a popup menu.)
+ */
+ onFeedButtonPMClick: function(event) {
+ event.stopPropagation();
+
+ if (event.target.hasAttribute("feed") &&
+ event.eventPhase == Event.AT_TARGET &&
+ (event.button == 0 || event.button == 1)) {
+ this.subscribeToFeed(null, event);
+ }
+ },
+
+ /**
+ * The click handler for the Feed icon in the toolbar. Opens the
+ * subscription page if user is not given a choice of feeds.
+ * (Otherwise the list of available feeds will be presented to the
+ * user in a popup menu.)
+ */
+ onFeedButtonClick: function(event) {
+ event.stopPropagation();
+
+ let feeds = gBrowser.selectedBrowser.feeds || [];
+ // If there are multiple feeds, the menu will open, so no need to do
+ // anything. If there are no feeds, nothing to do either.
+ if (feeds.length != 1)
+ return;
+
+ if (event.eventPhase == Event.AT_TARGET &&
+ (event.button == 0 || event.button == 1)) {
+ this.subscribeToFeed(feeds[0].href, event);
+ }
+ },
+
+ /** Called when the user clicks on the Subscribe to This Page... menu item.
+ * Builds a menu of unique feeds associated with the page, and if there
+ * is only one, shows the feed inline in the browser window.
+ * @param menuPopup
+ * The feed list menupopup to be populated.
+ * @returns true if the menu should be shown, false if there was only
+ * one feed and the feed should be shown inline in the browser
+ * window (do not show the menupopup).
+ */
+ buildFeedList: function(menuPopup) {
+ var feeds = gBrowser.selectedBrowser.feeds;
+ if (feeds == null) {
+ // XXX hack -- menu opening depends on setting of an "open"
+ // attribute, and the menu refuses to open if that attribute is
+ // set (because it thinks it's already open). onpopupshowing gets
+ // called after the attribute is unset, and it doesn't get unset
+ // if we return false. so we unset it here; otherwise, the menu
+ // refuses to work past this point.
+ menuPopup.parentNode.removeAttribute("open");
+ return false;
+ }
+
+ while (menuPopup.firstChild)
+ menuPopup.removeChild(menuPopup.firstChild);
+
+ if (feeds.length == 1) {
+ var feedButtonPM = document.getElementById("ub-feed-button");
+ if (feedButtonPM)
+ feedButtonPM.setAttribute("feed", feeds[0].href);
+ return false;
+ }
+
+ if (feeds.length <= 1)
+ return false;
+
+ // Build the menu showing the available feed choices for viewing.
+ for (let feedInfo of feeds) {
+ var menuItem = document.createElement("menuitem");
+ var baseTitle = feedInfo.title || feedInfo.href;
+ var labelStr = gNavigatorBundle.getFormattedString("feedShowFeedNew", [baseTitle]);
+ menuItem.setAttribute("class", "feed-menuitem");
+ menuItem.setAttribute("label", labelStr);
+ menuItem.setAttribute("feed", feedInfo.href);
+ menuItem.setAttribute("tooltiptext", feedInfo.href);
+ menuItem.setAttribute("crop", "center");
+ menuPopup.appendChild(menuItem);
+ }
+ return true;
+ },
+
+ /**
+ * Subscribe to a given feed. Called when
+ * 1. Page has a single feed and user clicks feed icon in location bar
+ * 2. Page has a single feed and user selects Subscribe menu item
+ * 3. Page has multiple feeds and user selects from feed icon popup
+ * 4. Page has multiple feeds and user selects from Subscribe submenu
+ * @param href
+ * The feed to subscribe to. May be null, in which case the
+ * event target's feed attribute is examined.
+ * @param event
+ * The event this method is handling. Used to decide where
+ * to open the preview UI. (Optional, unless href is null)
+ */
+ subscribeToFeed: function(href, event) {
+ // Just load the feed in the content area to either subscribe or show the
+ // preview UI
+ if (!href)
+ href = event.target.getAttribute("feed");
+ urlSecurityCheck(href, gBrowser.contentPrincipal,
+ Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
+ var feedURI = makeURI(href, document.characterSet);
+ // Use the feed scheme so X-Moz-Is-Feed will be set
+ // The value doesn't matter
+ if (/^https?$/.test(feedURI.scheme))
+ href = "feed:" + href;
+ this.loadFeed(href, event);
+ },
+
+ loadFeed: function(href, event) {
+ var feeds = gBrowser.selectedBrowser.feeds;
+ try {
+ openUILink(href, event, { ignoreAlt: true });
+ }
+ finally {
+ // We might default to a livebookmarks modal dialog,
+ // so reset that if the user happens to click it again
+ gBrowser.selectedBrowser.feeds = feeds;
+ }
+ },
+
+ get _feedMenuitem() {
+ delete this._feedMenuitem;
+ return this._feedMenuitem = document.getElementById("singleFeedMenuitemState");
+ },
+
+ get _feedMenupopup() {
+ delete this._feedMenupopup;
+ return this._feedMenupopup = document.getElementById("multipleFeedsMenuState");
+ },
+
+ /**
+ * Update the browser UI to show whether or not feeds are available when
+ * a page is loaded or the user switches tabs to a page that has feeds.
+ */
+ updateFeeds: function() {
+ if (this._updateFeedTimeout)
+ clearTimeout(this._updateFeedTimeout);
+
+ var feeds = gBrowser.selectedBrowser.feeds;
+ var haveFeeds = feeds && feeds.length > 0;
+
+ var feedButtonPM = document.getElementById("ub-feed-button");
+
+ var feedButton = document.getElementById("feed-button");
+
+ if (feedButton)
+ feedButton.disabled = !haveFeeds;
+
+ if (feedButtonPM) {
+ if (!haveFeeds) {
+ feedButtonPM.collapsed = true;
+ feedButtonPM.removeAttribute("feed");
+ } else {
+ feedButtonPM.collapsed = !gPrefService.getBoolPref("browser.urlbar.rss");
+ }
+ }
+
+ if (!haveFeeds) {
+ this._feedMenuitem.setAttribute("disabled", "true");
+ this._feedMenuitem.removeAttribute("hidden");
+ this._feedMenupopup.setAttribute("hidden", "true");
+ return;
+ }
+
+ if (feeds.length > 1) {
+ if (feedButtonPM)
+ feedButtonPM.removeAttribute("feed");
+ this._feedMenuitem.setAttribute("hidden", "true");
+ this._feedMenupopup.removeAttribute("hidden");
+ } else {
+ if (feedButtonPM)
+ feedButtonPM.setAttribute("feed", feeds[0].href);
+ this._feedMenuitem.setAttribute("feed", feeds[0].href);
+ this._feedMenuitem.removeAttribute("disabled");
+ this._feedMenuitem.removeAttribute("hidden");
+ this._feedMenupopup.setAttribute("hidden", "true");
+ }
+ },
+
+ addFeed: function(link, targetDoc) {
+ // find which tab this is for, and set the attribute on the browser
+ var browserForLink = gBrowser.getBrowserForDocument(targetDoc);
+ if (!browserForLink) {
+ // ignore feeds loaded in subframes (see bug 305472)
+ return;
+ }
+
+ if (!browserForLink.feeds)
+ browserForLink.feeds = [];
+
+ browserForLink.feeds.push({ href: link.href, title: link.title });
+
+ // If this addition was for the current browser, update the UI. For
+ // background browsers, we'll update on tab switch.
+ if (browserForLink == gBrowser.selectedBrowser) {
+ var feedButtonPM = document.getElementById("ub-feed-button");
+ if (feedButtonPM)
+ feedButtonPM.collapsed = !gPrefService.getBoolPref("browser.urlbar.rss");
+ // Batch updates to avoid updating the UI for multiple onLinkAdded events
+ // fired within 100ms of each other.
+ if (this._updateFeedTimeout)
+ clearTimeout(this._updateFeedTimeout);
+ this._updateFeedTimeout = setTimeout(this.updateFeeds.bind(this), 100);
+ }
+ }
+};
diff --git a/browser/base/content/browser-fullScreen.js b/browser/base/content/browser-fullScreen.js
new file mode 100644
index 000000000..26f1d9551
--- /dev/null
+++ b/browser/base/content/browser-fullScreen.js
@@ -0,0 +1,605 @@
+# -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+var FullScreen = {
+ _XULNS: "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ get _fullScrToggler() {
+ delete this._fullScrToggler;
+ return this._fullScrToggler = document.getElementById("fullscr-toggler");
+ },
+ toggle: function (event) {
+ var enterFS = window.fullScreen;
+
+ // We get the fullscreen event _before_ the window transitions into or out of FS mode.
+ if (event && event.type == "fullscreen")
+ enterFS = !enterFS;
+
+ // Toggle the View:FullScreen command, which controls elements like the
+ // fullscreen menuitem, menubars, and the appmenu.
+ let fullscreenCommand = document.getElementById("View:FullScreen");
+ if (enterFS) {
+ fullscreenCommand.setAttribute("checked", enterFS);
+ } else {
+ fullscreenCommand.removeAttribute("checked");
+ }
+
+#ifdef XP_MACOSX
+ // Make sure the menu items are adjusted.
+ document.getElementById("enterFullScreenItem").hidden = enterFS;
+ document.getElementById("exitFullScreenItem").hidden = !enterFS;
+#endif
+
+ // On OS X Lion we don't want to hide toolbars when entering fullscreen, unless
+ // we're entering DOM fullscreen, in which case we should hide the toolbars.
+ // If we're leaving fullscreen, then we'll go through the exit code below to
+ // make sure toolbars are made visible in the case of DOM fullscreen.
+ if (enterFS && this.useLionFullScreen) {
+ if (document.mozFullScreen) {
+ this.showXULChrome("toolbar", false);
+ }
+ else {
+ gNavToolbox.setAttribute("inFullscreen", true);
+ document.documentElement.setAttribute("inFullscreen", true);
+ }
+ return;
+ }
+
+ // show/hide menubars, toolbars (except the full screen toolbar)
+ this.showXULChrome("toolbar", !enterFS);
+
+ if (enterFS) {
+ // Add a tiny toolbar to receive mouseover and dragenter events, and provide affordance.
+ // This will help simulate the "collapse" metaphor while also requiring less code and
+ // events than raw listening of mouse coords. We don't add the toolbar in DOM full-screen
+ // mode, only browser full-screen mode.
+ if (!document.mozFullScreen) {
+ this._fullScrToggler.addEventListener("mouseover", this._expandCallback, false);
+ this._fullScrToggler.addEventListener("dragenter", this._expandCallback, false);
+ }
+ if (gPrefService.getBoolPref("browser.fullscreen.autohide"))
+ gBrowser.mPanelContainer.addEventListener("mousemove",
+ this._collapseCallback, false);
+
+ document.addEventListener("keypress", this._keyToggleCallback, false);
+ document.addEventListener("popupshown", this._setPopupOpen, false);
+ document.addEventListener("popuphidden", this._setPopupOpen, false);
+ // We don't animate the toolbar collapse if in DOM full-screen mode,
+ // as the size of the content area would still be changing after the
+ // mozfullscreenchange event fired, which could confuse content script.
+ this._shouldAnimate = !document.mozFullScreen;
+ this.mouseoverToggle(false);
+
+ // Autohide prefs
+ gPrefService.addObserver("browser.fullscreen", this, false);
+ }
+ else {
+ // The user may quit fullscreen during an animation
+ this._cancelAnimation();
+ gNavToolbox.style.marginTop = "";
+ if (this._isChromeCollapsed)
+ this.mouseoverToggle(true);
+ // This is needed if they use the context menu to quit fullscreen
+ this._isPopupOpen = false;
+
+ this.cleanup();
+ }
+ },
+
+ exitDomFullScreen : function() {
+ document.mozCancelFullScreen();
+ },
+
+ handleEvent: function (event) {
+ switch (event.type) {
+ case "activate":
+ if (document.mozFullScreen) {
+ this.showWarning(this.fullscreenDoc);
+ }
+ break;
+ case "transitionend":
+ if (event.propertyName == "opacity")
+ this.cancelWarning();
+ break;
+ }
+ },
+
+ enterDomFullscreen : function(event) {
+ if (!document.mozFullScreen)
+ return;
+
+ // However, if we receive a "MozEnteredDomFullScreen" event for a document
+ // which is not a subdocument of a currently active (ie. visible) browser
+ // or iframe, we know that we've switched to a different frame since the
+ // request to enter full-screen was made, so we should exit full-screen
+ // since the "full-screen document" isn't acutally visible.
+ if (!event.target.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell).isActive) {
+ document.mozCancelFullScreen();
+ return;
+ }
+
+ let focusManager = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
+ if (focusManager.activeWindow != window) {
+ // The top-level window has lost focus since the request to enter
+ // full-screen was made. Cancel full-screen.
+ document.mozCancelFullScreen();
+ return;
+ }
+
+ // Ensure the sidebar is hidden.
+ if (!document.getElementById("sidebar-box").hidden)
+ toggleSidebar();
+
+ if (gFindBarInitialized)
+ gFindBar.close();
+
+ this.showWarning(event.target);
+
+ // Exit DOM full-screen mode upon open, close, or change tab.
+ gBrowser.tabContainer.addEventListener("TabOpen", this.exitDomFullScreen);
+ gBrowser.tabContainer.addEventListener("TabClose", this.exitDomFullScreen);
+ gBrowser.tabContainer.addEventListener("TabSelect", this.exitDomFullScreen);
+
+ // Add listener to detect when the fullscreen window is re-focused.
+ // If a fullscreen window loses focus, we show a warning when the
+ // fullscreen window is refocused.
+ if (!this.useLionFullScreen) {
+ window.addEventListener("activate", this);
+ }
+
+ // Cancel any "hide the toolbar" animation which is in progress, and make
+ // the toolbar hide immediately.
+ this._cancelAnimation();
+ this.mouseoverToggle(false);
+
+ // Remove listeners on the full-screen toggler, so that mouseover
+ // the top of the screen will not cause the toolbar to re-appear.
+ this._fullScrToggler.removeEventListener("mouseover", this._expandCallback, false);
+ this._fullScrToggler.removeEventListener("dragenter", this._expandCallback, false);
+ },
+
+ cleanup: function () {
+ if (window.fullScreen) {
+ gBrowser.mPanelContainer.removeEventListener("mousemove",
+ this._collapseCallback, false);
+ document.removeEventListener("keypress", this._keyToggleCallback, false);
+ document.removeEventListener("popupshown", this._setPopupOpen, false);
+ document.removeEventListener("popuphidden", this._setPopupOpen, false);
+ gPrefService.removeObserver("browser.fullscreen", this);
+
+ this._fullScrToggler.removeEventListener("mouseover", this._expandCallback, false);
+ this._fullScrToggler.removeEventListener("dragenter", this._expandCallback, false);
+ this.cancelWarning();
+ gBrowser.tabContainer.removeEventListener("TabOpen", this.exitDomFullScreen);
+ gBrowser.tabContainer.removeEventListener("TabClose", this.exitDomFullScreen);
+ gBrowser.tabContainer.removeEventListener("TabSelect", this.exitDomFullScreen);
+ if (!this.useLionFullScreen)
+ window.removeEventListener("activate", this);
+ this.fullscreenDoc = null;
+ }
+ },
+
+ observe: function(aSubject, aTopic, aData)
+ {
+ if (aData == "browser.fullscreen.autohide") {
+ if (gPrefService.getBoolPref("browser.fullscreen.autohide")) {
+ gBrowser.mPanelContainer.addEventListener("mousemove",
+ this._collapseCallback, false);
+ }
+ else {
+ gBrowser.mPanelContainer.removeEventListener("mousemove",
+ this._collapseCallback, false);
+ }
+ }
+ },
+
+ // Event callbacks
+ _expandCallback: function()
+ {
+ FullScreen.mouseoverToggle(true);
+ },
+ _collapseCallback: function()
+ {
+ FullScreen.mouseoverToggle(false);
+ },
+ _keyToggleCallback: function(aEvent)
+ {
+ // if we can use the keyboard (eg Ctrl+L or Ctrl+E) to open the toolbars, we
+ // should provide a way to collapse them too.
+ if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) {
+ FullScreen._shouldAnimate = false;
+ FullScreen.mouseoverToggle(false, true);
+ }
+ // F6 is another shortcut to the address bar, but its not covered in OpenLocation()
+ else if (aEvent.keyCode == aEvent.DOM_VK_F6)
+ FullScreen.mouseoverToggle(true);
+ },
+
+ // Checks whether we are allowed to collapse the chrome
+ _isPopupOpen: false,
+ _isChromeCollapsed: false,
+ _safeToCollapse: function(forceHide)
+ {
+ if (!gPrefService.getBoolPref("browser.fullscreen.autohide"))
+ return false;
+
+ // a popup menu is open in chrome: don't collapse chrome
+ if (!forceHide && this._isPopupOpen)
+ return false;
+
+ // a textbox in chrome is focused (location bar anyone?): don't collapse chrome
+ if (document.commandDispatcher.focusedElement &&
+ document.commandDispatcher.focusedElement.ownerDocument == document &&
+ document.commandDispatcher.focusedElement.localName == "input") {
+ if (forceHide)
+ // hidden textboxes that still have focus are bad bad bad
+ document.commandDispatcher.focusedElement.blur();
+ else
+ return false;
+ }
+ return true;
+ },
+
+ _setPopupOpen: function(aEvent)
+ {
+ // Popups should only veto chrome collapsing if they were opened when the chrome was not collapsed.
+ // Otherwise, they would not affect chrome and the user would expect the chrome to go away.
+ // e.g. we wouldn't want the autoscroll icon firing this event, so when the user
+ // toggles chrome when moving mouse to the top, it doesn't go away again.
+ if (aEvent.type == "popupshown" && !FullScreen._isChromeCollapsed &&
+ aEvent.target.localName != "tooltip" && aEvent.target.localName != "window")
+ FullScreen._isPopupOpen = true;
+ else if (aEvent.type == "popuphidden" && aEvent.target.localName != "tooltip" &&
+ aEvent.target.localName != "window")
+ FullScreen._isPopupOpen = false;
+ },
+
+ // Autohide helpers for the context menu item
+ getAutohide: function(aItem)
+ {
+ aItem.setAttribute("checked", gPrefService.getBoolPref("browser.fullscreen.autohide"));
+ },
+ setAutohide: function()
+ {
+ gPrefService.setBoolPref("browser.fullscreen.autohide", !gPrefService.getBoolPref("browser.fullscreen.autohide"));
+ },
+
+ // Animate the toolbars disappearing
+ _shouldAnimate: true,
+ _isAnimating: false,
+ _animationTimeout: 0,
+ _animationHandle: 0,
+ _animateUp: function() {
+ // check again, the user may have done something before the animation was due to start
+ if (!window.fullScreen || !this._safeToCollapse(false)) {
+ this._isAnimating = false;
+ this._shouldAnimate = true;
+ return;
+ }
+
+ this._animateStartTime = window.mozAnimationStartTime;
+ if (!this._animationHandle)
+ this._animationHandle = window.mozRequestAnimationFrame(this);
+ },
+
+ sample: function (timeStamp) {
+ const duration = 1500;
+ const timePassed = timeStamp - this._animateStartTime;
+ const pos = timePassed >= duration ? 1 :
+ 1 - Math.pow(1 - timePassed / duration, 4);
+
+ if (pos >= 1) {
+ // We've animated enough
+ this._cancelAnimation();
+ gNavToolbox.style.marginTop = "";
+ this.mouseoverToggle(false);
+ return;
+ }
+
+ gNavToolbox.style.marginTop = (gNavToolbox.boxObject.height * pos * -1) + "px";
+ this._animationHandle = window.mozRequestAnimationFrame(this);
+ },
+
+ _cancelAnimation: function() {
+ window.mozCancelAnimationFrame(this._animationHandle);
+ this._animationHandle = 0;
+ clearTimeout(this._animationTimeout);
+ this._isAnimating = false;
+ this._shouldAnimate = false;
+ },
+
+ cancelWarning: function(event) {
+ if (!this.warningBox)
+ return;
+ this.warningBox.removeEventListener("transitionend", this);
+ if (this.warningFadeOutTimeout) {
+ clearTimeout(this.warningFadeOutTimeout);
+ this.warningFadeOutTimeout = null;
+ }
+
+ // Ensure focus switches away from the (now hidden) warning box. If the user
+ // clicked buttons in the fullscreen key authorization UI, it would have been
+ // focused, and any key events would be directed at the (now hidden) chrome
+ // document instead of the target document.
+ gBrowser.selectedBrowser.focus();
+
+ this.warningBox.setAttribute("hidden", true);
+ this.warningBox.removeAttribute("fade-warning-out");
+ this.warningBox.removeAttribute("obscure-browser");
+ this.warningBox = null;
+ },
+
+ setFullscreenAllowed: function(isApproved) {
+ // The "remember decision" checkbox is hidden when showing for documents that
+ // the permission manager can't handle (documents with URIs without a host).
+ // We simply require those to be approved every time instead.
+ let rememberCheckbox = document.getElementById("full-screen-remember-decision");
+ let uri = this.fullscreenDoc.nodePrincipal.URI;
+ if (!rememberCheckbox.hidden) {
+ if (rememberCheckbox.checked)
+ Services.perms.add(uri,
+ "fullscreen",
+ isApproved ? Services.perms.ALLOW_ACTION : Services.perms.DENY_ACTION,
+ Services.perms.EXPIRE_NEVER);
+ else if (isApproved) {
+ // The user has only temporarily approved fullscren for this fullscreen
+ // session only. Add the permission (so Gecko knows to approve any further
+ // fullscreen requests for this host in this fullscreen session) but add
+ // a listener to revoke the permission when the chrome document exits
+ // fullscreen.
+ Services.perms.add(uri,
+ "fullscreen",
+ Services.perms.ALLOW_ACTION,
+ Services.perms.EXPIRE_SESSION);
+ let host = uri.host;
+ var onFullscreenchange = function onFullscreenchange(event) {
+ if (event.target == document && document.mozFullScreenElement == null) {
+ // The chrome document has left fullscreen. Remove the temporary permission grant.
+ Services.perms.remove(host, "fullscreen");
+ document.removeEventListener("mozfullscreenchange", onFullscreenchange);
+ }
+ }
+ document.addEventListener("mozfullscreenchange", onFullscreenchange);
+ }
+ }
+ if (this.warningBox)
+ this.warningBox.setAttribute("fade-warning-out", "true");
+ // If the document has been granted fullscreen, notify Gecko so it can resume
+ // any pending pointer lock requests, otherwise exit fullscreen; the user denied
+ // the fullscreen request.
+ if (isApproved)
+ Services.obs.notifyObservers(this.fullscreenDoc, "fullscreen-approved", "");
+ else
+ document.mozCancelFullScreen();
+ },
+
+ warningBox: null,
+ warningFadeOutTimeout: null,
+ fullscreenDoc: null,
+
+ // Shows the fullscreen approval UI, or if the domain has already been approved
+ // for fullscreen, shows a warning that the site has entered fullscreen for a short
+ // duration.
+ showWarning: function(targetDoc) {
+ if (!document.mozFullScreen ||
+ !gPrefService.getBoolPref("full-screen-api.approval-required"))
+ return;
+
+ // Set the strings on the fullscreen approval UI.
+ this.fullscreenDoc = targetDoc;
+ let uri = this.fullscreenDoc.nodePrincipal.URI;
+ let host = null;
+ try {
+ host = uri.host;
+ } catch (e) { }
+ let hostLabel = document.getElementById("full-screen-domain-text");
+ let rememberCheckbox = document.getElementById("full-screen-remember-decision");
+ let isApproved = false;
+ if (host) {
+ // Document's principal's URI has a host. Display a warning including the hostname and
+ // show UI to enable the user to permanently grant this host permission to enter fullscreen.
+ let utils = {};
+ Cu.import("resource://gre/modules/DownloadUtils.jsm", utils);
+ let displayHost = utils.DownloadUtils.getURIHost(uri.spec)[0];
+ let bundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
+
+ hostLabel.textContent = bundle.formatStringFromName("fullscreen.entered", [displayHost], 1);
+ hostLabel.removeAttribute("hidden");
+
+ rememberCheckbox.label = bundle.formatStringFromName("fullscreen.rememberDecision", [displayHost], 1);
+ rememberCheckbox.checked = false;
+ rememberCheckbox.removeAttribute("hidden");
+
+ // Note we only allow documents whose principal's URI has a host to
+ // store permission grants.
+ isApproved = Services.perms.testPermission(uri, "fullscreen") == Services.perms.ALLOW_ACTION;
+ } else {
+ hostLabel.setAttribute("hidden", "true");
+ rememberCheckbox.setAttribute("hidden", "true");
+ }
+
+ // Note: the warning box can be non-null if the warning box from the previous request
+ // wasn't hidden before another request was made.
+ if (!this.warningBox) {
+ this.warningBox = document.getElementById("full-screen-warning-container");
+ // Add a listener to clean up state after the warning is hidden.
+ this.warningBox.addEventListener("transitionend", this);
+ this.warningBox.removeAttribute("hidden");
+ } else {
+ if (this.warningFadeOutTimeout) {
+ clearTimeout(this.warningFadeOutTimeout);
+ this.warningFadeOutTimeout = null;
+ }
+ this.warningBox.removeAttribute("fade-warning-out");
+ }
+
+ // If fullscreen mode has not yet been approved for the fullscreen
+ // document's domain, show the approval UI and don't auto fade out the
+ // fullscreen warning box. Otherwise, we're just notifying of entry into
+ // fullscreen mode. Note if the resource's host is null, we must be
+ // showing a local file or a local data URI, and we require explicit
+ // approval every time.
+ let authUI = document.getElementById("full-screen-approval-pane");
+ if (isApproved) {
+ authUI.setAttribute("hidden", "true");
+ this.warningBox.removeAttribute("obscure-browser");
+ } else {
+ // Partially obscure the <browser> element underneath the approval UI.
+ this.warningBox.setAttribute("obscure-browser", "true");
+ authUI.removeAttribute("hidden");
+ }
+
+ // If we're not showing the fullscreen approval UI, we're just notifying the user
+ // of the transition, so set a timeout to fade the warning out after a few moments.
+ if (isApproved)
+ this.warningFadeOutTimeout =
+ setTimeout(
+ function() {
+ if (this.warningBox)
+ this.warningBox.setAttribute("fade-warning-out", "true");
+ }.bind(this),
+ 3000);
+ },
+
+ mouseoverToggle: function(aShow, forceHide)
+ {
+ // Don't do anything if:
+ // a) we're already in the state we want,
+ // b) we're animating and will become collapsed soon, or
+ // c) we can't collapse because it would be undesirable right now
+ if (aShow != this._isChromeCollapsed || (!aShow && this._isAnimating) ||
+ (!aShow && !this._safeToCollapse(forceHide)))
+ return;
+
+ // browser.fullscreen.animateUp
+ // 0 - never animate up
+ // 1 - animate only for first collapse after entering fullscreen (default for perf's sake)
+ // 2 - animate every time it collapses
+ if (gPrefService.getIntPref("browser.fullscreen.animateUp") == 0)
+ this._shouldAnimate = false;
+
+ if (!aShow && this._shouldAnimate) {
+ this._isAnimating = true;
+ this._shouldAnimate = false;
+ this._animationTimeout = setTimeout(this._animateUp.bind(this), 800);
+ return;
+ }
+
+ // The chrome is collapsed so don't spam needless mousemove events
+ if (aShow) {
+ gBrowser.mPanelContainer.addEventListener("mousemove",
+ this._collapseCallback, false);
+ }
+ else {
+ gBrowser.mPanelContainer.removeEventListener("mousemove",
+ this._collapseCallback, false);
+ }
+
+ // Hiding/collapsing the toolbox interferes with the tab bar's scrollbox,
+ // so we just move it off-screen instead. See bug 430687.
+ gNavToolbox.style.marginTop =
+ aShow ? "" : -gNavToolbox.getBoundingClientRect().height + "px";
+
+ this._fullScrToggler.collapsed = aShow;
+ this._isChromeCollapsed = !aShow;
+ if (gPrefService.getIntPref("browser.fullscreen.animateUp") == 2)
+ this._shouldAnimate = true;
+ },
+
+ showXULChrome: function(aTag, aShow)
+ {
+ var els = document.getElementsByTagNameNS(this._XULNS, aTag);
+
+ for (let el of els) {
+ // XXX don't interfere with previously collapsed toolbars
+ if (el.getAttribute("fullscreentoolbar") == "true") {
+ if (!aShow) {
+
+ var toolbarMode = el.getAttribute("mode");
+ if (toolbarMode != "text") {
+ el.setAttribute("saved-mode", toolbarMode);
+ el.setAttribute("saved-iconsize", el.getAttribute("iconsize"));
+ el.setAttribute("mode", "icons");
+ el.setAttribute("iconsize", "small");
+ }
+
+ // Give the main nav bar and the tab bar the fullscreen context menu,
+ // otherwise remove context menu to prevent breakage
+ el.setAttribute("saved-context", el.getAttribute("context"));
+ if (el.id == "nav-bar" || el.id == "TabsToolbar")
+ el.setAttribute("context", "autohide-context");
+ else
+ el.removeAttribute("context");
+
+ // Set the inFullscreen attribute to allow specific styling
+ // in fullscreen mode
+ el.setAttribute("inFullscreen", true);
+ }
+ else {
+ var restoreAttr = function restoreAttr(attrName) {
+ var savedAttr = "saved-" + attrName;
+ if (el.hasAttribute(savedAttr)) {
+ el.setAttribute(attrName, el.getAttribute(savedAttr));
+ el.removeAttribute(savedAttr);
+ }
+ }
+
+ restoreAttr("mode");
+ restoreAttr("iconsize");
+ restoreAttr("context");
+
+ el.removeAttribute("inFullscreen");
+ }
+ } else {
+ // use moz-collapsed so it doesn't persist hidden/collapsed,
+ // so that new windows don't have missing toolbars
+ if (aShow)
+ el.removeAttribute("moz-collapsed");
+ else
+ el.setAttribute("moz-collapsed", "true");
+ }
+ }
+
+ if (aShow) {
+ gNavToolbox.removeAttribute("inFullscreen");
+ document.documentElement.removeAttribute("inFullscreen");
+ } else {
+ gNavToolbox.setAttribute("inFullscreen", true);
+ document.documentElement.setAttribute("inFullscreen", true);
+ }
+
+ // In tabs-on-top mode, move window controls to the tab bar,
+ // and in tabs-on-bottom mode, move them back to the navigation toolbar.
+ // When there is a chance the tab bar may be collapsed, put window
+ // controls on nav bar.
+ var fullscreenctls = document.getElementById("window-controls");
+ var navbar = document.getElementById("nav-bar");
+ var ctlsOnTabbar = window.toolbar.visible &&
+ (navbar.collapsed || (TabsOnTop.enabled &&
+ !gPrefService.getBoolPref("browser.tabs.autoHide")));
+ if (fullscreenctls.parentNode == navbar && ctlsOnTabbar) {
+ fullscreenctls.removeAttribute("flex");
+ document.getElementById("TabsToolbar").appendChild(fullscreenctls);
+ }
+ else if (fullscreenctls.parentNode.id == "TabsToolbar" && !ctlsOnTabbar) {
+ fullscreenctls.setAttribute("flex", "1");
+ navbar.appendChild(fullscreenctls);
+ }
+ fullscreenctls.hidden = aShow;
+ }
+};
+XPCOMUtils.defineLazyGetter(FullScreen, "useLionFullScreen", function() {
+ // We'll only use OS X Lion full screen if we're
+ // * on OS X
+ // * on Lion or higher (Darwin 11+)
+ // * have fullscreenbutton="true"
+#ifdef XP_MACOSX
+ return parseFloat(Services.sysinfo.getProperty("version")) >= 11 &&
+ document.documentElement.getAttribute("fullscreenbutton") == "true";
+#else
+ return false;
+#endif
+});
diff --git a/browser/base/content/browser-fullZoom.js b/browser/base/content/browser-fullZoom.js
new file mode 100644
index 000000000..4f0af5663
--- /dev/null
+++ b/browser/base/content/browser-fullZoom.js
@@ -0,0 +1,557 @@
+/*
+#ifdef 0
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#endif
+ */
+
+// One of the possible values for the mousewheel.* preferences.
+// From nsEventStateManager.cpp.
+const MOUSE_SCROLL_ZOOM = 3;
+
+/**
+ * Controls the "full zoom" setting and its site-specific preferences.
+ */
+var FullZoom = {
+ // Identifies the setting in the content prefs database.
+ name: "browser.content.full-zoom",
+
+ // browser.zoom.siteSpecific preference cache
+ _siteSpecificPref: undefined,
+
+ // browser.zoom.updateBackgroundTabs preference cache
+ updateBackgroundTabs: undefined,
+
+ // This maps browser outer window IDs to monotonically increasing integer
+ // tokens. _browserTokenMap[outerID] is increased each time the zoom is
+ // changed in the browser whose outer window ID is outerID. See
+ // _getBrowserToken and _ignorePendingZoomAccesses.
+ _browserTokenMap: new Map(),
+
+ get siteSpecific() {
+ return this._siteSpecificPref;
+ },
+
+ //**************************************************************************//
+ // nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMEventListener,
+ Ci.nsIObserver,
+ Ci.nsIContentPrefObserver,
+ Ci.nsISupportsWeakReference,
+ Ci.nsISupports]),
+
+ //**************************************************************************//
+ // Initialization & Destruction
+
+ init: function FullZoom_init() {
+ // Bug 691614 - zooming support for electrolysis
+ if (gMultiProcessBrowser)
+ return;
+
+ // Listen for scrollwheel events so we can save scrollwheel-based changes.
+ window.addEventListener("DOMMouseScroll", this, false);
+
+ // Register ourselves with the service so we know when our pref changes.
+ this._cps2 = Cc["@mozilla.org/content-pref/service;1"].
+ getService(Ci.nsIContentPrefService2);
+ this._cps2.addObserverForName(this.name, this);
+
+ this._siteSpecificPref =
+ gPrefService.getBoolPref("browser.zoom.siteSpecific");
+ this.updateBackgroundTabs =
+ gPrefService.getBoolPref("browser.zoom.updateBackgroundTabs");
+ // Listen for changes to the browser.zoom branch so we can enable/disable
+ // updating background tabs and per-site saving and restoring of zoom levels.
+ gPrefService.addObserver("browser.zoom.", this, true);
+
+ Services.obs.addObserver(this, "outer-window-destroyed", false);
+ },
+
+ destroy: function FullZoom_destroy() {
+ // Bug 691614 - zooming support for electrolysis
+ if (gMultiProcessBrowser)
+ return;
+
+ gPrefService.removeObserver("browser.zoom.", this);
+ this._cps2.removeObserverForName(this.name, this);
+ window.removeEventListener("DOMMouseScroll", this, false);
+ Services.obs.removeObserver(this, "outer-window-destroyed");
+ },
+
+
+ //**************************************************************************//
+ // Event Handlers
+
+ // nsIDOMEventListener
+
+ handleEvent: function FullZoom_handleEvent(event) {
+ switch (event.type) {
+ case "DOMMouseScroll":
+ this._handleMouseScrolled(event);
+ break;
+ }
+ },
+
+ _handleMouseScrolled: function FullZoom__handleMouseScrolled(event) {
+ // Construct the "mousewheel action" pref key corresponding to this event.
+ // Based on nsEventStateManager::WheelPrefs::GetBasePrefName().
+ var pref = "mousewheel.";
+
+ var pressedModifierCount = event.shiftKey + event.ctrlKey + event.altKey +
+ event.metaKey + event.getModifierState("OS");
+ if (pressedModifierCount != 1) {
+ pref += "default.";
+ } else if (event.shiftKey) {
+ pref += "with_shift.";
+ } else if (event.ctrlKey) {
+ pref += "with_control.";
+ } else if (event.altKey) {
+ pref += "with_alt.";
+ } else if (event.metaKey) {
+ pref += "with_meta.";
+ } else {
+ pref += "with_win.";
+ }
+
+ pref += "action";
+
+ // Don't do anything if this isn't a "zoom" scroll event.
+ var isZoomEvent = false;
+ try {
+ isZoomEvent = (gPrefService.getIntPref(pref) == MOUSE_SCROLL_ZOOM);
+ } catch (e) {}
+ if (!isZoomEvent)
+ return;
+
+ // XXX Lazily cache all the possible action prefs so we don't have to get
+ // them anew from the pref service for every scroll event? We'd have to
+ // make sure to observe them so we can update the cache when they change.
+
+ // We have to call _applyZoomToPref in a timeout because we handle the
+ // event before the event state manager has a chance to apply the zoom
+ // during nsEventStateManager::PostHandleEvent.
+ let browser = gBrowser.selectedBrowser;
+ let token = this._getBrowserToken(browser);
+ window.setTimeout(function () {
+ if (token.isCurrent)
+ this._applyZoomToPref(browser);
+ }.bind(this), 0);
+ },
+
+ // nsIObserver
+
+ observe: function (aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "nsPref:changed":
+ switch (aData) {
+ case "browser.zoom.siteSpecific":
+ this._siteSpecificPref =
+ gPrefService.getBoolPref("browser.zoom.siteSpecific");
+ break;
+ case "browser.zoom.updateBackgroundTabs":
+ this.updateBackgroundTabs =
+ gPrefService.getBoolPref("browser.zoom.updateBackgroundTabs");
+ break;
+ }
+ break;
+ case "outer-window-destroyed":
+ let outerID = aSubject.QueryInterface(Ci.nsISupportsPRUint64).data;
+ this._browserTokenMap.delete(outerID);
+ break;
+ }
+ },
+
+ // nsIContentPrefObserver
+
+ onContentPrefSet: function FullZoom_onContentPrefSet(aGroup, aName, aValue) {
+ this._onContentPrefChanged(aGroup, aValue);
+ },
+
+ onContentPrefRemoved: function FullZoom_onContentPrefRemoved(aGroup, aName) {
+ this._onContentPrefChanged(aGroup, undefined);
+ },
+
+ /**
+ * Appropriately updates the zoom level after a content preference has
+ * changed.
+ *
+ * @param aGroup The group of the changed preference.
+ * @param aValue The new value of the changed preference. Pass undefined to
+ * indicate the preference's removal.
+ */
+ _onContentPrefChanged: function FullZoom__onContentPrefChanged(aGroup, aValue) {
+ if (this._isNextContentPrefChangeInternal) {
+ // Ignore changes that FullZoom itself makes. This works because the
+ // content pref service calls callbacks before notifying observers, and it
+ // does both in the same turn of the event loop.
+ delete this._isNextContentPrefChangeInternal;
+ return;
+ }
+
+ let browser = gBrowser.selectedBrowser;
+ if (!browser.currentURI)
+ return;
+
+ let domain = this._cps2.extractDomain(browser.currentURI.spec);
+ if (aGroup) {
+ if (aGroup == domain)
+ this._applyPrefToZoom(aValue, browser);
+ return;
+ }
+
+ this._globalValue = aValue === undefined ? aValue :
+ this._ensureValid(aValue);
+
+ // If the current page doesn't have a site-specific preference, then its
+ // zoom should be set to the new global preference now that the global
+ // preference has changed.
+ let hasPref = false;
+ let ctxt = this._loadContextFromWindow(browser.contentWindow);
+ let token = this._getBrowserToken(browser);
+ this._cps2.getByDomainAndName(browser.currentURI.spec, this.name, ctxt, {
+ handleResult: function () hasPref = true,
+ handleCompletion: function () {
+ if (!hasPref && token.isCurrent)
+ this._applyPrefToZoom(undefined, browser);
+ }.bind(this)
+ });
+ },
+
+ // location change observer
+
+ /**
+ * Called when the location of a tab changes.
+ * When that happens, we need to update the current zoom level if appropriate.
+ *
+ * @param aURI
+ * A URI object representing the new location.
+ * @param aIsTabSwitch
+ * Whether this location change has happened because of a tab switch.
+ * @param aBrowser
+ * (optional) browser object displaying the document
+ */
+ onLocationChange: function FullZoom_onLocationChange(aURI, aIsTabSwitch, aBrowser) {
+ // Ignore all pending async zoom accesses in the browser. Pending accesses
+ // that started before the location change will be prevented from applying
+ // to the new location.
+ let browser = aBrowser || gBrowser.selectedBrowser;
+ this._ignorePendingZoomAccesses(browser);
+
+ if (!aURI || (aIsTabSwitch && !this.siteSpecific)) {
+ this._notifyOnLocationChange();
+ return;
+ }
+
+ // Avoid the cps roundtrip and apply the default/global pref.
+ if (aURI.spec == "about:blank") {
+ this._applyPrefToZoom(undefined, browser,
+ this._notifyOnLocationChange.bind(this));
+ return;
+ }
+
+ // Media documents should always start at 1, and are not affected by prefs.
+ if (!aIsTabSwitch && browser.contentDocument.mozSyntheticDocument) {
+ ZoomManager.setZoomForBrowser(browser, 1);
+ // _ignorePendingZoomAccesses already called above, so no need here.
+ this._notifyOnLocationChange();
+ return;
+ }
+
+ // See if the zoom pref is cached.
+ let ctxt = this._loadContextFromWindow(browser.contentWindow);
+ let pref = this._cps2.getCachedByDomainAndName(aURI.spec, this.name, ctxt);
+ if (pref) {
+ this._applyPrefToZoom(pref.value, browser,
+ this._notifyOnLocationChange.bind(this));
+ return;
+ }
+
+ // It's not cached, so we have to asynchronously fetch it.
+ let value = undefined;
+ let token = this._getBrowserToken(browser);
+ this._cps2.getByDomainAndName(aURI.spec, this.name, ctxt, {
+ handleResult: function (resultPref) value = resultPref.value,
+ handleCompletion: function () {
+ if (!token.isCurrent) {
+ this._notifyOnLocationChange();
+ return;
+ }
+ this._applyPrefToZoom(value, browser,
+ this._notifyOnLocationChange.bind(this));
+ }.bind(this)
+ });
+ },
+
+ // update state of zoom type menu item
+
+ updateMenu: function FullZoom_updateMenu() {
+ var menuItem = document.getElementById("toggle_zoom");
+
+ menuItem.setAttribute("checked", !ZoomManager.useFullZoom);
+ },
+
+ //**************************************************************************//
+ // Setting & Pref Manipulation
+
+ /**
+ * Reduces the zoom level of the page in the current browser.
+ */
+ reduce: function FullZoom_reduce() {
+ ZoomManager.reduce();
+ let browser = gBrowser.selectedBrowser;
+ this._ignorePendingZoomAccesses(browser);
+ this._applyZoomToPref(browser);
+ },
+
+ /**
+ * Enlarges the zoom level of the page in the current browser.
+ */
+ enlarge: function FullZoom_enlarge() {
+ ZoomManager.enlarge();
+ let browser = gBrowser.selectedBrowser;
+ this._ignorePendingZoomAccesses(browser);
+ this._applyZoomToPref(browser);
+ },
+
+ /**
+ * Sets the zoom level of the page in the current browser to the global zoom
+ * level.
+ */
+ reset: function FullZoom_reset() {
+ let browser = gBrowser.selectedBrowser;
+ let token = this._getBrowserToken(browser);
+ this._getGlobalValue(browser.contentWindow, function (value) {
+ if (token.isCurrent) {
+ ZoomManager.setZoomForBrowser(browser, value === undefined ? 1 : value);
+ this._ignorePendingZoomAccesses(browser);
+ }
+ });
+ this._removePref(browser);
+ },
+
+ /**
+ * Set the zoom level for a given browser.
+ *
+ * Per nsPresContext::setFullZoom, we can set the zoom to its current value
+ * without significant impact on performance, as the setting is only applied
+ * if it differs from the current setting. In fact getting the zoom and then
+ * checking ourselves if it differs costs more.
+ *
+ * And perhaps we should always set the zoom even if it was more expensive,
+ * since nsDocumentViewer::SetTextZoom claims that child documents can have
+ * a different text zoom (although it would be unusual), and it implies that
+ * those child text zooms should get updated when the parent zoom gets set,
+ * and perhaps the same is true for full zoom
+ * (although nsDocumentViewer::SetFullZoom doesn't mention it).
+ *
+ * So when we apply new zoom values to the browser, we simply set the zoom.
+ * We don't check first to see if the new value is the same as the current
+ * one.
+ *
+ * @param aValue The zoom level value.
+ * @param aBrowser The zoom is set in this browser. Required.
+ * @param aCallback If given, it's asynchronously called when complete.
+ */
+ _applyPrefToZoom: function FullZoom__applyPrefToZoom(aValue, aBrowser, aCallback) {
+ if (!this.siteSpecific || gInPrintPreviewMode) {
+ this._executeSoon(aCallback);
+ return;
+ }
+
+ // aBrowser.contentDocument is sometimes gone because this method is called
+ // by content pref service callbacks, which themselves can be called at any
+ // time, even after browsers are closed.
+ if (!aBrowser.contentDocument ||
+ aBrowser.contentDocument.mozSyntheticDocument) {
+ this._executeSoon(aCallback);
+ return;
+ }
+
+ if (aValue !== undefined) {
+ ZoomManager.setZoomForBrowser(aBrowser, this._ensureValid(aValue));
+ this._ignorePendingZoomAccesses(aBrowser);
+ this._executeSoon(aCallback);
+ return;
+ }
+
+ let token = this._getBrowserToken(aBrowser);
+ this._getGlobalValue(aBrowser.contentWindow, function (value) {
+ if (token.isCurrent) {
+ ZoomManager.setZoomForBrowser(aBrowser, value === undefined ? 1 : value);
+ this._ignorePendingZoomAccesses(aBrowser);
+ }
+ this._executeSoon(aCallback);
+ });
+ },
+
+ /**
+ * Saves the zoom level of the page in the given browser to the content
+ * prefs store.
+ *
+ * @param browser The zoom of this browser will be saved. Required.
+ */
+ _applyZoomToPref: function FullZoom__applyZoomToPref(browser) {
+ if (!this.siteSpecific ||
+ gInPrintPreviewMode ||
+ browser.contentDocument.mozSyntheticDocument)
+ return;
+
+ this._cps2.set(browser.currentURI.spec, this.name,
+ ZoomManager.getZoomForBrowser(browser),
+ this._loadContextFromWindow(browser.contentWindow), {
+ handleCompletion: function () {
+ this._isNextContentPrefChangeInternal = true;
+ }.bind(this),
+ });
+ },
+
+ /**
+ * Removes from the content prefs store the zoom level of the given browser.
+ *
+ * @param browser The zoom of this browser will be removed. Required.
+ */
+ _removePref: function FullZoom__removePref(browser) {
+ if (browser.contentDocument.mozSyntheticDocument)
+ return;
+ let ctxt = this._loadContextFromWindow(browser.contentWindow);
+ this._cps2.removeByDomainAndName(browser.currentURI.spec, this.name, ctxt, {
+ handleCompletion: function () {
+ this._isNextContentPrefChangeInternal = true;
+ }.bind(this),
+ });
+ },
+
+ //**************************************************************************//
+ // Utilities
+
+ /**
+ * Returns the zoom change token of the given browser. Asynchronous
+ * operations that access the given browser's zoom should use this method to
+ * capture the token before starting and use token.isCurrent to determine if
+ * it's safe to access the zoom when done. If token.isCurrent is false, then
+ * after the async operation started, either the browser's zoom was changed or
+ * the browser was destroyed, and depending on what the operation is doing, it
+ * may no longer be safe to set and get its zoom.
+ *
+ * @param browser The token of this browser will be returned.
+ * @return An object with an "isCurrent" getter.
+ */
+ _getBrowserToken: function FullZoom__getBrowserToken(browser) {
+ let outerID = this._browserOuterID(browser);
+ let map = this._browserTokenMap;
+ if (!map.has(outerID))
+ map.set(outerID, 0);
+ return {
+ token: map.get(outerID),
+ get isCurrent() {
+ // At this point, the browser may have been destructed and unbound but
+ // its outer ID not removed from the map because outer-window-destroyed
+ // hasn't been received yet. In that case, the browser is unusable, it
+ // has no properties, so return false. Check for this case by getting a
+ // property, say, docShell.
+ return map.get(outerID) === this.token && browser.docShell;
+ },
+ };
+ },
+
+ /**
+ * Increments the zoom change token for the given browser so that pending
+ * async operations know that it may be unsafe to access they zoom when they
+ * finish.
+ *
+ * @param browser Pending accesses in this browser will be ignored.
+ */
+ _ignorePendingZoomAccesses: function FullZoom__ignorePendingZoomAccesses(browser) {
+ let outerID = this._browserOuterID(browser);
+ let map = this._browserTokenMap;
+ map.set(outerID, (map.get(outerID) || 0) + 1);
+ },
+
+ _browserOuterID: function FullZoom__browserOuterID(browser) {
+ return browser.
+ contentWindow.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).
+ outerWindowID;
+ },
+
+ _ensureValid: function FullZoom__ensureValid(aValue) {
+ // Note that undefined is a valid value for aValue that indicates a known-
+ // not-to-exist value.
+ if (isNaN(aValue))
+ return 1;
+
+ if (aValue < ZoomManager.MIN)
+ return ZoomManager.MIN;
+
+ if (aValue > ZoomManager.MAX)
+ return ZoomManager.MAX;
+
+ return aValue;
+ },
+
+ /**
+ * Gets the global browser.content.full-zoom content preference.
+ *
+ * WARNING: callback may be called synchronously or asynchronously. The
+ * reason is that it's usually desirable to avoid turns of the event loop
+ * where possible, since they can lead to visible, jarring jumps in zoom
+ * level. It's not always possible to avoid them, though. As a convenience,
+ * then, this method takes a callback and returns nothing.
+ *
+ * @param window The content window pertaining to the zoom.
+ * @param callback Synchronously or asynchronously called when done. It's
+ * bound to this object (FullZoom) and called as:
+ * callback(prefValue)
+ */
+ _getGlobalValue: function FullZoom__getGlobalValue(window, callback) {
+ // * !("_globalValue" in this) => global value not yet cached.
+ // * this._globalValue === undefined => global value known not to exist.
+ // * Otherwise, this._globalValue is a number, the global value.
+ if ("_globalValue" in this) {
+ callback.call(this, this._globalValue, true);
+ return;
+ }
+ let value = undefined;
+ this._cps2.getGlobal(this.name, this._loadContextFromWindow(window), {
+ handleResult: function (pref) value = pref.value,
+ handleCompletion: function (reason) {
+ this._globalValue = this._ensureValid(value);
+ callback.call(this, this._globalValue);
+ }.bind(this)
+ });
+ },
+
+ /**
+ * Gets the load context from the given window.
+ *
+ * @param window The window whose load context will be returned.
+ * @return The nsILoadContext of the given window.
+ */
+ _loadContextFromWindow: function FullZoom__loadContextFromWindow(window) {
+ return window.
+ QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebNavigation).
+ QueryInterface(Ci.nsILoadContext);
+ },
+
+ /**
+ * Asynchronously broadcasts a "browser-fullZoom:locationChange" notification
+ * so that tests can select tabs, load pages, etc. and be notified when the
+ * zoom levels on those pages change. The notification is always asynchronous
+ * so that observers are guaranteed a consistent behavior.
+ */
+ _notifyOnLocationChange: function FullZoom__notifyOnLocationChange() {
+ this._executeSoon(function () {
+ Services.obs.notifyObservers(null, "browser-fullZoom:locationChange", "");
+ });
+ },
+
+ _executeSoon: function FullZoom__executeSoon(callback) {
+ if (!callback)
+ return;
+ Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
+ },
+};
diff --git a/browser/base/content/browser-gestureSupport.js b/browser/base/content/browser-gestureSupport.js
new file mode 100644
index 000000000..d88f47c79
--- /dev/null
+++ b/browser/base/content/browser-gestureSupport.js
@@ -0,0 +1,1059 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// Simple gestures support
+//
+// As per bug #412486, web content must not be allowed to receive any
+// simple gesture events. Multi-touch gesture APIs are in their
+// infancy and we do NOT want to be forced into supporting an API that
+// will probably have to change in the future. (The current Mac OS X
+// API is undocumented and was reverse-engineered.) Until support is
+// implemented in the event dispatcher to keep these events as
+// chrome-only, we must listen for the simple gesture events during
+// the capturing phase and call stopPropagation on every event.
+
+let gGestureSupport = {
+ _currentRotation: 0,
+ _lastRotateDelta: 0,
+ _rotateMomentumThreshold: .75,
+
+ /**
+ * Add or remove mouse gesture event listeners
+ *
+ * @param aAddListener
+ * True to add/init listeners and false to remove/uninit
+ */
+ init: function GS_init(aAddListener) {
+ // Bug 863514 - Make gesture support work in electrolysis
+ if (gMultiProcessBrowser)
+ return;
+
+ const gestureEvents = ["SwipeGestureStart",
+ "SwipeGestureUpdate", "SwipeGestureEnd", "SwipeGesture",
+ "MagnifyGestureStart", "MagnifyGestureUpdate", "MagnifyGesture",
+ "RotateGestureStart", "RotateGestureUpdate", "RotateGesture",
+ "TapGesture", "PressTapGesture"];
+
+ let addRemove = aAddListener ? window.addEventListener :
+ window.removeEventListener;
+
+ gestureEvents.forEach(function (event) addRemove("Moz" + event, this, true),
+ this);
+ },
+
+ /**
+ * Dispatch events based on the type of mouse gesture event. For now, make
+ * sure to stop propagation of every gesture event so that web content cannot
+ * receive gesture events.
+ *
+ * @param aEvent
+ * The gesture event to handle
+ */
+ handleEvent: function GS_handleEvent(aEvent) {
+ if (!Services.prefs.getBoolPref(
+ "dom.debug.propagate_gesture_events_through_content")) {
+ aEvent.stopPropagation();
+ }
+
+ // Create a preference object with some defaults
+ let def = function(aThreshold, aLatched)
+ ({ threshold: aThreshold, latched: !!aLatched });
+
+ switch (aEvent.type) {
+ case "MozSwipeGestureStart":
+ aEvent.preventDefault();
+ this._setupSwipeGesture(aEvent);
+ break;
+ case "MozSwipeGestureUpdate":
+ aEvent.preventDefault();
+ this._doUpdate(aEvent);
+ break;
+ case "MozSwipeGestureEnd":
+ aEvent.preventDefault();
+ this._doEnd(aEvent);
+ break;
+ case "MozSwipeGesture":
+ aEvent.preventDefault();
+ this.onSwipe(aEvent);
+ break;
+ case "MozMagnifyGestureStart":
+ aEvent.preventDefault();
+#ifdef XP_WIN
+ this._setupGesture(aEvent, "pinch", def(25, 0), "out", "in");
+#else
+ this._setupGesture(aEvent, "pinch", def(150, 1), "out", "in");
+#endif
+ break;
+ case "MozRotateGestureStart":
+ aEvent.preventDefault();
+ this._setupGesture(aEvent, "twist", def(25, 0), "right", "left");
+ break;
+ case "MozMagnifyGestureUpdate":
+ case "MozRotateGestureUpdate":
+ aEvent.preventDefault();
+ this._doUpdate(aEvent);
+ break;
+ case "MozTapGesture":
+ aEvent.preventDefault();
+ this._doAction(aEvent, ["tap"]);
+ break;
+ case "MozRotateGesture":
+ aEvent.preventDefault();
+ this._doAction(aEvent, ["twist", "end"]);
+ break;
+ /* case "MozPressTapGesture":
+ break; */
+ }
+ },
+
+ /**
+ * Called at the start of "pinch" and "twist" gestures to setup all of the
+ * information needed to process the gesture
+ *
+ * @param aEvent
+ * The continual motion start event to handle
+ * @param aGesture
+ * Name of the gesture to handle
+ * @param aPref
+ * Preference object with the names of preferences and defaults
+ * @param aInc
+ * Command to trigger for increasing motion (without gesture name)
+ * @param aDec
+ * Command to trigger for decreasing motion (without gesture name)
+ */
+ _setupGesture: function GS__setupGesture(aEvent, aGesture, aPref, aInc, aDec) {
+ // Try to load user-set values from preferences
+ for (let [pref, def] in Iterator(aPref))
+ aPref[pref] = this._getPref(aGesture + "." + pref, def);
+
+ // Keep track of the total deltas and latching behavior
+ let offset = 0;
+ let latchDir = aEvent.delta > 0 ? 1 : -1;
+ let isLatched = false;
+
+ // Create the update function here to capture closure state
+ this._doUpdate = function GS__doUpdate(aEvent) {
+ // Update the offset with new event data
+ offset += aEvent.delta;
+
+ // Check if the cumulative deltas exceed the threshold
+ if (Math.abs(offset) > aPref["threshold"]) {
+ // Trigger the action if we don't care about latching; otherwise, make
+ // sure either we're not latched and going the same direction of the
+ // initial motion; or we're latched and going the opposite way
+ let sameDir = (latchDir ^ offset) >= 0;
+ if (!aPref["latched"] || (isLatched ^ sameDir)) {
+ this._doAction(aEvent, [aGesture, offset > 0 ? aInc : aDec]);
+
+ // We must be getting latched or leaving it, so just toggle
+ isLatched = !isLatched;
+ }
+
+ // Reset motion counter to prepare for more of the same gesture
+ offset = 0;
+ }
+ };
+
+ // The start event also contains deltas, so handle an update right away
+ this._doUpdate(aEvent);
+ },
+
+ /**
+ * Checks whether a swipe gesture event can navigate the browser history or
+ * not.
+ *
+ * @param aEvent
+ * The swipe gesture event.
+ * @return true if the swipe event may navigate the history, false othwerwise.
+ */
+ _swipeNavigatesHistory: function GS__swipeNavigatesHistory(aEvent) {
+ return this._getCommand(aEvent, ["swipe", "left"])
+ == "Browser:BackOrBackDuplicate" &&
+ this._getCommand(aEvent, ["swipe", "right"])
+ == "Browser:ForwardOrForwardDuplicate";
+ },
+
+ /**
+ * Sets up the history swipe animations for a swipe gesture event, if enabled.
+ *
+ * @param aEvent
+ * The swipe gesture start event.
+ */
+ _setupSwipeGesture: function GS__setupSwipeGesture(aEvent) {
+ if (!this._swipeNavigatesHistory(aEvent))
+ return;
+
+ let canGoBack = gHistorySwipeAnimation.canGoBack();
+ let canGoForward = gHistorySwipeAnimation.canGoForward();
+ let isLTR = gHistorySwipeAnimation.isLTR;
+
+ if (canGoBack)
+ aEvent.allowedDirections |= isLTR ? aEvent.DIRECTION_LEFT :
+ aEvent.DIRECTION_RIGHT;
+ if (canGoForward)
+ aEvent.allowedDirections |= isLTR ? aEvent.DIRECTION_RIGHT :
+ aEvent.DIRECTION_LEFT;
+
+ gHistorySwipeAnimation.startAnimation();
+
+ this._doUpdate = function GS__doUpdate(aEvent) {
+ gHistorySwipeAnimation.updateAnimation(aEvent.delta);
+ };
+
+ this._doEnd = function GS__doEnd(aEvent) {
+ gHistorySwipeAnimation.swipeEndEventReceived();
+
+ this._doUpdate = function (aEvent) {};
+ this._doEnd = function (aEvent) {};
+ }
+ },
+
+ /**
+ * Generator producing the powerset of the input array where the first result
+ * is the complete set and the last result (before StopIteration) is empty.
+ *
+ * @param aArray
+ * Source array containing any number of elements
+ * @yield Array that is a subset of the input array from full set to empty
+ */
+ _power: function GS__power(aArray) {
+ // Create a bitmask based on the length of the array
+ let num = 1 << aArray.length;
+ while (--num >= 0) {
+ // Only select array elements where the current bit is set
+ yield aArray.reduce(function (aPrev, aCurr, aIndex) {
+ if (num & 1 << aIndex)
+ aPrev.push(aCurr);
+ return aPrev;
+ }, []);
+ }
+ },
+
+ /**
+ * Determine what action to do for the gesture based on which keys are
+ * pressed and which commands are set, and execute the command.
+ *
+ * @param aEvent
+ * The original gesture event to convert into a fake click event
+ * @param aGesture
+ * Array of gesture name parts (to be joined by periods)
+ * @return Name of the executed command. Returns null if no command is
+ * found.
+ */
+ _doAction: function GS__doAction(aEvent, aGesture) {
+ let command = this._getCommand(aEvent, aGesture);
+ return command && this._doCommand(aEvent, command);
+ },
+
+ /**
+ * Determine what action to do for the gesture based on which keys are
+ * pressed and which commands are set
+ *
+ * @param aEvent
+ * The original gesture event to convert into a fake click event
+ * @param aGesture
+ * Array of gesture name parts (to be joined by periods)
+ */
+ _getCommand: function GS__getCommand(aEvent, aGesture) {
+ // Create an array of pressed keys in a fixed order so that a command for
+ // "meta" is preferred over "ctrl" when both buttons are pressed (and a
+ // command for both don't exist)
+ let keyCombos = [];
+ ["shift", "alt", "ctrl", "meta"].forEach(function (key) {
+ if (aEvent[key + "Key"])
+ keyCombos.push(key);
+ });
+
+ // Try each combination of key presses in decreasing order for commands
+ for (let subCombo of this._power(keyCombos)) {
+ // Convert a gesture and pressed keys into the corresponding command
+ // action where the preference has the gesture before "shift" before
+ // "alt" before "ctrl" before "meta" all separated by periods
+ let command;
+ try {
+ command = this._getPref(aGesture.concat(subCombo).join("."));
+ } catch (e) {}
+
+ if (command)
+ return command;
+ }
+ return null;
+ },
+
+ /**
+ * Execute the specified command.
+ *
+ * @param aEvent
+ * The original gesture event to convert into a fake click event
+ * @param aCommand
+ * Name of the command found for the event's keys and gesture.
+ */
+ _doCommand: function GS__doCommand(aEvent, aCommand) {
+ let node = document.getElementById(aCommand);
+ if (node) {
+ if (node.getAttribute("disabled") != "true") {
+ let cmdEvent = document.createEvent("xulcommandevent");
+ cmdEvent.initCommandEvent("command", true, true, window, 0,
+ aEvent.ctrlKey, aEvent.altKey,
+ aEvent.shiftKey, aEvent.metaKey, aEvent);
+ node.dispatchEvent(cmdEvent);
+ }
+
+ }
+ else {
+ goDoCommand(aCommand);
+ }
+ },
+
+ /**
+ * Handle continual motion events. This function will be set by
+ * _setupGesture or _setupSwipe.
+ *
+ * @param aEvent
+ * The continual motion update event to handle
+ */
+ _doUpdate: function(aEvent) {},
+
+ /**
+ * Handle gesture end events. This function will be set by _setupSwipe.
+ *
+ * @param aEvent
+ * The gesture end event to handle
+ */
+ _doEnd: function(aEvent) {},
+
+ /**
+ * Convert the swipe gesture into a browser action based on the direction.
+ *
+ * @param aEvent
+ * The swipe event to handle
+ */
+ onSwipe: function GS_onSwipe(aEvent) {
+ // Figure out which one (and only one) direction was triggered
+ for (let dir of ["UP", "RIGHT", "DOWN", "LEFT"]) {
+ if (aEvent.direction == aEvent["DIRECTION_" + dir]) {
+ this._coordinateSwipeEventWithAnimation(aEvent, dir);
+ break;
+ }
+ }
+ },
+
+ /**
+ * Process a swipe event based on the given direction.
+ *
+ * @param aEvent
+ * The swipe event to handle
+ * @param aDir
+ * The direction for the swipe event
+ */
+ processSwipeEvent: function GS_processSwipeEvent(aEvent, aDir) {
+ this._doAction(aEvent, ["swipe", aDir.toLowerCase()]);
+ },
+
+ /**
+ * Coordinates the swipe event with the swipe animation, if any.
+ * If an animation is currently running, the swipe event will be
+ * processed once the animation stops. This will guarantee a fluid
+ * motion of the animation.
+ *
+ * @param aEvent
+ * The swipe event to handle
+ * @param aDir
+ * The direction for the swipe event
+ */
+ _coordinateSwipeEventWithAnimation:
+ function GS__coordinateSwipeEventWithAnimation(aEvent, aDir) {
+ if ((gHistorySwipeAnimation.isAnimationRunning()) &&
+ (aDir == "RIGHT" || aDir == "LEFT")) {
+ gHistorySwipeAnimation.processSwipeEvent(aEvent, aDir);
+ }
+ else {
+ this.processSwipeEvent(aEvent, aDir);
+ }
+ },
+
+ /**
+ * Get a gesture preference or use a default if it doesn't exist
+ *
+ * @param aPref
+ * Name of the preference to load under the gesture branch
+ * @param aDef
+ * Default value if the preference doesn't exist
+ */
+ _getPref: function GS__getPref(aPref, aDef) {
+ // Preferences branch under which all gestures preferences are stored
+ const branch = "browser.gesture.";
+
+ try {
+ // Determine what type of data to load based on default value's type
+ let type = typeof aDef;
+ let getFunc = "get" + (type == "boolean" ? "Bool" :
+ type == "number" ? "Int" : "Char") + "Pref";
+ return gPrefService[getFunc](branch + aPref);
+ }
+ catch (e) {
+ return aDef;
+ }
+ },
+
+ /**
+ * Perform rotation for ImageDocuments
+ *
+ * @param aEvent
+ * The MozRotateGestureUpdate event triggering this call
+ */
+ rotate: function(aEvent) {
+ if (!(content.document instanceof ImageDocument))
+ return;
+
+ let contentElement = content.document.body.firstElementChild;
+ if (!contentElement)
+ return;
+ // If we're currently snapping, cancel that snap
+ if (contentElement.classList.contains("completeRotation"))
+ this._clearCompleteRotation();
+
+ this.rotation = Math.round(this.rotation + aEvent.delta);
+ contentElement.style.transform = "rotate(" + this.rotation + "deg)";
+ this._lastRotateDelta = aEvent.delta;
+ },
+
+ /**
+ * Perform a rotation end for ImageDocuments
+ */
+ rotateEnd: function() {
+ if (!(content.document instanceof ImageDocument))
+ return;
+
+ let contentElement = content.document.body.firstElementChild;
+ if (!contentElement)
+ return;
+
+ let transitionRotation = 0;
+
+ // The reason that 360 is allowed here is because when rotating between
+ // 315 and 360, setting rotate(0deg) will cause it to rotate the wrong
+ // direction around--spinning wildly.
+ if (this.rotation <= 45)
+ transitionRotation = 0;
+ else if (this.rotation > 45 && this.rotation <= 135)
+ transitionRotation = 90;
+ else if (this.rotation > 135 && this.rotation <= 225)
+ transitionRotation = 180;
+ else if (this.rotation > 225 && this.rotation <= 315)
+ transitionRotation = 270;
+ else
+ transitionRotation = 360;
+
+ // If we're going fast enough, and we didn't already snap ahead of rotation,
+ // then snap ahead of rotation to simulate momentum
+ if (this._lastRotateDelta > this._rotateMomentumThreshold &&
+ this.rotation > transitionRotation)
+ transitionRotation += 90;
+ else if (this._lastRotateDelta < -1 * this._rotateMomentumThreshold &&
+ this.rotation < transitionRotation)
+ transitionRotation -= 90;
+
+ // Only add the completeRotation class if it is is necessary
+ if (transitionRotation != this.rotation) {
+ contentElement.classList.add("completeRotation");
+ contentElement.addEventListener("transitionend", this._clearCompleteRotation);
+ }
+
+ contentElement.style.transform = "rotate(" + transitionRotation + "deg)";
+ this.rotation = transitionRotation;
+ },
+
+ /**
+ * Gets the current rotation for the ImageDocument
+ */
+ get rotation() {
+ return this._currentRotation;
+ },
+
+ /**
+ * Sets the current rotation for the ImageDocument
+ *
+ * @param aVal
+ * The new value to take. Can be any value, but it will be bounded to
+ * 0 inclusive to 360 exclusive.
+ */
+ set rotation(aVal) {
+ this._currentRotation = aVal % 360;
+ if (this._currentRotation < 0)
+ this._currentRotation += 360;
+ return this._currentRotation;
+ },
+
+ /**
+ * When the location/tab changes, need to reload the current rotation for the
+ * image
+ */
+ restoreRotationState: function() {
+ // Bug 863514 - Make gesture support work in electrolysis
+ if (gMultiProcessBrowser)
+ return;
+
+ if (!(content.document instanceof ImageDocument))
+ return;
+
+ let contentElement = content.document.body.firstElementChild;
+ let transformValue = content.window.getComputedStyle(contentElement, null)
+ .transform;
+
+ if (transformValue == "none") {
+ this.rotation = 0;
+ return;
+ }
+
+ // transformValue is a rotation matrix--split it and do mathemagic to
+ // obtain the real rotation value
+ transformValue = transformValue.split("(")[1]
+ .split(")")[0]
+ .split(",");
+ this.rotation = Math.round(Math.atan2(transformValue[1], transformValue[0]) *
+ (180 / Math.PI));
+ },
+
+ /**
+ * Removes the transition rule by removing the completeRotation class
+ */
+ _clearCompleteRotation: function() {
+ let contentElement = content.document &&
+ content.document instanceof ImageDocument &&
+ content.document.body &&
+ content.document.body.firstElementChild;
+ if (!contentElement)
+ return;
+ contentElement.classList.remove("completeRotation");
+ contentElement.removeEventListener("transitionend", this._clearCompleteRotation);
+ },
+};
+
+// History Swipe Animation Support (bug 678392)
+let gHistorySwipeAnimation = {
+
+ active: false,
+ isLTR: false,
+
+ /**
+ * Initializes the support for history swipe animations, if it is supported
+ * by the platform/configuration.
+ */
+ init: function HSA_init() {
+ if (!this._isSupported())
+ return;
+
+ this.active = false;
+ this.isLTR = document.documentElement.mozMatchesSelector(
+ ":-moz-locale-dir(ltr)");
+ this._trackedSnapshots = [];
+ this._historyIndex = -1;
+ this._boxWidth = -1;
+ this._maxSnapshots = this._getMaxSnapshots();
+ this._lastSwipeDir = "";
+
+ // We only want to activate history swipe animations if we store snapshots.
+ // If we don't store any, we handle horizontal swipes without animations.
+ if (this._maxSnapshots > 0) {
+ this.active = true;
+ gBrowser.addEventListener("pagehide", this, false);
+ gBrowser.addEventListener("pageshow", this, false);
+ gBrowser.addEventListener("popstate", this, false);
+ gBrowser.tabContainer.addEventListener("TabClose", this, false);
+ }
+ },
+
+ /**
+ * Uninitializes the support for history swipe animations.
+ */
+ uninit: function HSA_uninit() {
+ gBrowser.removeEventListener("pagehide", this, false);
+ gBrowser.removeEventListener("pageshow", this, false);
+ gBrowser.removeEventListener("popstate", this, false);
+ gBrowser.tabContainer.removeEventListener("TabClose", this, false);
+
+ this.active = false;
+ this.isLTR = false;
+ },
+
+ /**
+ * Starts the swipe animation and handles fast swiping (i.e. a swipe animation
+ * is already in progress when a new one is initiated).
+ */
+ startAnimation: function HSA_startAnimation() {
+ if (this.isAnimationRunning()) {
+ gBrowser.stop();
+ this._lastSwipeDir = "RELOAD"; // just ensure that != ""
+ this._canGoBack = this.canGoBack();
+ this._canGoForward = this.canGoForward();
+ this._handleFastSwiping();
+ }
+ else {
+ this._historyIndex = gBrowser.webNavigation.sessionHistory.index;
+ this._canGoBack = this.canGoBack();
+ this._canGoForward = this.canGoForward();
+ if (this.active) {
+ this._takeSnapshot();
+ this._installPrevAndNextSnapshots();
+ this._addBoxes();
+ this._lastSwipeDir = "";
+ }
+ }
+ this.updateAnimation(0);
+ },
+
+ /**
+ * Stops the swipe animation.
+ */
+ stopAnimation: function HSA_stopAnimation() {
+ gHistorySwipeAnimation._removeBoxes();
+ },
+
+ /**
+ * Updates the animation between two pages in history.
+ *
+ * @param aVal
+ * A floating point value that represents the progress of the
+ * swipe gesture.
+ */
+ updateAnimation: function HSA_updateAnimation(aVal) {
+ if (!this.isAnimationRunning())
+ return;
+
+ if ((aVal >= 0 && this.isLTR) ||
+ (aVal <= 0 && !this.isLTR)) {
+ if (aVal > 1)
+ aVal = 1; // Cap value to avoid sliding the page further than allowed.
+
+ if (this._canGoBack)
+ this._prevBox.collapsed = false;
+ else
+ this._prevBox.collapsed = true;
+
+ // The current page is pushed to the right (LTR) or left (RTL),
+ // the intention is to go back.
+ // If there is a page to go back to, it should show in the background.
+ this._positionBox(this._curBox, aVal);
+
+ // The forward page should be pushed offscreen all the way to the right.
+ this._positionBox(this._nextBox, 1);
+ }
+ else {
+ if (aVal < -1)
+ aVal = -1; // Cap value to avoid sliding the page further than allowed.
+
+ // The intention is to go forward. If there is a page to go forward to,
+ // it should slide in from the right (LTR) or left (RTL).
+ // Otherwise, the current page should slide to the left (LTR) or
+ // right (RTL) and the backdrop should appear in the background.
+ // For the backdrop to be visible in that case, the previous page needs
+ // to be hidden (if it exists).
+ if (this._canGoForward) {
+ let offset = this.isLTR ? 1 : -1;
+ this._positionBox(this._curBox, 0);
+ this._positionBox(this._nextBox, offset + aVal); // aVal is negative
+ }
+ else {
+ this._prevBox.collapsed = true;
+ this._positionBox(this._curBox, aVal);
+ }
+ }
+ },
+
+ /**
+ * Event handler for events relevant to the history swipe animation.
+ *
+ * @param aEvent
+ * An event to process.
+ */
+ handleEvent: function HSA_handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "TabClose":
+ let browser = gBrowser.getBrowserForTab(aEvent.target);
+ this._removeTrackedSnapshot(-1, browser);
+ break;
+ case "pageshow":
+ case "popstate":
+ if (this.isAnimationRunning()) {
+ if (aEvent.target != gBrowser.selectedBrowser.contentDocument)
+ break;
+ this.stopAnimation();
+ }
+ this._historyIndex = gBrowser.webNavigation.sessionHistory.index;
+ break;
+ case "pagehide":
+ if (aEvent.target == gBrowser.selectedBrowser.contentDocument) {
+ // Take a snapshot of a page whenever it's about to be navigated away
+ // from.
+ this._takeSnapshot();
+ }
+ break;
+ }
+ },
+
+ /**
+ * Checks whether the history swipe animation is currently running or not.
+ *
+ * @return true if the animation is currently running, false otherwise.
+ */
+ isAnimationRunning: function HSA_isAnimationRunning() {
+ return !!this._container;
+ },
+
+ /**
+ * Process a swipe event based on the given direction.
+ *
+ * @param aEvent
+ * The swipe event to handle
+ * @param aDir
+ * The direction for the swipe event
+ */
+ processSwipeEvent: function HSA_processSwipeEvent(aEvent, aDir) {
+ if (aDir == "RIGHT")
+ this._historyIndex += this.isLTR ? 1 : -1;
+ else if (aDir == "LEFT")
+ this._historyIndex += this.isLTR ? -1 : 1;
+ else
+ return;
+ this._lastSwipeDir = aDir;
+ },
+
+ /**
+ * Checks if there is a page in the browser history to go back to.
+ *
+ * @return true if there is a previous page in history, false otherwise.
+ */
+ canGoBack: function HSA_canGoBack() {
+ if (this.isAnimationRunning())
+ return this._doesIndexExistInHistory(this._historyIndex - 1);
+ return gBrowser.webNavigation.canGoBack;
+ },
+
+ /**
+ * Checks if there is a page in the browser history to go forward to.
+ *
+ * @return true if there is a next page in history, false otherwise.
+ */
+ canGoForward: function HSA_canGoForward() {
+ if (this.isAnimationRunning())
+ return this._doesIndexExistInHistory(this._historyIndex + 1);
+ return gBrowser.webNavigation.canGoForward;
+ },
+
+ /**
+ * Used to notify the history swipe animation that the OS sent a swipe end
+ * event and that we should navigate to the page that the user swiped to, if
+ * any. This will also result in the animation overlay to be torn down.
+ */
+ swipeEndEventReceived: function HSA_swipeEndEventReceived() {
+ if (this._lastSwipeDir != "")
+ this._navigateToHistoryIndex();
+ else
+ this.stopAnimation();
+ },
+
+ /**
+ * Checks whether a particular index exists in the browser history or not.
+ *
+ * @param aIndex
+ * The index to check for availability for in the history.
+ * @return true if the index exists in the browser history, false otherwise.
+ */
+ _doesIndexExistInHistory: function HSA__doesIndexExistInHistory(aIndex) {
+ try {
+ gBrowser.webNavigation.sessionHistory.getEntryAtIndex(aIndex, false);
+ }
+ catch(ex) {
+ return false;
+ }
+ return true;
+ },
+
+ /**
+ * Navigates to the index in history that is currently being tracked by
+ * |this|.
+ */
+ _navigateToHistoryIndex: function HSA__navigateToHistoryIndex() {
+ if (this._doesIndexExistInHistory(this._historyIndex)) {
+ gBrowser.webNavigation.gotoIndex(this._historyIndex);
+ }
+ },
+
+ /**
+ * Checks to see if history swipe animations are supported by this
+ * platform/configuration.
+ *
+ * return true if supported, false otherwise.
+ */
+ _isSupported: function HSA__isSupported() {
+ return window.matchMedia("(-moz-swipe-animation-enabled)").matches;
+ },
+
+ /**
+ * Handle fast swiping (i.e. a swipe animation is already in
+ * progress when a new one is initiated). This will swap out the snapshots
+ * used in the previous animation with the appropriate new ones.
+ */
+ _handleFastSwiping: function HSA__handleFastSwiping() {
+ this._installCurrentPageSnapshot(null);
+ this._installPrevAndNextSnapshots();
+ },
+
+ /**
+ * Adds the boxes that contain the snapshots used during the swipe animation.
+ */
+ _addBoxes: function HSA__addBoxes() {
+ let browserStack =
+ document.getAnonymousElementByAttribute(gBrowser.getNotificationBox(),
+ "class", "browserStack");
+ this._container = this._createElement("historySwipeAnimationContainer",
+ "stack");
+ browserStack.appendChild(this._container);
+
+ this._prevBox = this._createElement("historySwipeAnimationPreviousPage",
+ "box");
+ this._container.appendChild(this._prevBox);
+
+ this._curBox = this._createElement("historySwipeAnimationCurrentPage",
+ "box");
+ this._container.appendChild(this._curBox);
+
+ this._nextBox = this._createElement("historySwipeAnimationNextPage",
+ "box");
+ this._container.appendChild(this._nextBox);
+
+ this._boxWidth = this._curBox.getBoundingClientRect().width; // cache width
+ },
+
+ /**
+ * Removes the boxes.
+ */
+ _removeBoxes: function HSA__removeBoxes() {
+ this._curBox = null;
+ this._prevBox = null;
+ this._nextBox = null;
+ if (this._container)
+ this._container.parentNode.removeChild(this._container);
+ this._container = null;
+ this._boxWidth = -1;
+ },
+
+ /**
+ * Creates an element with a given identifier and tag name.
+ *
+ * @param aID
+ * An identifier to create the element with.
+ * @param aTagName
+ * The name of the tag to create the element for.
+ * @return the newly created element.
+ */
+ _createElement: function HSA__createElement(aID, aTagName) {
+ let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ let element = document.createElementNS(XULNS, aTagName);
+ element.id = aID;
+ return element;
+ },
+
+ /**
+ * Moves a given box to a given X coordinate position.
+ *
+ * @param aBox
+ * The box element to position.
+ * @param aPosition
+ * The position (in X coordinates) to move the box element to.
+ */
+ _positionBox: function HSA__positionBox(aBox, aPosition) {
+ aBox.style.transform = "translateX(" + this._boxWidth * aPosition + "px)";
+ },
+
+ /**
+ * Takes a snapshot of the page the browser is currently on.
+ */
+ _takeSnapshot: function HSA__takeSnapshot() {
+ if ((this._maxSnapshots < 1) ||
+ (gBrowser.webNavigation.sessionHistory.index < 0))
+ return;
+
+ let browser = gBrowser.selectedBrowser;
+ let r = browser.getBoundingClientRect();
+ let canvas = document.createElementNS("http://www.w3.org/1999/xhtml",
+ "canvas");
+ canvas.mozOpaque = true;
+ canvas.width = r.width;
+ canvas.height = r.height;
+ let ctx = canvas.getContext("2d");
+ let zoom = browser.markupDocumentViewer.fullZoom;
+ ctx.scale(zoom, zoom);
+ ctx.drawWindow(browser.contentWindow, 0, 0, r.width, r.height, "white",
+ ctx.DRAWWINDOW_DO_NOT_FLUSH | ctx.DRAWWINDOW_DRAW_VIEW |
+ ctx.DRAWWINDOW_ASYNC_DECODE_IMAGES |
+ ctx.DRAWWINDOW_USE_WIDGET_LAYERS);
+
+ this._installCurrentPageSnapshot(canvas);
+ this._assignSnapshotToCurrentBrowser(canvas);
+ },
+
+ /**
+ * Retrieves the maximum number of snapshots that should be kept in memory.
+ * This limit is a global limit and is valid across all open tabs.
+ */
+ _getMaxSnapshots: function HSA__getMaxSnapshots() {
+ return gPrefService.getIntPref("browser.snapshots.limit");
+ },
+
+ /**
+ * Adds a snapshot to the list and initiates the compression of said snapshot.
+ * Once the compression is completed, it will replace the uncompressed
+ * snapshot in the list.
+ *
+ * @param aCanvas
+ * The snapshot to add to the list and compress.
+ */
+ _assignSnapshotToCurrentBrowser:
+ function HSA__assignSnapshotToCurrentBrowser(aCanvas) {
+ let browser = gBrowser.selectedBrowser;
+ let currIndex = browser.webNavigation.sessionHistory.index;
+
+ this._removeTrackedSnapshot(currIndex, browser);
+ this._addSnapshotRefToArray(currIndex, browser);
+
+ if (!("snapshots" in browser))
+ browser.snapshots = [];
+ let snapshots = browser.snapshots;
+ // Temporarily store the canvas as the compressed snapshot.
+ // This avoids a blank page if the user swipes quickly
+ // between pages before the compression could complete.
+ snapshots[currIndex] = aCanvas;
+
+ // Kick off snapshot compression.
+ aCanvas.toBlob(function(aBlob) {
+ snapshots[currIndex] = aBlob;
+ }, "image/png"
+ );
+ },
+
+ /**
+ * Removes a snapshot identified by the browser and index in the array of
+ * snapshots for that browser, if present. If no snapshot could be identified
+ * the method simply returns without taking any action. If aIndex is negative,
+ * all snapshots for a particular browser will be removed.
+ *
+ * @param aIndex
+ * The index in history of the new snapshot, or negative value if all
+ * snapshots for a browser should be removed.
+ * @param aBrowser
+ * The browser the new snapshot was taken in.
+ */
+ _removeTrackedSnapshot: function HSA__removeTrackedSnapshot(aIndex, aBrowser) {
+ let arr = this._trackedSnapshots;
+ let requiresExactIndexMatch = aIndex >= 0;
+ for (let i = 0; i < arr.length; i++) {
+ if ((arr[i].browser == aBrowser) &&
+ (aIndex < 0 || aIndex == arr[i].index)) {
+ delete aBrowser.snapshots[arr[i].index];
+ arr.splice(i, 1);
+ if (requiresExactIndexMatch)
+ return; // Found and removed the only element.
+ i--; // Make sure to revisit the index that we just removed an
+ // element at.
+ }
+ }
+ },
+
+ /**
+ * Adds a new snapshot reference for a given index and browser to the array
+ * of references to tracked snapshots.
+ *
+ * @param aIndex
+ * The index in history of the new snapshot.
+ * @param aBrowser
+ * The browser the new snapshot was taken in.
+ */
+ _addSnapshotRefToArray:
+ function HSA__addSnapshotRefToArray(aIndex, aBrowser) {
+ let id = { index: aIndex,
+ browser: aBrowser };
+ let arr = this._trackedSnapshots;
+ arr.unshift(id);
+
+ while (arr.length > this._maxSnapshots) {
+ let lastElem = arr[arr.length - 1];
+ delete lastElem.browser.snapshots[lastElem.index];
+ arr.splice(-1, 1);
+ }
+ },
+
+ /**
+ * Converts a compressed blob to an Image object. In some situations
+ * (especially during fast swiping) aBlob may still be a canvas, not a
+ * compressed blob. In this case, we simply return the canvas.
+ *
+ * @param aBlob
+ * The compressed blob to convert, or a canvas if a blob compression
+ * couldn't complete before this method was called.
+ * @return A new Image object representing the converted blob.
+ */
+ _convertToImg: function HSA__convertToImg(aBlob) {
+ if (!aBlob)
+ return null;
+
+ // Return aBlob if it's still a canvas and not a compressed blob yet.
+ if (aBlob instanceof HTMLCanvasElement)
+ return aBlob;
+
+ let img = new Image();
+ let url = URL.createObjectURL(aBlob);
+ img.onload = function() {
+ URL.revokeObjectURL(url);
+ };
+ img.src = url;
+ return img;
+ },
+
+ /**
+ * Sets the snapshot of the current page to the snapshot passed as parameter,
+ * or to the one previously stored for the current index in history if the
+ * parameter is null.
+ *
+ * @param aCanvas
+ * The snapshot to set the current page to. If this parameter is null,
+ * the previously stored snapshot for this index (if any) will be used.
+ */
+ _installCurrentPageSnapshot:
+ function HSA__installCurrentPageSnapshot(aCanvas) {
+ let currSnapshot = aCanvas;
+ if (!currSnapshot) {
+ let snapshots = gBrowser.selectedBrowser.snapshots || {};
+ let currIndex = this._historyIndex;
+ if (currIndex in snapshots)
+ currSnapshot = this._convertToImg(snapshots[currIndex]);
+ }
+ document.mozSetImageElement("historySwipeAnimationCurrentPageSnapshot",
+ currSnapshot);
+ },
+
+ /**
+ * Sets the snapshots of the previous and next pages to the snapshots
+ * previously stored for their respective indeces.
+ */
+ _installPrevAndNextSnapshots:
+ function HSA__installPrevAndNextSnapshots() {
+ let snapshots = gBrowser.selectedBrowser.snapshots || [];
+ let currIndex = this._historyIndex;
+ let prevIndex = currIndex - 1;
+ let prevSnapshot = null;
+ if (prevIndex in snapshots)
+ prevSnapshot = this._convertToImg(snapshots[prevIndex]);
+ document.mozSetImageElement("historySwipeAnimationPreviousPageSnapshot",
+ prevSnapshot);
+
+ let nextIndex = currIndex + 1;
+ let nextSnapshot = null;
+ if (nextIndex in snapshots)
+ nextSnapshot = this._convertToImg(snapshots[nextIndex]);
+ document.mozSetImageElement("historySwipeAnimationNextPageSnapshot",
+ nextSnapshot);
+ },
+};
diff --git a/browser/base/content/browser-menubar.inc b/browser/base/content/browser-menubar.inc
new file mode 100644
index 000000000..891e9e82a
--- /dev/null
+++ b/browser/base/content/browser-menubar.inc
@@ -0,0 +1,623 @@
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+ <menubar id="main-menubar"
+ onpopupshowing="if (event.target.parentNode.parentNode == this &amp;&amp;
+ !('@mozilla.org/widget/nativemenuservice;1' in Cc))
+ this.setAttribute('openedwithkey',
+ event.target.parentNode.openedWithKey);"
+ style="border:0px;padding:0px;margin:0px;-moz-appearance:none">
+ <menu id="file-menu" label="&fileMenu.label;"
+ accesskey="&fileMenu.accesskey;">
+ <menupopup id="menu_FilePopup">
+ <menuitem id="menu_newNavigatorTab"
+ label="&tabCmd.label;"
+ command="cmd_newNavigatorTab"
+ key="key_newNavigatorTab"
+ accesskey="&tabCmd.accesskey;"/>
+ <menuitem id="menu_newNavigator"
+ label="&newNavigatorCmd.label;"
+ accesskey="&newNavigatorCmd.accesskey;"
+ key="key_newNavigator"
+ command="cmd_newNavigator"/>
+ <menuitem id="menu_newPrivateWindow"
+ label="&newPrivateWindow.label;"
+ accesskey="&newPrivateWindow.accesskey;"
+ command="Tools:PrivateBrowsing"
+ key="key_privatebrowsing"/>
+ <menuitem id="menu_openLocation"
+ class="show-only-for-keyboard"
+ label="&openLocationCmd.label;"
+ command="Browser:OpenLocation"
+ key="focusURLBar"
+ accesskey="&openLocationCmd.accesskey;"/>
+ <menuitem id="menu_openFile"
+ label="&openFileCmd.label;"
+ command="Browser:OpenFile"
+ key="openFileKb"
+ accesskey="&openFileCmd.accesskey;"/>
+ <menuitem id="menu_close"
+ class="show-only-for-keyboard"
+ label="&closeCmd.label;"
+ key="key_close"
+ accesskey="&closeCmd.accesskey;"
+ command="cmd_close"/>
+ <menuitem id="menu_closeWindow"
+ class="show-only-for-keyboard"
+ hidden="true"
+ command="cmd_closeWindow"
+ key="key_closeWindow"
+ label="&closeWindow.label;"
+ accesskey="&closeWindow.accesskey;"/>
+ <menuseparator/>
+ <menuitem id="menu_savePage"
+ label="&savePageCmd.label;"
+ accesskey="&savePageCmd.accesskey;"
+ key="key_savePage"
+ command="Browser:SavePage"/>
+ <menuitem id="menu_sendLink"
+ label="&emailPageCmd.label;"
+ accesskey="&emailPageCmd.accesskey;"
+ command="Browser:SendLink"/>
+ <menuseparator/>
+ <menuitem id="menu_printSetup"
+ label="&printSetupCmd.label;"
+ accesskey="&printSetupCmd.accesskey;"
+ command="cmd_pageSetup"/>
+#ifndef XP_MACOSX
+ <menuitem id="menu_printPreview"
+ label="&printPreviewCmd.label;"
+ accesskey="&printPreviewCmd.accesskey;"
+ command="cmd_printPreview"/>
+#endif
+ <menuitem id="menu_print"
+ label="&printCmd.label;"
+ accesskey="&printCmd.accesskey;"
+ key="printKb"
+ command="cmd_print"/>
+ <menuseparator/>
+ <menuitem id="goOfflineMenuitem"
+ label="&goOfflineCmd.label;"
+ accesskey="&goOfflineCmd.accesskey;"
+ type="checkbox"
+ observes="workOfflineMenuitemState"
+ oncommand="BrowserOffline.toggleOfflineStatus();"/>
+ <menuitem id="menu_FileQuitItem"
+#ifdef XP_WIN
+ label="&quitApplicationCmdWin.label;"
+ accesskey="&quitApplicationCmdWin.accesskey;"
+#else
+#ifdef XP_MACOSX
+ label="&quitApplicationCmdMac.label;"
+#else
+ label="&quitApplicationCmd.label;"
+ accesskey="&quitApplicationCmd.accesskey;"
+#endif
+#ifdef XP_UNIX
+ key="key_quitApplication"
+#endif
+#endif
+ command="cmd_quitApplication"/>
+ </menupopup>
+ </menu>
+
+ <menu id="edit-menu" label="&editMenu.label;"
+ accesskey="&editMenu.accesskey;">
+ <menupopup id="menu_EditPopup"
+ onpopupshowing="updateEditUIVisibility()"
+ onpopuphidden="updateEditUIVisibility()">
+ <menuitem id="menu_undo"
+ label="&undoCmd.label;"
+ key="key_undo"
+ accesskey="&undoCmd.accesskey;"
+ command="cmd_undo"/>
+ <menuitem id="menu_redo"
+ label="&redoCmd.label;"
+ key="key_redo"
+ accesskey="&redoCmd.accesskey;"
+ command="cmd_redo"/>
+ <menuseparator/>
+ <menuitem id="menu_cut"
+ label="&cutCmd.label;"
+ key="key_cut"
+ accesskey="&cutCmd.accesskey;"
+ command="cmd_cut"/>
+ <menuitem id="menu_copy"
+ label="&copyCmd.label;"
+ key="key_copy"
+ accesskey="&copyCmd.accesskey;"
+ command="cmd_copy"/>
+ <menuitem id="menu_paste"
+ label="&pasteCmd.label;"
+ key="key_paste"
+ accesskey="&pasteCmd.accesskey;"
+ command="cmd_paste"/>
+ <menuitem id="menu_delete"
+ label="&deleteCmd.label;"
+ key="key_delete"
+ accesskey="&deleteCmd.accesskey;"
+ command="cmd_delete"/>
+ <menuseparator/>
+ <menuitem id="menu_selectAll"
+ label="&selectAllCmd.label;"
+ key="key_selectAll"
+ accesskey="&selectAllCmd.accesskey;"
+ command="cmd_selectAll"/>
+ <menuseparator/>
+ <menuitem id="menu_find"
+ label="&findOnCmd.label;"
+ accesskey="&findOnCmd.accesskey;"
+ key="key_find"
+ command="cmd_find"/>
+ <menuitem id="menu_findAgain"
+ class="show-only-for-keyboard"
+ label="&findAgainCmd.label;"
+ accesskey="&findAgainCmd.accesskey;"
+ key="key_findAgain"
+ command="cmd_findAgain"/>
+ <menuseparator hidden="true" id="textfieldDirection-separator"/>
+ <menuitem id="textfieldDirection-swap"
+ command="cmd_switchTextDirection"
+ key="key_switchTextDirection"
+ label="&bidiSwitchTextDirectionItem.label;"
+ accesskey="&bidiSwitchTextDirectionItem.accesskey;"
+ hidden="true"/>
+#ifdef XP_UNIX
+#ifndef XP_MACOSX
+ <menuseparator/>
+ <menuitem id="menu_preferences"
+ label="&preferencesCmdUnix.label;"
+ accesskey="&preferencesCmdUnix.accesskey;"
+ oncommand="openPreferences();"/>
+#endif
+#endif
+ </menupopup>
+ </menu>
+
+ <menu id="view-menu" label="&viewMenu.label;"
+ accesskey="&viewMenu.accesskey;">
+ <menupopup id="menu_viewPopup"
+ onpopupshowing="updateCharacterEncodingMenuState();">
+ <menu id="viewToolbarsMenu"
+ label="&viewToolbarsMenu.label;"
+ accesskey="&viewToolbarsMenu.accesskey;">
+ <menupopup onpopupshowing="onViewToolbarsPopupShowing(event);">
+ <menuseparator/>
+ <menuitem id="menu_tabsOnTop"
+ command="cmd_ToggleTabsOnTop"
+ type="checkbox"
+ label="&viewTabsOnTop.label;"
+ accesskey="&viewTabsOnTop.accesskey;"/>
+ <menuitem id="menu_customizeToolbars"
+ label="&viewCustomizeToolbar.label;"
+ accesskey="&viewCustomizeToolbar.accesskey;"
+ command="cmd_CustomizeToolbars"/>
+ </menupopup>
+ </menu>
+ <menu id="viewSidebarMenuMenu"
+ label="&viewSidebarMenu.label;"
+ accesskey="&viewSidebarMenu.accesskey;">
+ <menupopup id="viewSidebarMenu">
+ <menuitem id="menu_bookmarksSidebar"
+ key="viewBookmarksSidebarKb"
+ observes="viewBookmarksSidebar"/>
+ <menuitem id="menu_historySidebar"
+ key="key_gotoHistory"
+ observes="viewHistorySidebar"
+ label="&historyButton.label;"/>
+ <menuitem id="menu_socialSidebar"
+ type="checkbox"
+ autocheck="false"
+ command="Social:ToggleSidebar"/>
+ </menupopup>
+ </menu>
+ <menuseparator/>
+ <menuitem id="menu_stop"
+ class="show-only-for-keyboard"
+ label="&stopCmd.label;"
+ accesskey="&stopCmd.accesskey;"
+ command="Browser:Stop"
+#ifdef XP_MACOSX
+ key="key_stop_mac"/>
+#else
+ key="key_stop"/>
+#endif
+ <menuitem id="menu_reload"
+ class="show-only-for-keyboard"
+ label="&reloadCmd.label;"
+ accesskey="&reloadCmd.accesskey;"
+ key="key_reload"
+ command="Browser:ReloadOrDuplicate"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuseparator class="show-only-for-keyboard"/>
+ <menu id="viewFullZoomMenu" label="&fullZoom.label;"
+ accesskey="&fullZoom.accesskey;"
+ onpopupshowing="FullZoom.updateMenu();">
+ <menupopup>
+ <menuitem id="menu_zoomEnlarge"
+ key="key_fullZoomEnlarge"
+ label="&fullZoomEnlargeCmd.label;"
+ accesskey="&fullZoomEnlargeCmd.accesskey;"
+ command="cmd_fullZoomEnlarge"/>
+ <menuitem id="menu_zoomReduce"
+ key="key_fullZoomReduce"
+ label="&fullZoomReduceCmd.label;"
+ accesskey="&fullZoomReduceCmd.accesskey;"
+ command="cmd_fullZoomReduce"/>
+ <menuseparator/>
+ <menuitem id="menu_zoomReset"
+ key="key_fullZoomReset"
+ label="&fullZoomResetCmd.label;"
+ accesskey="&fullZoomResetCmd.accesskey;"
+ command="cmd_fullZoomReset"/>
+ <menuseparator/>
+ <menuitem id="toggle_zoom"
+ label="&fullZoomToggleCmd.label;"
+ accesskey="&fullZoomToggleCmd.accesskey;"
+ type="checkbox"
+ command="cmd_fullZoomToggle"
+ checked="false"/>
+ </menupopup>
+ </menu>
+ <menu id="pageStyleMenu" label="&pageStyleMenu.label;"
+ accesskey="&pageStyleMenu.accesskey;" observes="isImage">
+ <menupopup onpopupshowing="gPageStyleMenu.fillPopup(this);">
+ <menuitem id="menu_pageStyleNoStyle"
+ label="&pageStyleNoStyle.label;"
+ accesskey="&pageStyleNoStyle.accesskey;"
+ oncommand="gPageStyleMenu.disableStyle();"
+ type="radio"/>
+ <menuitem id="menu_pageStylePersistentOnly"
+ label="&pageStylePersistentOnly.label;"
+ accesskey="&pageStylePersistentOnly.accesskey;"
+ oncommand="gPageStyleMenu.switchStyleSheet('');"
+ type="radio"
+ checked="true"/>
+ <menuseparator/>
+ </menupopup>
+ </menu>
+#include browser-charsetmenu.inc
+ <menuseparator/>
+#ifdef XP_MACOSX
+ <menuitem id="enterFullScreenItem"
+ accesskey="&enterFullScreenCmd.accesskey;"
+ label="&enterFullScreenCmd.label;"
+ key="key_fullScreen">
+ <observes element="View:FullScreen" attribute="oncommand"/>
+ <observes element="View:FullScreen" attribute="disabled"/>
+ </menuitem>
+ <menuitem id="exitFullScreenItem"
+ accesskey="&exitFullScreenCmd.accesskey;"
+ label="&exitFullScreenCmd.label;"
+ key="key_fullScreen"
+ hidden="true">
+ <observes element="View:FullScreen" attribute="oncommand"/>
+ <observes element="View:FullScreen" attribute="disabled"/>
+ </menuitem>
+#else
+ <menuitem id="fullScreenItem"
+ accesskey="&fullScreenCmd.accesskey;"
+ label="&fullScreenCmd.label;"
+ key="key_fullScreen"
+ type="checkbox"
+ observes="View:FullScreen"/>
+#endif
+ <menuitem id="menu_showAllTabs"
+ hidden="true"
+ accesskey="&showAllTabsCmd.accesskey;"
+ label="&showAllTabsCmd.label;"
+ command="Browser:ShowAllTabs"
+ key="key_showAllTabs"/>
+ <menuseparator hidden="true" id="documentDirection-separator"/>
+ <menuitem id="documentDirection-swap"
+ hidden="true"
+ label="&bidiSwitchPageDirectionItem.label;"
+ accesskey="&bidiSwitchPageDirectionItem.accesskey;"
+ oncommand="SwitchDocumentDirection(window.content)"/>
+ </menupopup>
+ </menu>
+
+ <menu id="history-menu"
+ label="&historyMenu.label;"
+ accesskey="&historyMenu.accesskey;">
+ <menupopup id="goPopup"
+#ifndef XP_MACOSX
+ placespopup="true"
+#endif
+ oncommand="this.parentNode._placesView._onCommand(event);"
+ onclick="checkForMiddleClick(this, event);"
+ onpopupshowing="if (!this.parentNode._placesView)
+ new HistoryMenu(event);"
+ tooltip="bhTooltip"
+ popupsinherittooltip="true">
+ <menuitem id="historyMenuBack"
+ class="show-only-for-keyboard"
+ label="&backCmd.label;"
+#ifdef XP_MACOSX
+ key="goBackKb2"
+#else
+ key="goBackKb"
+#endif
+ command="Browser:BackOrBackDuplicate"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="historyMenuForward"
+ class="show-only-for-keyboard"
+ label="&forwardCmd.label;"
+#ifdef XP_MACOSX
+ key="goForwardKb2"
+#else
+ key="goForwardKb"
+#endif
+ command="Browser:ForwardOrForwardDuplicate"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="historyMenuHome"
+ class="show-only-for-keyboard"
+ label="&historyHomeCmd.label;"
+ oncommand="BrowserGoHome(event);"
+ onclick="checkForMiddleClick(this, event);"
+ key="goHome"/>
+ <menuseparator id="historyMenuHomeSeparator"
+ class="show-only-for-keyboard"/>
+ <menuitem id="menu_showAllHistory"
+ label="&showAllHistoryCmd2.label;"
+#ifndef XP_MACOSX
+ key="showAllHistoryKb"
+#endif
+ command="Browser:ShowAllHistory"/>
+ <menuitem id="sanitizeItem"
+ label="&clearRecentHistory.label;"
+ key="key_sanitize"
+ command="Tools:Sanitize"/>
+ <menuseparator id="sanitizeSeparator"/>
+#ifdef MOZ_SERVICES_SYNC
+ <menuitem id="sync-tabs-menuitem"
+ class="syncTabsMenuItem"
+ label="&syncTabsMenu2.label;"
+ oncommand="BrowserOpenSyncTabs();"
+ disabled="true"/>
+#endif
+ <menuitem id="historyRestoreLastSession"
+ label="&historyRestoreLastSession.label;"
+ command="Browser:RestoreLastSession"/>
+ <menu id="historyUndoMenu"
+ class="recentlyClosedTabsMenu"
+ label="&historyUndoMenu.label;"
+ disabled="true">
+ <menupopup id="historyUndoPopup"
+#ifndef XP_MACOSX
+ placespopup="true"
+#endif
+ onpopupshowing="document.getElementById('history-menu')._placesView.populateUndoSubmenu();"/>
+ </menu>
+ <menu id="historyUndoWindowMenu"
+ class="recentlyClosedWindowsMenu"
+ label="&historyUndoWindowMenu.label;"
+ disabled="true">
+ <menupopup id="historyUndoWindowPopup"
+#ifndef XP_MACOSX
+ placespopup="true"
+#endif
+ onpopupshowing="document.getElementById('history-menu')._placesView.populateUndoWindowSubmenu();"/>
+ </menu>
+ <menuseparator id="startHistorySeparator"
+ class="hide-if-empty-places-result"/>
+ </menupopup>
+ </menu>
+
+ <menu id="bookmarksMenu"
+ label="&bookmarksMenu.label;"
+ accesskey="&bookmarksMenu.accesskey;"
+ ondragenter="PlacesMenuDNDHandler.onDragEnter(event);"
+ ondragover="PlacesMenuDNDHandler.onDragOver(event);"
+ ondrop="PlacesMenuDNDHandler.onDrop(event);">
+ <menupopup id="bookmarksMenuPopup"
+#ifndef XP_MACOSX
+ placespopup="true"
+#endif
+ context="placesContext"
+ openInTabs="children"
+ oncommand="BookmarksEventHandler.onCommand(event, this.parentNode._placesView);"
+ onclick="BookmarksEventHandler.onClick(event, this.parentNode._placesView);"
+ onpopupshowing="PlacesCommandHook.updateBookmarkAllTabsCommand();
+ if (!this.parentNode._placesView)
+ new PlacesMenu(event, 'place:folder=BOOKMARKS_MENU');"
+ tooltip="bhTooltip" popupsinherittooltip="true">
+ <menuitem id="bookmarksShowAll"
+ label="&palemoon.menu.allBookmarks.label;"
+ command="Browser:ShowAllBookmarks"
+ key="manBookmarkKb"/>
+ <menuseparator id="organizeBookmarksSeparator"/>
+ <menuitem id="menu_bookmarkThisPage"
+ label="&bookmarkThisPageCmd.label;"
+ command="Browser:AddBookmarkAs"
+ key="addBookmarkAsKb"/>
+ <menuitem id="subscribeToPageMenuitem"
+#ifndef XP_MACOSX
+ class="menuitem-iconic"
+#endif
+ label="&subscribeToPageMenuitem.label;"
+ oncommand="return FeedHandler.subscribeToFeed(null, event);"
+ onclick="checkForMiddleClick(this, event);"
+ observes="singleFeedMenuitemState"/>
+ <menu id="subscribeToPageMenupopup"
+#ifndef XP_MACOSX
+ class="menu-iconic"
+#endif
+ label="&subscribeToPageMenupopup.label;"
+ observes="multipleFeedsMenuState">
+ <menupopup id="subscribeToPageSubmenuMenupopup"
+ onpopupshowing="return FeedHandler.buildFeedList(event.target);"
+ oncommand="return FeedHandler.subscribeToFeed(null, event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ </menu>
+ <menuitem id="menu_bookmarkAllTabs"
+ label="&addCurPagesCmd.label;"
+ class="show-only-for-keyboard"
+ command="Browser:BookmarkAllTabs"
+ key="bookmarkAllTabsKb"/>
+ <menuseparator id="bookmarksToolbarSeparator"/>
+ <menu id="bookmarksToolbarFolderMenu"
+ class="menu-iconic bookmark-item"
+ label="&personalbarCmd.label;"
+ container="true">
+ <menupopup id="bookmarksToolbarFolderPopup"
+#ifndef XP_MACOSX
+ placespopup="true"
+#endif
+ context="placesContext"
+ onpopupshowing="if (!this.parentNode._placesView)
+ new PlacesMenu(event, 'place:folder=TOOLBAR');"/>
+ </menu>
+ <menuseparator id="bookmarksMenuItemsSeparator"/>
+ <!-- Bookmarks menu items -->
+ <menuseparator builder="end"
+ class="hide-if-empty-places-result"/>
+ <menuitem id="menu_unsortedBookmarks"
+ label="&unsortedBookmarksCmd.label;"
+ oncommand="PlacesCommandHook.showPlacesOrganizer('UnfiledBookmarks');"/>
+ </menupopup>
+ </menu>
+
+ <menu id="tools-menu"
+ label="&toolsMenu.label;"
+ accesskey="&toolsMenu.accesskey;">
+ <menupopup id="menu_ToolsPopup"
+#ifdef MOZ_SERVICES_SYNC
+ onpopupshowing="gSyncUI.updateUI();"
+#endif
+ >
+ <menuitem id="menu_search"
+ class="show-only-for-keyboard"
+ label="&search.label;"
+ accesskey="&search.accesskey;"
+ key="key_search"
+ command="Tools:Search"/>
+ <menuseparator id="browserToolsSeparator"
+ class="show-only-for-keyboard"/>
+ <menuitem id="menu_openDownloads"
+ label="&downloads.label;"
+ accesskey="&downloads.accesskey;"
+ key="key_openDownloads"
+ command="Tools:Downloads"/>
+ <menuitem id="menu_openAddons"
+ label="&addons.label;"
+ accesskey="&addons.accesskey;"
+ key="key_openAddons"
+ command="Tools:Addons"/>
+ <menu id="menu_socialAmbientMenu"
+ observes="socialActiveBroadcaster">
+ <menupopup id="menu_social-statusarea-popup">
+ <menuitem class="social-statusarea-user menuitem-iconic" pack="start" align="center"
+ observes="socialBroadcaster_userDetails"
+ oncommand="SocialUI.showProfile(); document.getElementById('social-statusarea-popup').hidePopup();">
+ <image class="social-statusarea-user-portrait"
+ observes="socialBroadcaster_userDetails"/>
+ <vbox>
+ <label class="social-statusarea-loggedInStatus"
+ observes="socialBroadcaster_userDetails"/>
+ </vbox>
+ </menuitem>
+#ifndef XP_WIN
+ <menuseparator class="social-statusarea-separator"/>
+#endif
+ <menuseparator id="socialAmbientMenuSeparator"
+ hidden="true"/>
+ <menuitem class="social-toggle-sidebar-menuitem"
+ type="checkbox"
+ autocheck="false"
+ command="Social:ToggleSidebar"
+ label="&social.toggleSidebar.label;"
+ accesskey="&social.toggleSidebar.accesskey;"/>
+ <menuitem class="social-toggle-notifications-menuitem"
+ type="checkbox"
+ autocheck="false"
+ command="Social:ToggleNotifications"
+ label="&social.toggleNotifications.label;"
+ accesskey="&social.toggleNotifications.accesskey;"/>
+ <menuitem id="menu_focusChatBar"
+ label="&social.chatBar.label;"
+ accesskey="&social.chatBar.accesskey;"
+ key="focusChatBar"
+ command="Social:FocusChat"
+ class="show-only-for-keyboard"/>
+ <menuitem class="social-toggle-menuitem" command="Social:Toggle"/>
+ <menuseparator class="social-statusarea-separator"/>
+ <menuseparator class="social-provider-menu" hidden="true"/>
+ <menuitem class="social-addons-menuitem" command="Social:Addons"
+ label="&social.addons.label;"/>
+ </menupopup>
+ </menu>
+#ifdef MOZ_SERVICES_SYNC
+ <!-- only one of sync-setup or sync-menu will be showing at once -->
+ <menuitem id="sync-setup"
+ label="&syncSetup.label;"
+ accesskey="&syncSetup.accesskey;"
+ observes="sync-setup-state"
+ oncommand="gSyncUI.openSetup()"/>
+ <menuitem id="sync-syncnowitem"
+ label="&syncSyncNowItem.label;"
+ accesskey="&syncSyncNowItem.accesskey;"
+ observes="sync-syncnow-state"
+ oncommand="gSyncUI.doSync(event);"/>
+#endif
+ <menuseparator id="devToolsSeparator"/>
+ <menu id="webDeveloperMenu"
+ label="&webDeveloperMenu.label;"
+ accesskey="&webDeveloperMenu.accesskey;">
+ <menupopup id="menuWebDeveloperPopup">
+ <menuitem id="menu_devToolbox"
+ observes="devtoolsMenuBroadcaster_DevToolbox"
+ accesskey="&devToolboxMenuItem.accesskey;"/>
+ <menuseparator id="menu_devtools_separator"/>
+ <menuitem id="menu_devToolbar"
+ observes="devtoolsMenuBroadcaster_DevToolbar"
+ accesskey="&devToolbarMenu.accesskey;"/>
+ <menuitem id="menu_chromeDebugger"
+ observes="devtoolsMenuBroadcaster_ChromeDebugger"/>
+ <menuitem id="menu_browserConsole"
+ observes="devtoolsMenuBroadcaster_BrowserConsole"
+ accesskey="&browserConsoleCmd.accesskey;"/>
+ <menuitem id="menu_responsiveUI"
+ observes="devtoolsMenuBroadcaster_ResponsiveUI"
+ accesskey="&responsiveDesignTool.accesskey;"/>
+ <menuitem id="menu_scratchpad"
+ observes="devtoolsMenuBroadcaster_Scratchpad"
+ accesskey="&scratchpad.accesskey;"/>
+ <menuitem id="menu_pageSource"
+ observes="devtoolsMenuBroadcaster_PageSource"
+ accesskey="&pageSourceCmd.accesskey;"/>
+ <menuitem id="javascriptConsole"
+ observes="devtoolsMenuBroadcaster_ErrorConsole"
+ accesskey="&errorConsoleCmd.accesskey;"/>
+ <menuitem id="menu_devtools_connect"
+ observes="devtoolsMenuBroadcaster_connect"/>
+ <menuseparator id="devToolsEndSeparator"/>
+ <menuitem id="getMoreDevtools"
+ observes="devtoolsMenuBroadcaster_GetMoreTools"
+ accesskey="&getMoreDevtoolsCmd.accesskey;"/>
+ </menupopup>
+ </menu>
+ <menuitem id="menu_pageInfo"
+ accesskey="&pageInfoCmd.accesskey;"
+ label="&pageInfoCmd.label;"
+#ifndef XP_WIN
+ key="key_viewInfo"
+#endif
+ command="View:PageInfo"/>
+#ifndef XP_UNIX
+ <menuseparator id="prefSep"/>
+ <menuitem id="menu_preferences"
+ label="&preferencesCmd2.label;"
+ accesskey="&preferencesCmd2.accesskey;"
+ oncommand="openPreferences();"/>
+#endif
+ </menupopup>
+ </menu>
+
+#ifdef XP_MACOSX
+ <menu id="windowMenu" />
+#endif
+ <menu id="helpMenu" />
+ </menubar>
diff --git a/browser/base/content/browser-places.js b/browser/base/content/browser-places.js
new file mode 100644
index 000000000..d475000e9
--- /dev/null
+++ b/browser/base/content/browser-places.js
@@ -0,0 +1,1286 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+////////////////////////////////////////////////////////////////////////////////
+//// StarUI
+
+var StarUI = {
+ _itemId: -1,
+ uri: null,
+ _batching: false,
+
+ _element: function(aID) {
+ return document.getElementById(aID);
+ },
+
+ // Edit-bookmark panel
+ get panel() {
+ delete this.panel;
+ var element = this._element("editBookmarkPanel");
+ // initially the panel is hidden
+ // to avoid impacting startup / new window performance
+ element.hidden = false;
+ element.addEventListener("popuphidden", this, false);
+ element.addEventListener("keypress", this, false);
+ return this.panel = element;
+ },
+
+ // Array of command elements to disable when the panel is opened.
+ get _blockedCommands() {
+ delete this._blockedCommands;
+ return this._blockedCommands =
+ ["cmd_close", "cmd_closeWindow"].map(function (id) this._element(id), this);
+ },
+
+ _blockCommands: function SU__blockCommands() {
+ this._blockedCommands.forEach(function (elt) {
+ // make sure not to permanently disable this item (see bug 409155)
+ if (elt.hasAttribute("wasDisabled"))
+ return;
+ if (elt.getAttribute("disabled") == "true") {
+ elt.setAttribute("wasDisabled", "true");
+ } else {
+ elt.setAttribute("wasDisabled", "false");
+ elt.setAttribute("disabled", "true");
+ }
+ });
+ },
+
+ _restoreCommandsState: function SU__restoreCommandsState() {
+ this._blockedCommands.forEach(function (elt) {
+ if (elt.getAttribute("wasDisabled") != "true")
+ elt.removeAttribute("disabled");
+ elt.removeAttribute("wasDisabled");
+ });
+ },
+
+ // nsIDOMEventListener
+ handleEvent: function SU_handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "popuphidden":
+ if (aEvent.originalTarget == this.panel) {
+ if (!this._element("editBookmarkPanelContent").hidden)
+ this.quitEditMode();
+
+ this._restoreCommandsState();
+ this._itemId = -1;
+ if (this._batching) {
+ PlacesUtils.transactionManager.endBatch(false);
+ this._batching = false;
+ }
+
+ switch (this._actionOnHide) {
+ case "cancel": {
+ PlacesUtils.transactionManager.undoTransaction();
+ break;
+ }
+ case "remove": {
+ // Remove all bookmarks for the bookmark's url, this also removes
+ // the tags for the url.
+ PlacesUtils.transactionManager.beginBatch(null);
+ let itemIds = PlacesUtils.getBookmarksForURI(this._uriForRemoval);
+ for (let i = 0; i < itemIds.length; i++) {
+ let txn = new PlacesRemoveItemTransaction(itemIds[i]);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ }
+ PlacesUtils.transactionManager.endBatch(false);
+ break;
+ }
+ }
+ this._actionOnHide = "";
+ }
+ break;
+ case "keypress":
+ if (aEvent.defaultPrevented) {
+ // The event has already been consumed inside of the panel.
+ break;
+ }
+ switch (aEvent.keyCode) {
+ case KeyEvent.DOM_VK_ESCAPE:
+ if (!this._element("editBookmarkPanelContent").hidden)
+ this.cancelButtonOnCommand();
+ break;
+ case KeyEvent.DOM_VK_RETURN:
+ if (aEvent.target.className == "expander-up" ||
+ aEvent.target.className == "expander-down" ||
+ aEvent.target.id == "editBMPanel_newFolderButton") {
+ //XXX Why is this necessary? The defaultPrevented check should
+ // be enough.
+ break;
+ }
+ this.panel.hidePopup();
+ break;
+ }
+ break;
+ }
+ },
+
+ _overlayLoaded: false,
+ _overlayLoading: false,
+ showEditBookmarkPopup:
+ function SU_showEditBookmarkPopup(aItemId, aAnchorElement, aPosition) {
+ // Performance: load the overlay the first time the panel is opened
+ // (see bug 392443).
+ if (this._overlayLoading)
+ return;
+
+ if (this._overlayLoaded) {
+ this._doShowEditBookmarkPanel(aItemId, aAnchorElement, aPosition);
+ return;
+ }
+
+ this._overlayLoading = true;
+ document.loadOverlay(
+ "chrome://browser/content/places/editBookmarkOverlay.xul",
+ (function (aSubject, aTopic, aData) {
+ //XXX We just caused localstore.rdf to be re-applied (bug 640158)
+ retrieveToolbarIconsizesFromTheme();
+
+ // Move the header (star, title, button) into the grid,
+ // so that it aligns nicely with the other items (bug 484022).
+ let header = this._element("editBookmarkPanelHeader");
+ let rows = this._element("editBookmarkPanelGrid").lastChild;
+ rows.insertBefore(header, rows.firstChild);
+ header.hidden = false;
+
+ this._overlayLoading = false;
+ this._overlayLoaded = true;
+ this._doShowEditBookmarkPanel(aItemId, aAnchorElement, aPosition);
+ }).bind(this)
+ );
+ },
+
+ _doShowEditBookmarkPanel:
+ function SU__doShowEditBookmarkPanel(aItemId, aAnchorElement, aPosition) {
+ if (this.panel.state != "closed")
+ return;
+
+ this._blockCommands(); // un-done in the popuphiding handler
+
+ // Set panel title:
+ // if we are batching, i.e. the bookmark has been added now,
+ // then show Page Bookmarked, else if the bookmark did already exist,
+ // we are about editing it, then use Edit This Bookmark.
+ this._element("editBookmarkPanelTitle").value =
+ this._batching ?
+ gNavigatorBundle.getString("editBookmarkPanel.pageBookmarkedTitle") :
+ gNavigatorBundle.getString("editBookmarkPanel.editBookmarkTitle");
+
+ // No description; show the Done, Cancel;
+ this._element("editBookmarkPanelDescription").textContent = "";
+ this._element("editBookmarkPanelBottomButtons").hidden = false;
+ this._element("editBookmarkPanelContent").hidden = false;
+
+ // The remove button is shown only if we're not already batching, i.e.
+ // if the cancel button/ESC does not remove the bookmark.
+ this._element("editBookmarkPanelRemoveButton").hidden = this._batching;
+
+ // The label of the remove button differs if the URI is bookmarked
+ // multiple times.
+ var bookmarks = PlacesUtils.getBookmarksForURI(gBrowser.currentURI);
+ var forms = gNavigatorBundle.getString("editBookmark.removeBookmarks.label");
+ var label = PluralForm.get(bookmarks.length, forms).replace("#1", bookmarks.length);
+ this._element("editBookmarkPanelRemoveButton").label = label;
+
+ // unset the unstarred state, if set
+ this._element("editBookmarkPanelStarIcon").removeAttribute("unstarred");
+
+ this._itemId = aItemId !== undefined ? aItemId : this._itemId;
+ this.beginBatch();
+
+ this.panel.openPopup(aAnchorElement, aPosition);
+
+ gEditItemOverlay.initPanel(this._itemId,
+ { hiddenRows: ["description", "location",
+ "loadInSidebar", "keyword"] });
+ },
+
+ panelShown:
+ function SU_panelShown(aEvent) {
+ if (aEvent.target == this.panel) {
+ if (!this._element("editBookmarkPanelContent").hidden) {
+ let fieldToFocus = "editBMPanel_" +
+ gPrefService.getCharPref("browser.bookmarks.editDialog.firstEditField");
+ var elt = this._element(fieldToFocus);
+ elt.focus();
+ elt.select();
+ }
+ else {
+ // Note this isn't actually used anymore, we should remove this
+ // once we decide not to bring back the page bookmarked notification
+ this.panel.focus();
+ }
+ }
+ },
+
+ quitEditMode: function SU_quitEditMode() {
+ this._element("editBookmarkPanelContent").hidden = true;
+ this._element("editBookmarkPanelBottomButtons").hidden = true;
+ gEditItemOverlay.uninitPanel(true);
+ },
+
+ cancelButtonOnCommand: function SU_cancelButtonOnCommand() {
+ this._actionOnHide = "cancel";
+ this.panel.hidePopup();
+ },
+
+ removeBookmarkButtonCommand: function SU_removeBookmarkButtonCommand() {
+ this._uriForRemoval = PlacesUtils.bookmarks.getBookmarkURI(this._itemId);
+ this._actionOnHide = "remove";
+ this.panel.hidePopup();
+ },
+
+ beginBatch: function SU_beginBatch() {
+ if (!this._batching) {
+ PlacesUtils.transactionManager.beginBatch(null);
+ this._batching = true;
+ }
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// PlacesCommandHook
+
+var PlacesCommandHook = {
+ /**
+ * Adds a bookmark to the page loaded in the given browser.
+ *
+ * @param aBrowser
+ * a <browser> element.
+ * @param [optional] aParent
+ * The folder in which to create a new bookmark if the page loaded in
+ * aBrowser isn't bookmarked yet, defaults to the unfiled root.
+ * @param [optional] aShowEditUI
+ * whether or not to show the edit-bookmark UI for the bookmark item
+ */
+ bookmarkPage: function PCH_bookmarkPage(aBrowser, aParent, aShowEditUI) {
+ var uri = aBrowser.currentURI;
+ var itemId = PlacesUtils.getMostRecentBookmarkForURI(uri);
+ if (itemId == -1) {
+ // Copied over from addBookmarkForBrowser:
+ // Bug 52536: We obtain the URL and title from the nsIWebNavigation
+ // associated with a <browser/> rather than from a DOMWindow.
+ // This is because when a full page plugin is loaded, there is
+ // no DOMWindow (?) but information about the loaded document
+ // may still be obtained from the webNavigation.
+ var webNav = aBrowser.webNavigation;
+ var url = webNav.currentURI;
+ var title;
+ var description;
+ var charset;
+ try {
+ let isErrorPage = /^about:(neterror|certerror|blocked)/
+ .test(webNav.document.documentURI);
+ title = isErrorPage ? PlacesUtils.history.getPageTitle(url)
+ : webNav.document.title;
+ title = title || url.spec;
+ description = PlacesUIUtils.getDescriptionFromDocument(webNav.document);
+ charset = webNav.document.characterSet;
+ }
+ catch (e) { }
+
+ if (aShowEditUI) {
+ // If we bookmark the page here (i.e. page was not "starred" already)
+ // but open right into the "edit" state, start batching here, so
+ // "Cancel" in that state removes the bookmark.
+ StarUI.beginBatch();
+ }
+
+ var parent = aParent != undefined ?
+ aParent : PlacesUtils.unfiledBookmarksFolderId;
+ var descAnno = { name: PlacesUIUtils.DESCRIPTION_ANNO, value: description };
+ var txn = new PlacesCreateBookmarkTransaction(uri, parent,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ title, null, [descAnno]);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ itemId = txn.item.id;
+ // Set the character-set
+ if (charset && !PrivateBrowsingUtils.isWindowPrivate(aBrowser.contentWindow))
+ PlacesUtils.setCharsetForURI(uri, charset);
+ }
+
+ // Revert the contents of the location bar
+ if (gURLBar)
+ gURLBar.handleRevert();
+
+ // If it was not requested to open directly in "edit" mode, we are done.
+ if (!aShowEditUI)
+ return;
+
+ // Try to dock the panel to:
+ // 1. the bookmarks menu button
+ // 2. the page-proxy-favicon
+ // 3. the content area
+ if (BookmarkingUI.anchor) {
+ StarUI.showEditBookmarkPopup(itemId, BookmarkingUI.anchor,
+ "bottomcenter topright");
+ return;
+ }
+
+ let pageProxyFavicon = document.getElementById("page-proxy-favicon");
+ if (isElementVisible(pageProxyFavicon)) {
+ StarUI.showEditBookmarkPopup(itemId, pageProxyFavicon,
+ "bottomcenter topright");
+ } else {
+ StarUI.showEditBookmarkPopup(itemId, aBrowser, "overlap");
+ }
+ },
+
+ /**
+ * Adds a bookmark to the page loaded in the current tab.
+ */
+ bookmarkCurrentPage: function PCH_bookmarkCurrentPage(aShowEditUI, aParent) {
+ this.bookmarkPage(gBrowser.selectedBrowser, aParent, aShowEditUI);
+ },
+
+ /**
+ * Adds a bookmark to the page targeted by a link.
+ * @param aParent
+ * The folder in which to create a new bookmark if aURL isn't
+ * bookmarked.
+ * @param aURL (string)
+ * the address of the link target
+ * @param aTitle
+ * The link text
+ */
+ bookmarkLink: function PCH_bookmarkLink(aParent, aURL, aTitle) {
+ var linkURI = makeURI(aURL);
+ var itemId = PlacesUtils.getMostRecentBookmarkForURI(linkURI);
+ if (itemId == -1) {
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "bookmark"
+ , uri: linkURI
+ , title: aTitle
+ , hiddenRows: [ "description"
+ , "location"
+ , "loadInSidebar"
+ , "keyword" ]
+ }, window);
+ }
+ else {
+ PlacesUIUtils.showBookmarkDialog({ action: "edit"
+ , type: "bookmark"
+ , itemId: itemId
+ }, window);
+ }
+ },
+
+ /**
+ * List of nsIURI objects characterizing the tabs currently open in the
+ * browser, modulo pinned tabs. The URIs will be in the order in which their
+ * corresponding tabs appeared and duplicates are discarded.
+ */
+ get uniqueCurrentPages() {
+ let uniquePages = {};
+ let URIs = [];
+ gBrowser.visibleTabs.forEach(function (tab) {
+ let spec = tab.linkedBrowser.currentURI.spec;
+ if (!tab.pinned && !(spec in uniquePages)) {
+ uniquePages[spec] = null;
+ URIs.push(tab.linkedBrowser.currentURI);
+ }
+ });
+ return URIs;
+ },
+
+ /**
+ * Adds a folder with bookmarks to all of the currently open tabs in this
+ * window.
+ */
+ bookmarkCurrentPages: function PCH_bookmarkCurrentPages() {
+ let pages = this.uniqueCurrentPages;
+ if (pages.length > 1) {
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "folder"
+ , URIList: pages
+ , hiddenRows: [ "description" ]
+ }, window);
+ }
+ },
+
+ /**
+ * Updates disabled state for the "Bookmark All Tabs" command.
+ */
+ updateBookmarkAllTabsCommand:
+ function PCH_updateBookmarkAllTabsCommand() {
+ // There's nothing to do in non-browser windows.
+ if (window.location.href != getBrowserURL())
+ return;
+
+ // Disable "Bookmark All Tabs" if there are less than two
+ // "unique current pages".
+ goSetCommandEnabled("Browser:BookmarkAllTabs",
+ this.uniqueCurrentPages.length >= 2);
+ },
+
+ /**
+ * Adds a Live Bookmark to a feed associated with the current page.
+ * @param url
+ * The nsIURI of the page the feed was attached to
+ * @title title
+ * The title of the feed. Optional.
+ * @subtitle subtitle
+ * A short description of the feed. Optional.
+ */
+ addLiveBookmark: function PCH_addLiveBookmark(url, feedTitle, feedSubtitle) {
+ var feedURI = makeURI(url);
+
+ var doc = gBrowser.contentDocument;
+ var title = (arguments.length > 1) ? feedTitle : doc.title;
+
+ var description;
+ if (arguments.length > 2)
+ description = feedSubtitle;
+ else
+ description = PlacesUIUtils.getDescriptionFromDocument(doc);
+
+ var toolbarIP = new InsertionPoint(PlacesUtils.toolbarFolderId, -1);
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "livemark"
+ , feedURI: feedURI
+ , siteURI: gBrowser.currentURI
+ , title: title
+ , description: description
+ , defaultInsertionPoint: toolbarIP
+ , hiddenRows: [ "feedLocation"
+ , "siteLocation"
+ , "description" ]
+ }, window);
+ },
+
+ /**
+ * Opens the Places Organizer.
+ * @param aLeftPaneRoot
+ * The query to select in the organizer window - options
+ * are: History, AllBookmarks, BookmarksMenu, BookmarksToolbar,
+ * UnfiledBookmarks, Tags and Downloads.
+ */
+ showPlacesOrganizer: function PCH_showPlacesOrganizer(aLeftPaneRoot) {
+ var organizer = Services.wm.getMostRecentWindow("Places:Organizer");
+ if (!organizer) {
+ // No currently open places window, so open one with the specified mode.
+ openDialog("chrome://browser/content/places/places.xul",
+ "", "chrome,toolbar=yes,dialog=no,resizable", aLeftPaneRoot);
+ }
+ else {
+ organizer.PlacesOrganizer.selectLeftPaneQuery(aLeftPaneRoot);
+ organizer.focus();
+ }
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// HistoryMenu
+
+// View for the history menu.
+function HistoryMenu(aPopupShowingEvent) {
+ // Workaround for Bug 610187. The sidebar does not include all the Places
+ // views definitions, and we don't need them there.
+ // Defining the prototype inheritance in the prototype itself would cause
+ // browser.js to halt on "PlacesMenu is not defined" error.
+ this.__proto__.__proto__ = PlacesMenu.prototype;
+ XPCOMUtils.defineLazyServiceGetter(this, "_ss",
+ "@mozilla.org/browser/sessionstore;1",
+ "nsISessionStore");
+ PlacesMenu.call(this, aPopupShowingEvent,
+ "place:sort=4&maxResults=15");
+}
+
+HistoryMenu.prototype = {
+ toggleRecentlyClosedTabs: function HM_toggleRecentlyClosedTabs() {
+ // enable/disable the Recently Closed Tabs sub menu
+ var undoMenu = this._rootElt.getElementsByClassName("recentlyClosedTabsMenu")[0];
+
+ // no restorable tabs, so disable menu
+ if (this._ss.getClosedTabCount(window) == 0)
+ undoMenu.setAttribute("disabled", true);
+ else
+ undoMenu.removeAttribute("disabled");
+ },
+
+ /**
+ * Re-open a closed tab and put it to the end of the tab strip.
+ * Used for a middle click.
+ * @param aEvent
+ * The event when the user clicks the menu item
+ */
+ _undoCloseMiddleClick: function PHM__undoCloseMiddleClick(aEvent) {
+ if (aEvent.button != 1)
+ return;
+
+ undoCloseTab(aEvent.originalTarget.value);
+ gBrowser.moveTabToEnd();
+ },
+
+ /**
+ * Populate when the history menu is opened
+ */
+ populateUndoSubmenu: function PHM_populateUndoSubmenu() {
+ var undoMenu = this._rootElt.getElementsByClassName("recentlyClosedTabsMenu")[0];
+ var undoPopup = undoMenu.firstChild;
+
+ // remove existing menu items
+ while (undoPopup.hasChildNodes())
+ undoPopup.removeChild(undoPopup.firstChild);
+
+ // no restorable tabs, so make sure menu is disabled, and return
+ if (this._ss.getClosedTabCount(window) == 0) {
+ undoMenu.setAttribute("disabled", true);
+ return;
+ }
+
+ // enable menu
+ undoMenu.removeAttribute("disabled");
+
+ // populate menu
+ var undoItems = JSON.parse(this._ss.getClosedTabData(window));
+ for (var i = 0; i < undoItems.length; i++) {
+ var m = document.createElement("menuitem");
+ m.setAttribute("label", undoItems[i].title);
+ if (undoItems[i].image) {
+ let iconURL = undoItems[i].image;
+ // don't initiate a connection just to fetch a favicon (see bug 467828)
+ if (/^https?:/.test(iconURL))
+ iconURL = "moz-anno:favicon:" + iconURL;
+ m.setAttribute("image", iconURL);
+ }
+ m.setAttribute("class", "menuitem-iconic bookmark-item menuitem-with-favicon");
+ m.setAttribute("value", i);
+ m.setAttribute("oncommand", "undoCloseTab(" + i + ");");
+
+ // Set the targetURI attribute so it will be shown in tooltip and trigger
+ // onLinkHovered. SessionStore uses one-based indexes, so we need to
+ // normalize them.
+ let tabData = undoItems[i].state;
+ let activeIndex = (tabData.index || tabData.entries.length) - 1;
+ if (activeIndex >= 0 && tabData.entries[activeIndex])
+ m.setAttribute("targetURI", tabData.entries[activeIndex].url);
+
+ m.addEventListener("click", this._undoCloseMiddleClick, false);
+ if (i == 0)
+ m.setAttribute("key", "key_undoCloseTab");
+ undoPopup.appendChild(m);
+ }
+
+ // "Restore All Tabs"
+ var strings = gNavigatorBundle;
+ undoPopup.appendChild(document.createElement("menuseparator"));
+ m = undoPopup.appendChild(document.createElement("menuitem"));
+ m.id = "menu_restoreAllTabs";
+ m.setAttribute("label", strings.getString("menuRestoreAllTabs.label"));
+ m.addEventListener("command", function() {
+ for (var i = 0; i < undoItems.length; i++)
+ undoCloseTab();
+ }, false);
+ },
+
+ toggleRecentlyClosedWindows: function PHM_toggleRecentlyClosedWindows() {
+ // enable/disable the Recently Closed Windows sub menu
+ var undoMenu = this._rootElt.getElementsByClassName("recentlyClosedWindowsMenu")[0];
+
+ // no restorable windows, so disable menu
+ if (this._ss.getClosedWindowCount() == 0)
+ undoMenu.setAttribute("disabled", true);
+ else
+ undoMenu.removeAttribute("disabled");
+ },
+
+ /**
+ * Populate when the history menu is opened
+ */
+ populateUndoWindowSubmenu: function PHM_populateUndoWindowSubmenu() {
+ let undoMenu = this._rootElt.getElementsByClassName("recentlyClosedWindowsMenu")[0];
+ let undoPopup = undoMenu.firstChild;
+ let menuLabelString = gNavigatorBundle.getString("menuUndoCloseWindowLabel");
+ let menuLabelStringSingleTab =
+ gNavigatorBundle.getString("menuUndoCloseWindowSingleTabLabel");
+
+ // remove existing menu items
+ while (undoPopup.hasChildNodes())
+ undoPopup.removeChild(undoPopup.firstChild);
+
+ // no restorable windows, so make sure menu is disabled, and return
+ if (this._ss.getClosedWindowCount() == 0) {
+ undoMenu.setAttribute("disabled", true);
+ return;
+ }
+
+ // enable menu
+ undoMenu.removeAttribute("disabled");
+
+ // populate menu
+ let undoItems = JSON.parse(this._ss.getClosedWindowData());
+ for (let i = 0; i < undoItems.length; i++) {
+ let undoItem = undoItems[i];
+ let otherTabsCount = undoItem.tabs.length - 1;
+ let label = (otherTabsCount == 0) ? menuLabelStringSingleTab
+ : PluralForm.get(otherTabsCount, menuLabelString);
+ let menuLabel = label.replace("#1", undoItem.title)
+ .replace("#2", otherTabsCount);
+ let m = document.createElement("menuitem");
+ m.setAttribute("label", menuLabel);
+ let selectedTab = undoItem.tabs[undoItem.selected - 1];
+ if (selectedTab.image) {
+ let iconURL = selectedTab.image;
+ // don't initiate a connection just to fetch a favicon (see bug 467828)
+ if (/^https?:/.test(iconURL))
+ iconURL = "moz-anno:favicon:" + iconURL;
+ m.setAttribute("image", iconURL);
+ }
+ m.setAttribute("class", "menuitem-iconic bookmark-item menuitem-with-favicon");
+ m.setAttribute("oncommand", "undoCloseWindow(" + i + ");");
+
+ // Set the targetURI attribute so it will be shown in tooltip.
+ // SessionStore uses one-based indexes, so we need to normalize them.
+ let activeIndex = (selectedTab.index || selectedTab.entries.length) - 1;
+ if (activeIndex >= 0 && selectedTab.entries[activeIndex])
+ m.setAttribute("targetURI", selectedTab.entries[activeIndex].url);
+
+ if (i == 0)
+ m.setAttribute("key", "key_undoCloseWindow");
+ undoPopup.appendChild(m);
+ }
+
+ // "Open All in Windows"
+ undoPopup.appendChild(document.createElement("menuseparator"));
+ let m = undoPopup.appendChild(document.createElement("menuitem"));
+ m.id = "menu_restoreAllWindows";
+ m.setAttribute("label", gNavigatorBundle.getString("menuRestoreAllWindows.label"));
+ m.setAttribute("oncommand",
+ "for (var i = 0; i < " + undoItems.length + "; i++) undoCloseWindow();");
+ },
+
+ toggleTabsFromOtherComputers: function PHM_toggleTabsFromOtherComputers() {
+ // This is a no-op if MOZ_SERVICES_SYNC isn't defined
+#ifdef MOZ_SERVICES_SYNC
+ // Enable/disable the Tabs From Other Computers menu. Some of the menus handled
+ // by HistoryMenu do not have this menuitem.
+ let menuitem = this._rootElt.getElementsByClassName("syncTabsMenuItem")[0];
+ if (!menuitem)
+ return;
+
+ // If Sync isn't configured yet, then don't show the menuitem.
+ if (Weave.Status.checkSetup() == Weave.CLIENT_NOT_CONFIGURED ||
+ Weave.Svc.Prefs.get("firstSync", "") == "notReady") {
+ menuitem.setAttribute("hidden", true);
+ return;
+ }
+
+ // The tabs engine might never be inited (if services.sync.registerEngines
+ // is modified), so make sure we avoid undefined errors.
+ let enabled = Weave.Service.isLoggedIn &&
+ Weave.Service.engineManager.get("tabs") &&
+ Weave.Service.engineManager.get("tabs").enabled;
+ menuitem.setAttribute("disabled", !enabled);
+ menuitem.setAttribute("hidden", false);
+#endif
+ },
+
+ _onPopupShowing: function HM__onPopupShowing(aEvent) {
+ PlacesMenu.prototype._onPopupShowing.apply(this, arguments);
+
+ // Don't handle events for submenus.
+ if (aEvent.target != aEvent.currentTarget)
+ return;
+
+ this.toggleRecentlyClosedTabs();
+ this.toggleRecentlyClosedWindows();
+ this.toggleTabsFromOtherComputers();
+ },
+
+ _onCommand: function HM__onCommand(aEvent) {
+ let placesNode = aEvent.target._placesNode;
+ if (placesNode) {
+ if (!PrivateBrowsingUtils.isWindowPrivate(window))
+ PlacesUIUtils.markPageAsTyped(placesNode.uri);
+ openUILink(placesNode.uri, aEvent, { ignoreAlt: true });
+ }
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// BookmarksEventHandler
+
+/**
+ * Functions for handling events in the Bookmarks Toolbar and menu.
+ */
+var BookmarksEventHandler = {
+ /**
+ * Handler for click event for an item in the bookmarks toolbar or menu.
+ * Menus and submenus from the folder buttons bubble up to this handler.
+ * Left-click is handled in the onCommand function.
+ * When items are middle-clicked (or clicked with modifier), open in tabs.
+ * If the click came through a menu, close the menu.
+ * @param aEvent
+ * DOMEvent for the click
+ * @param aView
+ * The places view which aEvent should be associated with.
+ */
+ onClick: function BEH_onClick(aEvent, aView) {
+ // Only handle middle-click or left-click with modifiers.
+#ifdef XP_MACOSX
+ var modifKey = aEvent.metaKey || aEvent.shiftKey;
+#else
+ var modifKey = aEvent.ctrlKey || aEvent.shiftKey;
+#endif
+ if (aEvent.button == 2 || (aEvent.button == 0 && !modifKey))
+ return;
+
+ var target = aEvent.originalTarget;
+ // If this event bubbled up from a menu or menuitem, close the menus.
+ // Do this before opening tabs, to avoid hiding the open tabs confirm-dialog.
+ if (target.localName == "menu" || target.localName == "menuitem") {
+ for (node = target.parentNode; node; node = node.parentNode) {
+ if (node.localName == "menupopup")
+ node.hidePopup();
+ else if (node.localName != "menu" &&
+ node.localName != "splitmenu" &&
+ node.localName != "hbox" &&
+ node.localName != "vbox" )
+ break;
+ }
+ }
+
+ if (target._placesNode && PlacesUtils.nodeIsContainer(target._placesNode)) {
+ // Don't open the root folder in tabs when the empty area on the toolbar
+ // is middle-clicked or when a non-bookmark item except for Open in Tabs)
+ // in a bookmarks menupopup is middle-clicked.
+ if (target.localName == "menu" || target.localName == "toolbarbutton")
+ PlacesUIUtils.openContainerNodeInTabs(target._placesNode, aEvent, aView);
+ }
+ else if (aEvent.button == 1) {
+ // left-clicks with modifier are already served by onCommand
+ this.onCommand(aEvent, aView);
+ }
+ },
+
+ /**
+ * Handler for command event for an item in the bookmarks toolbar.
+ * Menus and submenus from the folder buttons bubble up to this handler.
+ * Opens the item.
+ * @param aEvent
+ * DOMEvent for the command
+ * @param aView
+ * The places view which aEvent should be associated with.
+ */
+ onCommand: function BEH_onCommand(aEvent, aView) {
+ var target = aEvent.originalTarget;
+ if (target._placesNode)
+ PlacesUIUtils.openNodeWithEvent(target._placesNode, aEvent, aView);
+ },
+
+ fillInBHTooltip: function BEH_fillInBHTooltip(aDocument, aEvent) {
+ var node;
+ var cropped = false;
+ var targetURI;
+
+ if (aDocument.tooltipNode.localName == "treechildren") {
+ var tree = aDocument.tooltipNode.parentNode;
+ var row = {}, column = {};
+ var tbo = tree.treeBoxObject;
+ tbo.getCellAt(aEvent.clientX, aEvent.clientY, row, column, {});
+ if (row.value == -1)
+ return false;
+ node = tree.view.nodeForTreeIndex(row.value);
+ cropped = tbo.isCellCropped(row.value, column.value);
+ }
+ else {
+ // Check whether the tooltipNode is a Places node.
+ // In such a case use it, otherwise check for targetURI attribute.
+ var tooltipNode = aDocument.tooltipNode;
+ if (tooltipNode._placesNode)
+ node = tooltipNode._placesNode;
+ else {
+ // This is a static non-Places node.
+ targetURI = tooltipNode.getAttribute("targetURI");
+ }
+ }
+
+ if (!node && !targetURI)
+ return false;
+
+ // Show node.label as tooltip's title for non-Places nodes.
+ var title = node ? node.title : tooltipNode.label;
+
+ // Show URL only for Places URI-nodes or nodes with a targetURI attribute.
+ var url;
+ if (targetURI || PlacesUtils.nodeIsURI(node))
+ url = targetURI || node.uri;
+
+ // Show tooltip for containers only if their title is cropped.
+ if (!cropped && !url)
+ return false;
+
+ var tooltipTitle = aDocument.getElementById("bhtTitleText");
+ tooltipTitle.hidden = (!title || (title == url));
+ if (!tooltipTitle.hidden)
+ tooltipTitle.textContent = title;
+
+ var tooltipUrl = aDocument.getElementById("bhtUrlText");
+ tooltipUrl.hidden = !url;
+ if (!tooltipUrl.hidden)
+ tooltipUrl.value = url;
+
+ // Show tooltip.
+ return true;
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// PlacesMenuDNDHandler
+
+// Handles special drag and drop functionality for Places menus that are not
+// part of a Places view (e.g. the bookmarks menu in the menubar).
+var PlacesMenuDNDHandler = {
+ _springLoadDelay: 350, // milliseconds
+ _loadTimer: null,
+ _closerTimer: null,
+
+ /**
+ * Called when the user enters the <menu> element during a drag.
+ * @param event
+ * The DragEnter event that spawned the opening.
+ */
+ onDragEnter: function PMDH_onDragEnter(event) {
+ // Opening menus in a Places popup is handled by the view itself.
+ if (!this._isStaticContainer(event.target))
+ return;
+
+ let popup = event.target.lastChild;
+ if (this._loadTimer || popup.state === "showing" || popup.state === "open")
+ return;
+
+ this._loadTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this._loadTimer.initWithCallback(() => {
+ this._loadTimer = null;
+ popup.setAttribute("autoopened", "true");
+ popup.showPopup(popup);
+ }, this._springLoadDelay, Ci.nsITimer.TYPE_ONE_SHOT);
+ event.preventDefault();
+ event.stopPropagation();
+ },
+
+ /**
+ * Handles dragleave on the <menu> element.
+ * @returns true if the element is a container element (menu or
+ * menu-toolbarbutton), false otherwise.
+ */
+ onDragLeave: function PMDH_onDragLeave(event) {
+ // Handle menu-button separate targets.
+ if (event.relatedTarget === event.currentTarget ||
+ event.relatedTarget.parentNode === event.currentTarget)
+ return;
+
+ // Closing menus in a Places popup is handled by the view itself.
+ if (!this._isStaticContainer(event.target))
+ return;
+
+ let popup = event.target.lastChild;
+
+ if (this._loadTimer) {
+ this._loadTimer.cancel();
+ this._loadTimer = null;
+ }
+ this._closeTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this._closeTimer.initWithCallback(function() {
+ this._closeTimer = null;
+ let node = PlacesControllerDragHelper.currentDropTarget;
+ let inHierarchy = false;
+ while (node && !inHierarchy) {
+ inHierarchy = node == event.target;
+ node = node.parentNode;
+ }
+ if (!inHierarchy && popup && popup.hasAttribute("autoopened")) {
+ popup.removeAttribute("autoopened");
+ popup.hidePopup();
+ }
+ }, this._springLoadDelay, Ci.nsITimer.TYPE_ONE_SHOT);
+ },
+
+ /**
+ * Determines if a XUL element represents a static container.
+ * @returns true if the element is a container element (menu or
+ *` menu-toolbarbutton), false otherwise.
+ */
+ _isStaticContainer: function PMDH__isContainer(node) {
+ let isMenu = node.localName == "menu" ||
+ (node.localName == "toolbarbutton" &&
+ (node.getAttribute("type") == "menu" ||
+ node.getAttribute("type") == "menu-button"));
+ let isStatic = !("_placesNode" in node) && node.lastChild &&
+ node.lastChild.hasAttribute("placespopup") &&
+ !node.parentNode.hasAttribute("placespopup");
+ return isMenu && isStatic;
+ },
+
+ /**
+ * Called when the user drags over the <menu> element.
+ * @param event
+ * The DragOver event.
+ */
+ onDragOver: function PMDH_onDragOver(event) {
+ let ip = new InsertionPoint(PlacesUtils.bookmarksMenuFolderId,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ Ci.nsITreeView.DROP_ON);
+ if (ip && PlacesControllerDragHelper.canDrop(ip, event.dataTransfer))
+ event.preventDefault();
+
+ event.stopPropagation();
+ },
+
+ /**
+ * Called when the user drops on the <menu> element.
+ * @param event
+ * The Drop event.
+ */
+ onDrop: function PMDH_onDrop(event) {
+ // Put the item at the end of bookmark menu.
+ let ip = new InsertionPoint(PlacesUtils.bookmarksMenuFolderId,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ Ci.nsITreeView.DROP_ON);
+ PlacesControllerDragHelper.onDrop(ip, event.dataTransfer);
+ event.stopPropagation();
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// PlacesToolbarHelper
+
+/**
+ * This object handles the initialization and uninitialization of the bookmarks
+ * toolbar.
+ */
+let PlacesToolbarHelper = {
+ _place: "place:folder=TOOLBAR",
+
+ get _viewElt() {
+ return document.getElementById("PlacesToolbar");
+ },
+
+ init: function PTH_init() {
+ let viewElt = this._viewElt;
+ if (!viewElt || viewElt._placesView)
+ return;
+
+ // If the bookmarks toolbar item is hidden because the parent toolbar is
+ // collapsed or hidden (i.e. in a popup), spare the initialization. Also,
+ // there is no need to initialize the toolbar if customizing because
+ // init() will be called when the customization is done.
+ let toolbar = viewElt.parentNode.parentNode;
+ if (toolbar.collapsed ||
+ getComputedStyle(toolbar, "").display == "none" ||
+ this._isCustomizing)
+ return;
+
+ new PlacesToolbar(this._place);
+ },
+
+ customizeStart: function PTH_customizeStart() {
+ let viewElt = this._viewElt;
+ if (viewElt && viewElt._placesView)
+ viewElt._placesView.uninit();
+
+ this._isCustomizing = true;
+ },
+
+ customizeDone: function PTH_customizeDone() {
+ this._isCustomizing = false;
+ this.init();
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// BookmarkingUI
+
+/**
+ * Handles the bookmarks star button in the URL bar, as well as the bookmark
+ * menu button.
+ */
+
+let BookmarkingUI = {
+ get button() {
+ if (!this._button) {
+ this._button = document.getElementById("bookmarks-menu-button");
+ }
+ return this._button;
+ },
+
+ get star() {
+ if (!this._star) {
+ this._star = document.getElementById("star-button");
+ }
+ return this._star;
+ },
+
+ get anchor() {
+ if (this.star && isElementVisible(this.star)) {
+ // Anchor to the icon, so the panel looks more natural.
+ return this.star;
+ }
+ return null;
+ },
+
+ STATUS_UPDATING: -1,
+ STATUS_UNSTARRED: 0,
+ STATUS_STARRED: 1,
+ get status() {
+ if (this._pendingStmt)
+ return this.STATUS_UPDATING;
+ return this.star &&
+ this.star.hasAttribute("starred") ? this.STATUS_STARRED
+ : this.STATUS_UNSTARRED;
+ },
+
+ get _starredTooltip()
+ {
+ delete this._starredTooltip;
+ return this._starredTooltip =
+ gNavigatorBundle.getString("starButtonOn.tooltip");
+ },
+
+ get _unstarredTooltip()
+ {
+ delete this._unstarredTooltip;
+ return this._unstarredTooltip =
+ gNavigatorBundle.getString("starButtonOff.tooltip");
+ },
+
+ /**
+ * The popup contents must be updated when the user customizes the UI, or
+ * changes the personal toolbar collapsed status. In such a case, any needed
+ * change should be handled in the popupshowing helper, for performance
+ * reasons.
+ */
+ _popupNeedsUpdate: true,
+ onToolbarVisibilityChange: function BUI_onToolbarVisibilityChange() {
+ this._popupNeedsUpdate = true;
+ },
+
+ onPopupShowing: function BUI_onPopupShowing(event) {
+ // Don't handle events for submenus.
+ if (event.target != event.currentTarget)
+ return;
+
+ if (!this._popupNeedsUpdate)
+ return;
+ this._popupNeedsUpdate = false;
+
+ let popup = event.target;
+ let getPlacesAnonymousElement =
+ aAnonId => document.getAnonymousElementByAttribute(popup.parentNode,
+ "placesanonid",
+ aAnonId);
+
+ let viewToolbarMenuitem = getPlacesAnonymousElement("view-toolbar");
+ if (viewToolbarMenuitem) {
+ // Update View bookmarks toolbar checkbox menuitem.
+ let personalToolbar = document.getElementById("PersonalToolbar");
+ viewToolbarMenuitem.setAttribute("checked", !personalToolbar.collapsed);
+ }
+
+ let toolbarMenuitem = getPlacesAnonymousElement("toolbar-autohide");
+ if (toolbarMenuitem) {
+ // If bookmarks items are visible, hide Bookmarks Toolbar menu and the
+ // separator after it.
+ toolbarMenuitem.collapsed = toolbarMenuitem.nextSibling.collapsed =
+ isElementVisible(document.getElementById("personal-bookmarks"));
+ }
+ },
+
+ /**
+ * Handles star styling based on page proxy state changes.
+ */
+ onPageProxyStateChanged: function BUI_onPageProxyStateChanged(aState) {
+ if (!this.star) {
+ return;
+ }
+
+ if (aState == "invalid") {
+ this.star.setAttribute("disabled", "true");
+ this.star.removeAttribute("starred");
+ }
+ else {
+ this.star.removeAttribute("disabled");
+ }
+ },
+
+ _updateToolbarStyle: function BUI__updateToolbarStyle() {
+ if (!this.button) {
+ return;
+ }
+
+ let personalToolbar = document.getElementById("PersonalToolbar");
+ let onPersonalToolbar = this.button.parentNode == personalToolbar ||
+ this.button.parentNode.parentNode == personalToolbar;
+
+ if (onPersonalToolbar) {
+ this.button.classList.add("bookmark-item");
+ this.button.classList.remove("toolbarbutton-1");
+ }
+ else {
+ this.button.classList.remove("bookmark-item");
+ this.button.classList.add("toolbarbutton-1");
+ }
+ },
+
+ _uninitView: function BUI__uninitView() {
+ // When an element with a placesView attached is removed and re-inserted,
+ // XBL reapplies the binding causing any kind of issues and possible leaks,
+ // so kill current view and let popupshowing generate a new one.
+ if (this.button && this.button._placesView) {
+ this.button._placesView.uninit();
+ }
+ },
+
+ customizeStart: function BUI_customizeStart() {
+ this._uninitView();
+ },
+
+ customizeChange: function BUI_customizeChange() {
+ this._updateToolbarStyle();
+ },
+
+ customizeDone: function BUI_customizeDone() {
+ delete this._button;
+ this.onToolbarVisibilityChange();
+ this._updateToolbarStyle();
+ },
+
+ _hasBookmarksObserver: false,
+ uninit: function BUI_uninit() {
+ this._uninitView();
+
+ if (this._hasBookmarksObserver) {
+ PlacesUtils.removeLazyBookmarkObserver(this);
+ }
+
+ if (this._pendingStmt) {
+ this._pendingStmt.cancel();
+ delete this._pendingStmt;
+ }
+ },
+
+ updateStarState: function BUI_updateStarState() {
+ if (!this.star || (this._uri && gBrowser.currentURI.equals(this._uri))) {
+ return;
+ }
+
+ // Reset tracked values.
+ this._uri = gBrowser.currentURI;
+ this._itemIds = [];
+
+ if (this._pendingStmt) {
+ this._pendingStmt.cancel();
+ delete this._pendingStmt;
+ }
+
+ // We can load about:blank before the actual page, but there is no point in handling that page.
+ if (isBlankPageURL(this._uri.spec)) {
+ return;
+ }
+
+ this._pendingStmt = PlacesUtils.asyncGetBookmarkIds(this._uri, function (aItemIds, aURI) {
+ // Safety check that the bookmarked URI equals the tracked one.
+ if (!aURI.equals(this._uri)) {
+ Components.utils.reportError("BookmarkingUI did not receive current URI");
+ return;
+ }
+
+ // It's possible that onItemAdded gets called before the async statement
+ // calls back. For such an edge case, retain all unique entries from both
+ // arrays.
+ this._itemIds = this._itemIds.filter(
+ function (id) aItemIds.indexOf(id) == -1
+ ).concat(aItemIds);
+
+ this._updateStar();
+
+ // Start observing bookmarks if needed.
+ if (!this._hasBookmarksObserver) {
+ try {
+ PlacesUtils.addLazyBookmarkObserver(this);
+ this._hasBookmarksObserver = true;
+ } catch(ex) {
+ Components.utils.reportError("BookmarkingUI failed adding a bookmarks observer: " + ex);
+ }
+ }
+
+ delete this._pendingStmt;
+ }, this);
+ },
+
+ _updateStar: function BUI__updateStar() {
+ if (!this.star) {
+ return;
+ }
+
+ if (this._itemIds.length > 0) {
+ this.star.setAttribute("starred", "true");
+ this.star.setAttribute("tooltiptext", this._starredTooltip);
+ }
+ else {
+ this.star.removeAttribute("starred");
+ this.star.setAttribute("tooltiptext", this._unstarredTooltip);
+ }
+ },
+
+ onCommand: function BUI_onCommand(aEvent) {
+ if (aEvent.target != aEvent.currentTarget) {
+ return;
+ }
+ // Ignore clicks on the star if we are updating its state.
+ if (!this._pendingStmt) {
+ PlacesCommandHook.bookmarkCurrentPage(this._itemIds.length > 0);
+ }
+ },
+
+ // nsINavBookmarkObserver
+ onItemAdded: function BUI_onItemAdded(aItemId, aParentId, aIndex, aItemType,
+ aURI) {
+ if (aURI && aURI.equals(this._uri)) {
+ // If a new bookmark has been added to the tracked uri, register it.
+ if (this._itemIds.indexOf(aItemId) == -1) {
+ this._itemIds.push(aItemId);
+ this._updateStar();
+ }
+ }
+ },
+
+ onItemRemoved: function BUI_onItemRemoved(aItemId) {
+ let index = this._itemIds.indexOf(aItemId);
+ // If one of the tracked bookmarks has been removed, unregister it.
+ if (index != -1) {
+ this._itemIds.splice(index, 1);
+ this._updateStar();
+ }
+ },
+
+ onItemChanged: function BUI_onItemChanged(aItemId, aProperty,
+ aIsAnnotationProperty, aNewValue) {
+ if (aProperty == "uri") {
+ let index = this._itemIds.indexOf(aItemId);
+ // If the changed bookmark was tracked, check if it is now pointing to
+ // a different uri and unregister it.
+ if (index != -1 && aNewValue != this._uri.spec) {
+ this._itemIds.splice(index, 1);
+ this._updateStar();
+ }
+ // If another bookmark is now pointing to the tracked uri, register it.
+ else if (index == -1 && aNewValue == this._uri.spec) {
+ this._itemIds.push(aItemId);
+ this._updateStar();
+ }
+ }
+ },
+
+ onBeginUpdateBatch: function () {},
+ onEndUpdateBatch: function () {},
+ onBeforeItemRemoved: function () {},
+ onItemVisited: function () {},
+ onItemMoved: function () {},
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsINavBookmarkObserver
+ ])
+};
diff --git a/browser/base/content/browser-plugins.js b/browser/base/content/browser-plugins.js
new file mode 100644
index 000000000..02c299d39
--- /dev/null
+++ b/browser/base/content/browser-plugins.js
@@ -0,0 +1,1053 @@
+# -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+const kPrefNotifyMissingFlash = "plugins.notifyMissingFlash";
+const kPrefSessionPersistMinutes = "plugin.sessionPermissionNow.intervalInMinutes";
+const kPrefPersistentDays = "plugin.persistentPermissionAlways.intervalInDays";
+
+var gPluginHandler = {
+ PLUGIN_SCRIPTED_STATE_NONE: 0,
+ PLUGIN_SCRIPTED_STATE_FIRED: 1,
+ PLUGIN_SCRIPTED_STATE_DONE: 2,
+
+ getPluginUI: function (plugin, className) {
+ return plugin.ownerDocument.
+ getAnonymousElementByAttribute(plugin, "class", className);
+ },
+
+#ifdef MOZ_CRASHREPORTER
+ get CrashSubmit() {
+ delete this.CrashSubmit;
+ Cu.import("resource://gre/modules/CrashSubmit.jsm", this);
+ return this.CrashSubmit;
+ },
+#endif
+
+ _getPluginInfo: function (pluginElement) {
+ let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+ pluginElement.QueryInterface(Ci.nsIObjectLoadingContent);
+
+ let tagMimetype;
+ let pluginName = gNavigatorBundle.getString("pluginInfo.unknownPlugin");
+ let pluginTag = null;
+ let permissionString = null;
+ let fallbackType = null;
+ let blocklistState = null;
+
+ if (pluginElement instanceof HTMLAppletElement) {
+ tagMimetype = "application/x-java-vm";
+ } else {
+ tagMimetype = pluginElement.actualType;
+
+ if (tagMimetype == "") {
+ tagMimetype = pluginElement.type;
+ }
+ }
+
+ if (gPluginHandler.isKnownPlugin(pluginElement)) {
+ pluginTag = pluginHost.getPluginTagForType(pluginElement.actualType);
+ pluginName = gPluginHandler.makeNicePluginName(pluginTag.name);
+
+ permissionString = pluginHost.getPermissionStringForType(pluginElement.actualType);
+ fallbackType = pluginElement.defaultFallbackType;
+ blocklistState = pluginHost.getBlocklistStateForType(pluginElement.actualType);
+ // Make state-softblocked == state-notblocked for our purposes,
+ // they have the same UI. STATE_OUTDATED should not exist for plugin
+ // items, but let's alias it anyway, just in case.
+ if (blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED ||
+ blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) {
+ blocklistState = Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+ }
+ }
+
+ return { mimetype: tagMimetype,
+ pluginName: pluginName,
+ pluginTag: pluginTag,
+ permissionString: permissionString,
+ fallbackType: fallbackType,
+ blocklistState: blocklistState,
+ };
+ },
+
+ // Map the plugin's name to a filtered version more suitable for user UI.
+ makeNicePluginName : function (aName) {
+ if (aName == "Shockwave Flash")
+ return "Adobe Flash";
+
+ // Clean up the plugin name by stripping off any trailing version numbers
+ // or "plugin". EG, "Foo Bar Plugin 1.23_02" --> "Foo Bar"
+ // Do this by first stripping the numbers, etc. off the end, and then
+ // removing "Plugin" (and then trimming to get rid of any whitespace).
+ // (Otherwise, something like "Java(TM) Plug-in 1.7.0_07" gets mangled)
+ let newName = aName.replace(/[\s\d\.\-\_\(\)]+$/, "").replace(/\bplug-?in\b/i, "").trim();
+ return newName;
+ },
+
+ isTooSmall : function (plugin, overlay) {
+ // Is the <object>'s size too small to hold what we want to show?
+ let pluginRect = plugin.getBoundingClientRect();
+ // XXX bug 446693. The text-shadow on the submitted-report text at
+ // the bottom causes scrollHeight to be larger than it should be.
+ let overflows = (overlay.scrollWidth > pluginRect.width) ||
+ (overlay.scrollHeight - 5 > pluginRect.height);
+ return overflows;
+ },
+
+ addLinkClickCallback: function (linkNode, callbackName /*callbackArgs...*/) {
+ // XXX just doing (callback)(arg) was giving a same-origin error. bug?
+ let self = this;
+ let callbackArgs = Array.prototype.slice.call(arguments).slice(2);
+ linkNode.addEventListener("click",
+ function(evt) {
+ if (!evt.isTrusted)
+ return;
+ evt.preventDefault();
+ if (callbackArgs.length == 0)
+ callbackArgs = [ evt ];
+ (self[callbackName]).apply(self, callbackArgs);
+ },
+ true);
+
+ linkNode.addEventListener("keydown",
+ function(evt) {
+ if (!evt.isTrusted)
+ return;
+ if (evt.keyCode == evt.DOM_VK_RETURN) {
+ evt.preventDefault();
+ if (callbackArgs.length == 0)
+ callbackArgs = [ evt ];
+ evt.preventDefault();
+ (self[callbackName]).apply(self, callbackArgs);
+ }
+ },
+ true);
+ },
+
+ // Helper to get the binding handler type from a plugin object
+ _getBindingType : function(plugin) {
+ if (!(plugin instanceof Ci.nsIObjectLoadingContent))
+ return null;
+
+ switch (plugin.pluginFallbackType) {
+ case Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED:
+ return "PluginNotFound";
+ case Ci.nsIObjectLoadingContent.PLUGIN_DISABLED:
+ return "PluginDisabled";
+ case Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED:
+ return "PluginBlocklisted";
+ case Ci.nsIObjectLoadingContent.PLUGIN_OUTDATED:
+ return "PluginOutdated";
+ case Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY:
+ return "PluginClickToPlay";
+ case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE:
+ return "PluginVulnerableUpdatable";
+ case Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE:
+ return "PluginVulnerableNoUpdate";
+ case Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW:
+ return "PluginPlayPreview";
+ default:
+ // Not all states map to a handler
+ return null;
+ }
+ },
+
+ supportedPlugins: {
+ "mimetypes": {
+ "application/x-shockwave-flash": "flash",
+ "application/futuresplash": "flash",
+ "application/x-java-.*": "java",
+ "application/x-director": "shockwave",
+ "application/(sdp|x-(mpeg|rtsp|sdp))": "quicktime",
+ "audio/(3gpp(2)?|AMR|aiff|basic|mid(i)?|mp4|mpeg|vnd\.qcelp|wav|x-(aiff|m4(a|b|p)|midi|mpeg|wav))": "quicktime",
+ "image/(pict|png|tiff|x-(macpaint|pict|png|quicktime|sgi|targa|tiff))": "quicktime",
+ "video/(3gpp(2)?|flc|mp4|mpeg|quicktime|sd-video|x-mpeg)": "quicktime",
+ "application/x-unknown": "test",
+ },
+
+ "plugins": {
+ "flash": {
+ "displayName": "Flash",
+ "installWINNT": true,
+ "installDarwin": true,
+ "installLinux": true,
+ },
+ "java": {
+ "displayName": "Java",
+ "installWINNT": true,
+ "installDarwin": true,
+ "installLinux": true,
+ },
+ "shockwave": {
+ "displayName": "Shockwave",
+ "installWINNT": true,
+ "installDarwin": true,
+ },
+ "quicktime": {
+ "displayName": "QuickTime",
+ "installWINNT": true,
+ },
+ "test": {
+ "displayName": "Test plugin",
+ "installWINNT": true,
+ "installLinux": true,
+ "installDarwin": true,
+ }
+ }
+ },
+
+ nameForSupportedPlugin: function (aMimeType) {
+ for (let type in this.supportedPlugins.mimetypes) {
+ let re = new RegExp(type);
+ if (re.test(aMimeType)) {
+ return this.supportedPlugins.mimetypes[type];
+ }
+ }
+ return null;
+ },
+
+ canInstallThisMimeType: function (aMimeType) {
+ let os = Services.appinfo.OS;
+ let pluginName = this.nameForSupportedPlugin(aMimeType);
+ if (pluginName && "install" + os in this.supportedPlugins.plugins[pluginName]) {
+ return true;
+ }
+ return false;
+ },
+
+ handleEvent : function(event) {
+ let plugin;
+ let doc;
+
+ let eventType = event.type;
+ if (eventType === "PluginRemoved") {
+ doc = event.target;
+ }
+ else {
+ plugin = event.target;
+ doc = plugin.ownerDocument;
+
+ if (!(plugin instanceof Ci.nsIObjectLoadingContent))
+ return;
+ }
+
+ if (eventType == "PluginBindingAttached") {
+ // The plugin binding fires this event when it is created.
+ // As an untrusted event, ensure that this object actually has a binding
+ // and make sure we don't handle it twice
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox");
+ if (!overlay || overlay._bindingHandled) {
+ return;
+ }
+ overlay._bindingHandled = true;
+
+ // Lookup the handler for this binding
+ eventType = this._getBindingType(plugin);
+ if (!eventType) {
+ // Not all bindings have handlers
+ return;
+ }
+ }
+
+ let shouldShowNotification = false;
+ let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
+
+ switch (eventType) {
+ case "PluginCrashed":
+ this.pluginInstanceCrashed(plugin, event);
+ break;
+
+ case "PluginNotFound":
+ let installable = this.showInstallNotification(plugin, eventType);
+ // For non-object plugin tags, register a click handler to install the
+ // plugin. Object tags can, and often do, deal with that themselves,
+ // so don't stomp on the page developers toes.
+ if (installable && !(plugin instanceof HTMLObjectElement)) {
+ let installStatus = doc.getAnonymousElementByAttribute(plugin, "class", "installStatus");
+ installStatus.setAttribute("installable", "true");
+ let iconStatus = doc.getAnonymousElementByAttribute(plugin, "class", "icon");
+ iconStatus.setAttribute("installable", "true");
+
+ let installLink = doc.getAnonymousElementByAttribute(plugin, "anonid", "installPluginLink");
+ this.addLinkClickCallback(installLink, "installSinglePlugin", plugin);
+ }
+ break;
+
+ case "PluginBlocklisted":
+ case "PluginOutdated":
+ shouldShowNotification = true;
+ break;
+
+ case "PluginVulnerableUpdatable":
+ let updateLink = doc.getAnonymousElementByAttribute(plugin, "anonid", "checkForUpdatesLink");
+ this.addLinkClickCallback(updateLink, "openPluginUpdatePage");
+ /* FALLTHRU */
+
+ case "PluginVulnerableNoUpdate":
+ case "PluginClickToPlay":
+ this._handleClickToPlayEvent(plugin);
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox");
+ let pluginName = this._getPluginInfo(plugin).pluginName;
+ let messageString = gNavigatorBundle.getFormattedString("PluginClickToActivate", [pluginName]);
+ let overlayText = doc.getAnonymousElementByAttribute(plugin, "class", "msg msgClickToPlay");
+ overlayText.textContent = messageString;
+ if (eventType == "PluginVulnerableUpdatable" ||
+ eventType == "PluginVulnerableNoUpdate") {
+ let vulnerabilityString = gNavigatorBundle.getString(eventType);
+ let vulnerabilityText = doc.getAnonymousElementByAttribute(plugin, "anonid", "vulnerabilityStatus");
+ vulnerabilityText.textContent = vulnerabilityString;
+ }
+ shouldShowNotification = true;
+ break;
+
+ case "PluginPlayPreview":
+ this._handlePlayPreviewEvent(plugin);
+ break;
+
+ case "PluginDisabled":
+ let manageLink = doc.getAnonymousElementByAttribute(plugin, "anonid", "managePluginsLink");
+ this.addLinkClickCallback(manageLink, "managePlugins");
+ shouldShowNotification = true;
+ break;
+
+ case "PluginInstantiated":
+ //Pale Moon: don't show the indicator when plugins are enabled/allowed
+ if (gPrefService.getBoolPref("plugins.always_show_indicator")) {
+ shouldShowNotification = true;
+ }
+ break;
+ case "PluginRemoved":
+ shouldShowNotification = true;
+ break;
+ }
+
+ // Hide the in-content UI if it's too big. The crashed plugin handler already did this.
+ if (eventType != "PluginCrashed" && eventType != "PluginRemoved") {
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox");
+ if (overlay != null && this.isTooSmall(plugin, overlay))
+ overlay.style.visibility = "hidden";
+ }
+
+ // Only show the notification after we've done the isTooSmall check, so
+ // that the notification can decide whether to show the "alert" icon
+ if (shouldShowNotification) {
+ this._showClickToPlayNotification(browser);
+ }
+ },
+
+ isKnownPlugin: function PH_isKnownPlugin(objLoadingContent) {
+ return (objLoadingContent.getContentTypeForMIMEType(objLoadingContent.actualType) ==
+ Ci.nsIObjectLoadingContent.TYPE_PLUGIN);
+ },
+
+ canActivatePlugin: function PH_canActivatePlugin(objLoadingContent) {
+ // if this isn't a known plugin, we can't activate it
+ // (this also guards pluginHost.getPermissionStringForType against
+ // unexpected input)
+ if (!gPluginHandler.isKnownPlugin(objLoadingContent))
+ return false;
+
+ let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+ let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
+ let principal = objLoadingContent.ownerDocument.defaultView.top.document.nodePrincipal;
+ let pluginPermission = Services.perms.testPermissionFromPrincipal(principal, permissionString);
+
+ let isFallbackTypeValid =
+ objLoadingContent.pluginFallbackType >= Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY &&
+ objLoadingContent.pluginFallbackType <= Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE;
+
+ if (objLoadingContent.pluginFallbackType == Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW) {
+ // checking if play preview is subject to CTP rules
+ let playPreviewInfo = pluginHost.getPlayPreviewInfo(objLoadingContent.actualType);
+ isFallbackTypeValid = !playPreviewInfo.ignoreCTP;
+ }
+
+ return !objLoadingContent.activated &&
+ pluginPermission != Ci.nsIPermissionManager.DENY_ACTION &&
+ isFallbackTypeValid;
+ },
+
+ hideClickToPlayOverlay: function(aPlugin) {
+ let overlay = aPlugin.ownerDocument.getAnonymousElementByAttribute(aPlugin, "class", "mainBox");
+ if (overlay)
+ overlay.style.visibility = "hidden";
+ },
+
+ stopPlayPreview: function PH_stopPlayPreview(aPlugin, aPlayPlugin) {
+ let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ if (objLoadingContent.activated)
+ return;
+
+ if (aPlayPlugin)
+ objLoadingContent.playPlugin();
+ else
+ objLoadingContent.cancelPlayPreview();
+ },
+
+ newPluginInstalled : function(event) {
+ // browser elements are anonymous so we can't just use target.
+ var browser = event.originalTarget;
+ // clear the plugin list, now that at least one plugin has been installed
+ browser.missingPlugins = null;
+
+ var notificationBox = gBrowser.getNotificationBox(browser);
+ var notification = notificationBox.getNotificationWithValue("missing-plugins");
+ if (notification)
+ notificationBox.removeNotification(notification);
+
+ // reload the browser to make the new plugin show.
+ browser.reload();
+ },
+
+ // Callback for user clicking on a missing (unsupported) plugin.
+ installSinglePlugin: function (plugin) {
+ var missingPlugins = new Map();
+
+ var pluginInfo = this._getPluginInfo(plugin);
+ missingPlugins.set(pluginInfo.mimetype, pluginInfo);
+
+ openDialog("chrome://mozapps/content/plugins/pluginInstallerWizard.xul",
+ "PFSWindow", "chrome,centerscreen,resizable=yes",
+ {plugins: missingPlugins, browser: gBrowser.selectedBrowser});
+ },
+
+ // Callback for user clicking on a disabled plugin
+ managePlugins: function (aEvent) {
+ BrowserOpenAddonsMgr("addons://list/plugin");
+ },
+
+ // Callback for user clicking on the link in a click-to-play plugin
+ // (where the plugin has an update)
+ openPluginUpdatePage: function (aEvent) {
+ openURL(Services.urlFormatter.formatURLPref("plugins.update.url"));
+ },
+
+#ifdef MOZ_CRASHREPORTER
+ submitReport: function submitReport(pluginDumpID, browserDumpID, plugin) {
+ let keyVals = {};
+ if (plugin) {
+ let userComment = this.getPluginUI(plugin, "submitComment").value.trim();
+ if (userComment)
+ keyVals.PluginUserComment = userComment;
+ if (this.getPluginUI(plugin, "submitURLOptIn").checked)
+ keyVals.PluginContentURL = plugin.ownerDocument.URL;
+ }
+ this.CrashSubmit.submit(pluginDumpID, { extraExtraKeyVals: keyVals });
+ if (browserDumpID)
+ this.CrashSubmit.submit(browserDumpID);
+ },
+#endif
+
+ // Callback for user clicking a "reload page" link
+ reloadPage: function (browser) {
+ browser.reload();
+ },
+
+ // Callback for user clicking the help icon
+ openHelpPage: function () {
+ openHelpLink("plugin-crashed", false);
+ },
+
+ showInstallNotification: function (aPlugin) {
+ let browser = gBrowser.getBrowserForDocument(aPlugin.ownerDocument
+ .defaultView.top.document);
+ if (!browser.missingPlugins)
+ browser.missingPlugins = new Map();
+
+ let pluginInfo = this._getPluginInfo(aPlugin);
+ browser.missingPlugins.set(pluginInfo.mimetype, pluginInfo);
+
+ // only show notification for small subset of plugins
+ let mimetype = pluginInfo.mimetype.split(";")[0];
+ if (!this.canInstallThisMimeType(mimetype))
+ return false;
+
+ let pluginIdentifier = this.nameForSupportedPlugin(mimetype);
+ if (!pluginIdentifier)
+ return false;
+
+ let displayName = this.supportedPlugins.plugins[pluginIdentifier].displayName;
+
+ // don't show several notifications
+ let notification = PopupNotifications.getNotification("plugins-not-found", browser);
+ if (notification)
+ return true;
+
+ let messageString = gNavigatorBundle.getString("installPlugin.message");
+ let mainAction = {
+ label: gNavigatorBundle.getFormattedString("installPlugin.button.label",
+ [displayName]),
+ accessKey: gNavigatorBundle.getString("installPlugin.button.accesskey"),
+ callback: function () {
+ openDialog("chrome://mozapps/content/plugins/pluginInstallerWizard.xul",
+ "PFSWindow", "chrome,centerscreen,resizable=yes",
+ {plugins: browser.missingPlugins, browser: browser});
+ }
+ };
+ let secondaryActions = null;
+ let options = { dismissed: true };
+
+ let showForFlash = Services.prefs.getBoolPref(kPrefNotifyMissingFlash);
+ if (pluginIdentifier == "flash" && showForFlash) {
+ secondaryActions = [{
+ label: gNavigatorBundle.getString("installPlugin.ignoreButton.label"),
+ accessKey: gNavigatorBundle.getString("installPlugin.ignoreButton.accesskey"),
+ callback: function () {
+ Services.prefs.setBoolPref(kPrefNotifyMissingFlash, false);
+ }
+ }];
+ options.dismissed = false;
+ }
+ PopupNotifications.show(browser, "plugins-not-found",
+ messageString, "plugin-install-notification-icon",
+ mainAction, secondaryActions, options);
+ return true;
+ },
+ // Event listener for click-to-play plugins.
+ _handleClickToPlayEvent: function PH_handleClickToPlayEvent(aPlugin) {
+ let doc = aPlugin.ownerDocument;
+ let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
+ let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+ let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ // guard against giving pluginHost.getPermissionStringForType a type
+ // not associated with any known plugin
+ if (!gPluginHandler.isKnownPlugin(objLoadingContent))
+ return;
+ let permissionString = pluginHost.getPermissionStringForType(objLoadingContent.actualType);
+ let principal = doc.defaultView.top.document.nodePrincipal;
+ let pluginPermission = Services.perms.testPermissionFromPrincipal(principal, permissionString);
+
+ let overlay = doc.getAnonymousElementByAttribute(aPlugin, "class", "mainBox");
+
+ if (pluginPermission == Ci.nsIPermissionManager.DENY_ACTION) {
+ if (overlay)
+ overlay.style.visibility = "hidden";
+ return;
+ }
+
+ if (overlay) {
+ overlay.addEventListener("click", gPluginHandler._overlayClickListener, true);
+ let closeIcon = doc.getAnonymousElementByAttribute(aPlugin, "anonid", "closeIcon");
+ closeIcon.addEventListener("click", function(aEvent) {
+ if (aEvent.button == 0 && aEvent.isTrusted)
+ gPluginHandler.hideClickToPlayOverlay(aPlugin);
+ }, true);
+ }
+ },
+
+ _overlayClickListener: {
+ handleEvent: function PH_handleOverlayClick(aEvent) {
+ let plugin = document.getBindingParent(aEvent.target);
+ let contentWindow = plugin.ownerDocument.defaultView.top;
+ // gBrowser.getBrowserForDocument does not exist in the case where we
+ // drag-and-dropped a tab from a window containing only that tab. In
+ // that case, the window gets destroyed.
+ let browser = gBrowser.getBrowserForDocument ?
+ gBrowser.getBrowserForDocument(contentWindow.document) :
+ null;
+ // If browser is null here, we've been drag-and-dropped from another
+ // window, and this is the wrong click handler.
+ if (!browser) {
+ aEvent.target.removeEventListener("click", gPluginHandler._overlayClickListener, true);
+ return;
+ }
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ // Have to check that the target is not the link to update the plugin
+ if (!(aEvent.originalTarget instanceof HTMLAnchorElement) &&
+ (aEvent.originalTarget.getAttribute('anonid') != 'closeIcon') &&
+ aEvent.button == 0 && aEvent.isTrusted) {
+ gPluginHandler._showClickToPlayNotification(browser, plugin);
+ aEvent.stopPropagation();
+ aEvent.preventDefault();
+ }
+ }
+ },
+
+ _handlePlayPreviewEvent: function PH_handlePlayPreviewEvent(aPlugin) {
+ let doc = aPlugin.ownerDocument;
+ let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
+ let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+ let pluginInfo = this._getPluginInfo(aPlugin);
+ let playPreviewInfo = pluginHost.getPlayPreviewInfo(pluginInfo.mimetype);
+
+ let previewContent = doc.getAnonymousElementByAttribute(aPlugin, "class", "previewPluginContent");
+ let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0];
+ if (!iframe) {
+ // lazy initialization of the iframe
+ iframe = doc.createElementNS("http://www.w3.org/1999/xhtml", "iframe");
+ iframe.className = "previewPluginContentFrame";
+ previewContent.appendChild(iframe);
+
+ // Force a style flush, so that we ensure our binding is attached.
+ aPlugin.clientTop;
+ }
+ iframe.src = playPreviewInfo.redirectURL;
+
+ // MozPlayPlugin event can be dispatched from the extension chrome
+ // code to replace the preview content with the native plugin
+ previewContent.addEventListener("MozPlayPlugin", function playPluginHandler(aEvent) {
+ if (!aEvent.isTrusted)
+ return;
+
+ previewContent.removeEventListener("MozPlayPlugin", playPluginHandler, true);
+
+ let playPlugin = !aEvent.detail;
+ gPluginHandler.stopPlayPreview(aPlugin, playPlugin);
+
+ // cleaning up: removes overlay iframe from the DOM
+ let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0];
+ if (iframe)
+ previewContent.removeChild(iframe);
+ }, true);
+
+ if (!playPreviewInfo.ignoreCTP) {
+ gPluginHandler._showClickToPlayNotification(browser);
+ }
+ },
+
+ reshowClickToPlayNotification: function PH_reshowClickToPlayNotification() {
+ let browser = gBrowser.selectedBrowser;
+ let contentWindow = browser.contentWindow;
+ let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ let doc = contentWindow.document;
+ let plugins = cwu.plugins;
+ for (let plugin of plugins) {
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox");
+ if (overlay)
+ overlay.removeEventListener("click", gPluginHandler._overlayClickListener, true);
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ if (gPluginHandler.canActivatePlugin(objLoadingContent))
+ gPluginHandler._handleClickToPlayEvent(plugin);
+ }
+ gPluginHandler._showClickToPlayNotification(browser);
+ },
+
+ _clickToPlayNotificationEventCallback: function PH_ctpEventCallback(event) {
+ if (event == "showing") {
+ gPluginHandler._makeCenterActions(this);
+ }
+ else if (event == "dismissed") {
+ // Once the popup is dismissed, clicking the icon should show the full
+ // list again
+ this.options.primaryPlugin = null;
+ }
+ },
+
+ // Match the behaviour of nsPermissionManager
+ _getHostFromPrincipal: function PH_getHostFromPrincipal(principal) {
+ if (!principal.URI || principal.URI.schemeIs("moz-nullprincipal")) {
+ return "(null)";
+ }
+
+ try {
+ if (principal.URI.host)
+ return principal.URI.host;
+ } catch (e) {}
+
+ return principal.origin;
+ },
+
+ _makeCenterActions: function PH_makeCenterActions(notification) {
+ let contentWindow = notification.browser.contentWindow;
+ let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+
+ let principal = contentWindow.document.nodePrincipal;
+ // This matches the behavior of nsPermssionManager, used for display purposes only
+ let principalHost = this._getHostFromPrincipal(principal);
+
+ let centerActions = [];
+ let pluginsFound = new Set();
+ for (let plugin of cwu.plugins) {
+ plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ if (plugin.getContentTypeForMIMEType(plugin.actualType) != Ci.nsIObjectLoadingContent.TYPE_PLUGIN) {
+ continue;
+ }
+
+ let pluginInfo = this._getPluginInfo(plugin);
+ if (pluginInfo.permissionString === null) {
+ Components.utils.reportError("No permission string for active plugin.");
+ continue;
+ }
+ if (pluginsFound.has(pluginInfo.permissionString)) {
+ continue;
+ }
+ pluginsFound.add(pluginInfo.permissionString);
+
+ // Add the per-site permissions and details URLs to pluginInfo here
+ // because they are more expensive to compute and so we avoid it in
+ // the tighter loop above.
+ let permissionObj = Services.perms.
+ getPermissionObject(principal, pluginInfo.permissionString, false);
+ if (permissionObj) {
+ pluginInfo.pluginPermissionHost = permissionObj.host;
+ pluginInfo.pluginPermissionType = permissionObj.expireType;
+ }
+ else {
+ pluginInfo.pluginPermissionHost = principalHost;
+ pluginInfo.pluginPermissionType = undefined;
+ }
+
+ let url;
+ // TODO: allow the blocklist to specify a better link, bug 873093
+ if (pluginInfo.blocklistState == Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE) {
+ url = Services.urlFormatter.formatURLPref("plugins.update.url");
+ }
+ else if (pluginInfo.blocklistState != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
+ url = Services.blocklist.getPluginBlocklistURL(pluginInfo.pluginTag);
+ }
+ pluginInfo.detailsLink = url;
+
+ centerActions.push(pluginInfo);
+ }
+ centerActions.sort(function(a, b) {
+ return a.pluginName.localeCompare(b.pluginName);
+ });
+
+ notification.options.centerActions = centerActions;
+ },
+
+ /**
+ * Called from the plugin doorhanger to set the new permissions for a plugin
+ * and activate plugins if necessary.
+ * aNewState should be either "allownow" "allowalways" or "block"
+ */
+ _updatePluginPermission: function PH_setPermissionForPlugins(aNotification, aPluginInfo, aNewState) {
+ let permission;
+ let expireType;
+ let expireTime;
+
+ switch (aNewState) {
+ case "allownow":
+ permission = Ci.nsIPermissionManager.ALLOW_ACTION;
+ expireType = Ci.nsIPermissionManager.EXPIRE_SESSION;
+ expireTime = Date.now() + Services.prefs.getIntPref(kPrefSessionPersistMinutes) * 60 * 1000;
+ break;
+
+ case "allowalways":
+ permission = Ci.nsIPermissionManager.ALLOW_ACTION;
+ expireType = Ci.nsIPermissionManager.EXPIRE_TIME;
+ expireTime = Date.now() +
+ Services.prefs.getIntPref(kPrefPersistentDays) * 24 * 60 * 60 * 1000;
+ break;
+
+ case "block":
+ permission = Ci.nsIPermissionManager.PROMPT_ACTION;
+ expireType = Ci.nsIPermissionManager.EXPIRE_NEVER;
+ expireTime = 0;
+ break;
+
+ // In case a plugin has already been allowed in another tab, the "continue allowing" button
+ // shouldn't change any permissions but should run the plugin-enablement code below.
+ case "continue":
+ break;
+
+ default:
+ Cu.reportError(Error("Unexpected plugin state: " + aNewState));
+ return;
+ }
+
+ let browser = aNotification.browser;
+ let contentWindow = browser.contentWindow;
+ if (aNewState != "continue") {
+ let principal = contentWindow.document.nodePrincipal;
+ Services.perms.addFromPrincipal(principal, aPluginInfo.permissionString,
+ permission, expireType, expireTime);
+
+ if (aNewState == "block") {
+ return;
+ }
+ }
+
+ // Manually activate the plugins that would have been automatically
+ // activated.
+ let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ let plugins = cwu.plugins;
+ let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+
+ for (let plugin of plugins) {
+ plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ // canActivatePlugin will return false if this isn't a known plugin type,
+ // so the pluginHost.getPermissionStringForType call is protected
+ if (gPluginHandler.canActivatePlugin(plugin) &&
+ aPluginInfo.permissionString == pluginHost.getPermissionStringForType(plugin.actualType)) {
+ plugin.playPlugin();
+ }
+ }
+ },
+
+ _showClickToPlayNotification: function PH_showClickToPlayNotification(aBrowser, aPrimaryPlugin) {
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", aBrowser);
+
+ let contentWindow = aBrowser.contentWindow;
+ let contentDoc = aBrowser.contentDocument;
+ let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ // Pale Moon: cwu.plugins may contain non-plugin <object>s, filter them out
+ let plugins = cwu.plugins.filter(function(plugin) {
+ return (plugin.getContentTypeForMIMEType(plugin.actualType) ==
+ Ci.nsIObjectLoadingContent.TYPE_PLUGIN);
+ });
+ if (plugins.length == 0) {
+ if (notification) {
+ PopupNotifications.remove(notification);
+ }
+ return;
+ }
+
+ let icon = 'plugins-notification-icon';
+ for (let plugin of plugins) {
+ let fallbackType = plugin.pluginFallbackType;
+ if (fallbackType == plugin.PLUGIN_VULNERABLE_UPDATABLE ||
+ fallbackType == plugin.PLUGIN_VULNERABLE_NO_UPDATE ||
+ fallbackType == plugin.PLUGIN_BLOCKLISTED) {
+ icon = 'blocked-plugins-notification-icon';
+ break;
+ }
+ if (fallbackType == plugin.PLUGIN_CLICK_TO_PLAY) {
+ let overlay = contentDoc.getAnonymousElementByAttribute(plugin, "class", "mainBox");
+ if (!overlay || overlay.style.visibility == 'hidden') {
+ icon = 'alert-plugins-notification-icon';
+ }
+ }
+ }
+
+ let dismissed = notification ? notification.dismissed : true;
+ if (aPrimaryPlugin)
+ dismissed = false;
+
+ let primaryPluginPermission = null;
+ if (aPrimaryPlugin) {
+ primaryPluginPermission = this._getPluginInfo(aPrimaryPlugin).permissionString;
+ }
+
+ let options = {
+ dismissed: dismissed,
+ eventCallback: this._clickToPlayNotificationEventCallback,
+ primaryPlugin: primaryPluginPermission
+ };
+ PopupNotifications.show(aBrowser, "click-to-play-plugins",
+ "", icon,
+ null, null, options);
+ },
+
+ // Crashed-plugin observer. Notified once per plugin crash, before events
+ // are dispatched to individual plugin instances.
+ pluginCrashed : function(subject, topic, data) {
+ let propertyBag = subject;
+ if (!(propertyBag instanceof Ci.nsIPropertyBag2) ||
+ !(propertyBag instanceof Ci.nsIWritablePropertyBag2))
+ return;
+
+#ifdef MOZ_CRASHREPORTER
+ let pluginDumpID = propertyBag.getPropertyAsAString("pluginDumpID");
+ let browserDumpID= propertyBag.getPropertyAsAString("browserDumpID");
+ let shouldSubmit = gCrashReporter.submitReports;
+ let doPrompt = true; // XXX followup to get via gCrashReporter
+
+ // Submit automatically when appropriate.
+ if (pluginDumpID && shouldSubmit && !doPrompt) {
+ this.submitReport(pluginDumpID, browserDumpID);
+ // Submission is async, so we can't easily show failure UI.
+ propertyBag.setPropertyAsBool("submittedCrashReport", true);
+ }
+#endif
+ },
+
+ // Crashed-plugin event listener. Called for every instance of a
+ // plugin in content.
+ pluginInstanceCrashed: function (plugin, aEvent) {
+ // Ensure the plugin and event are of the right type.
+ if (!(aEvent instanceof Ci.nsIDOMDataContainerEvent))
+ return;
+
+ let submittedReport = aEvent.getData("submittedCrashReport");
+ let doPrompt = true; // XXX followup for .getData("doPrompt");
+ let submitReports = true; // XXX followup for .getData("submitReports");
+ let pluginName = aEvent.getData("pluginName");
+ let pluginDumpID = aEvent.getData("pluginDumpID");
+ let browserDumpID = aEvent.getData("browserDumpID");
+
+ // Remap the plugin name to a more user-presentable form.
+ pluginName = this.makeNicePluginName(pluginName);
+
+ let messageString = gNavigatorBundle.getFormattedString("crashedpluginsMessage.title", [pluginName]);
+
+ //
+ // Configure the crashed-plugin placeholder.
+ //
+
+ // Force a layout flush so the binding is attached.
+ plugin.clientTop;
+ let doc = plugin.ownerDocument;
+ let overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox");
+ let statusDiv = doc.getAnonymousElementByAttribute(plugin, "class", "submitStatus");
+#ifdef MOZ_CRASHREPORTER
+ let status;
+
+ // Determine which message to show regarding crash reports.
+ if (submittedReport) { // submitReports && !doPrompt, handled in observer
+ status = "submitted";
+ }
+ else if (!submitReports && !doPrompt) {
+ status = "noSubmit";
+ }
+ else { // doPrompt
+ status = "please";
+ this.getPluginUI(plugin, "submitButton").addEventListener("click",
+ function (event) {
+ if (event.button != 0 || !event.isTrusted)
+ return;
+ this.submitReport(pluginDumpID, browserDumpID, plugin);
+ pref.setBoolPref("", optInCB.checked);
+ }.bind(this));
+ let optInCB = this.getPluginUI(plugin, "submitURLOptIn");
+ let pref = Services.prefs.getBranch("dom.ipc.plugins.reportCrashURL");
+ optInCB.checked = pref.getBoolPref("");
+ }
+
+ // If we don't have a minidumpID, we can't (or didn't) submit anything.
+ // This can happen if the plugin is killed from the task manager.
+ if (!pluginDumpID) {
+ status = "noReport";
+ }
+
+ statusDiv.setAttribute("status", status);
+
+ let helpIcon = doc.getAnonymousElementByAttribute(plugin, "class", "helpIcon");
+ this.addLinkClickCallback(helpIcon, "openHelpPage");
+
+ // If we're showing the link to manually trigger report submission, we'll
+ // want to be able to update all the instances of the UI for this crash to
+ // show an updated message when a report is submitted.
+ if (doPrompt) {
+ let observer = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]),
+ observe : function(subject, topic, data) {
+ let propertyBag = subject;
+ if (!(propertyBag instanceof Ci.nsIPropertyBag2))
+ return;
+ // Ignore notifications for other crashes.
+ if (propertyBag.get("minidumpID") != pluginDumpID)
+ return;
+ statusDiv.setAttribute("status", data);
+ },
+
+ handleEvent : function(event) {
+ // Not expected to be called, just here for the closure.
+ }
+ }
+
+ // Use a weak reference, so we don't have to remove it...
+ Services.obs.addObserver(observer, "crash-report-status", true);
+ // ...alas, now we need something to hold a strong reference to prevent
+ // it from being GC. But I don't want to manually manage the reference's
+ // lifetime (which should be no greater than the page).
+ // Clever solution? Use a closue with an event listener on the document.
+ // When the doc goes away, so do the listener references and the closure.
+ doc.addEventListener("mozCleverClosureHack", observer, false);
+ }
+#endif
+
+ let crashText = doc.getAnonymousElementByAttribute(plugin, "class", "msgCrashedText");
+ crashText.textContent = messageString;
+
+ let browser = gBrowser.getBrowserForDocument(doc.defaultView.top.document);
+
+ let link = doc.getAnonymousElementByAttribute(plugin, "class", "reloadLink");
+ this.addLinkClickCallback(link, "reloadPage", browser);
+
+ let notificationBox = gBrowser.getNotificationBox(browser);
+
+ let isShowing = true;
+
+ // Is the <object>'s size too small to hold what we want to show?
+ if (this.isTooSmall(plugin, overlay)) {
+ // First try hiding the crash report submission UI.
+ statusDiv.removeAttribute("status");
+
+ if (this.isTooSmall(plugin, overlay)) {
+ // Hide the overlay's contents. Use visibility style, so that it doesn't
+ // collapse down to 0x0.
+ overlay.style.visibility = "hidden";
+ isShowing = false;
+ }
+ }
+
+ if (isShowing) {
+ // If a previous plugin on the page was too small and resulted in adding a
+ // notification bar, then remove it because this plugin instance it big
+ // enough to serve as in-content notification.
+ hideNotificationBar();
+ doc.mozNoPluginCrashedNotification = true;
+ } else {
+ // If another plugin on the page was large enough to show our UI, we don't
+ // want to show a notification bar.
+ if (!doc.mozNoPluginCrashedNotification)
+ showNotificationBar(pluginDumpID, browserDumpID);
+ }
+
+ function hideNotificationBar() {
+ let notification = notificationBox.getNotificationWithValue("plugin-crashed");
+ if (notification)
+ notificationBox.removeNotification(notification, true);
+ }
+
+ function showNotificationBar(pluginDumpID, browserDumpID) {
+ // If there's already an existing notification bar, don't do anything.
+ let notification = notificationBox.getNotificationWithValue("plugin-crashed");
+ if (notification)
+ return;
+
+ // Configure the notification bar
+ let priority = notificationBox.PRIORITY_WARNING_MEDIUM;
+ let iconURL = "chrome://mozapps/skin/plugins/notifyPluginCrashed.png";
+ let reloadLabel = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.label");
+ let reloadKey = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.accesskey");
+ let submitLabel = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.label");
+ let submitKey = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.accesskey");
+
+ let buttons = [{
+ label: reloadLabel,
+ accessKey: reloadKey,
+ popup: null,
+ callback: function() { browser.reload(); },
+ }];
+#ifdef MOZ_CRASHREPORTER
+ let submitButton = {
+ label: submitLabel,
+ accessKey: submitKey,
+ popup: null,
+ callback: function() { gPluginHandler.submitReport(pluginDumpID, browserDumpID); },
+ };
+ if (pluginDumpID)
+ buttons.push(submitButton);
+#endif
+
+ let notification = notificationBox.appendNotification(messageString, "plugin-crashed",
+ iconURL, priority, buttons);
+
+ // Add the "learn more" link.
+ let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ let link = notification.ownerDocument.createElementNS(XULNS, "label");
+ link.className = "text-link";
+ link.setAttribute("value", gNavigatorBundle.getString("crashedpluginsMessage.learnMore"));
+ let crashurl = formatURL("app.support.baseURL", true);
+ crashurl += "plugin-crashed-notificationbar";
+ link.href = crashurl;
+
+ let description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText");
+ description.appendChild(link);
+
+ // Remove the notfication when the page is reloaded.
+ doc.defaultView.top.addEventListener("unload", function() {
+ notificationBox.removeNotification(notification);
+ }, false);
+ }
+
+ }
+};
diff --git a/browser/base/content/browser-safebrowsing.js b/browser/base/content/browser-safebrowsing.js
new file mode 100644
index 000000000..e40a31957
--- /dev/null
+++ b/browser/base/content/browser-safebrowsing.js
@@ -0,0 +1,53 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifdef MOZ_SAFE_BROWSING
+var gSafeBrowsing = {
+
+ setReportPhishingMenu: function() {
+
+ // A phishing page will have a specific about:blocked content documentURI
+ var isPhishingPage = content.document.documentURI.startsWith("about:blocked?e=phishingBlocked");
+
+ // Show/hide the appropriate menu item.
+ document.getElementById("menu_HelpPopup_reportPhishingtoolmenu")
+ .hidden = isPhishingPage;
+ document.getElementById("menu_HelpPopup_reportPhishingErrortoolmenu")
+ .hidden = !isPhishingPage;
+
+ var broadcasterId = isPhishingPage
+ ? "reportPhishingErrorBroadcaster"
+ : "reportPhishingBroadcaster";
+
+ var broadcaster = document.getElementById(broadcasterId);
+ if (!broadcaster)
+ return;
+
+ var uri = getBrowser().currentURI;
+ if (uri && (uri.schemeIs("http") || uri.schemeIs("https")))
+ broadcaster.removeAttribute("disabled");
+ else
+ broadcaster.setAttribute("disabled", true);
+ },
+
+ /**
+ * Used to report a phishing page or a false positive
+ * @param name String One of "Phish", "Error", "Malware" or "MalwareError"
+ * @return String the report phishing URL.
+ */
+ getReportURL: function(name) {
+ var reportUrl = SafeBrowsing.getReportURL(name);
+
+ var pageUri = gBrowser.currentURI.clone();
+
+ // Remove the query to avoid including potentially sensitive data
+ if (pageUri instanceof Ci.nsIURL)
+ pageUri.query = '';
+
+ reportUrl += "&url=" + encodeURIComponent(pageUri.asciiSpec);
+
+ return reportUrl;
+ }
+}
+#endif
diff --git a/browser/base/content/browser-sets.inc b/browser/base/content/browser-sets.inc
new file mode 100644
index 000000000..c42287ed4
--- /dev/null
+++ b/browser/base/content/browser-sets.inc
@@ -0,0 +1,420 @@
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifdef XP_UNIX
+#ifndef XP_MACOSX
+#define XP_GNOME 1
+#endif
+#endif
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
+ <stringbundle id="bundle_shell" src="chrome://browser/locale/shellservice.properties"/>
+ <stringbundle id="bundle_preferences" src="chrome://browser/locale/preferences/preferences.properties"/>
+ </stringbundleset>
+
+ <commandset id="mainCommandSet">
+ <command id="cmd_newNavigator" oncommand="OpenBrowserWindow()"/>
+ <command id="cmd_handleBackspace" oncommand="BrowserHandleBackspace();" />
+ <command id="cmd_handleShiftBackspace" oncommand="BrowserHandleShiftBackspace();" />
+
+ <command id="cmd_newNavigatorTab" oncommand="BrowserOpenTab();"/>
+ <command id="Browser:OpenFile" oncommand="BrowserOpenFileWindow();"/>
+ <command id="Browser:SavePage" oncommand="saveDocument(window.content.document);"/>
+
+ <command id="Browser:SendLink"
+ oncommand="MailIntegration.sendLinkForWindow(window.content);"/>
+
+ <command id="cmd_pageSetup" oncommand="PrintUtils.showPageSetup();"/>
+ <command id="cmd_print" oncommand="PrintUtils.print();"/>
+ <command id="cmd_printPreview" oncommand="PrintUtils.printPreview(PrintPreviewListener);"/>
+ <command id="cmd_close" oncommand="BrowserCloseTabOrWindow()"/>
+ <command id="cmd_closeWindow" oncommand="BrowserTryToCloseWindow()"/>
+ <command id="cmd_ToggleTabsOnTop" oncommand="TabsOnTop.toggle()"/>
+ <command id="cmd_CustomizeToolbars" oncommand="BrowserCustomizeToolbar()"/>
+ <command id="cmd_quitApplication" oncommand="goQuitApplication()"/>
+
+
+ <commandset id="editMenuCommands"/>
+
+ <command id="View:PageSource" oncommand="BrowserViewSourceOfDocument(content.document);" observes="isImage"/>
+ <command id="View:PageInfo" oncommand="BrowserPageInfo();"/>
+ <command id="View:FullScreen" oncommand="BrowserFullScreen();"/>
+ <command id="cmd_find"
+ oncommand="gFindBar.onFindCommand();"
+ observes="isImage"/>
+ <command id="cmd_findAgain"
+ oncommand="gFindBar.onFindAgainCommand(false);"
+ observes="isImage"/>
+ <command id="cmd_findPrevious"
+ oncommand="gFindBar.onFindAgainCommand(true);"
+ observes="isImage"/>
+ <!-- work-around bug 392512 -->
+ <command id="Browser:AddBookmarkAs"
+ oncommand="PlacesCommandHook.bookmarkCurrentPage(true, PlacesUtils.bookmarksMenuFolderId);"/>
+ <!-- The command disabled state must be manually updated through
+ PlacesCommandHook.updateBookmarkAllTabsCommand() -->
+ <command id="Browser:BookmarkAllTabs"
+ oncommand="PlacesCommandHook.bookmarkCurrentPages();"/>
+ <command id="Browser:Home" oncommand="BrowserHome();"/>
+ <command id="Browser:Back" oncommand="BrowserBack();" disabled="true"/>
+ <command id="Browser:BackOrBackDuplicate" oncommand="BrowserBack(event);" disabled="true">
+ <observes element="Browser:Back" attribute="disabled"/>
+ </command>
+ <command id="Browser:Forward" oncommand="BrowserForward();" disabled="true"/>
+ <command id="Browser:ForwardOrForwardDuplicate" oncommand="BrowserForward(event);" disabled="true">
+ <observes element="Browser:Forward" attribute="disabled"/>
+ </command>
+ <command id="Browser:Stop" oncommand="BrowserStop();" disabled="true"/>
+ <command id="Browser:Reload" oncommand="if (event.shiftKey) BrowserReloadSkipCache(); else BrowserReload()" disabled="true"/>
+ <command id="Browser:ReloadOrDuplicate" oncommand="BrowserReloadOrDuplicate(event)" disabled="true">
+ <observes element="Browser:Reload" attribute="disabled"/>
+ </command>
+ <command id="Browser:ReloadSkipCache" oncommand="BrowserReloadSkipCache()" disabled="true">
+ <observes element="Browser:Reload" attribute="disabled"/>
+ </command>
+ <command id="Browser:NextTab" oncommand="gBrowser.tabContainer.advanceSelectedTab(1, true);"/>
+ <command id="Browser:PrevTab" oncommand="gBrowser.tabContainer.advanceSelectedTab(-1, true);"/>
+ <command id="Browser:ShowAllTabs" oncommand="allTabs.open();"/>
+ <command id="Browser:FocusNextFrame" oncommand="focusNextFrame(event);"/>
+ <command id="cmd_fullZoomReduce" oncommand="FullZoom.reduce()"/>
+ <command id="cmd_fullZoomEnlarge" oncommand="FullZoom.enlarge()"/>
+ <command id="cmd_fullZoomReset" oncommand="FullZoom.reset()"/>
+ <command id="cmd_fullZoomToggle" oncommand="ZoomManager.toggleZoom();"/>
+ <command id="cmd_gestureRotateLeft" oncommand="gGestureSupport.rotate(event.sourceEvent)"/>
+ <command id="cmd_gestureRotateRight" oncommand="gGestureSupport.rotate(event.sourceEvent)"/>
+ <command id="cmd_gestureRotateEnd" oncommand="gGestureSupport.rotateEnd()"/>
+ <command id="Browser:OpenLocation" oncommand="openLocation();"/>
+ <command id="Browser:RestoreLastSession" oncommand="restoreLastSession();" disabled="true"/>
+
+ <command id="Tools:Search" oncommand="BrowserSearch.webSearch();"/>
+ <command id="Tools:Downloads" oncommand="BrowserDownloadsUI();"/>
+ <command id="Tools:DevToolbox" oncommand="gDevToolsBrowser.toggleToolboxCommand(gBrowser);"/>
+ <command id="Tools:DevToolbar" oncommand="DeveloperToolbar.toggle();" disabled="true" hidden="true"/>
+ <command id="Tools:DevToolbarFocus" oncommand="DeveloperToolbar.focusToggle();" disabled="true"/>
+ <command id="Tools:ChromeDebugger" oncommand="BrowserDebuggerProcess.init();" disabled="true" hidden="true"/>
+ <command id="Tools:BrowserConsole" oncommand="HUDConsoleUI.toggleBrowserConsole();"/>
+ <command id="Tools:Scratchpad" oncommand="Scratchpad.openScratchpad();" disabled="true" hidden="true"/>
+ <command id="Tools:ResponsiveUI" oncommand="ResponsiveUI.toggle();" disabled="true" hidden="true"/>
+ <command id="Tools:Addons" oncommand="BrowserOpenAddonsMgr();"/>
+ <command id="Tools:ErrorConsole" oncommand="toJavaScriptConsole()" disabled="true" hidden="true"/>
+ <command id="Tools:DevToolsConnect" oncommand="gDevToolsBrowser.openConnectScreen(gBrowser)" disabled="true" hidden="true"/>
+ <command id="Tools:Sanitize"
+ oncommand="Cc['@mozilla.org/browser/browserglue;1'].getService(Ci.nsIBrowserGlue).sanitize(window);"/>
+ <command id="Tools:PrivateBrowsing"
+ oncommand="OpenBrowserWindow({private: true});"/>
+ <command id="History:UndoCloseTab" oncommand="undoCloseTab();"/>
+ <command id="History:UndoCloseWindow" oncommand="undoCloseWindow();"/>
+ <command id="Browser:ToggleAddonBar" oncommand="toggleAddonBar();"/>
+ <command id="Social:TogglePageMark" oncommand="SocialMark.togglePageMark();" disabled="true"/>
+ <command id="Social:SharePage" oncommand="SocialShare.sharePage();" disabled="true"/>
+ <command id="Social:ToggleSidebar" oncommand="Social.toggleSidebar();"/>
+ <command id="Social:ToggleNotifications" oncommand="Social.toggleNotifications();" hidden="true"/>
+ <command id="Social:FocusChat" oncommand="SocialChatBar.focus();" hidden="true" disabled="true"/>
+ <command id="Social:Toggle" oncommand="Social.toggle();" hidden="true"/>
+ <command id="Social:Addons" oncommand="BrowserOpenAddonsMgr('addons://list/service');"/>
+ </commandset>
+
+ <commandset id="placesCommands">
+ <command id="Browser:ShowAllBookmarks"
+ oncommand="PlacesCommandHook.showPlacesOrganizer('AllBookmarks');"/>
+ <command id="Browser:ShowAllHistory"
+ oncommand="PlacesCommandHook.showPlacesOrganizer('History');"/>
+ </commandset>
+
+ <broadcasterset id="mainBroadcasterSet">
+ <broadcaster id="viewBookmarksSidebar" autoCheck="false" label="&bookmarksButton.label;"
+ type="checkbox" group="sidebar" sidebarurl="chrome://browser/content/bookmarks/bookmarksPanel.xul"
+ oncommand="toggleSidebar('viewBookmarksSidebar');"/>
+
+ <!-- for both places and non-places, the sidebar lives at
+ chrome://browser/content/history/history-panel.xul so there are no
+ problems when switching between versions -->
+ <broadcaster id="viewHistorySidebar" autoCheck="false" sidebartitle="&historyButton.label;"
+ type="checkbox" group="sidebar"
+ sidebarurl="chrome://browser/content/history/history-panel.xul"
+ oncommand="toggleSidebar('viewHistorySidebar');"/>
+
+ <broadcaster id="viewWebPanelsSidebar" autoCheck="false"
+ type="checkbox" group="sidebar" sidebarurl="chrome://browser/content/web-panels.xul"
+ oncommand="toggleSidebar('viewWebPanelsSidebar');"/>
+
+ <!-- popup blocking menu items -->
+ <broadcaster id="blockedPopupAllowSite"
+ accesskey="&allowPopups.accesskey;"
+ oncommand="gPopupBlockerObserver.toggleAllowPopupsForSite(event);"/>
+ <broadcaster id="blockedPopupEditSettings"
+#ifdef XP_WIN
+ label="&editPopupSettings.label;"
+#else
+ label="&editPopupSettingsUnix.label;"
+#endif
+ accesskey="&editPopupSettings.accesskey;"
+ oncommand="gPopupBlockerObserver.editPopupSettings();"/>
+ <broadcaster id="blockedPopupDontShowMessage"
+ accesskey="&dontShowMessage.accesskey;"
+ type="checkbox"
+ oncommand="gPopupBlockerObserver.dontShowMessage();"/>
+ <broadcaster id="blockedPopupsSeparator"/>
+ <broadcaster id="isImage"/>
+ <broadcaster id="isFrameImage"/>
+ <broadcaster id="singleFeedMenuitemState" disabled="true"/>
+ <broadcaster id="multipleFeedsMenuState" hidden="true"/>
+#ifdef MOZ_SERVICES_SYNC
+ <broadcaster id="sync-setup-state"/>
+ <broadcaster id="sync-syncnow-state"/>
+#endif
+ <broadcaster id="workOfflineMenuitemState"/>
+ <broadcaster id="socialSidebarBroadcaster" hidden="true"/>
+ <broadcaster id="socialActiveBroadcaster" hidden="true"/>
+
+ <!-- DevTools broadcasters -->
+ <broadcaster id="devtoolsMenuBroadcaster_DevToolbox"
+ label="&devToolboxMenuItem.label;"
+ type="checkbox" autocheck="false"
+ command="Tools:DevToolbox"/>
+ <broadcaster id="devtoolsMenuBroadcaster_DevToolbar"
+ label="&devToolbarMenu.label;"
+ type="checkbox" autocheck="false"
+ command="Tools:DevToolbar"
+ key="key_devToolbar"/>
+ <broadcaster id="devtoolsMenuBroadcaster_ChromeDebugger"
+ label="&chromeDebuggerMenu.label;"
+ command="Tools:ChromeDebugger"/>
+ <broadcaster id="devtoolsMenuBroadcaster_BrowserConsole"
+ label="&browserConsoleCmd.label;"
+ key="key_browserConsole"
+ command="Tools:BrowserConsole"/>
+ <broadcaster id="devtoolsMenuBroadcaster_Scratchpad"
+ label="&scratchpad.label;"
+ command="Tools:Scratchpad"
+ key="key_scratchpad"/>
+ <broadcaster id="devtoolsMenuBroadcaster_ResponsiveUI"
+ label="&responsiveDesignTool.label;"
+ type="checkbox" autocheck="false"
+ command="Tools:ResponsiveUI"
+ key="key_responsiveUI"/>
+ <broadcaster id="devtoolsMenuBroadcaster_PageSource"
+ label="&pageSourceCmd.label;"
+ key="key_viewSource"
+ command="View:PageSource"/>
+ <broadcaster id="devtoolsMenuBroadcaster_ErrorConsole"
+ label="&errorConsoleCmd.label;"
+ command="Tools:ErrorConsole"/>
+ <broadcaster id="devtoolsMenuBroadcaster_GetMoreTools"
+ label="&getMoreDevtoolsCmd.label;"
+ oncommand="openUILinkIn('https://addons.mozilla.org/firefox/collections/mozilla/webdeveloper/', 'tab');"/>
+ <broadcaster id="devtoolsMenuBroadcaster_connect"
+ label="&devtoolsConnect.label;"
+ command="Tools:DevToolsConnect"/>
+
+ <!-- SocialAPI broadcasters -->
+ <broadcaster id="socialBroadcaster_userDetails"
+ notLoggedInLabel="&social.notLoggedIn.label;"/>
+ </broadcasterset>
+
+ <keyset id="mainKeyset">
+ <key id="key_newNavigator"
+ key="&newNavigatorCmd.key;"
+ command="cmd_newNavigator"
+ modifiers="accel"/>
+ <key id="key_newNavigatorTab" key="&tabCmd.commandkey;" modifiers="accel" command="cmd_newNavigatorTab"/>
+ <key id="focusURLBar" key="&openCmd.commandkey;" command="Browser:OpenLocation"
+ modifiers="accel"/>
+#ifndef XP_MACOSX
+ <key id="focusURLBar2" key="&urlbar.accesskey;" command="Browser:OpenLocation"
+ modifiers="alt"/>
+#endif
+
+#
+# Search Command Key Logic works like this:
+#
+# Unix: Ctrl+K (cross platform binding)
+# Ctrl+J (in case of emacs Ctrl-K conflict)
+# Mac: Cmd+K (cross platform binding)
+# Cmd+Opt+F (platform convention)
+# Win: Ctrl+K (cross platform binding)
+# Ctrl+E (IE compat)
+#
+# We support Ctrl+K on all platforms now and advertise it in the menu since it is
+# our standard - it is a "safe" choice since it is near no harmful keys like "W" as
+# "E" is. People mourning the loss of Ctrl+K for emacs compat can switch their GTK
+# system setting to use emacs emulation, and we should respect it. Focus-Search-Box
+# is a fundamental keybinding and we are maintaining a XP binding so that it is easy
+# for people to switch to Linux.
+#
+ <key id="key_search" key="&searchFocus.commandkey;" command="Tools:Search" modifiers="accel"/>
+#ifdef XP_MACOSX
+ <key id="key_search2" key="&findOnCmd.commandkey;" command="Tools:Search" modifiers="accel,alt"/>
+#endif
+#ifdef XP_WIN
+ <key id="key_search2" key="&searchFocus.commandkey2;" command="Tools:Search" modifiers="accel"/>
+#endif
+#ifdef XP_GNOME
+ <key id="key_search2" key="&searchFocusUnix.commandkey;" command="Tools:Search" modifiers="accel"/>
+ <key id="key_openDownloads" key="&downloadsUnix.commandkey;" command="Tools:Downloads" modifiers="accel,shift"/>
+#else
+ <key id="key_openDownloads" key="&downloads.commandkey;" command="Tools:Downloads" modifiers="accel"/>
+#endif
+ <key id="key_openAddons" key="&addons.commandkey;" command="Tools:Addons" modifiers="accel,shift"/>
+ <key id="key_browserConsole" key="&browserConsoleCmd.commandkey;" command="Tools:BrowserConsole" modifiers="accel,shift"/>
+ <key id="key_devToolbar" keycode="&devToolbar.keycode;" modifiers="shift"
+ keytext="&devToolbar.keytext;" command="Tools:DevToolbarFocus"/>
+ <key id="key_responsiveUI" key="&responsiveDesignTool.commandkey;" command="Tools:ResponsiveUI"
+#ifdef XP_MACOSX
+ modifiers="accel,alt"
+#else
+ modifiers="accel,shift"
+#endif
+ />
+ <key id="key_scratchpad" keycode="&scratchpad.keycode;" modifiers="shift"
+ keytext="&scratchpad.keytext;" command="Tools:Scratchpad"/>
+ <key id="openFileKb" key="&openFileCmd.commandkey;" command="Browser:OpenFile" modifiers="accel"/>
+ <key id="key_savePage" key="&savePageCmd.commandkey;" command="Browser:SavePage" modifiers="accel"/>
+ <key id="printKb" key="&printCmd.commandkey;" command="cmd_print" modifiers="accel"/>
+ <key id="key_close" key="&closeCmd.key;" command="cmd_close" modifiers="accel"/>
+ <key id="key_closeWindow" key="&closeCmd.key;" command="cmd_closeWindow" modifiers="accel,shift"/>
+ <key id="key_undo"
+ key="&undoCmd.key;"
+ modifiers="accel"/>
+#ifdef XP_UNIX
+ <key id="key_redo" key="&undoCmd.key;" modifiers="accel,shift"/>
+#else
+ <key id="key_redo" key="&redoCmd.key;" modifiers="accel"/>
+#endif
+ <key id="key_cut"
+ key="&cutCmd.key;"
+ modifiers="accel"/>
+ <key id="key_copy"
+ key="&copyCmd.key;"
+ modifiers="accel"/>
+ <key id="key_paste"
+ key="&pasteCmd.key;"
+ modifiers="accel"/>
+ <key id="key_delete" keycode="VK_DELETE" command="cmd_delete"/>
+ <key id="key_selectAll" key="&selectAllCmd.key;" modifiers="accel"/>
+
+ <key keycode="VK_BACK" command="cmd_handleBackspace"/>
+ <key keycode="VK_BACK" command="cmd_handleShiftBackspace" modifiers="shift"/>
+#ifndef XP_MACOSX
+ <key id="goBackKb" keycode="VK_LEFT" command="Browser:Back" modifiers="alt"/>
+ <key id="goForwardKb" keycode="VK_RIGHT" command="Browser:Forward" modifiers="alt"/>
+#else
+ <key id="goBackKb" keycode="VK_LEFT" command="Browser:Back" modifiers="accel" />
+ <key id="goForwardKb" keycode="VK_RIGHT" command="Browser:Forward" modifiers="accel" />
+#endif
+#ifdef XP_UNIX
+ <key id="goBackKb2" key="&goBackCmd.commandKey;" command="Browser:Back" modifiers="accel"/>
+ <key id="goForwardKb2" key="&goForwardCmd.commandKey;" command="Browser:Forward" modifiers="accel"/>
+#endif
+ <key id="goHome" keycode="VK_HOME" command="Browser:Home" modifiers="alt"/>
+ <key keycode="VK_F5" command="Browser:Reload"/>
+#ifndef XP_MACOSX
+ <key id="showAllHistoryKb" key="&showAllHistoryCmd.commandkey;" command="Browser:ShowAllHistory" modifiers="accel,shift"/>
+ <key keycode="VK_F5" command="Browser:ReloadSkipCache" modifiers="accel"/>
+ <key keycode="VK_F6" command="Browser:FocusNextFrame"/>
+ <key keycode="VK_F6" command="Browser:FocusNextFrame" modifiers="shift"/>
+ <key id="key_fullScreen" keycode="VK_F11" command="View:FullScreen"/>
+#else
+ <key id="key_fullScreen" key="&fullScreenCmd.macCommandKey;" command="View:FullScreen" modifiers="accel,control"/>
+ <key id="key_fullScreen_old" key="&fullScreenCmd.macCommandKey;" command="View:FullScreen" modifiers="accel,shift"/>
+ <key keycode="VK_F11" command="View:FullScreen"/>
+#endif
+ <key key="&reloadCmd.commandkey;" command="Browser:Reload" modifiers="accel" id="key_reload"/>
+ <key key="&reloadCmd.commandkey;" command="Browser:ReloadSkipCache" modifiers="accel,shift"/>
+ <key id="key_viewSource" key="&pageSourceCmd.commandkey;" command="View:PageSource" modifiers="accel"/>
+#ifndef XP_WIN
+ <key id="key_viewInfo" key="&pageInfoCmd.commandkey;" command="View:PageInfo" modifiers="accel"/>
+#endif
+ <key id="key_find" key="&findOnCmd.commandkey;" command="cmd_find" modifiers="accel"/>
+ <key id="key_findAgain" key="&findAgainCmd.commandkey;" command="cmd_findAgain" modifiers="accel"/>
+ <key id="key_findPrevious" key="&findAgainCmd.commandkey;" command="cmd_findPrevious" modifiers="accel,shift"/>
+ <key keycode="&findAgainCmd.commandkey2;" command="cmd_findAgain"/>
+ <key keycode="&findAgainCmd.commandkey2;" command="cmd_findPrevious" modifiers="shift"/>
+
+ <key id="addBookmarkAsKb" key="&bookmarkThisPageCmd.commandkey;" command="Browser:AddBookmarkAs" modifiers="accel"/>
+# Accel+Shift+A-F are reserved on GTK
+#ifndef MOZ_WIDGET_GTK
+ <key id="bookmarkAllTabsKb" key="&bookmarkThisPageCmd.commandkey;" oncommand="PlacesCommandHook.bookmarkCurrentPages();" modifiers="accel,shift"/>
+ <key id="manBookmarkKb" key="&bookmarksCmd.commandkey;" command="Browser:ShowAllBookmarks" modifiers="accel,shift"/>
+#else
+ <key id="manBookmarkKb" key="&bookmarksGtkCmd.commandkey;" command="Browser:ShowAllBookmarks" modifiers="accel,shift"/>
+#endif
+ <key id="viewBookmarksSidebarKb" key="&bookmarksCmd.commandkey;" command="viewBookmarksSidebar" modifiers="accel"/>
+#ifdef XP_WIN
+# Cmd+I is conventially mapped to Info on MacOS X, thus it should not be
+# overridden for other purposes there.
+ <key id="viewBookmarksSidebarWinKb" key="&bookmarksWinCmd.commandkey;" command="viewBookmarksSidebar" modifiers="accel"/>
+#endif
+
+ <key id="markPage" key="&markPageCmd.commandkey;" command="Social:TogglePageMark" modifiers="accel,shift"/>
+ <key id="focusChatBar" key="&social.chatBar.commandkey;" command="Social:FocusChat" modifiers="accel,shift"/>
+
+ <key id="key_stop" keycode="VK_ESCAPE" command="Browser:Stop"/>
+
+#ifdef XP_MACOSX
+ <key id="key_stop_mac" modifiers="accel" key="&stopCmd.macCommandKey;" command="Browser:Stop"/>
+#endif
+
+ <key id="key_gotoHistory"
+ key="&historySidebarCmd.commandKey;"
+#ifdef XP_MACOSX
+ modifiers="accel,shift"
+#else
+ modifiers="accel"
+#endif
+ command="viewHistorySidebar"/>
+
+ <key id="key_fullZoomReduce" key="&fullZoomReduceCmd.commandkey;" command="cmd_fullZoomReduce" modifiers="accel"/>
+ <key key="&fullZoomReduceCmd.commandkey2;" command="cmd_fullZoomReduce" modifiers="accel"/>
+ <key id="key_fullZoomEnlarge" key="&fullZoomEnlargeCmd.commandkey;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
+ <key key="&fullZoomEnlargeCmd.commandkey2;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
+ <key key="&fullZoomEnlargeCmd.commandkey3;" command="cmd_fullZoomEnlarge" modifiers="accel"/>
+ <key id="key_fullZoomReset" key="&fullZoomResetCmd.commandkey;" command="cmd_fullZoomReset" modifiers="accel"/>
+ <key key="&fullZoomResetCmd.commandkey2;" command="cmd_fullZoomReset" modifiers="accel"/>
+
+ <key id="key_showAllTabs" command="Browser:ShowAllTabs" keycode="VK_TAB" modifiers="control,shift"/>
+
+ <key id="key_switchTextDirection" key="&bidiSwitchTextDirectionItem.commandkey;" command="cmd_switchTextDirection" modifiers="accel,shift" />
+
+ <key id="key_privatebrowsing" command="Tools:PrivateBrowsing" key="&privateBrowsingCmd.commandkey;" modifiers="accel,shift"/>
+ <key id="key_sanitize" command="Tools:Sanitize" keycode="VK_DELETE" modifiers="accel,shift"/>
+#ifdef XP_MACOSX
+ <key id="key_sanitize_mac" command="Tools:Sanitize" keycode="VK_BACK" modifiers="accel,shift"/>
+#endif
+#ifdef XP_UNIX
+ <key id="key_quitApplication" key="&quitApplicationCmdUnix.key;" command="cmd_quitApplication" modifiers="accel"/>
+#endif
+
+#ifdef FULL_BROWSER_WINDOW
+ <key id="key_undoCloseTab" command="History:UndoCloseTab" key="&tabCmd.commandkey;" modifiers="accel,shift"/>
+#endif
+ <key id="key_undoCloseWindow" command="History:UndoCloseWindow" key="&newNavigatorCmd.key;" modifiers="accel,shift"/>
+
+#ifdef XP_GNOME
+#define NUM_SELECT_TAB_MODIFIER alt
+#else
+#define NUM_SELECT_TAB_MODIFIER accel
+#endif
+
+#expand <key id="key_selectTab1" oncommand="gBrowser.selectTabAtIndex(0, event);" key="1" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
+#expand <key id="key_selectTab2" oncommand="gBrowser.selectTabAtIndex(1, event);" key="2" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
+#expand <key id="key_selectTab3" oncommand="gBrowser.selectTabAtIndex(2, event);" key="3" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
+#expand <key id="key_selectTab4" oncommand="gBrowser.selectTabAtIndex(3, event);" key="4" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
+#expand <key id="key_selectTab5" oncommand="gBrowser.selectTabAtIndex(4, event);" key="5" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
+#expand <key id="key_selectTab6" oncommand="gBrowser.selectTabAtIndex(5, event);" key="6" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
+#expand <key id="key_selectTab7" oncommand="gBrowser.selectTabAtIndex(6, event);" key="7" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
+#expand <key id="key_selectTab8" oncommand="gBrowser.selectTabAtIndex(7, event);" key="8" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
+#expand <key id="key_selectLastTab" oncommand="gBrowser.selectTabAtIndex(-1, event);" key="9" modifiers="__NUM_SELECT_TAB_MODIFIER__"/>
+
+ <key id="key_toggleAddonBar" command="Browser:ToggleAddonBar" key="&toggleAddonBarCmd.key;" modifiers="accel"/>
+
+ </keyset>
+
+# Used by baseMenuOverlay
+#ifdef XP_MACOSX
+ <commandset id="baseMenuCommandSet" />
+#endif
+ <keyset id="baseMenuKeyset" />
diff --git a/browser/base/content/browser-social.js b/browser/base/content/browser-social.js
new file mode 100644
index 000000000..7a0ab726c
--- /dev/null
+++ b/browser/base/content/browser-social.js
@@ -0,0 +1,1406 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// the "exported" symbols
+let SocialUI,
+ SocialChatBar,
+ SocialFlyout,
+ SocialMark,
+ SocialShare,
+ SocialMenu,
+ SocialToolbar,
+ SocialSidebar;
+
+(function() {
+
+// The minimum sizes for the auto-resize panel code.
+const PANEL_MIN_HEIGHT = 100;
+const PANEL_MIN_WIDTH = 330;
+
+XPCOMUtils.defineLazyModuleGetter(this, "SharedFrame",
+ "resource:///modules/SharedFrame.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "OpenGraphBuilder", function() {
+ let tmp = {};
+ Cu.import("resource:///modules/Social.jsm", tmp);
+ return tmp.OpenGraphBuilder;
+});
+
+SocialUI = {
+ // Called on delayed startup to initialize the UI
+ init: function SocialUI_init() {
+ Services.obs.addObserver(this, "social:ambient-notification-changed", false);
+ Services.obs.addObserver(this, "social:profile-changed", false);
+ Services.obs.addObserver(this, "social:page-mark-config", false);
+ Services.obs.addObserver(this, "social:frameworker-error", false);
+ Services.obs.addObserver(this, "social:provider-set", false);
+ Services.obs.addObserver(this, "social:providers-changed", false);
+
+ Services.prefs.addObserver("social.sidebar.open", this, false);
+ Services.prefs.addObserver("social.toast-notifications.enabled", this, false);
+
+ gBrowser.addEventListener("ActivateSocialFeature", this._activationEventHandler.bind(this), true, true);
+
+ SocialChatBar.init();
+ SocialMark.init();
+ SocialShare.init();
+ SocialMenu.init();
+ SocialToolbar.init();
+ SocialSidebar.init();
+
+ if (!Social.initialized) {
+ Social.init();
+ } else {
+ // social was previously initialized, so it's not going to notify us of
+ // anything, so handle that now.
+ this.observe(null, "social:providers-changed", null);
+ this.observe(null, "social:provider-set", Social.provider ? Social.provider.origin : null);
+ }
+ },
+
+ // Called on window unload
+ uninit: function SocialUI_uninit() {
+ Services.obs.removeObserver(this, "social:ambient-notification-changed");
+ Services.obs.removeObserver(this, "social:profile-changed");
+ Services.obs.removeObserver(this, "social:page-mark-config");
+ Services.obs.removeObserver(this, "social:frameworker-error");
+ Services.obs.removeObserver(this, "social:provider-set");
+ Services.obs.removeObserver(this, "social:providers-changed");
+
+ Services.prefs.removeObserver("social.sidebar.open", this);
+ Services.prefs.removeObserver("social.toast-notifications.enabled", this);
+ },
+
+ _matchesCurrentProvider: function (origin) {
+ return Social.provider && Social.provider.origin == origin;
+ },
+
+ observe: function SocialUI_observe(subject, topic, data) {
+ // Exceptions here sometimes don't get reported properly, report them
+ // manually :(
+ try {
+ switch (topic) {
+ case "social:provider-set":
+ // Social.provider has changed (possibly to null), update any state
+ // which depends on it.
+ this._updateActiveUI();
+ this._updateMenuItems();
+
+ SocialFlyout.unload();
+ SocialChatBar.update();
+ SocialShare.update();
+ SocialSidebar.update();
+ SocialMark.update();
+ SocialToolbar.update();
+ SocialMenu.populate();
+ break;
+ case "social:providers-changed":
+ // the list of providers changed - this may impact the "active" UI.
+ this._updateActiveUI();
+ // and the multi-provider menu
+ SocialToolbar.populateProviderMenus();
+ SocialShare.populateProviderMenu();
+ break;
+
+ // Provider-specific notifications
+ case "social:ambient-notification-changed":
+ if (this._matchesCurrentProvider(data)) {
+ SocialToolbar.updateButton();
+ SocialMenu.populate();
+ }
+ break;
+ case "social:profile-changed":
+ if (this._matchesCurrentProvider(data)) {
+ SocialToolbar.updateProvider();
+ SocialMark.update();
+ SocialChatBar.update();
+ }
+ break;
+ case "social:page-mark-config":
+ if (this._matchesCurrentProvider(data)) {
+ SocialMark.updateMarkState();
+ }
+ break;
+ case "social:frameworker-error":
+ if (this.enabled && Social.provider.origin == data) {
+ SocialSidebar.setSidebarErrorMessage();
+ }
+ break;
+
+ case "nsPref:changed":
+ if (data == "social.sidebar.open") {
+ SocialSidebar.update();
+ } else if (data == "social.toast-notifications.enabled") {
+ SocialToolbar.updateButton();
+ }
+ break;
+ }
+ } catch (e) {
+ Components.utils.reportError(e + "\n" + e.stack);
+ throw e;
+ }
+ },
+
+ nonBrowserWindowInit: function SocialUI_nonBrowserInit() {
+ // Disable the social menu item in non-browser windows
+ document.getElementById("menu_socialAmbientMenu").hidden = true;
+ },
+
+ // Miscellaneous helpers
+ showProfile: function SocialUI_showProfile() {
+ if (Social.haveLoggedInUser())
+ openUILinkIn(Social.provider.profile.profileURL, "tab");
+ else {
+ // XXX Bug 789585 will implement an API for provider-specified login pages.
+ openUILinkIn(Social.provider.origin, "tab");
+ }
+ },
+
+ _updateActiveUI: function SocialUI_updateActiveUI() {
+ // The "active" UI isn't dependent on there being a provider, just on
+ // social being "active" (but also chromeless/PB)
+ let enabled = Social.providers.length > 0 && !this._chromeless &&
+ !PrivateBrowsingUtils.isWindowPrivate(window);
+ let broadcaster = document.getElementById("socialActiveBroadcaster");
+ broadcaster.hidden = !enabled;
+
+ let toggleCommand = document.getElementById("Social:Toggle");
+ toggleCommand.setAttribute("hidden", enabled ? "false" : "true");
+
+ if (enabled) {
+ // enabled == true means we at least have a defaultProvider
+ let provider = Social.provider || Social.defaultProvider;
+ // We only need to update the command itself - all our menu items use it.
+ let label = gNavigatorBundle.getFormattedString(Social.provider ?
+ "social.turnOff.label" :
+ "social.turnOn.label",
+ [provider.name]);
+ let accesskey = gNavigatorBundle.getString(Social.provider ?
+ "social.turnOff.accesskey" :
+ "social.turnOn.accesskey");
+ toggleCommand.setAttribute("label", label);
+ toggleCommand.setAttribute("accesskey", accesskey);
+ }
+ },
+
+ _updateMenuItems: function () {
+ let provider = Social.provider || Social.defaultProvider;
+ if (!provider)
+ return;
+ // The View->Sidebar and Menubar->Tools menu.
+ for (let id of ["menu_socialSidebar", "menu_socialAmbientMenu"])
+ document.getElementById(id).setAttribute("label", provider.name);
+ },
+
+ // This handles "ActivateSocialFeature" events fired against content documents
+ // in this window.
+ _activationEventHandler: function SocialUI_activationHandler(e) {
+ let targetDoc;
+ let node;
+ if (e.target instanceof HTMLDocument) {
+ // version 0 support
+ targetDoc = e.target;
+ node = targetDoc.documentElement
+ } else {
+ targetDoc = e.target.ownerDocument;
+ node = e.target;
+ }
+ if (!(targetDoc instanceof HTMLDocument))
+ return;
+
+ // Ignore events fired in background tabs or iframes
+ if (targetDoc.defaultView != content)
+ return;
+
+ // If we are in PB mode, we silently do nothing (bug 829404 exists to
+ // do something sensible here...)
+ if (PrivateBrowsingUtils.isWindowPrivate(window))
+ return;
+
+ // If the last event was received < 1s ago, ignore this one
+ let now = Date.now();
+ if (now - Social.lastEventReceived < 1000)
+ return;
+ Social.lastEventReceived = now;
+
+ // We only want to activate if it is as a result of user input.
+ let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ if (!dwu.isHandlingUserInput) {
+ Cu.reportError("attempt to activate provider without user input from " + targetDoc.nodePrincipal.origin);
+ return;
+ }
+
+ let data = node.getAttribute("data-service");
+ if (data) {
+ try {
+ data = JSON.parse(data);
+ } catch(e) {
+ Cu.reportError("Social Service manifest parse error: "+e);
+ return;
+ }
+ }
+ Social.installProvider(targetDoc, data, function(manifest) {
+ this.doActivation(manifest.origin);
+ }.bind(this));
+ },
+
+ doActivation: function SocialUI_doActivation(origin) {
+ // Keep track of the old provider in case of undo
+ let oldOrigin = Social.provider ? Social.provider.origin : "";
+
+ // Enable the social functionality, and indicate that it was activated
+ Social.activateFromOrigin(origin, function(provider) {
+ // Provider to activate may not have been found
+ if (!provider)
+ return;
+
+ // Show a warning, allow undoing the activation
+ let description = document.getElementById("social-activation-message");
+ let labels = description.getElementsByTagName("label");
+ let uri = Services.io.newURI(provider.origin, null, null)
+ labels[0].setAttribute("value", uri.host);
+ labels[1].setAttribute("onclick", "BrowserOpenAddonsMgr('addons://list/service'); SocialUI.activationPanel.hidePopup();")
+
+ let icon = document.getElementById("social-activation-icon");
+ if (provider.icon64URL || provider.icon32URL) {
+ icon.setAttribute('src', provider.icon64URL || provider.icon32URL);
+ icon.hidden = false;
+ } else {
+ icon.removeAttribute('src');
+ icon.hidden = true;
+ }
+
+ let notificationPanel = SocialUI.activationPanel;
+ // Set the origin being activated and the previously active one, to allow undo
+ notificationPanel.setAttribute("origin", provider.origin);
+ notificationPanel.setAttribute("oldorigin", oldOrigin);
+
+ // Show the panel
+ notificationPanel.hidden = false;
+ setTimeout(function () {
+ notificationPanel.openPopup(SocialToolbar.button, "bottomcenter topright");
+ }, 0);
+ });
+ },
+
+ undoActivation: function SocialUI_undoActivation() {
+ let origin = this.activationPanel.getAttribute("origin");
+ let oldOrigin = this.activationPanel.getAttribute("oldorigin");
+ Social.deactivateFromOrigin(origin, oldOrigin);
+ this.activationPanel.hidePopup();
+ Social.uninstallProvider(origin);
+ },
+
+ showLearnMore: function() {
+ this.activationPanel.hidePopup();
+ let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "social-api";
+ openUILinkIn(url, "tab");
+ },
+
+ get activationPanel() {
+ return document.getElementById("socialActivatedNotification");
+ },
+
+ closeSocialPanelForLinkTraversal: function (target, linkNode) {
+ // No need to close the panel if this traversal was not retargeted
+ if (target == "" || target == "_self")
+ return;
+
+ // Check to see whether this link traversal was in a social panel
+ let win = linkNode.ownerDocument.defaultView;
+ let container = win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+ let containerParent = container.parentNode;
+ if (containerParent.classList.contains("social-panel") &&
+ containerParent instanceof Ci.nsIDOMXULPopupElement) {
+ // allow the link traversal to finish before closing the panel
+ setTimeout(() => {
+ containerParent.hidePopup();
+ }, 0);
+ }
+ },
+
+ get _chromeless() {
+ // Is this a popup window that doesn't want chrome shown?
+ let docElem = document.documentElement;
+ // extrachrome is not restored during session restore, so we need
+ // to check for the toolbar as well.
+ let chromeless = docElem.getAttribute("chromehidden").contains("extrachrome") ||
+ docElem.getAttribute('chromehidden').contains("toolbar");
+ // This property is "fixed" for a window, so avoid doing the check above
+ // multiple times...
+ delete this._chromeless;
+ this._chromeless = chromeless;
+ return chromeless;
+ },
+
+ get enabled() {
+ // Returns whether social is enabled *for this window*.
+ if (this._chromeless || PrivateBrowsingUtils.isWindowPrivate(window))
+ return false;
+ return !!Social.provider;
+ },
+
+}
+
+SocialChatBar = {
+ init: function() {
+ },
+ get chatbar() {
+ return document.getElementById("pinnedchats");
+ },
+ // Whether the chatbar is available for this window. Note that in full-screen
+ // mode chats are available, but not shown.
+ get isAvailable() {
+ return SocialUI.enabled && Social.haveLoggedInUser();
+ },
+ // Does this chatbar have any chats (whether minimized, collapsed or normal)
+ get hasChats() {
+ return !!this.chatbar.firstElementChild;
+ },
+ openChat: function(aProvider, aURL, aCallback, aMode) {
+ if (!this.isAvailable)
+ return false;
+ this.chatbar.openChat(aProvider, aURL, aCallback, aMode);
+ // We only want to focus the chat if it is as a result of user input.
+ let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ if (dwu.isHandlingUserInput)
+ this.chatbar.focus();
+ return true;
+ },
+ update: function() {
+ let command = document.getElementById("Social:FocusChat");
+ if (!this.isAvailable) {
+ this.chatbar.removeAll();
+ this.chatbar.hidden = command.hidden = true;
+ } else {
+ this.chatbar.hidden = command.hidden = false;
+ }
+ command.setAttribute("disabled", command.hidden ? "true" : "false");
+ },
+ focus: function SocialChatBar_focus() {
+ this.chatbar.focus();
+ }
+}
+
+function sizeSocialPanelToContent(panel, iframe) {
+ // FIXME: bug 764787: Maybe we can use nsIDOMWindowUtils.getRootBounds() here?
+ let doc = iframe.contentDocument;
+ if (!doc || !doc.body) {
+ return;
+ }
+ // We need an element to use for sizing our panel. See if the body defines
+ // an id for that element, otherwise use the body itself.
+ let body = doc.body;
+ let bodyId = body.getAttribute("contentid");
+ if (bodyId) {
+ body = doc.getElementById(bodyId) || doc.body;
+ }
+ // offsetHeight/Width don't include margins, so account for that.
+ let cs = doc.defaultView.getComputedStyle(body);
+ let computedHeight = parseInt(cs.marginTop) + body.offsetHeight + parseInt(cs.marginBottom);
+ let height = Math.max(computedHeight, PANEL_MIN_HEIGHT);
+ let computedWidth = parseInt(cs.marginLeft) + body.offsetWidth + parseInt(cs.marginRight);
+ let width = Math.max(computedWidth, PANEL_MIN_WIDTH);
+ iframe.style.width = width + "px";
+ iframe.style.height = height + "px";
+ // since we do not use panel.sizeTo, we need to adjust the arrow ourselves
+ if (panel.state == "open")
+ panel.adjustArrowPosition();
+}
+
+function DynamicResizeWatcher() {
+ this._mutationObserver = null;
+}
+
+DynamicResizeWatcher.prototype = {
+ start: function DynamicResizeWatcher_start(panel, iframe) {
+ this.stop(); // just in case...
+ let doc = iframe.contentDocument;
+ this._mutationObserver = new iframe.contentWindow.MutationObserver(function(mutations) {
+ sizeSocialPanelToContent(panel, iframe);
+ });
+ // Observe anything that causes the size to change.
+ let config = {attributes: true, characterData: true, childList: true, subtree: true};
+ this._mutationObserver.observe(doc, config);
+ // and since this may be setup after the load event has fired we do an
+ // initial resize now.
+ sizeSocialPanelToContent(panel, iframe);
+ },
+ stop: function DynamicResizeWatcher_stop() {
+ if (this._mutationObserver) {
+ try {
+ this._mutationObserver.disconnect();
+ } catch (ex) {
+ // may get "TypeError: can't access dead object" which seems strange,
+ // but doesn't seem to indicate a real problem, so ignore it...
+ }
+ this._mutationObserver = null;
+ }
+ }
+}
+
+SocialFlyout = {
+ get panel() {
+ return document.getElementById("social-flyout-panel");
+ },
+
+ get iframe() {
+ if (!this.panel.firstChild)
+ this._createFrame();
+ return this.panel.firstChild;
+ },
+
+ dispatchPanelEvent: function(name) {
+ let doc = this.iframe.contentDocument;
+ let evt = doc.createEvent("CustomEvent");
+ evt.initCustomEvent(name, true, true, {});
+ doc.documentElement.dispatchEvent(evt);
+ },
+
+ _createFrame: function() {
+ let panel = this.panel;
+ if (!SocialUI.enabled || panel.firstChild)
+ return;
+ // create and initialize the panel for this window
+ let iframe = document.createElement("iframe");
+ iframe.setAttribute("type", "content");
+ iframe.setAttribute("class", "social-panel-frame");
+ iframe.setAttribute("flex", "1");
+ iframe.setAttribute("tooltip", "aHTMLTooltip");
+ iframe.setAttribute("origin", Social.provider.origin);
+ panel.appendChild(iframe);
+ },
+
+ setFlyoutErrorMessage: function SF_setFlyoutErrorMessage() {
+ this.iframe.removeAttribute("src");
+ this.iframe.webNavigation.loadURI("about:socialerror?mode=compactInfo", null, null, null, null);
+ sizeSocialPanelToContent(this.panel, this.iframe);
+ },
+
+ unload: function() {
+ let panel = this.panel;
+ panel.hidePopup();
+ if (!panel.firstChild)
+ return
+ let iframe = panel.firstChild;
+ if (iframe.socialErrorListener)
+ iframe.socialErrorListener.remove();
+ panel.removeChild(iframe);
+ },
+
+ onShown: function(aEvent) {
+ let panel = this.panel;
+ let iframe = this.iframe;
+ this._dynamicResizer = new DynamicResizeWatcher();
+ iframe.docShell.isActive = true;
+ iframe.docShell.isAppTab = true;
+ if (iframe.contentDocument.readyState == "complete") {
+ this._dynamicResizer.start(panel, iframe);
+ this.dispatchPanelEvent("socialFrameShow");
+ } else {
+ // first time load, wait for load and dispatch after load
+ iframe.addEventListener("load", function panelBrowserOnload(e) {
+ iframe.removeEventListener("load", panelBrowserOnload, true);
+ setTimeout(function() {
+ if (SocialFlyout._dynamicResizer) { // may go null if hidden quickly
+ SocialFlyout._dynamicResizer.start(panel, iframe);
+ SocialFlyout.dispatchPanelEvent("socialFrameShow");
+ }
+ }, 0);
+ }, true);
+ }
+ },
+
+ onHidden: function(aEvent) {
+ this._dynamicResizer.stop();
+ this._dynamicResizer = null;
+ this.iframe.docShell.isActive = false;
+ this.dispatchPanelEvent("socialFrameHide");
+ },
+
+ load: function(aURL, cb) {
+ if (!Social.provider)
+ return;
+
+ this.panel.hidden = false;
+ let iframe = this.iframe;
+ // same url with only ref difference does not cause a new load, so we
+ // want to go right to the callback
+ let src = iframe.contentDocument && iframe.contentDocument.documentURIObject;
+ if (!src || !src.equalsExceptRef(Services.io.newURI(aURL, null, null))) {
+ iframe.addEventListener("load", function documentLoaded() {
+ iframe.removeEventListener("load", documentLoaded, true);
+ cb();
+ }, true);
+ // Force a layout flush by calling .clientTop so
+ // that the docShell of this frame is created
+ iframe.clientTop;
+ Social.setErrorListener(iframe, SocialFlyout.setFlyoutErrorMessage.bind(SocialFlyout))
+ iframe.setAttribute("src", aURL);
+ } else {
+ // we still need to set the src to trigger the contents hashchange event
+ // for ref changes
+ iframe.setAttribute("src", aURL);
+ cb();
+ }
+ },
+
+ open: function(aURL, yOffset, aCallback) {
+ // Hide any other social panels that may be open.
+ document.getElementById("social-notification-panel").hidePopup();
+
+ if (!SocialUI.enabled)
+ return;
+ let panel = this.panel;
+ let iframe = this.iframe;
+
+ this.load(aURL, function() {
+ sizeSocialPanelToContent(panel, iframe);
+ let anchor = document.getElementById("social-sidebar-browser");
+ if (panel.state == "open") {
+ panel.moveToAnchor(anchor, "start_before", 0, yOffset, false);
+ } else {
+ panel.openPopup(anchor, "start_before", 0, yOffset, false, false);
+ }
+ if (aCallback) {
+ try {
+ aCallback(iframe.contentWindow);
+ } catch(e) {
+ Cu.reportError(e);
+ }
+ }
+ });
+ }
+}
+
+SocialShare = {
+ // Called once, after window load, when the Social.provider object is initialized
+ init: function() {},
+
+ get panel() {
+ return document.getElementById("social-share-panel");
+ },
+
+ get iframe() {
+ // first element is our menu vbox.
+ if (this.panel.childElementCount == 1)
+ return null;
+ else
+ return this.panel.lastChild;
+ },
+
+ _createFrame: function() {
+ let panel = this.panel;
+ if (!SocialUI.enabled || this.iframe)
+ return;
+ this.panel.hidden = false;
+ // create and initialize the panel for this window
+ let iframe = document.createElement("iframe");
+ iframe.setAttribute("type", "content");
+ iframe.setAttribute("class", "social-share-frame");
+ iframe.setAttribute("flex", "1");
+ panel.appendChild(iframe);
+ this.populateProviderMenu();
+ },
+
+ getSelectedProvider: function() {
+ let provider;
+ let lastProviderOrigin = this.iframe && this.iframe.getAttribute("origin");
+ if (lastProviderOrigin) {
+ provider = Social._getProviderFromOrigin(lastProviderOrigin);
+ }
+ if (!provider)
+ provider = Social.provider || Social.defaultProvider;
+ // if our provider has no shareURL, select the first one that does
+ if (provider && !provider.shareURL) {
+ let providers = [p for (p of Social.providers) if (p.shareURL)];
+ provider = providers.length > 0 && providers[0];
+ }
+ return provider;
+ },
+
+ populateProviderMenu: function() {
+ if (!this.iframe)
+ return;
+ let providers = [p for (p of Social.providers) if (p.shareURL)];
+ let hbox = document.getElementById("social-share-provider-buttons");
+ // selectable providers are inserted before the provider-menu seperator,
+ // remove any menuitems in that area
+ while (hbox.firstChild) {
+ hbox.removeChild(hbox.firstChild);
+ }
+ // reset our share toolbar
+ // only show a selection if there is more than one
+ if (!SocialUI.enabled || providers.length < 2) {
+ this.panel.firstChild.hidden = true;
+ return;
+ }
+ let selectedProvider = this.getSelectedProvider();
+ for (let provider of providers) {
+ let button = document.createElement("toolbarbutton");
+ button.setAttribute("class", "toolbarbutton share-provider-button");
+ button.setAttribute("type", "radio");
+ button.setAttribute("group", "share-providers");
+ button.setAttribute("image", provider.iconURL);
+ button.setAttribute("tooltiptext", provider.name);
+ button.setAttribute("origin", provider.origin);
+ button.setAttribute("oncommand", "SocialShare.sharePage(this.getAttribute('origin')); this.checked=true;");
+ if (provider == selectedProvider) {
+ this.defaultButton = button;
+ }
+ hbox.appendChild(button);
+ }
+ if (!this.defaultButton) {
+ this.defaultButton = hbox.firstChild
+ }
+ this.defaultButton.setAttribute("checked", "true");
+ this.panel.firstChild.hidden = false;
+ },
+
+ get shareButton() {
+ return document.getElementById("social-share-button");
+ },
+
+ canSharePage: function(aURI) {
+ // we do not enable sharing from private sessions
+ if (PrivateBrowsingUtils.isWindowPrivate(window))
+ return false;
+
+ if (!aURI || !(aURI.schemeIs('http') || aURI.schemeIs('https')))
+ return false;
+ return true;
+ },
+
+ update: function() {
+ let shareButton = this.shareButton;
+ shareButton.hidden = !SocialUI.enabled ||
+ [p for (p of Social.providers) if (p.shareURL)].length == 0;
+ shareButton.disabled = shareButton.hidden || !this.canSharePage(gBrowser.currentURI);
+
+ // also update the relevent command's disabled state so the keyboard
+ // shortcut only works when available.
+ let cmd = document.getElementById("Social:SharePage");
+ cmd.setAttribute("disabled", shareButton.disabled ? "true" : "false");
+ },
+
+ onShowing: function() {
+ this.shareButton.setAttribute("open", "true");
+ },
+
+ onHidden: function() {
+ this.shareButton.removeAttribute("open");
+ this.iframe.setAttribute("src", "data:text/plain;charset=utf8,");
+ this.currentShare = null;
+ },
+
+ setErrorMessage: function() {
+ let iframe = this.iframe;
+ if (!iframe)
+ return;
+
+ iframe.removeAttribute("src");
+ iframe.webNavigation.loadURI("about:socialerror?mode=compactInfo&origin=" +
+ encodeURIComponent(iframe.getAttribute("origin")),
+ null, null, null, null);
+ sizeSocialPanelToContent(this.panel, iframe);
+ },
+
+ sharePage: function(providerOrigin, graphData) {
+ // if providerOrigin is undefined, we use the last-used provider, or the
+ // current/default provider. The provider selection in the share panel
+ // will call sharePage with an origin for us to switch to.
+ this._createFrame();
+ let iframe = this.iframe;
+ let provider;
+ if (providerOrigin)
+ provider = Social._getProviderFromOrigin(providerOrigin);
+ else
+ provider = this.getSelectedProvider();
+ if (!provider || !provider.shareURL)
+ return;
+
+ // graphData is an optional param that either defines the full set of data
+ // to be shared, or partial data about the current page. It is set by a call
+ // in mozSocial API, or via nsContentMenu calls. If it is present, it MUST
+ // define at least url. If it is undefined, we're sharing the current url in
+ // the browser tab.
+ let sharedURI = graphData ? Services.io.newURI(graphData.url, null, null) :
+ gBrowser.currentURI;
+ if (!this.canSharePage(sharedURI))
+ return;
+
+ // the point of this action type is that we can use existing share
+ // endpoints (e.g. oexchange) that do not support additional
+ // socialapi functionality. One tweak is that we shoot an event
+ // containing the open graph data.
+ let pageData = graphData ? graphData : this.currentShare;
+ if (!pageData || sharedURI == gBrowser.currentURI) {
+ pageData = OpenGraphBuilder.getData(gBrowser);
+ if (graphData) {
+ // overwrite data retreived from page with data given to us as a param
+ for (let p in graphData) {
+ pageData[p] = graphData[p];
+ }
+ }
+ }
+ this.currentShare = pageData;
+
+ let shareEndpoint = this._generateShareEndpointURL(provider.shareURL, pageData);
+
+ this._dynamicResizer = new DynamicResizeWatcher();
+ // if we've already loaded this provider/page share endpoint, we don't want
+ // to add another load event listener.
+ let reload = true;
+ let endpointMatch = shareEndpoint == iframe.getAttribute("src");
+ let docLoaded = iframe.contentDocument && iframe.contentDocument.readyState == "complete";
+ if (endpointMatch && docLoaded) {
+ reload = shareEndpoint != iframe.contentDocument.location.spec;
+ }
+ if (!reload) {
+ this._dynamicResizer.start(this.panel, iframe);
+ iframe.docShell.isActive = true;
+ iframe.docShell.isAppTab = true;
+ let evt = iframe.contentDocument.createEvent("CustomEvent");
+ evt.initCustomEvent("OpenGraphData", true, true, JSON.stringify(pageData));
+ iframe.contentDocument.documentElement.dispatchEvent(evt);
+ } else {
+ // first time load, wait for load and dispatch after load
+ iframe.addEventListener("load", function panelBrowserOnload(e) {
+ iframe.removeEventListener("load", panelBrowserOnload, true);
+ iframe.docShell.isActive = true;
+ iframe.docShell.isAppTab = true;
+ setTimeout(function() {
+ if (SocialShare._dynamicResizer) { // may go null if hidden quickly
+ SocialShare._dynamicResizer.start(iframe.parentNode, iframe);
+ }
+ }, 0);
+ let evt = iframe.contentDocument.createEvent("CustomEvent");
+ evt.initCustomEvent("OpenGraphData", true, true, JSON.stringify(pageData));
+ iframe.contentDocument.documentElement.dispatchEvent(evt);
+ }, true);
+ }
+ // always ensure that origin belongs to the endpoint
+ let uri = Services.io.newURI(shareEndpoint, null, null);
+ iframe.setAttribute("origin", provider.origin);
+ iframe.setAttribute("src", shareEndpoint);
+
+ let navBar = document.getElementById("nav-bar");
+ let anchor = navBar.getAttribute("mode") == "text" ?
+ document.getAnonymousElementByAttribute(this.shareButton, "class", "toolbarbutton-text") :
+ document.getAnonymousElementByAttribute(this.shareButton, "class", "toolbarbutton-icon");
+ this.panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
+ Social.setErrorListener(iframe, this.setErrorMessage.bind(this));
+ },
+
+ _generateShareEndpointURL: function(shareURL, pageData) {
+ // support for existing share endpoints by supporting their querystring
+ // arguments. parse the query string template and do replacements where
+ // necessary the query names may be different than ours, so we could see
+ // u=%{url} or url=%{url}
+ let [shareEndpoint, queryString] = shareURL.split("?");
+ let query = {};
+ if (queryString) {
+ queryString.split('&').forEach(function (val) {
+ let [name, value] = val.split('=');
+ let p = /%\{(.+)\}/.exec(value);
+ if (!p) {
+ // preserve non-template query vars
+ query[name] = value;
+ } else if (pageData[p[1]]) {
+ query[name] = pageData[p[1]];
+ } else if (p[1] == "body") {
+ // build a body for emailers
+ let body = "";
+ if (pageData.title)
+ body += pageData.title + "\n\n";
+ if (pageData.description)
+ body += pageData.description + "\n\n";
+ if (pageData.text)
+ body += pageData.text + "\n\n";
+ body += pageData.url;
+ query["body"] = body;
+ }
+ });
+ }
+ var str = [];
+ for (let p in query)
+ str.push(p + "=" + encodeURIComponent(query[p]));
+ if (str.length)
+ shareEndpoint = shareEndpoint + "?" + str.join("&");
+ return shareEndpoint;
+ }
+};
+
+SocialMark = {
+ // Called once, after window load, when the Social.provider object is initialized
+ init: function SSB_init() {
+ },
+
+ get button() {
+ return document.getElementById("social-mark-button");
+ },
+
+ canMarkPage: function SSB_canMarkPage(aURI) {
+ // We only allow sharing of http or https
+ return aURI && (aURI.schemeIs('http') || aURI.schemeIs('https'));
+ },
+
+ // Called when the Social.provider changes
+ update: function SSB_updateButtonState() {
+ let markButton = this.button;
+ // always show button if provider supports marks
+ markButton.hidden = !SocialUI.enabled || Social.provider.pageMarkInfo == null;
+ markButton.disabled = markButton.hidden || !this.canMarkPage(gBrowser.currentURI);
+
+ // also update the relevent command's disabled state so the keyboard
+ // shortcut only works when available.
+ let cmd = document.getElementById("Social:TogglePageMark");
+ cmd.setAttribute("disabled", markButton.disabled ? "true" : "false");
+ },
+
+ togglePageMark: function(aCallback) {
+ if (this.button.disabled)
+ return;
+ this.toggleURIMark(gBrowser.currentURI, aCallback)
+ },
+
+ toggleURIMark: function(aURI, aCallback) {
+ let update = function(marked) {
+ this._updateMarkState(marked);
+ if (aCallback)
+ aCallback(marked);
+ }.bind(this);
+ Social.isURIMarked(aURI, function(marked) {
+ if (marked) {
+ Social.unmarkURI(aURI, update);
+ } else {
+ Social.markURI(aURI, update);
+ }
+ });
+ },
+
+ updateMarkState: function SSB_updateMarkState() {
+ this.update();
+ if (!this.button.hidden)
+ Social.isURIMarked(gBrowser.currentURI, this._updateMarkState.bind(this));
+ },
+
+ _updateMarkState: function(currentPageMarked) {
+ // callback for isURIMarked
+ let markButton = this.button;
+ let pageMarkInfo = SocialUI.enabled ? Social.provider.pageMarkInfo : null;
+
+ // Update the mark button, if present
+ if (!markButton || markButton.hidden || !pageMarkInfo)
+ return;
+
+ let imageURL;
+ if (!markButton.disabled && currentPageMarked) {
+ markButton.setAttribute("marked", "true");
+ markButton.setAttribute("label", pageMarkInfo.messages.markedLabel);
+ markButton.setAttribute("tooltiptext", pageMarkInfo.messages.markedTooltip);
+ imageURL = pageMarkInfo.images.marked;
+ } else {
+ markButton.removeAttribute("marked");
+ markButton.setAttribute("label", pageMarkInfo.messages.unmarkedLabel);
+ markButton.setAttribute("tooltiptext", pageMarkInfo.messages.unmarkedTooltip);
+ imageURL = pageMarkInfo.images.unmarked;
+ }
+ markButton.style.listStyleImage = "url(" + imageURL + ")";
+ }
+};
+
+SocialMenu = {
+ init: function SocialMenu_init() {
+ },
+
+ populate: function SocialMenu_populate() {
+ let submenu = document.getElementById("menu_social-statusarea-popup");
+ let ambientMenuItems = submenu.getElementsByClassName("ambient-menuitem");
+ while (ambientMenuItems.length)
+ submenu.removeChild(ambientMenuItems.item(0));
+
+ let separator = document.getElementById("socialAmbientMenuSeparator");
+ separator.hidden = true;
+ let provider = SocialUI.enabled ? Social.provider : null;
+ if (!provider)
+ return;
+
+ let iconNames = Object.keys(provider.ambientNotificationIcons);
+ for (let name of iconNames) {
+ let icon = provider.ambientNotificationIcons[name];
+ if (!icon.label || !icon.menuURL)
+ continue;
+ separator.hidden = false;
+ let menuitem = document.createElement("menuitem");
+ menuitem.setAttribute("label", icon.label);
+ menuitem.classList.add("ambient-menuitem");
+ menuitem.addEventListener("command", function() {
+ openUILinkIn(icon.menuURL, "tab");
+ }, false);
+ submenu.insertBefore(menuitem, separator);
+ }
+ }
+};
+
+// XXX Need to audit that this is being initialized correctly
+SocialToolbar = {
+ // Called once, after window load, when the Social.provider object is
+ // initialized.
+ init: function SocialToolbar_init() {
+ this._dynamicResizer = new DynamicResizeWatcher();
+ },
+
+ update: function() {
+ this._updateButtonHiddenState();
+ this.updateProvider();
+ this.populateProviderMenus();
+ },
+
+ // Called when the Social.provider changes
+ updateProvider: function () {
+ let provider = Social.provider;
+ if (provider) {
+ this.button.setAttribute("label", provider.name);
+ this.button.setAttribute("tooltiptext", provider.name);
+ this.button.style.listStyleImage = "url(" + provider.iconURL + ")";
+
+ this.updateProfile();
+ } else {
+ this.button.setAttribute("label", gNavigatorBundle.getString("service.toolbarbutton.label"));
+ this.button.setAttribute("tooltiptext", gNavigatorBundle.getString("service.toolbarbutton.tooltiptext"));
+ this.button.style.removeProperty("list-style-image");
+ }
+ this.updateButton();
+ },
+
+ get button() {
+ return document.getElementById("social-provider-button");
+ },
+
+ // Note: this doesn't actually handle hiding the toolbar button,
+ // socialActiveBroadcaster is responsible for that.
+ _updateButtonHiddenState: function SocialToolbar_updateButtonHiddenState() {
+ let socialEnabled = SocialUI.enabled;
+ for (let className of ["social-statusarea-separator", "social-statusarea-user"]) {
+ for (let element of document.getElementsByClassName(className))
+ element.hidden = !socialEnabled;
+ }
+ let toggleNotificationsCommand = document.getElementById("Social:ToggleNotifications");
+ toggleNotificationsCommand.setAttribute("hidden", !socialEnabled);
+
+ if (!Social.haveLoggedInUser() || !socialEnabled) {
+ let parent = document.getElementById("social-notification-panel");
+ while (parent.hasChildNodes()) {
+ let frame = parent.firstChild;
+ SharedFrame.forgetGroup(frame.id);
+ parent.removeChild(frame);
+ }
+
+ let tbi = document.getElementById("social-toolbar-item");
+ if (tbi) {
+ // SocialMark is the last button allways
+ let next = SocialMark.button.previousSibling;
+ while (next != this.button) {
+ tbi.removeChild(next);
+ next = SocialMark.button.previousSibling;
+ }
+ }
+ }
+ },
+
+ updateProfile: function SocialToolbar_updateProfile() {
+ // Profile may not have been initialized yet, since it depends on a worker
+ // response. In that case we'll be called again when it's available, via
+ // social:profile-changed
+ if (!Social.provider)
+ return;
+ let profile = Social.provider.profile || {};
+ let userPortrait = profile.portrait;
+
+ let userDetailsBroadcaster = document.getElementById("socialBroadcaster_userDetails");
+ let loggedInStatusValue = profile.userName ||
+ userDetailsBroadcaster.getAttribute("notLoggedInLabel");
+
+ // "image" and "label" are used by Mac's native menus that do not render the menuitem's children
+ // elements. "src" and "value" are used by the image/label children on the other platforms.
+ if (userPortrait) {
+ userDetailsBroadcaster.setAttribute("src", userPortrait);
+ userDetailsBroadcaster.setAttribute("image", userPortrait);
+ } else {
+ userDetailsBroadcaster.removeAttribute("src");
+ userDetailsBroadcaster.removeAttribute("image");
+ }
+
+ userDetailsBroadcaster.setAttribute("value", loggedInStatusValue);
+ userDetailsBroadcaster.setAttribute("label", loggedInStatusValue);
+ },
+
+ updateButton: function SocialToolbar_updateButton() {
+ this._updateButtonHiddenState();
+ let panel = document.getElementById("social-notification-panel");
+ panel.hidden = !SocialUI.enabled;
+
+ let command = document.getElementById("Social:ToggleNotifications");
+ command.setAttribute("checked", Services.prefs.getBoolPref("social.toast-notifications.enabled"));
+
+ const CACHE_PREF_NAME = "social.cached.ambientNotificationIcons";
+ // provider.profile == undefined means no response yet from the provider
+ // to tell us whether the user is logged in or not.
+ if (!SocialUI.enabled ||
+ (!Social.haveLoggedInUser() && Social.provider.profile !== undefined)) {
+ // Either no enabled provider, or there is a provider and it has
+ // responded with a profile and the user isn't loggedin. The icons
+ // etc have already been removed by updateButtonHiddenState, so we want
+ // to nuke any cached icons we have and get out of here!
+ Services.prefs.clearUserPref(CACHE_PREF_NAME);
+ return;
+ }
+ let icons = Social.provider.ambientNotificationIcons;
+ let iconNames = Object.keys(icons);
+
+ if (Social.provider.profile === undefined) {
+ // provider has not told us about the login state yet - see if we have
+ // a cached version for this provider.
+ let cached;
+ try {
+ cached = JSON.parse(Services.prefs.getComplexValue(CACHE_PREF_NAME,
+ Ci.nsISupportsString).data);
+ } catch (ex) {}
+ if (cached && cached.provider == Social.provider.origin && cached.data) {
+ icons = cached.data;
+ iconNames = Object.keys(icons);
+ // delete the counter data as it is almost certainly stale.
+ for each(let name in iconNames) {
+ icons[name].counter = '';
+ }
+ }
+ } else {
+ // We have a logged in user - save the current set of icons back to the
+ // "cache" so we can use them next startup.
+ let str = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
+ str.data = JSON.stringify({provider: Social.provider.origin, data: icons});
+ Services.prefs.setComplexValue(CACHE_PREF_NAME,
+ Ci.nsISupportsString,
+ str);
+ }
+
+ let toolbarButtons = document.createDocumentFragment();
+
+ let createdFrames = [];
+
+ for each(let name in iconNames) {
+ let icon = icons[name];
+
+ let notificationFrameId = "social-status-" + icon.name;
+ let notificationFrame = document.getElementById(notificationFrameId);
+
+ if (!notificationFrame) {
+ notificationFrame = SharedFrame.createFrame(
+ notificationFrameId, /* frame name */
+ panel, /* parent */
+ {
+ "type": "content",
+ "mozbrowser": "true",
+ "class": "social-panel-frame",
+ "id": notificationFrameId,
+ "tooltip": "aHTMLTooltip",
+
+ // work around bug 793057 - by making the panel roughly the final size
+ // we are more likely to have the anchor in the correct position.
+ "style": "width: " + PANEL_MIN_WIDTH + "px;",
+
+ "origin": Social.provider.origin,
+ "src": icon.contentPanel
+ }
+ );
+
+ createdFrames.push(notificationFrame);
+ } else {
+ notificationFrame.setAttribute("origin", Social.provider.origin);
+ SharedFrame.updateURL(notificationFrameId, icon.contentPanel);
+ }
+
+ let toolbarButtonId = "social-notification-icon-" + icon.name;
+ let toolbarButton = document.getElementById(toolbarButtonId);
+ if (!toolbarButton) {
+ toolbarButton = document.createElement("toolbarbutton");
+ toolbarButton.setAttribute("type", "badged");
+ toolbarButton.classList.add("toolbarbutton-1");
+ toolbarButton.setAttribute("id", toolbarButtonId);
+ toolbarButton.setAttribute("notificationFrameId", notificationFrameId);
+ toolbarButton.addEventListener("mousedown", function (event) {
+ if (event.button == 0 && panel.state == "closed")
+ SocialToolbar.showAmbientPopup(toolbarButton);
+ });
+
+ toolbarButtons.appendChild(toolbarButton);
+ }
+
+ toolbarButton.style.listStyleImage = "url(" + icon.iconURL + ")";
+ toolbarButton.setAttribute("label", icon.label);
+ toolbarButton.setAttribute("tooltiptext", icon.label);
+
+ let badge = icon.counter || "";
+ toolbarButton.setAttribute("badge", badge);
+ let ariaLabel = icon.label;
+ // if there is a badge value, we must use a localizable string to insert it.
+ if (badge)
+ ariaLabel = gNavigatorBundle.getFormattedString("social.aria.toolbarButtonBadgeText",
+ [ariaLabel, badge]);
+ toolbarButton.setAttribute("aria-label", ariaLabel);
+ }
+ let socialToolbarItem = document.getElementById("social-toolbar-item");
+ socialToolbarItem.insertBefore(toolbarButtons, SocialMark.button);
+
+ for (let frame of createdFrames) {
+ if (frame.socialErrorListener) {
+ frame.socialErrorListener.remove();
+ }
+ if (frame.docShell) {
+ frame.docShell.isActive = false;
+ Social.setErrorListener(frame, this.setPanelErrorMessage.bind(this));
+ }
+ }
+ },
+
+ showAmbientPopup: function SocialToolbar_showAmbientPopup(aToolbarButton) {
+ // Hide any other social panels that may be open.
+ SocialFlyout.panel.hidePopup();
+
+ let panel = document.getElementById("social-notification-panel");
+ let notificationFrameId = aToolbarButton.getAttribute("notificationFrameId");
+ let notificationFrame = document.getElementById(notificationFrameId);
+
+ let wasAlive = SharedFrame.isGroupAlive(notificationFrameId);
+ SharedFrame.setOwner(notificationFrameId, notificationFrame);
+
+ // Clear dimensions on all browsers so the panel size will
+ // only use the selected browser.
+ let frameIter = panel.firstElementChild;
+ while (frameIter) {
+ frameIter.collapsed = (frameIter != notificationFrame);
+ frameIter = frameIter.nextElementSibling;
+ }
+
+ function dispatchPanelEvent(name) {
+ let evt = notificationFrame.contentDocument.createEvent("CustomEvent");
+ evt.initCustomEvent(name, true, true, {});
+ notificationFrame.contentDocument.documentElement.dispatchEvent(evt);
+ }
+
+ let dynamicResizer = this._dynamicResizer;
+ panel.addEventListener("popuphidden", function onpopuphiding() {
+ panel.removeEventListener("popuphidden", onpopuphiding);
+ aToolbarButton.removeAttribute("open");
+ aToolbarButton.parentNode.removeAttribute("open");
+ dynamicResizer.stop();
+ notificationFrame.docShell.isActive = false;
+ dispatchPanelEvent("socialFrameHide");
+ });
+
+ panel.addEventListener("popupshown", function onpopupshown() {
+ panel.removeEventListener("popupshown", onpopupshown);
+ // This attribute is needed on both the button and the
+ // containing toolbaritem since the buttons on OS X have
+ // moz-appearance:none, while their container gets
+ // moz-appearance:toolbarbutton due to the way that toolbar buttons
+ // get combined on OS X.
+ aToolbarButton.setAttribute("open", "true");
+ aToolbarButton.parentNode.setAttribute("open", "true");
+ notificationFrame.docShell.isActive = true;
+ notificationFrame.docShell.isAppTab = true;
+ if (notificationFrame.contentDocument.readyState == "complete" && wasAlive) {
+ dynamicResizer.start(panel, notificationFrame);
+ dispatchPanelEvent("socialFrameShow");
+ } else {
+ // first time load, wait for load and dispatch after load
+ notificationFrame.addEventListener("load", function panelBrowserOnload(e) {
+ notificationFrame.removeEventListener("load", panelBrowserOnload, true);
+ dynamicResizer.start(panel, notificationFrame);
+ setTimeout(function() {
+ dispatchPanelEvent("socialFrameShow");
+ }, 0);
+ }, true);
+ }
+ });
+
+ let navBar = document.getElementById("nav-bar");
+ let anchor = navBar.getAttribute("mode") == "text" ?
+ document.getAnonymousElementByAttribute(aToolbarButton, "class", "toolbarbutton-text") :
+ document.getAnonymousElementByAttribute(aToolbarButton, "class", "toolbarbutton-badge-container");
+ // Bug 849216 - open the popup in a setTimeout so we avoid the auto-rollup
+ // handling from preventing it being opened in some cases.
+ setTimeout(function() {
+ panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
+ }, 0);
+ },
+
+ setPanelErrorMessage: function SocialToolbar_setPanelErrorMessage(aNotificationFrame) {
+ if (!aNotificationFrame)
+ return;
+
+ let src = aNotificationFrame.getAttribute("src");
+ aNotificationFrame.removeAttribute("src");
+ aNotificationFrame.webNavigation.loadURI("about:socialerror?mode=tryAgainOnly&url=" +
+ encodeURIComponent(src), null, null, null, null);
+ let panel = aNotificationFrame.parentNode;
+ sizeSocialPanelToContent(panel, aNotificationFrame);
+ },
+
+ populateProviderMenus: function SocialToolbar_renderProviderMenus() {
+ let providerMenuSeps = document.getElementsByClassName("social-provider-menu");
+ for (let providerMenuSep of providerMenuSeps)
+ this._populateProviderMenu(providerMenuSep);
+ },
+
+ _populateProviderMenu: function SocialToolbar_renderProviderMenu(providerMenuSep) {
+ let menu = providerMenuSep.parentNode;
+ // selectable providers are inserted before the provider-menu seperator,
+ // remove any menuitems in that area
+ while (providerMenuSep.previousSibling.nodeName == "menuitem") {
+ menu.removeChild(providerMenuSep.previousSibling);
+ }
+ // only show a selection if enabled and there is more than one
+ let providers = [p for (p of Social.providers) if (p.workerURL || p.sidebarURL)];
+ if (providers.length < 2) {
+ providerMenuSep.hidden = true;
+ return;
+ }
+ for (let provider of providers) {
+ let menuitem = document.createElement("menuitem");
+ menuitem.className = "menuitem-iconic social-provider-menuitem";
+ menuitem.setAttribute("image", provider.iconURL);
+ menuitem.setAttribute("label", provider.name);
+ menuitem.setAttribute("origin", provider.origin);
+ if (provider == Social.provider) {
+ menuitem.setAttribute("checked", "true");
+ } else {
+ menuitem.setAttribute("oncommand", "Social.setProviderByOrigin(this.getAttribute('origin'));");
+ }
+ menu.insertBefore(menuitem, providerMenuSep);
+ }
+ providerMenuSep.hidden = false;
+ }
+}
+
+SocialSidebar = {
+ // Called once, after window load, when the Social.provider object is initialized
+ init: function SocialSidebar_init() {
+ let sbrowser = document.getElementById("social-sidebar-browser");
+ Social.setErrorListener(sbrowser, this.setSidebarErrorMessage.bind(this));
+ // setting isAppTab causes clicks on untargeted links to open new tabs
+ sbrowser.docShell.isAppTab = true;
+ },
+
+ // Whether the sidebar can be shown for this window.
+ get canShow() {
+ return SocialUI.enabled && Social.provider.sidebarURL;
+ },
+
+ // Whether the user has toggled the sidebar on (for windows where it can appear)
+ get opened() {
+ return Services.prefs.getBoolPref("social.sidebar.open") && !document.mozFullScreen;
+ },
+
+ setSidebarVisibilityState: function(aEnabled) {
+ let sbrowser = document.getElementById("social-sidebar-browser");
+ // it's possible we'll be called twice with aEnabled=false so let's
+ // just assume we may often be called with the same state.
+ if (aEnabled == sbrowser.docShellIsActive)
+ return;
+ sbrowser.docShellIsActive = aEnabled;
+ let evt = sbrowser.contentDocument.createEvent("CustomEvent");
+ evt.initCustomEvent(aEnabled ? "socialFrameShow" : "socialFrameHide", true, true, {});
+ sbrowser.contentDocument.documentElement.dispatchEvent(evt);
+ },
+
+ update: function SocialSidebar_update() {
+ clearTimeout(this._unloadTimeoutId);
+ // Hide the toggle menu item if the sidebar cannot appear
+ let command = document.getElementById("Social:ToggleSidebar");
+ command.setAttribute("hidden", this.canShow ? "false" : "true");
+
+ // Hide the sidebar if it cannot appear, or has been toggled off.
+ // Also set the command "checked" state accordingly.
+ let hideSidebar = !this.canShow || !this.opened;
+ let broadcaster = document.getElementById("socialSidebarBroadcaster");
+ broadcaster.hidden = hideSidebar;
+ command.setAttribute("checked", !hideSidebar);
+
+ let sbrowser = document.getElementById("social-sidebar-browser");
+
+ if (hideSidebar) {
+ sbrowser.removeEventListener("load", SocialSidebar._loadListener, true);
+ this.setSidebarVisibilityState(false);
+ // If we've been disabled, unload the sidebar content immediately;
+ // if the sidebar was just toggled to invisible, wait a timeout
+ // before unloading.
+ if (!this.canShow) {
+ this.unloadSidebar();
+ } else {
+ this._unloadTimeoutId = setTimeout(
+ this.unloadSidebar,
+ Services.prefs.getIntPref("social.sidebar.unload_timeout_ms")
+ );
+ }
+ } else {
+ sbrowser.setAttribute("origin", Social.provider.origin);
+ if (Social.provider.errorState == "frameworker-error") {
+ SocialSidebar.setSidebarErrorMessage();
+ return;
+ }
+
+ // Make sure the right sidebar URL is loaded
+ if (sbrowser.getAttribute("src") != Social.provider.sidebarURL) {
+ sbrowser.setAttribute("src", Social.provider.sidebarURL);
+ PopupNotifications.locationChange(sbrowser);
+ }
+
+ // if the document has not loaded, delay until it is
+ if (sbrowser.contentDocument.readyState != "complete") {
+ sbrowser.addEventListener("load", SocialSidebar._loadListener, true);
+ } else {
+ this.setSidebarVisibilityState(true);
+ }
+ }
+ },
+
+ _loadListener: function SocialSidebar_loadListener() {
+ let sbrowser = document.getElementById("social-sidebar-browser");
+ sbrowser.removeEventListener("load", SocialSidebar._loadListener, true);
+ SocialSidebar.setSidebarVisibilityState(true);
+ },
+
+ unloadSidebar: function SocialSidebar_unloadSidebar() {
+ let sbrowser = document.getElementById("social-sidebar-browser");
+ if (!sbrowser.hasAttribute("origin"))
+ return;
+
+ sbrowser.stop();
+ sbrowser.removeAttribute("origin");
+ sbrowser.setAttribute("src", "about:blank");
+ SocialFlyout.unload();
+ },
+
+ _unloadTimeoutId: 0,
+
+ setSidebarErrorMessage: function() {
+ let sbrowser = document.getElementById("social-sidebar-browser");
+ // a frameworker error "trumps" a sidebar error.
+ if (Social.provider.errorState == "frameworker-error") {
+ sbrowser.setAttribute("src", "about:socialerror?mode=workerFailure");
+ } else {
+ let url = encodeURIComponent(Social.provider.sidebarURL);
+ sbrowser.loadURI("about:socialerror?mode=tryAgain&url=" + url, null, null);
+ }
+ }
+}
+
+})();
diff --git a/browser/base/content/browser-syncui.js b/browser/base/content/browser-syncui.js
new file mode 100644
index 000000000..7294794e7
--- /dev/null
+++ b/browser/base/content/browser-syncui.js
@@ -0,0 +1,467 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// gSyncUI handles updating the tools menu
+let gSyncUI = {
+ _obs: ["weave:service:sync:start",
+ "weave:service:sync:delayed",
+ "weave:service:quota:remaining",
+ "weave:service:setup-complete",
+ "weave:service:login:start",
+ "weave:service:login:finish",
+ "weave:service:logout:finish",
+ "weave:service:start-over",
+ "weave:ui:login:error",
+ "weave:ui:sync:error",
+ "weave:ui:sync:finish",
+ "weave:ui:clear-error",
+ ],
+
+ _unloaded: false,
+
+ init: function SUI_init() {
+ // Proceed to set up the UI if Sync has already started up.
+ // Otherwise we'll do it when Sync is firing up.
+ let xps = Components.classes["@mozilla.org/weave/service;1"]
+ .getService(Components.interfaces.nsISupports)
+ .wrappedJSObject;
+ if (xps.ready) {
+ this.initUI();
+ return;
+ }
+
+ Services.obs.addObserver(this, "weave:service:ready", true);
+
+ // Remove the observer if the window is closed before the observer
+ // was triggered.
+ window.addEventListener("unload", function onUnload() {
+ gSyncUI._unloaded = true;
+ window.removeEventListener("unload", onUnload, false);
+ Services.obs.removeObserver(gSyncUI, "weave:service:ready");
+
+ if (Weave.Status.ready) {
+ gSyncUI._obs.forEach(function(topic) {
+ Services.obs.removeObserver(gSyncUI, topic);
+ });
+ }
+ }, false);
+ },
+
+ initUI: function SUI_initUI() {
+ // If this is a browser window?
+ if (gBrowser) {
+ this._obs.push("weave:notification:added");
+ }
+
+ this._obs.forEach(function(topic) {
+ Services.obs.addObserver(this, topic, true);
+ }, this);
+
+ if (gBrowser && Weave.Notifications.notifications.length) {
+ this.initNotifications();
+ }
+ this.updateUI();
+ },
+
+ initNotifications: function SUI_initNotifications() {
+ const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ let notificationbox = document.createElementNS(XULNS, "notificationbox");
+ notificationbox.id = "sync-notifications";
+ notificationbox.setAttribute("flex", "1");
+
+ let bottombox = document.getElementById("browser-bottombox");
+ bottombox.insertBefore(notificationbox, bottombox.firstChild);
+
+ // Force a style flush to ensure that our binding is attached.
+ notificationbox.clientTop;
+
+ // notificationbox will listen to observers from now on.
+ Services.obs.removeObserver(this, "weave:notification:added");
+ },
+
+ _wasDelayed: false,
+
+ _needsSetup: function SUI__needsSetup() {
+ let firstSync = "";
+ try {
+ firstSync = Services.prefs.getCharPref("services.sync.firstSync");
+ } catch (e) { }
+ return Weave.Status.checkSetup() == Weave.CLIENT_NOT_CONFIGURED ||
+ firstSync == "notReady";
+ },
+
+ updateUI: function SUI_updateUI() {
+ let needsSetup = this._needsSetup();
+ document.getElementById("sync-setup-state").hidden = !needsSetup;
+ document.getElementById("sync-syncnow-state").hidden = needsSetup;
+
+ if (!gBrowser)
+ return;
+
+ let button = document.getElementById("sync-button");
+ if (!button)
+ return;
+
+ button.removeAttribute("status");
+ this._updateLastSyncTime();
+ if (needsSetup)
+ button.removeAttribute("tooltiptext");
+ },
+
+
+ // Functions called by observers
+ onActivityStart: function SUI_onActivityStart() {
+ if (!gBrowser)
+ return;
+
+ let button = document.getElementById("sync-button");
+ if (!button)
+ return;
+
+ button.setAttribute("status", "active");
+ },
+
+ onSyncDelay: function SUI_onSyncDelay() {
+ // basically, we want to just inform users that stuff is going to take a while
+ let title = this._stringBundle.GetStringFromName("error.sync.no_node_found.title");
+ let description = this._stringBundle.GetStringFromName("error.sync.no_node_found");
+ let buttons = [new Weave.NotificationButton(
+ this._stringBundle.GetStringFromName("error.sync.serverStatusButton.label"),
+ this._stringBundle.GetStringFromName("error.sync.serverStatusButton.accesskey"),
+ function() { gSyncUI.openServerStatus(); return true; }
+ )];
+ let notification = new Weave.Notification(
+ title, description, null, Weave.Notifications.PRIORITY_INFO, buttons);
+ Weave.Notifications.replaceTitle(notification);
+ this._wasDelayed = true;
+ },
+
+ onLoginFinish: function SUI_onLoginFinish() {
+ // Clear out any login failure notifications
+ let title = this._stringBundle.GetStringFromName("error.login.title");
+ this.clearError(title);
+ },
+
+ onSetupComplete: function SUI_onSetupComplete() {
+ this.onLoginFinish();
+ },
+
+ onLoginError: function SUI_onLoginError() {
+ // if login fails, any other notifications are essentially moot
+ Weave.Notifications.removeAll();
+
+ // if we haven't set up the client, don't show errors
+ if (this._needsSetup()) {
+ this.updateUI();
+ return;
+ }
+
+ let title = this._stringBundle.GetStringFromName("error.login.title");
+
+ let description;
+ if (Weave.Status.sync == Weave.PROLONGED_SYNC_FAILURE) {
+ // Convert to days
+ let lastSync =
+ Services.prefs.getIntPref("services.sync.errorhandler.networkFailureReportTimeout") / 86400;
+ description =
+ this._stringBundle.formatStringFromName("error.sync.prolonged_failure", [lastSync], 1);
+ } else {
+ let reason = Weave.Utils.getErrorString(Weave.Status.login);
+ description =
+ this._stringBundle.formatStringFromName("error.sync.description", [reason], 1);
+ }
+
+ let buttons = [];
+ buttons.push(new Weave.NotificationButton(
+ this._stringBundle.GetStringFromName("error.login.prefs.label"),
+ this._stringBundle.GetStringFromName("error.login.prefs.accesskey"),
+ function() { gSyncUI.openPrefs(); return true; }
+ ));
+
+ let notification = new Weave.Notification(title, description, null,
+ Weave.Notifications.PRIORITY_WARNING, buttons);
+ Weave.Notifications.replaceTitle(notification);
+ this.updateUI();
+ },
+
+ onLogout: function SUI_onLogout() {
+ this.updateUI();
+ },
+
+ onStartOver: function SUI_onStartOver() {
+ this.clearError();
+ },
+
+ onQuotaNotice: function onQuotaNotice(subject, data) {
+ let title = this._stringBundle.GetStringFromName("warning.sync.quota.label");
+ let description = this._stringBundle.GetStringFromName("warning.sync.quota.description");
+ let buttons = [];
+ buttons.push(new Weave.NotificationButton(
+ this._stringBundle.GetStringFromName("error.sync.viewQuotaButton.label"),
+ this._stringBundle.GetStringFromName("error.sync.viewQuotaButton.accesskey"),
+ function() { gSyncUI.openQuotaDialog(); return true; }
+ ));
+
+ let notification = new Weave.Notification(
+ title, description, null, Weave.Notifications.PRIORITY_WARNING, buttons);
+ Weave.Notifications.replaceTitle(notification);
+ },
+
+ openServerStatus: function () {
+ let statusURL = Services.prefs.getCharPref("services.sync.statusURL");
+ window.openUILinkIn(statusURL, "tab");
+ },
+
+ // Commands
+ doSync: function SUI_doSync() {
+ setTimeout(function() Weave.Service.errorHandler.syncAndReportErrors(), 0);
+ },
+
+ handleToolbarButton: function SUI_handleStatusbarButton() {
+ if (this._needsSetup())
+ this.openSetup();
+ else
+ this.doSync();
+ },
+
+ //XXXzpao should be part of syncCommon.js - which we might want to make a module...
+ // To be fixed in a followup (bug 583366)
+
+ /**
+ * Invoke the Sync setup wizard.
+ *
+ * @param wizardType
+ * Indicates type of wizard to launch:
+ * null -- regular set up wizard
+ * "pair" -- pair a device first
+ * "reset" -- reset sync
+ */
+
+ openSetup: function SUI_openSetup(wizardType) {
+ let win = Services.wm.getMostRecentWindow("Weave:AccountSetup");
+ if (win)
+ win.focus();
+ else {
+ window.openDialog("chrome://browser/content/sync/setup.xul",
+ "weaveSetup", "centerscreen,chrome,resizable=no",
+ wizardType);
+ }
+ },
+
+ openAddDevice: function () {
+ if (!Weave.Utils.ensureMPUnlocked())
+ return;
+
+ let win = Services.wm.getMostRecentWindow("Sync:AddDevice");
+ if (win)
+ win.focus();
+ else
+ window.openDialog("chrome://browser/content/sync/addDevice.xul",
+ "syncAddDevice", "centerscreen,chrome,resizable=no");
+ },
+
+ openQuotaDialog: function SUI_openQuotaDialog() {
+ let win = Services.wm.getMostRecentWindow("Sync:ViewQuota");
+ if (win)
+ win.focus();
+ else
+ Services.ww.activeWindow.openDialog(
+ "chrome://browser/content/sync/quota.xul", "",
+ "centerscreen,chrome,dialog,modal");
+ },
+
+ openPrefs: function SUI_openPrefs() {
+ openPreferences("paneSync");
+ },
+
+
+ // Helpers
+ _updateLastSyncTime: function SUI__updateLastSyncTime() {
+ if (!gBrowser)
+ return;
+
+ let syncButton = document.getElementById("sync-button");
+ if (!syncButton)
+ return;
+
+ let lastSync;
+ try {
+ lastSync = Services.prefs.getCharPref("services.sync.lastSync");
+ }
+ catch (e) { };
+ if (!lastSync || this._needsSetup()) {
+ syncButton.removeAttribute("tooltiptext");
+ return;
+ }
+
+ // Show the day-of-week and time (HH:MM) of last sync
+ let lastSyncDate = new Date(lastSync).toLocaleFormat("%a %H:%M");
+ let lastSyncLabel =
+ this._stringBundle.formatStringFromName("lastSync2.label", [lastSyncDate], 1);
+
+ syncButton.setAttribute("tooltiptext", lastSyncLabel);
+ },
+
+ clearError: function SUI_clearError(errorString) {
+ Weave.Notifications.removeAll(errorString);
+ this.updateUI();
+ },
+
+ onSyncFinish: function SUI_onSyncFinish() {
+ let title = this._stringBundle.GetStringFromName("error.sync.title");
+
+ // Clear out sync failures on a successful sync
+ this.clearError(title);
+
+ if (this._wasDelayed && Weave.Status.sync != Weave.NO_SYNC_NODE_FOUND) {
+ title = this._stringBundle.GetStringFromName("error.sync.no_node_found.title");
+ this.clearError(title);
+ this._wasDelayed = false;
+ }
+ },
+
+ onSyncError: function SUI_onSyncError() {
+ let title = this._stringBundle.GetStringFromName("error.sync.title");
+
+ if (Weave.Status.login != Weave.LOGIN_SUCCEEDED) {
+ this.onLoginError();
+ return;
+ }
+
+ let description;
+ if (Weave.Status.sync == Weave.PROLONGED_SYNC_FAILURE) {
+ // Convert to days
+ let lastSync =
+ Services.prefs.getIntPref("services.sync.errorhandler.networkFailureReportTimeout") / 86400;
+ description =
+ this._stringBundle.formatStringFromName("error.sync.prolonged_failure", [lastSync], 1);
+ } else {
+ let error = Weave.Utils.getErrorString(Weave.Status.sync);
+ description =
+ this._stringBundle.formatStringFromName("error.sync.description", [error], 1);
+ }
+ let priority = Weave.Notifications.PRIORITY_WARNING;
+ let buttons = [];
+
+ // Check if the client is outdated in some way
+ let outdated = Weave.Status.sync == Weave.VERSION_OUT_OF_DATE;
+ for (let [engine, reason] in Iterator(Weave.Status.engines))
+ outdated = outdated || reason == Weave.VERSION_OUT_OF_DATE;
+
+ if (outdated) {
+ description = this._stringBundle.GetStringFromName(
+ "error.sync.needUpdate.description");
+ buttons.push(new Weave.NotificationButton(
+ this._stringBundle.GetStringFromName("error.sync.needUpdate.label"),
+ this._stringBundle.GetStringFromName("error.sync.needUpdate.accesskey"),
+ function() { window.openUILinkIn("https://services.mozilla.com/update/", "tab"); return true; }
+ ));
+ }
+ else if (Weave.Status.sync == Weave.OVER_QUOTA) {
+ description = this._stringBundle.GetStringFromName(
+ "error.sync.quota.description");
+ buttons.push(new Weave.NotificationButton(
+ this._stringBundle.GetStringFromName(
+ "error.sync.viewQuotaButton.label"),
+ this._stringBundle.GetStringFromName(
+ "error.sync.viewQuotaButton.accesskey"),
+ function() { gSyncUI.openQuotaDialog(); return true; } )
+ );
+ }
+ else if (Weave.Status.enforceBackoff) {
+ priority = Weave.Notifications.PRIORITY_INFO;
+ buttons.push(new Weave.NotificationButton(
+ this._stringBundle.GetStringFromName("error.sync.serverStatusButton.label"),
+ this._stringBundle.GetStringFromName("error.sync.serverStatusButton.accesskey"),
+ function() { gSyncUI.openServerStatus(); return true; }
+ ));
+ }
+ else {
+ priority = Weave.Notifications.PRIORITY_INFO;
+ buttons.push(new Weave.NotificationButton(
+ this._stringBundle.GetStringFromName("error.sync.tryAgainButton.label"),
+ this._stringBundle.GetStringFromName("error.sync.tryAgainButton.accesskey"),
+ function() { gSyncUI.doSync(); return true; }
+ ));
+ }
+
+ let notification =
+ new Weave.Notification(title, description, null, priority, buttons);
+ Weave.Notifications.replaceTitle(notification);
+
+ if (this._wasDelayed && Weave.Status.sync != Weave.NO_SYNC_NODE_FOUND) {
+ title = this._stringBundle.GetStringFromName("error.sync.no_node_found.title");
+ Weave.Notifications.removeAll(title);
+ this._wasDelayed = false;
+ }
+
+ this.updateUI();
+ },
+
+ observe: function SUI_observe(subject, topic, data) {
+ if (this._unloaded) {
+ Cu.reportError("SyncUI observer called after unload: " + topic);
+ return;
+ }
+
+ switch (topic) {
+ case "weave:service:sync:start":
+ this.onActivityStart();
+ break;
+ case "weave:ui:sync:finish":
+ this.onSyncFinish();
+ break;
+ case "weave:ui:sync:error":
+ this.onSyncError();
+ break;
+ case "weave:service:sync:delayed":
+ this.onSyncDelay();
+ break;
+ case "weave:service:quota:remaining":
+ this.onQuotaNotice();
+ break;
+ case "weave:service:setup-complete":
+ this.onSetupComplete();
+ break;
+ case "weave:service:login:start":
+ this.onActivityStart();
+ break;
+ case "weave:service:login:finish":
+ this.onLoginFinish();
+ break;
+ case "weave:ui:login:error":
+ this.onLoginError();
+ break;
+ case "weave:service:logout:finish":
+ this.onLogout();
+ break;
+ case "weave:service:start-over":
+ this.onStartOver();
+ break;
+ case "weave:service:ready":
+ this.initUI();
+ break;
+ case "weave:notification:added":
+ this.initNotifications();
+ break;
+ case "weave:ui:clear-error":
+ this.clearError();
+ break;
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIObserver,
+ Ci.nsISupportsWeakReference
+ ])
+};
+
+XPCOMUtils.defineLazyGetter(gSyncUI, "_stringBundle", function() {
+ //XXXzpao these strings should probably be moved from /services to /browser... (bug 583381)
+ // but for now just make it work
+ return Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService).
+ createBundle("chrome://weave/locale/services/sync.properties");
+});
+
diff --git a/browser/base/content/browser-tabPreviews.js b/browser/base/content/browser-tabPreviews.js
new file mode 100644
index 000000000..6d639f16e
--- /dev/null
+++ b/browser/base/content/browser-tabPreviews.js
@@ -0,0 +1,1051 @@
+/*
+#ifdef 0
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#endif
+ */
+
+/**
+ * Tab previews utility, produces thumbnails
+ */
+var tabPreviews = {
+ aspectRatio: 0.5625, // 16:9
+
+ get width() {
+ delete this.width;
+ return this.width = Math.ceil(screen.availWidth / 5.75);
+ },
+
+ get height() {
+ delete this.height;
+ return this.height = Math.round(this.width * this.aspectRatio);
+ },
+
+ init: function tabPreviews_init() {
+ if (this._selectedTab)
+ return;
+ this._selectedTab = gBrowser.selectedTab;
+
+ gBrowser.tabContainer.addEventListener("TabSelect", this, false);
+ gBrowser.tabContainer.addEventListener("SSTabRestored", this, false);
+ },
+
+ get: function tabPreviews_get(aTab) {
+ let uri = aTab.linkedBrowser.currentURI.spec;
+
+ if (aTab.__thumbnail_lastURI &&
+ aTab.__thumbnail_lastURI != uri) {
+ aTab.__thumbnail = null;
+ aTab.__thumbnail_lastURI = null;
+ }
+
+ if (aTab.__thumbnail)
+ return aTab.__thumbnail;
+
+ if (aTab.getAttribute("pending") == "true") {
+ let img = new Image;
+ img.src = PageThumbs.getThumbnailURL(uri);
+ return img;
+ }
+
+ return this.capture(aTab, !aTab.hasAttribute("busy"));
+ },
+
+ capture: function tabPreviews_capture(aTab, aStore) {
+ var thumbnail = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+ thumbnail.mozOpaque = true;
+ thumbnail.height = this.height;
+ thumbnail.width = this.width;
+
+ var ctx = thumbnail.getContext("2d");
+ var win = aTab.linkedBrowser.contentWindow;
+ var snippetWidth = win.innerWidth * .6;
+ var scale = this.width / snippetWidth;
+ ctx.scale(scale, scale);
+ ctx.drawWindow(win, win.scrollX, win.scrollY,
+ snippetWidth, snippetWidth * this.aspectRatio, "rgb(255,255,255)");
+
+ if (aStore &&
+ aTab.linkedBrowser /* bug 795608: the tab may got removed while drawing the thumbnail */) {
+ aTab.__thumbnail = thumbnail;
+ aTab.__thumbnail_lastURI = aTab.linkedBrowser.currentURI.spec;
+ }
+
+ return thumbnail;
+ },
+
+ handleEvent: function tabPreviews_handleEvent(event) {
+ switch (event.type) {
+ case "TabSelect":
+ if (this._selectedTab &&
+ this._selectedTab.parentNode &&
+ !this._pendingUpdate) {
+ // Generate a thumbnail for the tab that was selected.
+ // The timeout keeps the UI snappy and prevents us from generating thumbnails
+ // for tabs that will be closed. During that timeout, don't generate other
+ // thumbnails in case multiple TabSelect events occur fast in succession.
+ this._pendingUpdate = true;
+ setTimeout(function (self, aTab) {
+ self._pendingUpdate = false;
+ if (aTab.parentNode &&
+ !aTab.hasAttribute("busy") &&
+ !aTab.hasAttribute("pending"))
+ self.capture(aTab, true);
+ }, 2000, this, this._selectedTab);
+ }
+ this._selectedTab = event.target;
+ break;
+ case "SSTabRestored":
+ this.capture(event.target, true);
+ break;
+ }
+ }
+};
+
+var tabPreviewPanelHelper = {
+ opening: function (host) {
+ host.panel.hidden = false;
+
+ var handler = this._generateHandler(host);
+ host.panel.addEventListener("popupshown", handler, false);
+ host.panel.addEventListener("popuphiding", handler, false);
+
+ host._prevFocus = document.commandDispatcher.focusedElement;
+ },
+ _generateHandler: function (host) {
+ var self = this;
+ return function (event) {
+ if (event.target == host.panel) {
+ host.panel.removeEventListener(event.type, arguments.callee, false);
+ self["_" + event.type](host);
+ }
+ };
+ },
+ _popupshown: function (host) {
+ if ("setupGUI" in host)
+ host.setupGUI();
+ },
+ _popuphiding: function (host) {
+ if ("suspendGUI" in host)
+ host.suspendGUI();
+
+ if (host._prevFocus) {
+ Cc["@mozilla.org/focus-manager;1"]
+ .getService(Ci.nsIFocusManager)
+ .setFocus(host._prevFocus, Ci.nsIFocusManager.FLAG_NOSCROLL);
+ host._prevFocus = null;
+ } else
+ gBrowser.selectedBrowser.focus();
+
+ if (host.tabToSelect) {
+ gBrowser.selectedTab = host.tabToSelect;
+ host.tabToSelect = null;
+ }
+ }
+};
+
+/**
+ * Ctrl-Tab panel
+ */
+var ctrlTab = {
+ get panel () {
+ delete this.panel;
+ return this.panel = document.getElementById("ctrlTab-panel");
+ },
+ get showAllButton () {
+ delete this.showAllButton;
+ return this.showAllButton = document.getElementById("ctrlTab-showAll");
+ },
+ get previews () {
+ delete this.previews;
+ return this.previews = this.panel.getElementsByClassName("ctrlTab-preview");
+ },
+ get recentlyUsedLimit () {
+ delete this.recentlyUsedLimit;
+ return this.recentlyUsedLimit = gPrefService.getIntPref("browser.ctrlTab.recentlyUsedLimit");
+ },
+ get keys () {
+ var keys = {};
+ ["close", "find", "selectAll"].forEach(function (key) {
+ keys[key] = document.getElementById("key_" + key)
+ .getAttribute("key")
+ .toLocaleLowerCase().charCodeAt(0);
+ });
+ delete this.keys;
+ return this.keys = keys;
+ },
+ _selectedIndex: 0,
+ get selected () this._selectedIndex < 0 ?
+ document.activeElement :
+ this.previews.item(this._selectedIndex),
+ get isOpen () this.panel.state == "open" || this.panel.state == "showing" || this._timer,
+ get tabCount () this.tabList.length,
+ get tabPreviewCount () Math.min(this.previews.length - 1, this.tabCount),
+ get canvasWidth () Math.min(tabPreviews.width,
+ Math.ceil(screen.availWidth * .85 / this.tabPreviewCount)),
+ get canvasHeight () Math.round(this.canvasWidth * tabPreviews.aspectRatio),
+
+ get tabList () {
+ if (this._tabList)
+ return this._tabList;
+
+ // Using gBrowser.tabs instead of gBrowser.visibleTabs, as the latter
+ // exlcudes closing tabs, breaking the following loop in case the the
+ // selected tab is closing.
+ let list = Array.filter(gBrowser.tabs, function (tab) !tab.hidden);
+
+ // Rotate the list until the selected tab is first
+ while (!list[0].selected)
+ list.push(list.shift());
+
+ list = list.filter(function (tab) !tab.closing);
+
+ if (this.recentlyUsedLimit != 0) {
+ let recentlyUsedTabs = [];
+ for (let tab of this._recentlyUsedTabs) {
+ if (!tab.hidden && !tab.closing) {
+ recentlyUsedTabs.push(tab);
+ if (this.recentlyUsedLimit > 0 && recentlyUsedTabs.length >= this.recentlyUsedLimit)
+ break;
+ }
+ }
+ for (let i = recentlyUsedTabs.length - 1; i >= 0; i--) {
+ list.splice(list.indexOf(recentlyUsedTabs[i]), 1);
+ list.unshift(recentlyUsedTabs[i]);
+ }
+ }
+
+ return this._tabList = list;
+ },
+
+ init: function ctrlTab_init() {
+ if (!this._recentlyUsedTabs) {
+ tabPreviews.init();
+
+ this._recentlyUsedTabs = [gBrowser.selectedTab];
+ this._init(true);
+ }
+ },
+
+ uninit: function ctrlTab_uninit() {
+ this._recentlyUsedTabs = null;
+ this._init(false);
+ },
+
+ prefName: "browser.ctrlTab.previews",
+ readPref: function ctrlTab_readPref() {
+ var enable =
+ gPrefService.getBoolPref(this.prefName) &&
+ (!gPrefService.prefHasUserValue("browser.ctrlTab.disallowForScreenReaders") ||
+ !gPrefService.getBoolPref("browser.ctrlTab.disallowForScreenReaders"));
+
+ if (enable)
+ this.init();
+ else
+ this.uninit();
+ },
+ observe: function (aSubject, aTopic, aPrefName) {
+ this.readPref();
+ },
+
+ updatePreviews: function ctrlTab_updatePreviews() {
+ for (let i = 0; i < this.previews.length; i++)
+ this.updatePreview(this.previews[i], this.tabList[i]);
+
+ var showAllLabel = gNavigatorBundle.getString("ctrlTab.showAll.label");
+ this.showAllButton.label =
+ PluralForm.get(this.tabCount, showAllLabel).replace("#1", this.tabCount);
+ },
+
+ updatePreview: function ctrlTab_updatePreview(aPreview, aTab) {
+ if (aPreview == this.showAllButton)
+ return;
+
+ aPreview._tab = aTab;
+
+ if (aPreview.firstChild)
+ aPreview.removeChild(aPreview.firstChild);
+ if (aTab) {
+ let canvasWidth = this.canvasWidth;
+ let canvasHeight = this.canvasHeight;
+ aPreview.appendChild(tabPreviews.get(aTab));
+ aPreview.setAttribute("label", aTab.label);
+ aPreview.setAttribute("tooltiptext", aTab.label);
+ aPreview.setAttribute("crop", aTab.crop);
+ aPreview.setAttribute("canvaswidth", canvasWidth);
+ aPreview.setAttribute("canvasstyle",
+ "max-width:" + canvasWidth + "px;" +
+ "min-width:" + canvasWidth + "px;" +
+ "max-height:" + canvasHeight + "px;" +
+ "min-height:" + canvasHeight + "px;");
+ if (aTab.image)
+ aPreview.setAttribute("image", aTab.image);
+ else
+ aPreview.removeAttribute("image");
+ aPreview.hidden = false;
+ } else {
+ aPreview.hidden = true;
+ aPreview.removeAttribute("label");
+ aPreview.removeAttribute("tooltiptext");
+ aPreview.removeAttribute("image");
+ }
+ },
+
+ advanceFocus: function ctrlTab_advanceFocus(aForward) {
+ let selectedIndex = Array.indexOf(this.previews, this.selected);
+ do {
+ selectedIndex += aForward ? 1 : -1;
+ if (selectedIndex < 0)
+ selectedIndex = this.previews.length - 1;
+ else if (selectedIndex >= this.previews.length)
+ selectedIndex = 0;
+ } while (this.previews[selectedIndex].hidden);
+
+ if (this._selectedIndex == -1) {
+ // Focus is already in the panel.
+ this.previews[selectedIndex].focus();
+ } else {
+ this._selectedIndex = selectedIndex;
+ }
+
+ if (this._timer) {
+ clearTimeout(this._timer);
+ this._timer = null;
+ this._openPanel();
+ }
+ },
+
+ _mouseOverFocus: function ctrlTab_mouseOverFocus(aPreview) {
+ if (this._trackMouseOver)
+ aPreview.focus();
+ },
+
+ pick: function ctrlTab_pick(aPreview) {
+ if (!this.tabCount)
+ return;
+
+ var select = (aPreview || this.selected);
+
+ if (select == this.showAllButton)
+ this.showAllTabs();
+ else
+ this.close(select._tab);
+ },
+
+ showAllTabs: function ctrlTab_showAllTabs(aPreview) {
+ this.close();
+ document.getElementById("Browser:ShowAllTabs").doCommand();
+ },
+
+ remove: function ctrlTab_remove(aPreview) {
+ if (aPreview._tab)
+ gBrowser.removeTab(aPreview._tab);
+ },
+
+ attachTab: function ctrlTab_attachTab(aTab, aPos) {
+ if (aPos == 0)
+ this._recentlyUsedTabs.unshift(aTab);
+ else if (aPos)
+ this._recentlyUsedTabs.splice(aPos, 0, aTab);
+ else
+ this._recentlyUsedTabs.push(aTab);
+ },
+ detachTab: function ctrlTab_detachTab(aTab) {
+ var i = this._recentlyUsedTabs.indexOf(aTab);
+ if (i >= 0)
+ this._recentlyUsedTabs.splice(i, 1);
+ },
+
+ open: function ctrlTab_open() {
+ if (this.isOpen)
+ return;
+
+ allTabs.close();
+
+ document.addEventListener("keyup", this, true);
+
+ this.updatePreviews();
+ this._selectedIndex = 1;
+
+ // Add a slight delay before showing the UI, so that a quick
+ // "ctrl-tab" keypress just flips back to the MRU tab.
+ this._timer = setTimeout(function (self) {
+ self._timer = null;
+ self._openPanel();
+ }, 200, this);
+ },
+
+ _openPanel: function ctrlTab_openPanel() {
+ tabPreviewPanelHelper.opening(this);
+
+ this.panel.width = Math.min(screen.availWidth * .99,
+ this.canvasWidth * 1.25 * this.tabPreviewCount);
+ var estimateHeight = this.canvasHeight * 1.25 + 75;
+ this.panel.openPopupAtScreen(screen.availLeft + (screen.availWidth - this.panel.width) / 2,
+ screen.availTop + (screen.availHeight - estimateHeight) / 2,
+ false);
+ },
+
+ close: function ctrlTab_close(aTabToSelect) {
+ if (!this.isOpen)
+ return;
+
+ if (this._timer) {
+ clearTimeout(this._timer);
+ this._timer = null;
+ this.suspendGUI();
+ if (aTabToSelect)
+ gBrowser.selectedTab = aTabToSelect;
+ return;
+ }
+
+ this.tabToSelect = aTabToSelect;
+ this.panel.hidePopup();
+ },
+
+ setupGUI: function ctrlTab_setupGUI() {
+ this.selected.focus();
+ this._selectedIndex = -1;
+
+ // Track mouse movement after a brief delay so that the item that happens
+ // to be under the mouse pointer initially won't be selected unintentionally.
+ this._trackMouseOver = false;
+ setTimeout(function (self) {
+ if (self.isOpen)
+ self._trackMouseOver = true;
+ }, 0, this);
+ },
+
+ suspendGUI: function ctrlTab_suspendGUI() {
+ document.removeEventListener("keyup", this, true);
+
+ Array.forEach(this.previews, function (preview) {
+ this.updatePreview(preview, null);
+ }, this);
+
+ this._tabList = null;
+ },
+
+ onKeyPress: function ctrlTab_onKeyPress(event) {
+ var isOpen = this.isOpen;
+
+ if (isOpen) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+
+ switch (event.keyCode) {
+ case event.DOM_VK_TAB:
+ if (event.ctrlKey && !event.altKey && !event.metaKey) {
+ if (isOpen) {
+ this.advanceFocus(!event.shiftKey);
+ } else if (!event.shiftKey) {
+ event.preventDefault();
+ event.stopPropagation();
+ let tabs = gBrowser.visibleTabs;
+ if (tabs.length > 2) {
+ this.open();
+ } else if (tabs.length == 2) {
+ let index = tabs[0].selected ? 1 : 0;
+ gBrowser.selectedTab = tabs[index];
+ }
+ }
+ }
+ break;
+ default:
+ if (isOpen && event.ctrlKey) {
+ if (event.keyCode == event.DOM_VK_DELETE) {
+ this.remove(this.selected);
+ break;
+ }
+ switch (event.charCode) {
+ case this.keys.close:
+ this.remove(this.selected);
+ break;
+ case this.keys.find:
+ case this.keys.selectAll:
+ this.showAllTabs();
+ break;
+ }
+ }
+ }
+ },
+
+ removeClosingTabFromUI: function ctrlTab_removeClosingTabFromUI(aTab) {
+ if (this.tabCount == 2) {
+ this.close();
+ return;
+ }
+
+ this._tabList = null;
+ this.updatePreviews();
+
+ if (this.selected.hidden)
+ this.advanceFocus(false);
+ if (this.selected == this.showAllButton)
+ this.advanceFocus(false);
+
+ // If the current tab is removed, another tab can steal our focus.
+ if (aTab.selected && this.panel.state == "open") {
+ setTimeout(function (selected) {
+ selected.focus();
+ }, 0, this.selected);
+ }
+ },
+
+ handleEvent: function ctrlTab_handleEvent(event) {
+ switch (event.type) {
+ case "TabAttrModified":
+ // tab attribute modified (e.g. label, crop, busy, image, selected)
+ for (let i = this.previews.length - 1; i >= 0; i--) {
+ if (this.previews[i]._tab && this.previews[i]._tab == event.target) {
+ this.updatePreview(this.previews[i], event.target);
+ break;
+ }
+ }
+ break;
+ case "TabSelect":
+ this.detachTab(event.target);
+ this.attachTab(event.target, 0);
+ break;
+ case "TabOpen":
+ this.attachTab(event.target, 1);
+ break;
+ case "TabClose":
+ this.detachTab(event.target);
+ if (this.isOpen)
+ this.removeClosingTabFromUI(event.target);
+ break;
+ case "keypress":
+ this.onKeyPress(event);
+ break;
+ case "keyup":
+ if (event.keyCode == event.DOM_VK_CONTROL)
+ this.pick();
+ break;
+ }
+ },
+
+ _init: function ctrlTab__init(enable) {
+ var toggleEventListener = enable ? "addEventListener" : "removeEventListener";
+
+ var tabContainer = gBrowser.tabContainer;
+ tabContainer[toggleEventListener]("TabOpen", this, false);
+ tabContainer[toggleEventListener]("TabAttrModified", this, false);
+ tabContainer[toggleEventListener]("TabSelect", this, false);
+ tabContainer[toggleEventListener]("TabClose", this, false);
+
+ document[toggleEventListener]("keypress", this, false);
+ gBrowser.mTabBox.handleCtrlTab = !enable;
+
+ // If we're not running, hide the "Show All Tabs" menu item,
+ // as Shift+Ctrl+Tab will be handled by the tab bar.
+ document.getElementById("menu_showAllTabs").hidden = !enable;
+
+ // Also disable the <key> to ensure Shift+Ctrl+Tab never triggers
+ // Show All Tabs.
+ var key_showAllTabs = document.getElementById("key_showAllTabs");
+ if (enable)
+ key_showAllTabs.removeAttribute("disabled");
+ else
+ key_showAllTabs.setAttribute("disabled", "true");
+ }
+};
+
+
+/**
+ * All Tabs panel
+ */
+var allTabs = {
+ get panel () {
+ delete this.panel;
+ return this.panel = document.getElementById("allTabs-panel");
+ },
+ get filterField () {
+ delete this.filterField;
+ return this.filterField = document.getElementById("allTabs-filter");
+ },
+ get container () {
+ delete this.container;
+ return this.container = document.getElementById("allTabs-container");
+ },
+ get tabCloseButton () {
+ delete this.tabCloseButton;
+ return this.tabCloseButton = document.getElementById("allTabs-tab-close-button");
+ },
+ get toolbarButton() document.getElementById("alltabs-button"),
+ get previews () this.container.getElementsByClassName("allTabs-preview"),
+ get isOpen () this.panel.state == "open" || this.panel.state == "showing",
+
+ init: function allTabs_init() {
+ if (this._initiated)
+ return;
+ this._initiated = true;
+
+ tabPreviews.init();
+
+ Array.forEach(gBrowser.tabs, function (tab) {
+ this._addPreview(tab);
+ }, this);
+
+ gBrowser.tabContainer.addEventListener("TabOpen", this, false);
+ gBrowser.tabContainer.addEventListener("TabAttrModified", this, false);
+ gBrowser.tabContainer.addEventListener("TabMove", this, false);
+ gBrowser.tabContainer.addEventListener("TabClose", this, false);
+ },
+
+ uninit: function allTabs_uninit() {
+ if (!this._initiated)
+ return;
+
+ gBrowser.tabContainer.removeEventListener("TabOpen", this, false);
+ gBrowser.tabContainer.removeEventListener("TabAttrModified", this, false);
+ gBrowser.tabContainer.removeEventListener("TabMove", this, false);
+ gBrowser.tabContainer.removeEventListener("TabClose", this, false);
+
+ while (this.container.hasChildNodes())
+ this.container.removeChild(this.container.firstChild);
+
+ this._initiated = false;
+ },
+
+ prefName: "browser.allTabs.previews",
+ readPref: function allTabs_readPref() {
+ var allTabsButton = this.toolbarButton;
+ if (!allTabsButton)
+ return;
+
+ if (gPrefService.getBoolPref(this.prefName)) {
+ allTabsButton.removeAttribute("type");
+ allTabsButton.setAttribute("command", "Browser:ShowAllTabs");
+ } else {
+ allTabsButton.setAttribute("type", "menu");
+ allTabsButton.removeAttribute("command");
+ allTabsButton.removeAttribute("oncommand");
+ }
+ },
+ observe: function (aSubject, aTopic, aPrefName) {
+ this.readPref();
+ },
+
+ pick: function allTabs_pick(aPreview) {
+ if (!aPreview)
+ aPreview = this._firstVisiblePreview;
+ if (aPreview)
+ this.tabToSelect = aPreview._tab;
+
+ this.close();
+ },
+
+ closeTab: function allTabs_closeTab(event) {
+ this.filterField.focus();
+ gBrowser.removeTab(event.currentTarget._targetPreview._tab);
+ },
+
+ filter: function allTabs_filter() {
+ if (this._currentFilter == this.filterField.value)
+ return;
+
+ this._currentFilter = this.filterField.value;
+
+ var filter = this._currentFilter.split(/\s+/g);
+ this._visible = 0;
+ Array.forEach(this.previews, function (preview) {
+ var tab = preview._tab;
+ var matches = 0;
+ if (filter.length && !tab.hidden) {
+ let tabstring = tab.linkedBrowser.currentURI.spec;
+ try {
+ tabstring = decodeURI(tabstring);
+ } catch (e) {}
+ tabstring = tab.label + " " + tab.label.toLocaleLowerCase() + " " + tabstring;
+ for (let i = 0; i < filter.length; i++)
+ matches += tabstring.contains(filter[i]);
+ }
+ if (matches < filter.length || tab.hidden) {
+ preview.hidden = true;
+ }
+ else {
+ this._visible++;
+ this._updatePreview(preview);
+ preview.hidden = false;
+ }
+ }, this);
+
+ this._reflow();
+ },
+
+ open: function allTabs_open() {
+ var allTabsButton = this.toolbarButton;
+ if (allTabsButton &&
+ allTabsButton.getAttribute("type") == "menu") {
+ // Without setTimeout, the menupopup won't stay open when invoking
+ // "View > Show All Tabs" and the menu bar auto-hides.
+ setTimeout(function () {
+ allTabsButton.open = true;
+ }, 0);
+ return;
+ }
+
+ this.init();
+
+ if (this.isOpen)
+ return;
+
+ this._maxPanelHeight = Math.max(gBrowser.clientHeight, screen.availHeight / 2);
+ this._maxPanelWidth = Math.max(gBrowser.clientWidth, screen.availWidth / 2);
+
+ this.filter();
+
+ tabPreviewPanelHelper.opening(this);
+
+ this.panel.popupBoxObject.setConsumeRollupEvent(Ci.nsIPopupBoxObject.ROLLUP_NO_CONSUME);
+ this.panel.openPopup(gBrowser, "overlap", 0, 0, false, true);
+ },
+
+ close: function allTabs_close() {
+ this.panel.hidePopup();
+ },
+
+ setupGUI: function allTabs_setupGUI() {
+ this.filterField.focus();
+ this.filterField.placeholder = this.filterField.tooltipText;
+
+ this.panel.addEventListener("keypress", this, false);
+ this.panel.addEventListener("keypress", this, true);
+ this._browserCommandSet.addEventListener("command", this, false);
+
+ // When the panel is open, a second click on the all tabs button should
+ // close the panel but not re-open it.
+ document.getElementById("Browser:ShowAllTabs").setAttribute("disabled", "true");
+ },
+
+ suspendGUI: function allTabs_suspendGUI() {
+ this.filterField.placeholder = "";
+ this.filterField.value = "";
+ this._currentFilter = null;
+
+ this._updateTabCloseButton();
+
+ this.panel.removeEventListener("keypress", this, false);
+ this.panel.removeEventListener("keypress", this, true);
+ this._browserCommandSet.removeEventListener("command", this, false);
+
+ setTimeout(function () {
+ document.getElementById("Browser:ShowAllTabs").removeAttribute("disabled");
+ }, 300);
+ },
+
+ handleEvent: function allTabs_handleEvent(event) {
+ if (event.type.startsWith("Tab")) {
+ var tab = event.target;
+ if (event.type != "TabOpen")
+ var preview = this._getPreview(tab);
+ }
+ switch (event.type) {
+ case "TabAttrModified":
+ // tab attribute modified (e.g. label, crop, busy, image)
+ if (!preview.hidden)
+ this._updatePreview(preview);
+ break;
+ case "TabOpen":
+ if (this.isOpen)
+ this.close();
+ this._addPreview(tab);
+ break;
+ case "TabMove":
+ let siblingPreview = tab.nextSibling &&
+ this._getPreview(tab.nextSibling);
+ if (siblingPreview)
+ siblingPreview.parentNode.insertBefore(preview, siblingPreview);
+ else
+ this.container.lastChild.appendChild(preview);
+ if (this.isOpen && !preview.hidden) {
+ this._reflow();
+ preview.focus();
+ }
+ break;
+ case "TabClose":
+ this._removePreview(preview);
+ break;
+ case "keypress":
+ this._onKeyPress(event);
+ break;
+ case "command":
+ if (event.target.id != "Browser:ShowAllTabs") {
+ // Close the panel when there's a browser command executing in the background.
+ this.close();
+ }
+ break;
+ }
+ },
+
+ _visible: 0,
+ _currentFilter: null,
+ get _stack () {
+ delete this._stack;
+ return this._stack = document.getElementById("allTabs-stack");
+ },
+ get _browserCommandSet () {
+ delete this._browserCommandSet;
+ return this._browserCommandSet = document.getElementById("mainCommandSet");
+ },
+ get _previewLabelHeight () {
+ delete this._previewLabelHeight;
+ return this._previewLabelHeight = parseInt(getComputedStyle(this.previews[0], "").lineHeight);
+ },
+
+ get _visiblePreviews ()
+ Array.filter(this.previews, function (preview) !preview.hidden),
+
+ get _firstVisiblePreview () {
+ if (this._visible == 0)
+ return null;
+ var previews = this.previews;
+ for (let i = 0; i < previews.length; i++) {
+ if (!previews[i].hidden)
+ return previews[i];
+ }
+ return null;
+ },
+
+ _reflow: function allTabs_reflow() {
+ this._updateTabCloseButton();
+
+ const CONTAINER_MAX_WIDTH = this._maxPanelWidth * .95;
+ const CONTAINER_MAX_HEIGHT = this._maxPanelHeight - 35;
+ // the size of the whole preview relative to the thumbnail
+ const REL_PREVIEW_THUMBNAIL = 1.2;
+ const REL_PREVIEW_HEIGHT_WIDTH = tabPreviews.height / tabPreviews.width;
+ const PREVIEW_MAX_WIDTH = tabPreviews.width * REL_PREVIEW_THUMBNAIL;
+
+ var rows, previewHeight, previewWidth, outerHeight;
+ this._columns = Math.floor(CONTAINER_MAX_WIDTH / PREVIEW_MAX_WIDTH);
+ do {
+ rows = Math.ceil(this._visible / this._columns);
+ previewWidth = Math.min(PREVIEW_MAX_WIDTH,
+ Math.round(CONTAINER_MAX_WIDTH / this._columns));
+ previewHeight = Math.round(previewWidth * REL_PREVIEW_HEIGHT_WIDTH);
+ outerHeight = previewHeight + this._previewLabelHeight;
+ } while (rows * outerHeight > CONTAINER_MAX_HEIGHT && ++this._columns);
+
+ var outerWidth = previewWidth;
+ {
+ let innerWidth = Math.ceil(previewWidth / REL_PREVIEW_THUMBNAIL);
+ let innerHeight = Math.ceil(previewHeight / REL_PREVIEW_THUMBNAIL);
+ var canvasStyle = "max-width:" + innerWidth + "px;" +
+ "min-width:" + innerWidth + "px;" +
+ "max-height:" + innerHeight + "px;" +
+ "min-height:" + innerHeight + "px;";
+ }
+
+ var previews = Array.slice(this.previews);
+
+ while (this.container.hasChildNodes())
+ this.container.removeChild(this.container.firstChild);
+ for (let i = rows || 1; i > 0; i--)
+ this.container.appendChild(document.createElement("hbox"));
+
+ var row = this.container.firstChild;
+ var colCount = 0;
+ previews.forEach(function (preview) {
+ if (!preview.hidden &&
+ ++colCount > this._columns) {
+ row = row.nextSibling;
+ colCount = 1;
+ }
+ preview.setAttribute("minwidth", outerWidth);
+ preview.setAttribute("height", outerHeight);
+ preview.setAttribute("canvasstyle", canvasStyle);
+ preview.removeAttribute("closebuttonhover");
+ row.appendChild(preview);
+ }, this);
+
+ this._stack.width = this._maxPanelWidth;
+ this.container.width = Math.ceil(outerWidth * Math.min(this._columns, this._visible));
+ this.container.left = Math.round((this._maxPanelWidth - this.container.width) / 2);
+ this.container.maxWidth = this._maxPanelWidth - this.container.left;
+ this.container.maxHeight = rows * outerHeight;
+ },
+
+ _addPreview: function allTabs_addPreview(aTab) {
+ var preview = document.createElement("button");
+ preview.className = "allTabs-preview";
+ preview._tab = aTab;
+ this.container.lastChild.appendChild(preview);
+ },
+
+ _removePreview: function allTabs_removePreview(aPreview) {
+ var updateUI = (this.isOpen && !aPreview.hidden);
+ aPreview._tab = null;
+ aPreview.parentNode.removeChild(aPreview);
+ if (updateUI) {
+ this._visible--;
+ this._reflow();
+ this.filterField.focus();
+ }
+ },
+
+ _getPreview: function allTabs_getPreview(aTab) {
+ var previews = this.previews;
+ for (let i = 0; i < previews.length; i++)
+ if (previews[i]._tab == aTab)
+ return previews[i];
+ return null;
+ },
+
+ _updateTabCloseButton: function allTabs_updateTabCloseButton(event) {
+ if (event && event.target == this.tabCloseButton)
+ return;
+
+ if (this.tabCloseButton._targetPreview) {
+ if (event && event.target == this.tabCloseButton._targetPreview)
+ return;
+
+ this.tabCloseButton._targetPreview.removeAttribute("closebuttonhover");
+ }
+
+ if (event &&
+ event.target.parentNode.parentNode == this.container &&
+ (event.target._tab.previousSibling || event.target._tab.nextSibling)) {
+ let canvas = event.target.firstChild.getBoundingClientRect();
+ let container = this.container.getBoundingClientRect();
+ let tabCloseButton = this.tabCloseButton.getBoundingClientRect();
+ let alignLeft = getComputedStyle(this.panel, "").direction == "rtl";
+#ifdef XP_MACOSX
+ alignLeft = !alignLeft;
+#endif
+ this.tabCloseButton.left = canvas.left -
+ container.left +
+ parseInt(this.container.left) +
+ (alignLeft ? 0 :
+ canvas.width - tabCloseButton.width);
+ this.tabCloseButton.top = canvas.top - container.top;
+ this.tabCloseButton._targetPreview = event.target;
+ this.tabCloseButton.style.visibility = "visible";
+ event.target.setAttribute("closebuttonhover", "true");
+ } else {
+ this.tabCloseButton.style.visibility = "hidden";
+ this.tabCloseButton.left = this.tabCloseButton.top = 0;
+ this.tabCloseButton._targetPreview = null;
+ }
+ },
+
+ _updatePreview: function allTabs_updatePreview(aPreview) {
+ aPreview.setAttribute("label", aPreview._tab.label);
+ aPreview.setAttribute("tooltiptext", aPreview._tab.label);
+ aPreview.setAttribute("crop", aPreview._tab.crop);
+ if (aPreview._tab.image)
+ aPreview.setAttribute("image", aPreview._tab.image);
+ else
+ aPreview.removeAttribute("image");
+
+ var thumbnail = tabPreviews.get(aPreview._tab);
+ if (aPreview.firstChild) {
+ if (aPreview.firstChild == thumbnail)
+ return;
+ aPreview.removeChild(aPreview.firstChild);
+ }
+ aPreview.appendChild(thumbnail);
+ },
+
+ _onKeyPress: function allTabs_onKeyPress(event) {
+ if (event.eventPhase == event.CAPTURING_PHASE) {
+ this._onCapturingKeyPress(event);
+ return;
+ }
+
+ if (event.keyCode == event.DOM_VK_ESCAPE) {
+ this.close();
+ event.preventDefault();
+ event.stopPropagation();
+ return;
+ }
+
+ if (event.target == this.filterField) {
+ switch (event.keyCode) {
+ case event.DOM_VK_UP:
+ if (this._visible) {
+ let previews = this._visiblePreviews;
+ let columns = Math.min(previews.length, this._columns);
+ previews[Math.floor(previews.length / columns) * columns - 1].focus();
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ break;
+ case event.DOM_VK_DOWN:
+ if (this._visible) {
+ this._firstVisiblePreview.focus();
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ break;
+ }
+ }
+ },
+
+ _onCapturingKeyPress: function allTabs_onCapturingKeyPress(event) {
+ switch (event.keyCode) {
+ case event.DOM_VK_UP:
+ case event.DOM_VK_DOWN:
+ if (event.target != this.filterField)
+ this._advanceFocusVertically(event);
+ break;
+ case event.DOM_VK_RETURN:
+ if (event.target == this.filterField) {
+ this.filter();
+ this.pick();
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ break;
+ }
+ },
+
+ _advanceFocusVertically: function allTabs_advanceFocusVertically(event) {
+ var preview = document.activeElement;
+ if (!preview || preview.parentNode.parentNode != this.container)
+ return;
+
+ event.stopPropagation();
+
+ var up = (event.keyCode == event.DOM_VK_UP);
+ var previews = this._visiblePreviews;
+
+ if (up && preview == previews[0]) {
+ this.filterField.focus();
+ return;
+ }
+
+ var i = previews.indexOf(preview);
+ var columns = Math.min(previews.length, this._columns);
+ var column = i % columns;
+ var row = Math.floor(i / columns);
+
+ function newIndex() row * columns + column;
+ function outOfBounds() newIndex() >= previews.length;
+
+ if (up) {
+ row--;
+ if (row < 0) {
+ let rows = Math.ceil(previews.length / columns);
+ row = rows - 1;
+ column--;
+ if (outOfBounds())
+ row--;
+ }
+ } else {
+ row++;
+ if (outOfBounds()) {
+ if (column == columns - 1) {
+ this.filterField.focus();
+ return;
+ }
+ row = 0;
+ column++;
+ }
+ }
+ previews[newIndex()].focus();
+ }
+};
diff --git a/browser/base/content/browser-tabPreviews.xml b/browser/base/content/browser-tabPreviews.xml
new file mode 100644
index 000000000..e957649e7
--- /dev/null
+++ b/browser/base/content/browser-tabPreviews.xml
@@ -0,0 +1,75 @@
+<?xml version="1.0"?>
+
+# -*- Mode: HTML -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<bindings id="tabPreviews"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+ <binding id="ctrlTab-preview" extends="chrome://global/content/bindings/button.xml#button-base">
+ <content pack="center">
+ <xul:stack>
+ <xul:vbox class="ctrlTab-preview-inner" align="center" pack="center"
+ xbl:inherits="width=canvaswidth">
+ <xul:hbox class="tabPreview-canvas" xbl:inherits="style=canvasstyle">
+ <children/>
+ </xul:hbox>
+ <xul:label xbl:inherits="value=label,crop" class="plain"/>
+ </xul:vbox>
+ <xul:hbox class="ctrlTab-favicon-container" xbl:inherits="hidden=noicon">
+ <xul:image class="ctrlTab-favicon" xbl:inherits="src=image"/>
+ </xul:hbox>
+ </xul:stack>
+ </content>
+ <handlers>
+ <handler event="mouseover" action="ctrlTab._mouseOverFocus(this);"/>
+ <handler event="command" action="ctrlTab.pick(this);"/>
+ <handler event="click" button="1" action="ctrlTab.remove(this);"/>
+#ifdef XP_MACOSX
+# Control+click is a right click on OS X
+ <handler event="click" button="2" action="ctrlTab.pick(this);"/>
+#endif
+ </handlers>
+ </binding>
+
+ <binding id="allTabs-preview" extends="chrome://global/content/bindings/button.xml#button-base">
+ <content pack="center" align="center">
+ <xul:stack>
+ <xul:vbox class="allTabs-preview-inner" align="center" pack="center">
+ <xul:hbox class="tabPreview-canvas" xbl:inherits="style=canvasstyle">
+ <children/>
+ </xul:hbox>
+ <xul:label flex="1" xbl:inherits="value=label,crop" class="allTabs-preview-label plain"/>
+ </xul:vbox>
+ <xul:hbox class="allTabs-favicon-container">
+ <xul:image class="allTabs-favicon" xbl:inherits="src=image"/>
+ </xul:hbox>
+ </xul:stack>
+ </content>
+ <handlers>
+ <handler event="command" action="allTabs.pick(this);"/>
+ <handler event="click" button="1" action="gBrowser.removeTab(this._tab);"/>
+
+ <handler event="dragstart"><![CDATA[
+ event.dataTransfer.mozSetDataAt("application/x-moz-node", this._tab, 0);
+ ]]></handler>
+
+ <handler event="dragover"><![CDATA[
+ let tab = event.dataTransfer.mozGetDataAt("application/x-moz-node", 0);
+ if (tab && tab.parentNode == gBrowser.tabContainer)
+ event.preventDefault();
+ ]]></handler>
+
+ <handler event="drop"><![CDATA[
+ let tab = event.dataTransfer.mozGetDataAt("application/x-moz-node", 0);
+ if (tab && tab.parentNode == gBrowser.tabContainer) {
+ let newIndex = Array.indexOf(gBrowser.tabs, this._tab);
+ gBrowser.moveTabTo(tab, newIndex);
+ }
+ ]]></handler>
+ </handlers>
+ </binding>
+</bindings>
diff --git a/browser/base/content/browser-thumbnails.js b/browser/base/content/browser-thumbnails.js
new file mode 100644
index 000000000..dbe33e3ed
--- /dev/null
+++ b/browser/base/content/browser-thumbnails.js
@@ -0,0 +1,198 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * Keeps thumbnails of open web pages up-to-date.
+ */
+let gBrowserThumbnails = {
+ /**
+ * Pref that controls whether we can store SSL content on disk
+ */
+ PREF_DISK_CACHE_SSL: "browser.cache.disk_cache_ssl",
+
+ _captureDelayMS: 1000,
+
+ /**
+ * Used to keep track of disk_cache_ssl preference
+ */
+ _sslDiskCacheEnabled: null,
+
+ /**
+ * Map of capture() timeouts assigned to their browsers.
+ */
+ _timeouts: null,
+
+ /**
+ * List of tab events we want to listen for.
+ */
+ _tabEvents: ["TabClose", "TabSelect"],
+
+ init: function Thumbnails_init() {
+ // Bug 863512 - Make page thumbnails work in electrolysis
+ if (gMultiProcessBrowser)
+ return;
+
+ try {
+ if (Services.prefs.getBoolPref("browser.pagethumbnails.capturing_disabled"))
+ return;
+ } catch (e) {}
+
+ PageThumbs.addExpirationFilter(this);
+ gBrowser.addTabsProgressListener(this);
+ Services.prefs.addObserver(this.PREF_DISK_CACHE_SSL, this, false);
+
+ this._sslDiskCacheEnabled =
+ Services.prefs.getBoolPref(this.PREF_DISK_CACHE_SSL);
+
+ this._tabEvents.forEach(function (aEvent) {
+ gBrowser.tabContainer.addEventListener(aEvent, this, false);
+ }, this);
+
+ this._timeouts = new WeakMap();
+ },
+
+ uninit: function Thumbnails_uninit() {
+ // Bug 863512 - Make page thumbnails work in electrolysis
+ if (gMultiProcessBrowser)
+ return;
+
+ PageThumbs.removeExpirationFilter(this);
+ gBrowser.removeTabsProgressListener(this);
+ Services.prefs.removeObserver(this.PREF_DISK_CACHE_SSL, this);
+
+ this._tabEvents.forEach(function (aEvent) {
+ gBrowser.tabContainer.removeEventListener(aEvent, this, false);
+ }, this);
+ },
+
+ handleEvent: function Thumbnails_handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "scroll":
+ let browser = aEvent.currentTarget;
+ if (this._timeouts.has(browser))
+ this._delayedCapture(browser);
+ break;
+ case "TabSelect":
+ this._delayedCapture(aEvent.target.linkedBrowser);
+ break;
+ case "TabClose": {
+ this._clearTimeout(aEvent.target.linkedBrowser);
+ break;
+ }
+ }
+ },
+
+ observe: function Thumbnails_observe() {
+ this._sslDiskCacheEnabled =
+ Services.prefs.getBoolPref(this.PREF_DISK_CACHE_SSL);
+ },
+
+ filterForThumbnailExpiration:
+ function Thumbnails_filterForThumbnailExpiration(aCallback) {
+ aCallback([browser.currentURI.spec for (browser of gBrowser.browsers)]);
+ },
+
+ /**
+ * State change progress listener for all tabs.
+ */
+ onStateChange: function Thumbnails_onStateChange(aBrowser, aWebProgress,
+ aRequest, aStateFlags, aStatus) {
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK)
+ this._delayedCapture(aBrowser);
+ },
+
+ _capture: function Thumbnails_capture(aBrowser) {
+ if (this._shouldCapture(aBrowser))
+ PageThumbs.captureAndStore(aBrowser);
+ },
+
+ _delayedCapture: function Thumbnails_delayedCapture(aBrowser) {
+ if (this._timeouts.has(aBrowser))
+ clearTimeout(this._timeouts.get(aBrowser));
+ else
+ aBrowser.addEventListener("scroll", this, true);
+
+ let timeout = setTimeout(function () {
+ this._clearTimeout(aBrowser);
+ this._capture(aBrowser);
+ }.bind(this), this._captureDelayMS);
+
+ this._timeouts.set(aBrowser, timeout);
+ },
+
+ _shouldCapture: function Thumbnails_shouldCapture(aBrowser) {
+ // Capture only if it's the currently selected tab.
+ if (aBrowser != gBrowser.selectedBrowser)
+ return false;
+
+ // Don't capture in per-window private browsing mode.
+ if (PrivateBrowsingUtils.isWindowPrivate(window))
+ return false;
+
+ let doc = aBrowser.contentDocument;
+
+ // FIXME Bug 720575 - Don't capture thumbnails for SVG or XML documents as
+ // that currently regresses Talos SVG tests.
+ if (doc instanceof SVGDocument || doc instanceof XMLDocument)
+ return false;
+
+ // There's no point in taking screenshot of loading pages.
+ if (aBrowser.docShell.busyFlags != Ci.nsIDocShell.BUSY_FLAGS_NONE)
+ return false;
+
+ // Don't take screenshots of about: pages.
+ if (aBrowser.currentURI.schemeIs("about"))
+ return false;
+
+ let channel = aBrowser.docShell.currentDocumentChannel;
+
+ // No valid document channel. We shouldn't take a screenshot.
+ if (!channel)
+ return false;
+
+ // Don't take screenshots of internally redirecting about: pages.
+ // This includes error pages.
+ let uri = channel.originalURI;
+ if (uri.schemeIs("about"))
+ return false;
+
+ let httpChannel;
+ try {
+ httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
+ } catch (e) { /* Not an HTTP channel. */ }
+
+ if (httpChannel) {
+ // Continue only if we have a 2xx status code.
+ try {
+ if (Math.floor(httpChannel.responseStatus / 100) != 2)
+ return false;
+ } catch (e) {
+ // Can't get response information from the httpChannel
+ // because mResponseHead is not available.
+ return false;
+ }
+
+ // Cache-Control: no-store.
+ if (httpChannel.isNoStoreResponse())
+ return false;
+
+ // Don't capture HTTPS pages unless the user explicitly enabled it.
+ if (uri.schemeIs("https") && !this._sslDiskCacheEnabled)
+ return false;
+ }
+
+ return true;
+ },
+
+ _clearTimeout: function Thumbnails_clearTimeout(aBrowser) {
+ if (this._timeouts.has(aBrowser)) {
+ aBrowser.removeEventListener("scroll", this, false);
+ clearTimeout(this._timeouts.get(aBrowser));
+ this._timeouts.delete(aBrowser);
+ }
+ }
+};
diff --git a/browser/base/content/browser-title.css b/browser/base/content/browser-title.css
new file mode 100644
index 000000000..664d4bb93
--- /dev/null
+++ b/browser/base/content/browser-title.css
@@ -0,0 +1,181 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#main-window::after {
+ content: attr(title);
+ line-height: 50px;
+ max-height: 50px;
+ overflow: -moz-hidden-unscrollable;
+ pointer-events: none;
+ position: fixed;
+ word-wrap: break-word;
+ -moz-hyphens: auto;
+ color: CaptionText;
+ font-weight: bold;
+ text-align: left;
+}
+
+#main-window:-moz-window-inactive::after {
+ color: InactiveCaptionText;
+}
+
+/* Hide in fullscreen/TiT mode */
+#main-window[inFullscreen="true"]::after,
+#main-window[sizemode="maximized"][tabsintitlebar="true"]::after,
+#main-window:not([chromemargin])::after {
+ opacity: 0 !important;
+}
+
+
+#main-window::after {
+ padding: 0 132px; /* AppMenu button/wincontrols width offset */
+ left: 0;
+ right: 0;
+}
+
+#main-window[privatebrowsingmode=temporary]::after {
+ padding: 0px 132px 0px 152px; /* AppMenu button width offset for PB mode */
+}
+
+#main-window[sizemode="normal"]::after {
+ left: -12px;
+ right: -12px;
+}
+
+
+/* Windows Classic theme */
+
+@media all and (-moz-windows-classic) {
+
+ #main-window::after {
+ top: -13px;
+ text-shadow: none !important;
+ background-position: 2px 18px;
+ }
+
+}
+
+
+/* Windows Aero (Vista, non-glass 7/8) */
+
+@media all and (-moz-windows-theme: aero) {
+
+ #main-window::after {
+ top: -11px;
+ font-weight: normal;
+ text-shadow: none;
+ background-position: 2px 17px;
+ }
+
+ #main-window[sizemode="maximized"]::after {
+ top: -7px;
+ }
+
+}
+
+
+/* Windows Aero Glass */
+
+@media (-moz-windows-glass) {
+
+ #main-window::after {
+ top: -13px;
+ color: black;
+ text-shadow: rgba(255,255,255,.6) 7px -1px 12px,
+ rgba(255,255,255,.6) 6px -1px 13px,
+ rgba(255,255,255,.9) 5px -1px 14px,
+ rgba(255,255,255,.6) -7px -1px 12px,
+ rgba(255,255,255,.6) -6px -1px 13px,
+ rgba(255,255,255,.9) -5px -1px 14px;
+ z-index: -99999;
+ background-position: 2px 18px;
+ font-weight: bold;
+ }
+
+ #main-window[sizemode="maximized"]::after {
+ top: -7px;
+ }
+
+ #main-window:-moz-window-inactive::after {
+ opacity: .9;
+ color: black;
+ text-shadow: rgba(255,255,255,.7) 7px -1px 12px,
+ rgba(255,255,255,.5) 6px -1px 13px,
+ rgba(255,255,255,.5) 5px -1px 14px,
+ rgba(255,255,255,.7) -7px -1px 12px,
+ rgba(255,255,255,.5) -6px -1px 13px,
+ rgba(255,255,255,.5) -5px -1px 14px;
+ }
+
+}
+
+
+/* Windows XP Blue/Olive */
+
+@media all and (-moz-windows-theme: luna-blue), all and (-moz-windows-theme: luna-olive) {
+
+ #main-window::after {
+ font-family: trebuchet MS;
+ font-size: 13px;
+ text-shadow: 1px 1px rgba(0, 0, 0, .6);
+ top: -9px;
+ background-position: 2px 17px;
+ }
+
+ #main-window:-moz-window-inactive::after {
+ text-shadow: none;
+ }
+
+}
+
+
+/* Windows XP Silver, Royale, Zune, generic other themes */
+
+@media all and (-moz-windows-theme: luna-silver), all and (-moz-windows-theme: royale), all and (-moz-windows-theme: zune), all and (-moz-windows-theme: generic) {
+
+ #main-window::after {
+ font-family: trebuchet MS;
+ font-size: 13px;
+ text-shadow: 1px 1px rgba(0, 0, 0, .2);
+ top: -9px;
+ background-position: 2px 16px;
+ }
+
+ #main-window:-moz-window-inactive::after {
+ text-shadow: none;
+ }
+
+}
+
+
+/* Compositor style for Win 8 */
+
+@media all and (-moz-windows-compositor) {
+ @media not all and (-moz-windows-glass) {
+
+ #main-window::after {
+ top: -13px;
+ font-size: 15px;
+ background-position: 4px 17px;
+ text-align: center;
+ }
+
+ #main-window[sizemode="maximized"]::after {
+ top: -5px;
+ }
+
+ }
+
+}
+
+
+/* Hide for small windows */
+
+@media not all and (min-width: 320px) {
+
+ #main-window::after {
+ opacity: 0 !important;
+ }
+
+} \ No newline at end of file
diff --git a/browser/base/content/browser-webrtcUI.js b/browser/base/content/browser-webrtcUI.js
new file mode 100644
index 000000000..a6c9008ca
--- /dev/null
+++ b/browser/base/content/browser-webrtcUI.js
@@ -0,0 +1,55 @@
+# -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+let WebrtcIndicator = {
+ init: function () {
+ let temp = {};
+ Cu.import("resource:///modules/webrtcUI.jsm", temp);
+ this.UIModule = temp.webrtcUI;
+
+ this.updateButton();
+ },
+
+ get button() {
+ delete this.button;
+ return this.button = document.getElementById("webrtc-status-button");
+ },
+
+ updateButton: function () {
+ this.button.hidden = !this.UIModule.showGlobalIndicator;
+ },
+
+ fillPopup: function (aPopup) {
+ this._menuitemData = new WeakMap;
+ for (let streamData of this.UIModule.activeStreams) {
+ let menuitem = document.createElement("menuitem");
+ menuitem.setAttribute("label", streamData.uri);
+ menuitem.setAttribute("tooltiptext", streamData.uri);
+
+ this._menuitemData.set(menuitem, streamData);
+
+ aPopup.appendChild(menuitem);
+ }
+ },
+
+ clearPopup: function (aPopup) {
+ while (aPopup.lastChild)
+ aPopup.removeChild(aPopup.lastChild);
+ },
+
+ menuCommand: function (aMenuitem) {
+ let streamData = this._menuitemData.get(aMenuitem);
+ if (!streamData)
+ return;
+
+ let browserWindow = streamData.browser.ownerDocument.defaultView;
+ if (streamData.tab) {
+ browserWindow.gBrowser.selectedTab = streamData.tab;
+ } else {
+ streamData.browser.focus();
+ }
+ browserWindow.focus();
+ }
+}
diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css
new file mode 100644
index 000000000..b04241bdc
--- /dev/null
+++ b/browser/base/content/browser.css
@@ -0,0 +1,723 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
+
+searchbar {
+ -moz-binding: url("chrome://browser/content/search/search.xml#searchbar");
+}
+
+browser[remote="true"] {
+ -moz-binding: url("chrome://global/content/bindings/remote-browser.xml#remote-browser");
+}
+
+tabbrowser {
+ -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser");
+}
+
+.tabbrowser-tabs {
+ -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-tabs");
+}
+
+#tabbrowser-tabs:not([overflow="true"]) ~ #alltabs-button,
+#tabbrowser-tabs:not([overflow="true"]) + #new-tab-button,
+#tabbrowser-tabs[overflow="true"] > .tabbrowser-arrowscrollbox > .tabs-newtab-button,
+#TabsToolbar[currentset]:not([currentset*="tabbrowser-tabs,new-tab-button"]) > #tabbrowser-tabs > .tabbrowser-arrowscrollbox > .tabs-newtab-button,
+#TabsToolbar[customizing="true"] > #tabbrowser-tabs > .tabbrowser-arrowscrollbox > .tabs-newtab-button {
+ visibility: collapse;
+}
+
+#alltabs-button { /* Pale Moon: Always show this button! (less jumpy UI) */
+ visibility: visible !important;
+}
+
+#tabbrowser-tabs:not([overflow="true"])[using-closing-tabs-spacer] ~ #alltabs-button {
+ visibility: hidden; /* temporary space to keep a tab's close button under the cursor */
+}
+
+.tabbrowser-tab {
+ -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-tab");
+}
+
+.tabbrowser-tab:not([pinned]) {
+ -moz-box-flex: 100;
+ max-width: 250px;
+ min-width: 100px;
+ width: 0;
+ transition: min-width 175ms ease-out,
+ max-width 200ms ease-out,
+ opacity 80ms ease-out 20ms /* hide the tab for the first 20ms of the max-width transition */;
+}
+
+.tabbrowser-tab:not([pinned]):not([fadein]) {
+ max-width: 0.1px;
+ min-width: 0.1px;
+ opacity: 0 !important;
+ transition: min-width 175ms ease-out,
+ max-width 200ms ease-out,
+ opacity 80ms ease-out 180ms /* hide the tab for the last 20ms of the max-width transition */;
+}
+
+.tab-throbber:not([fadein]):not([pinned]),
+.tab-label:not([fadein]):not([pinned]),
+.tab-icon-image:not([fadein]):not([pinned]),
+.tab-close-button:not([fadein]):not([pinned]) {
+ display: none;
+}
+
+.tabbrowser-tabs[positionpinnedtabs] > .tabbrowser-tab[pinned] {
+ position: fixed !important;
+ display: block; /* position:fixed already does this (bug 579776), but let's be explicit */
+}
+
+.tabbrowser-tabs[movingtab] > .tabbrowser-tab[selected] {
+ position: relative;
+ z-index: 2;
+ pointer-events: none; /* avoid blocking dragover events on scroll buttons */
+}
+
+.tabbrowser-tabs[movingtab] > .tabbrowser-tab[fadein]:not([selected]) {
+ transition: transform 200ms ease-out;
+}
+
+#alltabs-popup {
+ -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-alltabs-popup");
+}
+
+toolbar[printpreview="true"] {
+ -moz-binding: url("chrome://global/content/printPreviewBindings.xml#printpreviewtoolbar");
+}
+
+#toolbar-menubar {
+ -moz-box-ordinal-group: 5;
+}
+
+#navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar) {
+ -moz-box-ordinal-group: 50;
+}
+
+#TabsToolbar {
+ -moz-box-ordinal-group: 100;
+}
+
+#TabsToolbar[tabsontop="true"] {
+ -moz-box-ordinal-group: 10;
+}
+
+%ifdef CAN_DRAW_IN_TITLEBAR
+#main-window[inFullscreen] > #titlebar,
+#main-window[inFullscreen] .titlebar-placeholder,
+#main-window:not([tabsintitlebar]) .titlebar-placeholder {
+ display: none;
+}
+
+#titlebar {
+ -moz-binding: url("chrome://global/content/bindings/general.xml#windowdragbox");
+}
+
+#titlebar-spacer {
+ pointer-events: none;
+}
+
+#main-window[tabsintitlebar] #appmenu-button-container,
+#main-window[tabsintitlebar] #titlebar-buttonbox {
+ position: relative;
+}
+%endif
+
+.bookmarks-toolbar-customize,
+#wrapper-personal-bookmarks > #personal-bookmarks > #PlacesToolbar > hbox > #PlacesToolbarItems {
+ display: none;
+}
+
+#wrapper-personal-bookmarks[place="toolbar"] > #personal-bookmarks > #PlacesToolbar > .bookmarks-toolbar-customize {
+ display: -moz-box;
+}
+
+#main-window[disablechrome] #navigator-toolbox[tabsontop="true"] > toolbar:not(#toolbar-menubar):not(#TabsToolbar) {
+ visibility: collapse;
+}
+
+#wrapper-urlbar-container #urlbar-container > #urlbar > toolbarbutton,
+#urlbar-container:not([combined]) > #urlbar > toolbarbutton,
+#urlbar-container[combined] + #reload-button + #stop-button,
+#urlbar-container[combined] + #reload-button,
+toolbar:not([mode="icons"]) > #urlbar-container > #urlbar > toolbarbutton,
+toolbar[mode="icons"] > #urlbar-container > #urlbar > #urlbar-reload-button:not([displaystop]) + #urlbar-stop-button,
+toolbar[mode="icons"] > #urlbar-container > #urlbar > #urlbar-reload-button[displaystop],
+toolbar[mode="icons"] > #reload-button:not([displaystop]) + #stop-button,
+toolbar[mode="icons"] > #reload-button[displaystop] {
+ visibility: collapse;
+}
+
+#feed-button > .toolbarbutton-menu-dropmarker {
+ display: none;
+}
+
+#feed-menu > .feed-menuitem:-moz-locale-dir(rtl) {
+ direction: rtl;
+}
+
+#main-window:-moz-lwtheme {
+ background-repeat: no-repeat;
+ background-position: top right;
+}
+
+%ifdef XP_MACOSX
+#main-window[inFullscreen="true"] {
+ padding-top: 0; /* override drawintitlebar="true" */
+}
+%endif
+
+#browser-bottombox[lwthemefooter="true"] {
+ background-repeat: no-repeat;
+ background-position: bottom left;
+}
+
+splitmenu {
+ -moz-binding: url("chrome://browser/content/urlbarBindings.xml#splitmenu");
+}
+
+.splitmenu-menuitem {
+ -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem");
+ list-style-image: inherit;
+ -moz-image-region: inherit;
+}
+
+.splitmenu-menuitem[iconic="true"] {
+ -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem-iconic");
+}
+
+.splitmenu-menu > .menu-text,
+:-moz-any(.splitmenu-menu, .splitmenu-menuitem) > .menu-accel-container,
+#appmenu-editmenu > .menu-text,
+#appmenu-editmenu > .menu-accel-container {
+ display: none;
+}
+
+.menuitem-tooltip {
+ -moz-binding: url("chrome://browser/content/urlbarBindings.xml#menuitem-tooltip");
+}
+
+.menuitem-iconic-tooltip,
+.menuitem-tooltip[type="checkbox"],
+.menuitem-tooltip[type="radio"] {
+ -moz-binding: url("chrome://browser/content/urlbarBindings.xml#menuitem-iconic-tooltip");
+}
+
+%ifdef MENUBAR_CAN_AUTOHIDE
+%ifndef CAN_DRAW_IN_TITLEBAR
+#appmenu-toolbar-button > .toolbarbutton-text {
+ display: -moz-box;
+}
+%endif
+
+#appmenu_offlineModeRecovery:not([checked=true]) {
+ display: none;
+}
+%endif
+
+/* Hide menu elements intended for keyboard access support */
+#main-menubar[openedwithkey=false] .show-only-for-keyboard {
+ display: none;
+}
+
+/* ::::: location bar ::::: */
+#urlbar {
+ -moz-binding: url(chrome://browser/content/urlbarBindings.xml#urlbar);
+}
+
+.ac-url-text:-moz-locale-dir(rtl),
+.ac-title:-moz-locale-dir(rtl) > description {
+ direction: ltr !important;
+}
+
+/* For results that are actions, their description text is shown instead of
+ the URL - this needs to follow the locale's direction, unlike URLs. */
+panel:not([noactions]) > richlistbox > richlistitem[type~="action"]:-moz-locale-dir(rtl) > .ac-url-box {
+ direction: rtl;
+}
+
+panel[noactions] > richlistbox > richlistitem[type~="action"] > .ac-url-box > .ac-url > .ac-action-text,
+panel[noactions] > richlistbox > richlistitem[type~="action"] > .ac-url-box > .ac-action-icon {
+ visibility: collapse;
+}
+
+panel[noactions] > richlistbox > richlistitem[type~="action"] > .ac-url-box > .ac-url > .ac-url-text {
+ visibility: visible;
+}
+
+#urlbar:not([actiontype]) > #urlbar-display-box {
+ display: none;
+}
+
+#wrapper-urlbar-container > #urlbar-container > #urlbar {
+ -moz-user-input: disabled;
+ cursor: -moz-grab;
+}
+
+#PopupAutoComplete {
+ -moz-binding: url("chrome://browser/content/urlbarBindings.xml#browser-autocomplete-result-popup");
+}
+
+#PopupAutoCompleteRichResult {
+ -moz-binding: url("chrome://browser/content/urlbarBindings.xml#urlbar-rich-result-popup");
+}
+
+/* Pale Moon: Address bar: Feeds */
+#ub-feed-button > .button-box > .box-inherit > .button-text,
+#ub-feed-button > .button-box > .button-menu-dropmarker {
+ display: none;
+}
+
+#ub-feed-menu > .feed-menuitem:-moz-locale-dir(rtl) {
+ direction: rtl;
+}
+
+
+#urlbar-container[combined] > #urlbar > #urlbar-icons > #go-button,
+#urlbar[pageproxystate="invalid"] > #urlbar-icons > .urlbar-icon:not(#go-button),
+#urlbar[pageproxystate="valid"] > #urlbar-icons > #go-button,
+#urlbar[pageproxystate="invalid"][focused="true"] > #urlbar-go-button ~ toolbarbutton,
+#urlbar[pageproxystate="valid"] > #urlbar-go-button,
+#urlbar:not([focused="true"]) > #urlbar-go-button {
+ visibility: collapse;
+}
+
+#urlbar[pageproxystate="invalid"] > #identity-box > #identity-icon-labels {
+ visibility: collapse;
+}
+
+#urlbar[pageproxystate="invalid"] > #identity-box {
+ pointer-events: none;
+}
+
+#identity-icon-labels {
+ max-width: 18em;
+}
+
+#identity-icon-country-label {
+ direction: ltr;
+}
+
+#identity-box.verifiedIdentity > #identity-icon-labels > #identity-icon-label {
+ -moz-margin-end: 0.25em !important;
+}
+
+#wrapper-search-container > #search-container > #searchbar > .searchbar-textbox > .autocomplete-textbox-container > .textbox-input-box > html|*.textbox-input {
+ visibility: hidden;
+}
+
+/* ::::: Unified Back-/Forward Button ::::: */
+#back-button > .toolbarbutton-menu-dropmarker,
+#forward-button > .toolbarbutton-menu-dropmarker {
+ display: none;
+}
+.unified-nav-current {
+ font-weight: bold;
+}
+
+toolbarbutton.bookmark-item {
+ max-width: 13em;
+}
+
+%ifdef MENUBAR_CAN_AUTOHIDE
+#toolbar-menubar:not([autohide="true"]) ~ toolbar > #bookmarks-menu-button,
+#toolbar-menubar:not([autohide="true"]) > #bookmarks-menu-button {
+ display: none;
+}
+%endif
+
+#editBMPanel_tagsSelector {
+ /* override default listbox width from xul.css */
+ width: auto;
+}
+
+menupopup[emptyplacesresult="true"] > .hide-if-empty-places-result {
+ display: none;
+}
+
+menuitem.spell-suggestion {
+ font-weight: bold;
+}
+
+#sidebar-header > .tabs-closebutton {
+ -moz-user-focus: normal;
+}
+
+/* apply Fitts' law to the notification bar's close button */
+window[sizemode="maximized"] #content .notification-inner {
+ border-right: 0px !important;
+}
+
+/* Hide extension toolbars that neglected to set the proper class */
+window[chromehidden~="location"][chromehidden~="toolbar"] toolbar:not(.chromeclass-menubar),
+window[chromehidden~="toolbar"] toolbar:not(.toolbar-primary):not(.chromeclass-menubar) {
+ display: none;
+}
+
+#navigator-toolbox ,
+#status-bar ,
+#mainPopupSet {
+ min-width: 1px;
+}
+
+%ifdef MOZ_SERVICES_SYNC
+/* Sync notification UI */
+#sync-notifications {
+ -moz-binding: url("chrome://browser/content/sync/notification.xml#notificationbox");
+ overflow-y: visible !important;
+}
+
+#sync-notifications notification {
+ -moz-binding: url("chrome://browser/content/sync/notification.xml#notification");
+}
+%endif
+
+/* History Swipe Animation */
+
+#historySwipeAnimationContainer {
+ overflow: hidden;
+}
+
+#historySwipeAnimationPreviousPage,
+#historySwipeAnimationCurrentPage,
+#historySwipeAnimationNextPage {
+ background: none top left no-repeat white;
+}
+
+#historySwipeAnimationPreviousPage {
+ background-image: -moz-element(#historySwipeAnimationPreviousPageSnapshot);
+}
+
+#historySwipeAnimationCurrentPage {
+ background-image: -moz-element(#historySwipeAnimationCurrentPageSnapshot);
+}
+
+#historySwipeAnimationNextPage {
+ background-image: -moz-element(#historySwipeAnimationNextPageSnapshot);
+}
+
+/* Identity UI */
+#identity-popup-content-box.unknownIdentity > #identity-popup-connectedToLabel ,
+#identity-popup-content-box.unknownIdentity > #identity-popup-runByLabel ,
+#identity-popup-content-box.unknownIdentity > #identity-popup-content-host ,
+#identity-popup-content-box.unknownIdentity > #identity-popup-content-owner ,
+#identity-popup-content-box.verifiedIdentity > #identity-popup-connectedToLabel2 ,
+#identity-popup-content-box.verifiedDomain > #identity-popup-connectedToLabel2 {
+ display: none;
+}
+
+/* Full Screen UI */
+
+#fullscr-toggler {
+ height: 1px;
+ background: black;
+}
+
+#full-screen-warning-container {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ z-index: 2147483647 !important;
+}
+
+#full-screen-warning-container[fade-warning-out] {
+ transition-property: opacity !important;
+ transition-duration: 500ms !important;
+ opacity: 0.0;
+}
+
+/* When the modal fullscreen approval UI is showing, don't allow interaction
+ with the page, but when we're just showing the warning upon entering
+ fullscreen on an already approved page, do allow interaction with the page.
+ */
+#full-screen-warning-container:not([obscure-browser]) {
+ pointer-events: none;
+}
+
+#full-screen-warning-message {
+ /* We must specify a max-width, otherwise word-wrap:break-word doesn't
+ work in descendant <description> and <label> elements. Bug 630864. */
+ max-width: 800px;
+}
+
+#full-screen-domain-text,
+#full-screen-remember-decision > .checkbox-label-box > .checkbox-label {
+ word-wrap: break-word;
+ /* We must specify a min-width, otherwise word-wrap:break-word doesn't work. Bug 630864. */
+ min-width: 1px;
+}
+
+#nav-bar[mode="text"] > #window-controls > toolbarbutton > .toolbarbutton-icon {
+ display: -moz-box;
+}
+
+#nav-bar[mode="text"] > #window-controls > toolbarbutton > .toolbarbutton-text {
+ display: none;
+}
+
+/* ::::: Keyboard UI Panel ::::: */
+.KUI-panel-closebutton {
+ -moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-image");
+}
+
+:-moz-any(.ctrlTab-preview, .allTabs-preview) > html|img,
+:-moz-any(.ctrlTab-preview, .allTabs-preview) > html|canvas {
+ min-width: inherit;
+ max-width: inherit;
+ min-height: inherit;
+ max-height: inherit;
+}
+
+.ctrlTab-favicon-container,
+.allTabs-favicon-container {
+ -moz-box-align: start;
+%ifdef XP_MACOSX
+ -moz-box-pack: end;
+%else
+ -moz-box-pack: start;
+%endif
+}
+
+.ctrlTab-favicon,
+.allTabs-favicon {
+ width: 16px;
+ height: 16px;
+}
+
+/* ::::: Ctrl-Tab Panel ::::: */
+.ctrlTab-preview {
+ -moz-binding: url("chrome://browser/content/browser-tabPreviews.xml#ctrlTab-preview");
+}
+
+/* ::::: All Tabs Panel ::::: */
+.allTabs-preview {
+ -moz-binding: url("chrome://browser/content/browser-tabPreviews.xml#allTabs-preview");
+}
+
+#allTabs-tab-close-button {
+ -moz-binding: url("chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-image");
+ margin: 0;
+}
+
+
+/* notification anchors should only be visible when their associated
+ notifications are */
+.notification-anchor-icon {
+ -moz-user-focus: normal;
+}
+
+.notification-anchor-icon:not([showing]) {
+ display: none;
+}
+
+#notification-popup .text-link.custom-link {
+ -moz-binding: url("chrome://global/content/bindings/text.xml#text-label");
+ text-decoration: none;
+}
+
+#invalid-form-popup > description {
+ max-width: 280px;
+}
+
+#addon-progress-notification {
+ -moz-binding: url("chrome://browser/content/urlbarBindings.xml#addon-progress-notification");
+}
+
+#identity-request-notification {
+ -moz-binding: url("chrome://browser/content/urlbarBindings.xml#identity-request-notification");
+}
+
+#click-to-play-plugins-notification {
+ -moz-binding: url("chrome://browser/content/urlbarBindings.xml#click-to-play-plugins-notification");
+}
+
+.plugin-popupnotification-centeritem {
+ -moz-binding: url("chrome://browser/content/urlbarBindings.xml#plugin-popupnotification-center-item");
+}
+
+/* override hidden="true" for the status bar compatibility shim
+ in case it was persisted for the real status bar */
+#status-bar {
+ display: -moz-box;
+}
+
+/* Remove the resizer from the statusbar compatibility shim */
+#status-bar[hideresizer] > .statusbar-resizerpanel {
+ display: none;
+}
+
+browser[tabmodalPromptShowing] {
+ -moz-user-focus: none !important;
+}
+
+/* Status panel */
+
+statuspanel {
+ -moz-binding: url("chrome://browser/content/tabbrowser.xml#statuspanel");
+ position: fixed;
+ margin-top: -3em;
+ left: 0;
+ max-width: calc(100% - 5px);
+ pointer-events: none;
+}
+
+statuspanel:-moz-locale-dir(ltr)[mirror],
+statuspanel:-moz-locale-dir(rtl):not([mirror]) {
+ left: auto;
+ right: 0;
+}
+
+statuspanel[sizelimit] {
+ max-width: 50%;
+}
+
+statuspanel[type=status] {
+ min-width: 23em;
+}
+
+@media all and (max-width: 800px) {
+ statuspanel[type=status] {
+ min-width: 33%;
+ }
+}
+
+statuspanel[type=overLink] {
+ transition: opacity 120ms ease-out;
+ direction: ltr;
+}
+
+statuspanel[inactive] {
+ transition: none;
+ opacity: 0;
+}
+
+statuspanel[inactive][previoustype=overLink] {
+ transition: opacity 200ms ease-out;
+}
+
+.statuspanel-inner {
+ height: 3em;
+ width: 100%;
+ -moz-box-align: end;
+}
+
+.panel-inner-arrowcontentfooter[footertype="promobox"] {
+ -moz-binding: url("chrome://browser/content/urlbarBindings.xml#promobox");
+}
+
+/* highlighter */
+%include highlighter.css
+
+/* gcli */
+
+html|*#gcli-tooltip-frame,
+html|*#gcli-output-frame,
+#gcli-output,
+#gcli-tooltip {
+ overflow-x: hidden;
+}
+
+.gclitoolbar-input-node,
+.gclitoolbar-complete-node {
+ direction: ltr;
+}
+
+#developer-toolbar-toolbox-button[error-count] > .toolbarbutton-icon {
+ display: none;
+}
+
+#developer-toolbar-toolbox-button[error-count]:before {
+ content: attr(error-count);
+ display: -moz-box;
+ -moz-box-pack: center;
+}
+
+/* Responsive Mode */
+
+.browserContainer[responsivemode] {
+ overflow: auto;
+}
+
+.devtools-responsiveui-toolbar:-moz-locale-dir(rtl) {
+ -moz-box-pack: end;
+}
+
+.browserStack[responsivemode] {
+ transition-duration: 200ms;
+ transition-timing-function: linear;
+}
+
+.browserStack[responsivemode] {
+ transition-property: min-width, max-width, min-height, max-height;
+}
+
+.browserStack[responsivemode][notransition] {
+ transition: none;
+}
+
+.toolbarbutton-badge[badge]:not([badge=""])::after {
+ content: attr(badge);
+}
+
+toolbarbutton[type="badged"] {
+ -moz-binding: url("chrome://browser/content/urlbarBindings.xml#toolbarbutton-badged");
+}
+
+/* Note the chatbox 'width' values are duplicated in socialchat.xml */
+chatbox {
+ -moz-binding: url("chrome://browser/content/socialchat.xml#chatbox");
+ transition: height 150ms ease-out, width 150ms ease-out;
+ height: 285px;
+ width: 260px; /* CHAT_WIDTH_OPEN in socialchat.xml */
+}
+
+chatbox[minimized="true"] {
+ width: 160px;
+ height: 20px; /* CHAT_WIDTH_MINIMIZED in socialchat.xml */
+}
+
+chatbar {
+ -moz-binding: url("chrome://browser/content/socialchat.xml#chatbar");
+ height: 0;
+ max-height: 0;
+}
+
+/* full screen chat window support */
+chatbar:-moz-full-screen-ancestor,
+chatbox:-moz-full-screen-ancestor {
+ border: none;
+ position: fixed !important;
+ top: 0 !important;
+ left: 0 !important;
+ right: 0 !important;
+ bottom: 0 !important;
+ width: 100% !important;
+ height: 100% !important;
+ margin: 0 !important;
+ min-width: 0 !important;
+ max-width: none !important;
+ min-height: 0 !important;
+ max-height: none !important;
+ -moz-box-sizing: border-box !important;
+}
+
+/* hide chat chrome when chat is fullscreen */
+chatbox:-moz-full-screen-ancestor > .chat-titlebar {
+ display: none;
+}
+
+/* hide chatbar if browser tab is fullscreen */
+*:-moz-full-screen-ancestor chatbar:not(:-moz-full-screen-ancestor) {
+ display: none;
+}
+
+/* hide sidebar when fullscreen */
+*:-moz-full-screen-ancestor #social-sidebar-box:not(:-moz-full-screen-ancestor) {
+ display: none;
+}
diff --git a/browser/base/content/browser.js b/browser/base/content/browser.js
new file mode 100644
index 000000000..520c74ecc
--- /dev/null
+++ b/browser/base/content/browser.js
@@ -0,0 +1,7265 @@
+# -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+let Ci = Components.interfaces;
+let Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource:///modules/RecentWindow.jsm");
+
+const nsIWebNavigation = Ci.nsIWebNavigation;
+
+var gCharsetMenu = null;
+var gLastBrowserCharset = null;
+var gPrevCharset = null;
+var gProxyFavIcon = null;
+var gLastValidURLStr = "";
+var gInPrintPreviewMode = false;
+var gContextMenu = null; // nsContextMenu instance
+var gMultiProcessBrowser = false;
+
+#ifndef XP_MACOSX
+var gEditUIVisible = true;
+#endif
+
+[
+ ["gBrowser", "content"],
+ ["gNavToolbox", "navigator-toolbox"],
+ ["gURLBar", "urlbar"],
+ ["gNavigatorBundle", "bundle_browser"]
+].forEach(function (elementGlobal) {
+ var [name, id] = elementGlobal;
+ window.__defineGetter__(name, function () {
+ var element = document.getElementById(id);
+ if (!element)
+ return null;
+ delete window[name];
+ return window[name] = element;
+ });
+ window.__defineSetter__(name, function (val) {
+ delete window[name];
+ return window[name] = val;
+ });
+});
+
+// Smart getter for the findbar. If you don't wish to force the creation of
+// the findbar, check gFindBarInitialized first.
+var gFindBarInitialized = false;
+XPCOMUtils.defineLazyGetter(window, "gFindBar", function() {
+ let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ let findbar = document.createElementNS(XULNS, "findbar");
+ findbar.id = "FindToolbar";
+
+ let browserBottomBox = document.getElementById("browser-bottombox");
+ browserBottomBox.insertBefore(findbar, browserBottomBox.firstChild);
+
+ // Force a style flush to ensure that our binding is attached.
+ findbar.clientTop;
+ findbar.browser = gBrowser;
+ window.gFindBarInitialized = true;
+ return findbar;
+});
+
+XPCOMUtils.defineLazyGetter(this, "gPrefService", function() {
+ return Services.prefs;
+});
+
+this.__defineGetter__("AddonManager", function() {
+ let tmp = {};
+ Cu.import("resource://gre/modules/AddonManager.jsm", tmp);
+ return this.AddonManager = tmp.AddonManager;
+});
+this.__defineSetter__("AddonManager", function (val) {
+ delete this.AddonManager;
+ return this.AddonManager = val;
+});
+
+this.__defineGetter__("PluralForm", function() {
+ Cu.import("resource://gre/modules/PluralForm.jsm");
+ return this.PluralForm;
+});
+this.__defineSetter__("PluralForm", function (val) {
+ delete this.PluralForm;
+ return this.PluralForm = val;
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
+ "resource://gre/modules/TelemetryStopwatch.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "AboutHomeUtils",
+ "resource:///modules/AboutHomeUtils.jsm");
+
+#ifdef MOZ_SERVICES_SYNC
+XPCOMUtils.defineLazyModuleGetter(this, "Weave",
+ "resource://services-sync/main.js");
+#endif
+
+XPCOMUtils.defineLazyGetter(this, "PopupNotifications", function () {
+ let tmp = {};
+ Cu.import("resource://gre/modules/PopupNotifications.jsm", tmp);
+ try {
+ return new tmp.PopupNotifications(gBrowser,
+ document.getElementById("notification-popup"),
+ document.getElementById("notification-popup-box"));
+ } catch (ex) {
+ Cu.reportError(ex);
+ return null;
+ }
+});
+
+XPCOMUtils.defineLazyGetter(this, "DeveloperToolbar", function() {
+ let tmp = {};
+ Cu.import("resource:///modules/devtools/DeveloperToolbar.jsm", tmp);
+ return new tmp.DeveloperToolbar(window, document.getElementById("developer-toolbar"));
+});
+
+XPCOMUtils.defineLazyGetter(this, "BrowserDebuggerProcess", function() {
+ let tmp = {};
+ Cu.import("resource:///modules/devtools/DebuggerProcess.jsm", tmp);
+ return tmp.BrowserDebuggerProcess;
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "Social",
+ "resource:///modules/Social.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs",
+ "resource://gre/modules/PageThumbs.jsm");
+
+#ifdef MOZ_SAFE_BROWSING
+XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
+ "resource://gre/modules/SafeBrowsing.jsm");
+#endif
+
+XPCOMUtils.defineLazyModuleGetter(this, "gBrowserNewTabPreloader",
+ "resource:///modules/BrowserNewTabPreloader.jsm", "BrowserNewTabPreloader");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+let gInitialPages = [
+ "about:blank",
+ "about:newtab",
+ "about:home",
+ "about:privatebrowsing",
+ "about:sessionrestore"
+];
+
+#include browser-addons.js
+#include browser-feeds.js
+#include browser-fullScreen.js
+#include browser-fullZoom.js
+#include browser-places.js
+#include browser-plugins.js
+#include browser-safebrowsing.js
+#include browser-social.js
+#include browser-tabPreviews.js
+#include browser-thumbnails.js
+#include browser-webrtcUI.js
+#include browser-gestureSupport.js
+
+#ifdef MOZ_DATA_REPORTING
+#include browser-data-submission-info-bar.js
+#endif
+
+#ifdef MOZ_SERVICES_SYNC
+#include browser-syncui.js
+#endif
+
+XPCOMUtils.defineLazyGetter(this, "Win7Features", function () {
+#ifdef XP_WIN
+ const WINTASKBAR_CONTRACTID = "@mozilla.org/windows-taskbar;1";
+ if (WINTASKBAR_CONTRACTID in Cc &&
+ Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar).available) {
+ let AeroPeek = Cu.import("resource:///modules/WindowsPreviewPerTab.jsm", {}).AeroPeek;
+ return {
+ onOpenWindow: function () {
+ AeroPeek.onOpenWindow(window);
+ },
+ onCloseWindow: function () {
+ AeroPeek.onCloseWindow(window);
+ }
+ };
+ }
+#endif
+ return null;
+});
+
+#ifdef MOZ_CRASHREPORTER
+XPCOMUtils.defineLazyServiceGetter(this, "gCrashReporter",
+ "@mozilla.org/xre/app-info;1",
+ "nsICrashReporter");
+#endif
+
+XPCOMUtils.defineLazyGetter(this, "PageMenu", function() {
+ let tmp = {};
+ Cu.import("resource://gre/modules/PageMenu.jsm", tmp);
+ return new tmp.PageMenu();
+});
+
+/**
+* We can avoid adding multiple load event listeners and save some time by adding
+* one listener that calls all real handlers.
+*/
+function pageShowEventHandlers(persisted) {
+ charsetLoadListener();
+ XULBrowserWindow.asyncUpdateUI();
+
+ // The PluginClickToPlay events are not fired when navigating using the
+ // BF cache. |persisted| is true when the page is loaded from the
+ // BF cache, so this code reshows the notification if necessary.
+ if (persisted)
+ gPluginHandler.reshowClickToPlayNotification();
+}
+
+function UpdateBackForwardCommands(aWebNavigation) {
+ var backBroadcaster = document.getElementById("Browser:Back");
+ var forwardBroadcaster = document.getElementById("Browser:Forward");
+
+ // Avoid setting attributes on broadcasters if the value hasn't changed!
+ // Remember, guys, setting attributes on elements is expensive! They
+ // get inherited into anonymous content, broadcast to other widgets, etc.!
+ // Don't do it if the value hasn't changed! - dwh
+
+ var backDisabled = backBroadcaster.hasAttribute("disabled");
+ var forwardDisabled = forwardBroadcaster.hasAttribute("disabled");
+ if (backDisabled == aWebNavigation.canGoBack) {
+ if (backDisabled)
+ backBroadcaster.removeAttribute("disabled");
+ else
+ backBroadcaster.setAttribute("disabled", true);
+ }
+
+ if (forwardDisabled == aWebNavigation.canGoForward) {
+ if (forwardDisabled)
+ forwardBroadcaster.removeAttribute("disabled");
+ else
+ forwardBroadcaster.setAttribute("disabled", true);
+ }
+}
+
+/**
+ * Click-and-Hold implementation for the Back and Forward buttons
+ * XXXmano: should this live in toolbarbutton.xml?
+ */
+function SetClickAndHoldHandlers() {
+ var timer;
+
+ function openMenu(aButton) {
+ cancelHold(aButton);
+ aButton.firstChild.hidden = false;
+ aButton.open = true;
+ }
+
+ function mousedownHandler(aEvent) {
+ if (aEvent.button != 0 ||
+ aEvent.currentTarget.open ||
+ aEvent.currentTarget.disabled)
+ return;
+
+ // Prevent the menupopup from opening immediately
+ aEvent.currentTarget.firstChild.hidden = true;
+
+ aEvent.currentTarget.addEventListener("mouseout", mouseoutHandler, false);
+ aEvent.currentTarget.addEventListener("mouseup", mouseupHandler, false);
+ timer = setTimeout(openMenu, 500, aEvent.currentTarget);
+ }
+
+ function mouseoutHandler(aEvent) {
+ let buttonRect = aEvent.currentTarget.getBoundingClientRect();
+ if (aEvent.clientX >= buttonRect.left &&
+ aEvent.clientX <= buttonRect.right &&
+ aEvent.clientY >= buttonRect.bottom)
+ openMenu(aEvent.currentTarget);
+ else
+ cancelHold(aEvent.currentTarget);
+ }
+
+ function mouseupHandler(aEvent) {
+ cancelHold(aEvent.currentTarget);
+ }
+
+ function cancelHold(aButton) {
+ clearTimeout(timer);
+ aButton.removeEventListener("mouseout", mouseoutHandler, false);
+ aButton.removeEventListener("mouseup", mouseupHandler, false);
+ }
+
+ function clickHandler(aEvent) {
+ if (aEvent.button == 0 &&
+ aEvent.target == aEvent.currentTarget &&
+ !aEvent.currentTarget.open &&
+ !aEvent.currentTarget.disabled) {
+ let cmdEvent = document.createEvent("xulcommandevent");
+ cmdEvent.initCommandEvent("command", true, true, window, 0,
+ aEvent.ctrlKey, aEvent.altKey, aEvent.shiftKey,
+ aEvent.metaKey, null);
+ aEvent.currentTarget.dispatchEvent(cmdEvent);
+ }
+ }
+
+ function _addClickAndHoldListenersOnElement(aElm) {
+ aElm.addEventListener("mousedown", mousedownHandler, true);
+ aElm.addEventListener("click", clickHandler, true);
+ }
+
+ // Bug 414797: Clone unified-back-forward-button's context menu into both the
+ // back and the forward buttons.
+ var unifiedButton = document.getElementById("unified-back-forward-button");
+ if (unifiedButton && !unifiedButton._clickHandlersAttached) {
+ unifiedButton._clickHandlersAttached = true;
+
+ let popup = document.getElementById("backForwardMenu").cloneNode(true);
+ popup.removeAttribute("id");
+ // Prevent the context attribute on unified-back-forward-button from being
+ // inherited.
+ popup.setAttribute("context", "");
+
+ let backButton = document.getElementById("back-button");
+ backButton.setAttribute("type", "menu");
+ backButton.appendChild(popup);
+ _addClickAndHoldListenersOnElement(backButton);
+
+ let forwardButton = document.getElementById("forward-button");
+ popup = popup.cloneNode(true);
+ forwardButton.setAttribute("type", "menu");
+ forwardButton.appendChild(popup);
+ _addClickAndHoldListenersOnElement(forwardButton);
+ }
+}
+
+const gSessionHistoryObserver = {
+ observe: function(subject, topic, data)
+ {
+ if (topic != "browser:purge-session-history")
+ return;
+
+ var backCommand = document.getElementById("Browser:Back");
+ backCommand.setAttribute("disabled", "true");
+ var fwdCommand = document.getElementById("Browser:Forward");
+ fwdCommand.setAttribute("disabled", "true");
+
+ // Hide session restore button on about:home
+ window.messageManager.broadcastAsyncMessage("Browser:HideSessionRestoreButton");
+
+ if (gURLBar) {
+ // Clear undo history of the URL bar
+ gURLBar.editor.transactionManager.clear()
+ }
+ }
+};
+
+/**
+ * Given a starting docshell and a URI to look up, find the docshell the URI
+ * is loaded in.
+ * @param aDocument
+ * A document to find instead of using just a URI - this is more specific.
+ * @param aDocShell
+ * The doc shell to start at
+ * @param aSoughtURI
+ * The URI that we're looking for
+ * @returns The doc shell that the sought URI is loaded in. Can be in
+ * subframes.
+ */
+function findChildShell(aDocument, aDocShell, aSoughtURI) {
+ aDocShell.QueryInterface(Components.interfaces.nsIWebNavigation);
+ aDocShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
+ var doc = aDocShell.getInterface(Components.interfaces.nsIDOMDocument);
+ if ((aDocument && doc == aDocument) ||
+ (aSoughtURI && aSoughtURI.spec == aDocShell.currentURI.spec))
+ return aDocShell;
+
+ var node = aDocShell.QueryInterface(Components.interfaces.nsIDocShellTreeNode);
+ for (var i = 0; i < node.childCount; ++i) {
+ var docShell = node.getChildAt(i);
+ docShell = findChildShell(aDocument, docShell, aSoughtURI);
+ if (docShell)
+ return docShell;
+ }
+ return null;
+}
+
+var gPopupBlockerObserver = {
+ _reportButton: null,
+
+ onReportButtonClick: function (aEvent)
+ {
+ if (aEvent.button != 0 || aEvent.target != this._reportButton)
+ return;
+
+ document.getElementById("blockedPopupOptions")
+ .openPopup(this._reportButton, "after_end", 0, 2, false, false, aEvent);
+ },
+
+ handleEvent: function (aEvent)
+ {
+ if (aEvent.originalTarget != gBrowser.selectedBrowser)
+ return;
+
+ if (!this._reportButton && gURLBar)
+ this._reportButton = document.getElementById("page-report-button");
+
+ if (!gBrowser.pageReport) {
+ // Hide the icon in the location bar (if the location bar exists)
+ if (gURLBar)
+ this._reportButton.hidden = true;
+ return;
+ }
+
+ if (gURLBar)
+ this._reportButton.hidden = false;
+
+ // Only show the notification again if we've not already shown it. Since
+ // notifications are per-browser, we don't need to worry about re-adding
+ // it.
+ if (!gBrowser.pageReport.reported) {
+ if (gPrefService.getBoolPref("privacy.popups.showBrowserMessage")) {
+ var brandBundle = document.getElementById("bundle_brand");
+ var brandShortName = brandBundle.getString("brandShortName");
+ var popupCount = gBrowser.pageReport.length;
+#ifdef XP_WIN
+ var popupButtonText = gNavigatorBundle.getString("popupWarningButton");
+ var popupButtonAccesskey = gNavigatorBundle.getString("popupWarningButton.accesskey");
+#else
+ var popupButtonText = gNavigatorBundle.getString("popupWarningButtonUnix");
+ var popupButtonAccesskey = gNavigatorBundle.getString("popupWarningButtonUnix.accesskey");
+#endif
+ var messageBase = gNavigatorBundle.getString("popupWarning.message");
+ var message = PluralForm.get(popupCount, messageBase)
+ .replace("#1", brandShortName)
+ .replace("#2", popupCount);
+
+ var notificationBox = gBrowser.getNotificationBox();
+ var notification = notificationBox.getNotificationWithValue("popup-blocked");
+ if (notification) {
+ notification.label = message;
+ }
+ else {
+ var buttons = [{
+ label: popupButtonText,
+ accessKey: popupButtonAccesskey,
+ popup: "blockedPopupOptions",
+ callback: null
+ }];
+
+ const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
+ notificationBox.appendNotification(message, "popup-blocked",
+ "chrome://browser/skin/Info.png",
+ priority, buttons);
+ }
+ }
+
+ // Record the fact that we've reported this blocked popup, so we don't
+ // show it again.
+ gBrowser.pageReport.reported = true;
+ }
+ },
+
+ toggleAllowPopupsForSite: function (aEvent)
+ {
+ var pm = Services.perms;
+ var shouldBlock = aEvent.target.getAttribute("block") == "true";
+ var perm = shouldBlock ? pm.DENY_ACTION : pm.ALLOW_ACTION;
+ pm.add(gBrowser.currentURI, "popup", perm);
+
+ gBrowser.getNotificationBox().removeCurrentNotification();
+ },
+
+ fillPopupList: function (aEvent)
+ {
+ // XXXben - rather than using |currentURI| here, which breaks down on multi-framed sites
+ // we should really walk the pageReport and create a list of "allow for <host>"
+ // menuitems for the common subset of hosts present in the report, this will
+ // make us frame-safe.
+ //
+ // XXXjst - Note that when this is fixed to work with multi-framed sites,
+ // also back out the fix for bug 343772 where
+ // nsGlobalWindow::CheckOpenAllow() was changed to also
+ // check if the top window's location is whitelisted.
+ var uri = gBrowser.currentURI;
+ var blockedPopupAllowSite = document.getElementById("blockedPopupAllowSite");
+ try {
+ blockedPopupAllowSite.removeAttribute("hidden");
+
+ var pm = Services.perms;
+ if (pm.testPermission(uri, "popup") == pm.ALLOW_ACTION) {
+ // Offer an item to block popups for this site, if a whitelist entry exists
+ // already for it.
+ let blockString = gNavigatorBundle.getFormattedString("popupBlock", [uri.host || uri.spec]);
+ blockedPopupAllowSite.setAttribute("label", blockString);
+ blockedPopupAllowSite.setAttribute("block", "true");
+ }
+ else {
+ // Offer an item to allow popups for this site
+ let allowString = gNavigatorBundle.getFormattedString("popupAllow", [uri.host || uri.spec]);
+ blockedPopupAllowSite.setAttribute("label", allowString);
+ blockedPopupAllowSite.removeAttribute("block");
+ }
+ }
+ catch (e) {
+ blockedPopupAllowSite.setAttribute("hidden", "true");
+ }
+
+ if (PrivateBrowsingUtils.isWindowPrivate(window))
+ blockedPopupAllowSite.setAttribute("disabled", "true");
+ else
+ blockedPopupAllowSite.removeAttribute("disabled");
+
+ var foundUsablePopupURI = false;
+ var pageReports = gBrowser.pageReport;
+ if (pageReports) {
+ for (let pageReport of pageReports) {
+ // popupWindowURI will be null if the file picker popup is blocked.
+ // xxxdz this should make the option say "Show file picker" and do it (Bug 590306)
+ if (!pageReport.popupWindowURI)
+ continue;
+ var popupURIspec = pageReport.popupWindowURI.spec;
+
+ // Sometimes the popup URI that we get back from the pageReport
+ // isn't useful (for instance, netscape.com's popup URI ends up
+ // being "http://www.netscape.com", which isn't really the URI of
+ // the popup they're trying to show). This isn't going to be
+ // useful to the user, so we won't create a menu item for it.
+ if (popupURIspec == "" || popupURIspec == "about:blank" ||
+ popupURIspec == uri.spec)
+ continue;
+
+ // Because of the short-circuit above, we may end up in a situation
+ // in which we don't have any usable popup addresses to show in
+ // the menu, and therefore we shouldn't show the separator. However,
+ // since we got past the short-circuit, we must've found at least
+ // one usable popup URI and thus we'll turn on the separator later.
+ foundUsablePopupURI = true;
+
+ var menuitem = document.createElement("menuitem");
+ var label = gNavigatorBundle.getFormattedString("popupShowPopupPrefix",
+ [popupURIspec]);
+ menuitem.setAttribute("label", label);
+ menuitem.setAttribute("popupWindowURI", popupURIspec);
+ menuitem.setAttribute("popupWindowFeatures", pageReport.popupWindowFeatures);
+ menuitem.setAttribute("popupWindowName", pageReport.popupWindowName);
+ menuitem.setAttribute("oncommand", "gPopupBlockerObserver.showBlockedPopup(event);");
+ menuitem.requestingWindow = pageReport.requestingWindow;
+ menuitem.requestingDocument = pageReport.requestingDocument;
+ aEvent.target.appendChild(menuitem);
+ }
+ }
+
+ // Show or hide the separator, depending on whether we added any
+ // showable popup addresses to the menu.
+ var blockedPopupsSeparator =
+ document.getElementById("blockedPopupsSeparator");
+ if (foundUsablePopupURI)
+ blockedPopupsSeparator.removeAttribute("hidden");
+ else
+ blockedPopupsSeparator.setAttribute("hidden", true);
+
+ var blockedPopupDontShowMessage = document.getElementById("blockedPopupDontShowMessage");
+ var showMessage = gPrefService.getBoolPref("privacy.popups.showBrowserMessage");
+ blockedPopupDontShowMessage.setAttribute("checked", !showMessage);
+ if (aEvent.target.anchorNode.id == "page-report-button") {
+ aEvent.target.anchorNode.setAttribute("open", "true");
+ blockedPopupDontShowMessage.setAttribute("label", gNavigatorBundle.getString("popupWarningDontShowFromLocationbar"));
+ } else
+ blockedPopupDontShowMessage.setAttribute("label", gNavigatorBundle.getString("popupWarningDontShowFromMessage"));
+ },
+
+ onPopupHiding: function (aEvent) {
+ if (aEvent.target.anchorNode.id == "page-report-button")
+ aEvent.target.anchorNode.removeAttribute("open");
+
+ let item = aEvent.target.lastChild;
+ while (item && item.getAttribute("observes") != "blockedPopupsSeparator") {
+ let next = item.previousSibling;
+ item.parentNode.removeChild(item);
+ item = next;
+ }
+ },
+
+ showBlockedPopup: function (aEvent)
+ {
+ var target = aEvent.target;
+ var popupWindowURI = target.getAttribute("popupWindowURI");
+ var features = target.getAttribute("popupWindowFeatures");
+ var name = target.getAttribute("popupWindowName");
+
+ var dwi = target.requestingWindow;
+
+ // If we have a requesting window and the requesting document is
+ // still the current document, open the popup.
+ if (dwi && dwi.document == target.requestingDocument) {
+ dwi.open(popupWindowURI, name, features);
+ }
+ },
+
+ editPopupSettings: function ()
+ {
+ var host = "";
+ try {
+ host = gBrowser.currentURI.host;
+ }
+ catch (e) { }
+
+ var bundlePreferences = document.getElementById("bundle_preferences");
+ var params = { blockVisible : false,
+ sessionVisible : false,
+ allowVisible : true,
+ prefilledHost : host,
+ permissionType : "popup",
+ windowTitle : bundlePreferences.getString("popuppermissionstitle"),
+ introText : bundlePreferences.getString("popuppermissionstext") };
+ var existingWindow = Services.wm.getMostRecentWindow("Browser:Permissions");
+ if (existingWindow) {
+ existingWindow.initWithParams(params);
+ existingWindow.focus();
+ }
+ else
+ window.openDialog("chrome://browser/content/preferences/permissions.xul",
+ "_blank", "resizable,dialog=no,centerscreen", params);
+ },
+
+ dontShowMessage: function ()
+ {
+ var showMessage = gPrefService.getBoolPref("privacy.popups.showBrowserMessage");
+ gPrefService.setBoolPref("privacy.popups.showBrowserMessage", !showMessage);
+ gBrowser.getNotificationBox().removeCurrentNotification();
+ }
+};
+
+const gFormSubmitObserver = {
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormSubmitObserver]),
+
+ panel: null,
+
+ init: function()
+ {
+ this.panel = document.getElementById('invalid-form-popup');
+ },
+
+ notifyInvalidSubmit : function (aFormElement, aInvalidElements)
+ {
+ // We are going to handle invalid form submission attempt by focusing the
+ // first invalid element and show the corresponding validation message in a
+ // panel attached to the element.
+ if (!aInvalidElements.length) {
+ return;
+ }
+
+ // Don't show the popup if the current tab doesn't contain the invalid form.
+ if (gBrowser.contentDocument !=
+ aFormElement.ownerDocument.defaultView.top.document) {
+ return;
+ }
+
+ let element = aInvalidElements.queryElementAt(0, Ci.nsISupports);
+
+ if (!(element instanceof HTMLInputElement ||
+ element instanceof HTMLTextAreaElement ||
+ element instanceof HTMLSelectElement ||
+ element instanceof HTMLButtonElement)) {
+ return;
+ }
+
+ this.panel.firstChild.textContent = element.validationMessage;
+
+ element.focus();
+
+ // If the user interacts with the element and makes it valid or leaves it,
+ // we want to remove the popup.
+ // We could check for clicks but a click is already removing the popup.
+ function blurHandler() {
+ gFormSubmitObserver.panel.hidePopup();
+ };
+ function inputHandler(e) {
+ if (e.originalTarget.validity.valid) {
+ gFormSubmitObserver.panel.hidePopup();
+ } else {
+ // If the element is now invalid for a new reason, we should update the
+ // error message.
+ if (gFormSubmitObserver.panel.firstChild.textContent !=
+ e.originalTarget.validationMessage) {
+ gFormSubmitObserver.panel.firstChild.textContent =
+ e.originalTarget.validationMessage;
+ }
+ }
+ };
+ element.addEventListener("input", inputHandler, false);
+ element.addEventListener("blur", blurHandler, false);
+
+ // One event to bring them all and in the darkness bind them.
+ this.panel.addEventListener("popuphiding", function onPopupHiding(aEvent) {
+ aEvent.target.removeEventListener("popuphiding", onPopupHiding, false);
+ element.removeEventListener("input", inputHandler, false);
+ element.removeEventListener("blur", blurHandler, false);
+ }, false);
+
+ this.panel.hidden = false;
+
+ // We want to show the popup at the middle of checkbox and radio buttons
+ // and where the content begin for the other elements.
+ let offset = 0;
+ let position = "";
+
+ if (element.tagName == 'INPUT' &&
+ (element.type == 'radio' || element.type == 'checkbox')) {
+ position = "bottomcenter topleft";
+ } else {
+ let win = element.ownerDocument.defaultView;
+ let style = win.getComputedStyle(element, null);
+ let utils = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+
+ if (style.direction == 'rtl') {
+ offset = parseInt(style.paddingRight) + parseInt(style.borderRightWidth);
+ } else {
+ offset = parseInt(style.paddingLeft) + parseInt(style.borderLeftWidth);
+ }
+
+ offset = Math.round(offset * utils.fullZoom);
+
+ position = "after_start";
+ }
+
+ this.panel.openPopup(element, position, offset, 0);
+ }
+};
+
+var gBrowserInit = {
+ onLoad: function() {
+ gMultiProcessBrowser = gPrefService.getBoolPref("browser.tabs.remote");
+
+ var mustLoadSidebar = false;
+
+ Cc["@mozilla.org/eventlistenerservice;1"]
+ .getService(Ci.nsIEventListenerService)
+ .addSystemEventListener(gBrowser, "click", contentAreaClick, true);
+
+ gBrowser.addEventListener("DOMUpdatePageReport", gPopupBlockerObserver, false);
+
+ // Note that the XBL binding is untrusted
+ gBrowser.addEventListener("PluginBindingAttached", gPluginHandler, true, true);
+ gBrowser.addEventListener("PluginCrashed", gPluginHandler, true);
+ gBrowser.addEventListener("PluginOutdated", gPluginHandler, true);
+ gBrowser.addEventListener("PluginInstantiated", gPluginHandler, true);
+ gBrowser.addEventListener("PluginRemoved", gPluginHandler, true);
+
+ gBrowser.addEventListener("NewPluginInstalled", gPluginHandler.newPluginInstalled, true);
+
+ Services.obs.addObserver(gPluginHandler.pluginCrashed, "plugin-crashed", false);
+
+ window.addEventListener("AppCommand", HandleAppCommandEvent, true);
+
+ messageManager.loadFrameScript("chrome://browser/content/content.js", true);
+ messageManager.loadFrameScript("chrome://browser/content/content-sessionStore.js", true);
+
+ // initialize observers and listeners
+ // and give C++ access to gBrowser
+ XULBrowserWindow.init();
+ window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIXULWindow)
+ .XULBrowserWindow = window.XULBrowserWindow;
+ window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow =
+ new nsBrowserAccess();
+
+ // set default character set if provided
+ // window.arguments[1]: character set (string)
+ if ("arguments" in window && window.arguments.length > 1 && window.arguments[1]) {
+ if (window.arguments[1].startsWith("charset=")) {
+ var arrayArgComponents = window.arguments[1].split("=");
+ if (arrayArgComponents) {
+ //we should "inherit" the charset menu setting in a new window
+ getMarkupDocumentViewer().defaultCharacterSet = arrayArgComponents[1];
+ }
+ }
+ }
+
+ // Manually hook up session and global history for the first browser
+ // so that we don't have to load global history before bringing up a
+ // window.
+ // Wire up session and global history before any possible
+ // progress notifications for back/forward button updating
+ gBrowser.webNavigation.sessionHistory = Cc["@mozilla.org/browser/shistory;1"].
+ createInstance(Ci.nsISHistory);
+ Services.obs.addObserver(gBrowser.browsers[0], "browser:purge-session-history", false);
+
+ // remove the disablehistory attribute so the browser cleans up, as
+ // though it had done this work itself
+ gBrowser.browsers[0].removeAttribute("disablehistory");
+
+ // enable global history
+ try {
+ if (!gMultiProcessBrowser)
+ gBrowser.docShell.useGlobalHistory = true;
+ } catch(ex) {
+ Cu.reportError("Places database may be locked: " + ex);
+ }
+
+ // hook up UI through progress listener
+ gBrowser.addProgressListener(window.XULBrowserWindow);
+ gBrowser.addTabsProgressListener(window.TabsProgressListener);
+
+ // setup our common DOMLinkAdded listener
+ gBrowser.addEventListener("DOMLinkAdded", DOMLinkHandler, false);
+
+ // setup our MozApplicationManifest listener
+ gBrowser.addEventListener("MozApplicationManifest",
+ OfflineApps, false);
+ // listen for offline apps on social
+ let socialBrowser = document.getElementById("social-sidebar-browser");
+ socialBrowser.addEventListener("MozApplicationManifest",
+ OfflineApps, false);
+
+ // setup simple gestures support
+ gGestureSupport.init(true);
+
+ // setup history swipe animation
+ gHistorySwipeAnimation.init();
+
+ if (window.opener && !window.opener.closed) {
+ let openerSidebarBox = window.opener.document.getElementById("sidebar-box");
+ // If the opener had a sidebar, open the same sidebar in our window.
+ // The opener can be the hidden window too, if we're coming from the state
+ // where no windows are open, and the hidden window has no sidebar box.
+ if (openerSidebarBox && !openerSidebarBox.hidden) {
+ let sidebarCmd = openerSidebarBox.getAttribute("sidebarcommand");
+ let sidebarCmdElem = document.getElementById(sidebarCmd);
+
+ // dynamically generated sidebars will fail this check.
+ if (sidebarCmdElem) {
+ let sidebarBox = document.getElementById("sidebar-box");
+ let sidebarTitle = document.getElementById("sidebar-title");
+
+ sidebarTitle.setAttribute(
+ "value", window.opener.document.getElementById("sidebar-title").getAttribute("value"));
+ sidebarBox.setAttribute("width", openerSidebarBox.boxObject.width);
+
+ sidebarBox.setAttribute("sidebarcommand", sidebarCmd);
+ // Note: we're setting 'src' on sidebarBox, which is a <vbox>, not on
+ // the <browser id="sidebar">. This lets us delay the actual load until
+ // delayedStartup().
+ sidebarBox.setAttribute(
+ "src", window.opener.document.getElementById("sidebar").getAttribute("src"));
+ mustLoadSidebar = true;
+
+ sidebarBox.hidden = false;
+ document.getElementById("sidebar-splitter").hidden = false;
+ sidebarCmdElem.setAttribute("checked", "true");
+ }
+ }
+ }
+ else {
+ let box = document.getElementById("sidebar-box");
+ if (box.hasAttribute("sidebarcommand")) {
+ let commandID = box.getAttribute("sidebarcommand");
+ if (commandID) {
+ let command = document.getElementById(commandID);
+ if (command) {
+ mustLoadSidebar = true;
+ box.hidden = false;
+ document.getElementById("sidebar-splitter").hidden = false;
+ command.setAttribute("checked", "true");
+ }
+ else {
+ // Remove the |sidebarcommand| attribute, because the element it
+ // refers to no longer exists, so we should assume this sidebar
+ // panel has been uninstalled. (249883)
+ box.removeAttribute("sidebarcommand");
+ }
+ }
+ }
+ }
+
+ // Certain kinds of automigration rely on this notification to complete their
+ // tasks BEFORE the browser window is shown.
+ Services.obs.notifyObservers(null, "browser-window-before-show", "");
+
+ // Set a sane starting width/height for all resolutions on new profiles.
+ if (!document.documentElement.hasAttribute("width")) {
+ let defaultWidth;
+ let defaultHeight;
+
+ // Very small: maximize the window
+ // Portrait : use about full width and 3/4 height, to view entire pages
+ // at once (without being obnoxiously tall)
+ // Widescreen: use about half width, to suggest side-by-side page view
+ // Otherwise : use 3/4 height and width
+ if (screen.availHeight <= 600) {
+ document.documentElement.setAttribute("sizemode", "maximized");
+ defaultWidth = 610;
+ defaultHeight = 450;
+ }
+ else {
+ if (screen.availWidth <= screen.availHeight) {
+ defaultWidth = screen.availWidth * .9;
+ defaultHeight = screen.availHeight * .75;
+ }
+ else if (screen.availWidth >= 2048) {
+ defaultWidth = (screen.availWidth / 2) - 20;
+ defaultHeight = screen.availHeight - 10;
+ }
+ else {
+ defaultWidth = screen.availWidth * .75;
+ defaultHeight = screen.availHeight * .75;
+ }
+
+#ifdef MOZ_WIDGET_GTK2
+ // On X, we're not currently able to account for the size of the window
+ // border. Use 28px as a guess (titlebar + bottom window border)
+ defaultHeight -= 28;
+#endif
+ }
+ document.documentElement.setAttribute("width", defaultWidth);
+ document.documentElement.setAttribute("height", defaultHeight);
+ }
+
+ if (!gShowPageResizers)
+ document.getElementById("status-bar").setAttribute("hideresizer", "true");
+
+ if (!window.toolbar.visible) {
+ // adjust browser UI for popups
+ if (gURLBar) {
+ gURLBar.setAttribute("readonly", "true");
+ gURLBar.setAttribute("enablehistory", "false");
+ }
+ goSetCommandEnabled("cmd_newNavigatorTab", false);
+ }
+
+#ifdef MENUBAR_CAN_AUTOHIDE
+ updateAppButtonDisplay();
+#endif
+
+ // Misc. inits.
+ CombinedStopReload.init();
+ allTabs.readPref();
+ TabsOnTop.init();
+ gPrivateBrowsingUI.init();
+ TabsInTitlebar.init();
+ retrieveToolbarIconsizesFromTheme();
+
+ // Wait until chrome is painted before executing code not critical to making the window visible
+ this._boundDelayedStartup = this._delayedStartup.bind(this, mustLoadSidebar);
+ window.addEventListener("MozAfterPaint", this._boundDelayedStartup);
+
+ this._loadHandled = true;
+ },
+
+ _cancelDelayedStartup: function () {
+ window.removeEventListener("MozAfterPaint", this._boundDelayedStartup);
+ this._boundDelayedStartup = null;
+ },
+
+ _delayedStartup: function(mustLoadSidebar) {
+ let tmp = {};
+ Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", tmp);
+ let TelemetryTimestamps = tmp.TelemetryTimestamps;
+ TelemetryTimestamps.add("delayedStartupStarted");
+
+ this._cancelDelayedStartup();
+
+ let uriToLoad = this._getUriToLoad();
+ var isLoadingBlank = isBlankPageURL(uriToLoad);
+
+ // This pageshow listener needs to be registered before we may call
+ // swapBrowsersAndCloseOther() to receive pageshow events fired by that.
+ gBrowser.addEventListener("pageshow", function(event) {
+ // Filter out events that are not about the document load we are interested in
+ if (content && event.target == content.document)
+ setTimeout(pageShowEventHandlers, 0, event.persisted);
+ }, true);
+
+ if (uriToLoad && uriToLoad != "about:blank") {
+ if (uriToLoad instanceof Ci.nsISupportsArray) {
+ let count = uriToLoad.Count();
+ let specs = [];
+ for (let i = 0; i < count; i++) {
+ let urisstring = uriToLoad.GetElementAt(i).QueryInterface(Ci.nsISupportsString);
+ specs.push(urisstring.data);
+ }
+
+ // This function throws for certain malformed URIs, so use exception handling
+ // so that we don't disrupt startup
+ try {
+ gBrowser.loadTabs(specs, false, true);
+ } catch (e) {}
+ }
+ else if (uriToLoad instanceof XULElement) {
+ // swap the given tab with the default about:blank tab and then close
+ // the original tab in the other window.
+
+ // Stop the about:blank load
+ gBrowser.stop();
+ // make sure it has a docshell
+ gBrowser.docShell;
+
+ gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, uriToLoad);
+ }
+ // window.arguments[2]: referrer (nsIURI)
+ // [3]: postData (nsIInputStream)
+ // [4]: allowThirdPartyFixup (bool)
+ else if (window.arguments.length >= 3) {
+ loadURI(uriToLoad, window.arguments[2], window.arguments[3] || null,
+ window.arguments[4] || false);
+ window.focus();
+ }
+ // Note: loadOneOrMoreURIs *must not* be called if window.arguments.length >= 3.
+ // Such callers expect that window.arguments[0] is handled as a single URI.
+ else
+ loadOneOrMoreURIs(uriToLoad);
+ }
+
+#ifdef MOZ_SAFE_BROWSING
+ // Bug 778855 - Perf regression if we do this here. To be addressed in bug 779008.
+ setTimeout(function() { SafeBrowsing.init(); }, 2000);
+#endif
+
+ Services.obs.addObserver(gSessionHistoryObserver, "browser:purge-session-history", false);
+ Services.obs.addObserver(gXPInstallObserver, "addon-install-disabled", false);
+ Services.obs.addObserver(gXPInstallObserver, "addon-install-started", false);
+ Services.obs.addObserver(gXPInstallObserver, "addon-install-blocked", false);
+ Services.obs.addObserver(gXPInstallObserver, "addon-install-failed", false);
+ Services.obs.addObserver(gXPInstallObserver, "addon-install-complete", false);
+ Services.obs.addObserver(gFormSubmitObserver, "invalidformsubmit", false);
+
+ BrowserOffline.init();
+ OfflineApps.init();
+ IndexedDBPromptHelper.init();
+ gFormSubmitObserver.init();
+ SocialUI.init();
+ AddonManager.addAddonListener(AddonsMgrListener);
+ WebrtcIndicator.init();
+
+ // Ensure login manager is up and running.
+ Services.logins;
+
+ if (mustLoadSidebar) {
+ let sidebar = document.getElementById("sidebar");
+ let sidebarBox = document.getElementById("sidebar-box");
+ sidebar.setAttribute("src", sidebarBox.getAttribute("src"));
+ }
+
+ UpdateUrlbarSearchSplitterState();
+
+ if (!isLoadingBlank || !focusAndSelectUrlBar())
+ gBrowser.selectedBrowser.focus();
+
+ gNavToolbox.customizeDone = BrowserToolboxCustomizeDone;
+ gNavToolbox.customizeChange = BrowserToolboxCustomizeChange;
+
+ // Set up Sanitize Item
+ this._initializeSanitizer();
+
+ // Enable/Disable auto-hide tabbar
+ gBrowser.tabContainer.updateVisibility();
+
+ gPrefService.addObserver(gHomeButton.prefDomain, gHomeButton, false);
+
+ var homeButton = document.getElementById("home-button");
+ gHomeButton.updateTooltip(homeButton);
+ gHomeButton.updatePersonalToolbarStyle(homeButton);
+
+ // BiDi UI
+ gBidiUI = isBidiEnabled();
+ if (gBidiUI) {
+ document.getElementById("documentDirection-separator").hidden = false;
+ document.getElementById("documentDirection-swap").hidden = false;
+ document.getElementById("textfieldDirection-separator").hidden = false;
+ document.getElementById("textfieldDirection-swap").hidden = false;
+ }
+
+ // Setup click-and-hold gestures access to the session history
+ // menus if global click-and-hold isn't turned on
+ if (!getBoolPref("ui.click_hold_context_menus", false))
+ SetClickAndHoldHandlers();
+
+ // Initialize the full zoom setting.
+ // We do this before the session restore service gets initialized so we can
+ // apply full zoom settings to tabs restored by the session restore service.
+ FullZoom.init();
+
+ // Bug 666804 - NetworkPrioritizer support for e10s
+ if (!gMultiProcessBrowser) {
+ let NP = {};
+ Cu.import("resource:///modules/NetworkPrioritizer.jsm", NP);
+ NP.trackBrowserWindow(window);
+ }
+
+ // initialize the session-restore service (in case it's not already running)
+ let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
+ ss.init(window);
+
+ // Enable the Restore Last Session command if needed
+ if (ss.canRestoreLastSession &&
+ !PrivateBrowsingUtils.isWindowPrivate(window))
+ goSetCommandEnabled("Browser:RestoreLastSession", true);
+
+ PlacesToolbarHelper.init();
+
+ ctrlTab.readPref();
+ gPrefService.addObserver(ctrlTab.prefName, ctrlTab, false);
+ gPrefService.addObserver(allTabs.prefName, allTabs, false);
+
+ // Initialize the download manager some time after the app starts so that
+ // auto-resume downloads begin (such as after crashing or quitting with
+ // active downloads) and speeds up the first-load of the download manager UI.
+ // If the user manually opens the download manager before the timeout, the
+ // downloads will start right away, and getting the service again won't hurt.
+ setTimeout(function() {
+ Services.downloads;
+ let DownloadTaskbarProgress =
+ Cu.import("resource://gre/modules/DownloadTaskbarProgress.jsm", {}).DownloadTaskbarProgress;
+ DownloadTaskbarProgress.onBrowserWindowLoad(window);
+ }, 10000);
+
+ // The object handling the downloads indicator is also initialized here in the
+ // delayed startup function, but the actual indicator element is not loaded
+ // unless there are downloads to be displayed.
+ DownloadsButton.initializeIndicator();
+
+#ifndef XP_MACOSX
+ updateEditUIVisibility();
+ let placesContext = document.getElementById("placesContext");
+ placesContext.addEventListener("popupshowing", updateEditUIVisibility, false);
+ placesContext.addEventListener("popuphiding", updateEditUIVisibility, false);
+#endif
+
+ gBrowser.mPanelContainer.addEventListener("InstallBrowserTheme", LightWeightThemeWebInstaller, false, true);
+ gBrowser.mPanelContainer.addEventListener("PreviewBrowserTheme", LightWeightThemeWebInstaller, false, true);
+ gBrowser.mPanelContainer.addEventListener("ResetBrowserThemePreview", LightWeightThemeWebInstaller, false, true);
+
+ // Bug 666808 - AeroPeek support for e10s
+ if (!gMultiProcessBrowser) {
+ if (Win7Features)
+ Win7Features.onOpenWindow();
+ }
+
+ // called when we go into full screen, even if initiated by a web page script
+ window.addEventListener("fullscreen", onFullScreen, true);
+
+ // Called when we enter DOM full-screen mode. Note we can already be in browser
+ // full-screen mode when we enter DOM full-screen mode.
+ window.addEventListener("MozEnteredDomFullscreen", onMozEnteredDomFullscreen, true);
+
+ if (window.fullScreen)
+ onFullScreen();
+ if (document.mozFullScreen)
+ onMozEnteredDomFullscreen();
+
+#ifdef MOZ_SERVICES_SYNC
+ // initialize the sync UI
+ gSyncUI.init();
+#endif
+
+#ifdef MOZ_DATA_REPORTING
+ gDataNotificationInfoBar.init();
+#endif
+
+ gBrowserThumbnails.init();
+
+ setUrlAndSearchBarWidthForConditionalForwardButton();
+ window.addEventListener("resize", function resizeHandler(event) {
+ if (event.target == window)
+ setUrlAndSearchBarWidthForConditionalForwardButton();
+ });
+
+ // Enable developer toolbar?
+ let devToolbarEnabled = gPrefService.getBoolPref("devtools.toolbar.enabled");
+ if (devToolbarEnabled) {
+ let cmd = document.getElementById("Tools:DevToolbar");
+ cmd.removeAttribute("disabled");
+ cmd.removeAttribute("hidden");
+ document.getElementById("Tools:DevToolbarFocus").removeAttribute("disabled");
+
+ // Show the toolbar if it was previously visible
+ if (gPrefService.getBoolPref("devtools.toolbar.visible")) {
+ DeveloperToolbar.show(false);
+ }
+ }
+
+ // Enable Chrome Debugger?
+ let chromeEnabled = gPrefService.getBoolPref("devtools.chrome.enabled");
+ let remoteEnabled = chromeEnabled &&
+ gPrefService.getBoolPref("devtools.debugger.chrome-enabled") &&
+ gPrefService.getBoolPref("devtools.debugger.remote-enabled");
+ if (remoteEnabled) {
+ let cmd = document.getElementById("Tools:ChromeDebugger");
+ cmd.removeAttribute("disabled");
+ cmd.removeAttribute("hidden");
+ }
+
+ // Enable Error Console?
+ let consoleEnabled = gPrefService.getBoolPref("devtools.errorconsole.enabled");
+ if (consoleEnabled) {
+ let cmd = document.getElementById("Tools:ErrorConsole");
+ cmd.removeAttribute("disabled");
+ cmd.removeAttribute("hidden");
+ }
+
+ // Enable Scratchpad in the UI, if the preference allows this.
+ let scratchpadEnabled = gPrefService.getBoolPref(Scratchpad.prefEnabledName);
+ if (scratchpadEnabled) {
+ let cmd = document.getElementById("Tools:Scratchpad");
+ cmd.removeAttribute("disabled");
+ cmd.removeAttribute("hidden");
+ }
+
+ // Enable DevTools connection screen, if the preference allows this.
+ let devtoolsRemoteEnabled = gPrefService.getBoolPref("devtools.debugger.remote-enabled");
+ if (devtoolsRemoteEnabled) {
+ let cmd = document.getElementById("Tools:DevToolsConnect");
+ cmd.removeAttribute("disabled");
+ cmd.removeAttribute("hidden");
+ }
+
+#ifdef MENUBAR_CAN_AUTOHIDE
+ // If the user (or the locale) hasn't enabled the top-level "Character
+ // Encoding" menu via the "browser.menu.showCharacterEncoding" preference,
+ // hide it.
+ if ("true" != gPrefService.getComplexValue("browser.menu.showCharacterEncoding",
+ Ci.nsIPrefLocalizedString).data)
+ document.getElementById("appmenu_charsetMenu").hidden = true;
+#endif
+
+ // Enable Responsive UI?
+ let responsiveUIEnabled = gPrefService.getBoolPref("devtools.responsiveUI.enabled");
+ if (responsiveUIEnabled) {
+ let cmd = document.getElementById("Tools:ResponsiveUI");
+ cmd.removeAttribute("disabled");
+ cmd.removeAttribute("hidden");
+ }
+
+ // Add Devtools menuitems and listeners
+ gDevToolsBrowser.registerBrowserWindow(window);
+
+ let appMenuButton = document.getElementById("appmenu-button");
+ let appMenuPopup = document.getElementById("appmenu-popup");
+ if (appMenuButton && appMenuPopup) {
+ let appMenuOpening = null;
+ appMenuButton.addEventListener("mousedown", function(event) {
+ if (event.button == 0)
+ appMenuOpening = new Date();
+ }, false);
+ appMenuPopup.addEventListener("popupshown", function(event) {
+ if (event.target != appMenuPopup || !appMenuOpening)
+ return;
+ let duration = new Date() - appMenuOpening;
+ appMenuOpening = null;
+ Services.telemetry.getHistogramById("FX_APP_MENU_OPEN_MS").add(duration);
+ }, false);
+ }
+
+ window.addEventListener("mousemove", MousePosTracker, false);
+ window.addEventListener("dragover", MousePosTracker, false);
+
+ // End startup crash tracking after a delay to catch crashes while restoring
+ // tabs and to postpone saving the pref to disk.
+ try {
+ const startupCrashEndDelay = 30 * 1000;
+ setTimeout(Services.startup.trackStartupCrashEnd, startupCrashEndDelay);
+ } catch (ex) {
+ Cu.reportError("Could not end startup crash tracking: " + ex);
+ }
+
+#ifdef XP_WIN
+#ifdef MOZ_METRO
+ gMetroPrefs.prefDomain.forEach(function(prefName) {
+ gMetroPrefs.pushDesktopControlledPrefToMetro(prefName);
+ Services.prefs.addObserver(prefName, gMetroPrefs, false);
+ }, this);
+#endif
+#endif
+
+ Services.obs.notifyObservers(window, "browser-delayed-startup-finished", "");
+ setTimeout(function () { BrowserChromeTest.markAsReady(); }, 0);
+ TelemetryTimestamps.add("delayedStartupFinished");
+ },
+
+ // Returns the URI(s) to load at startup.
+ _getUriToLoad: function () {
+ // window.arguments[0]: URI to load (string), or an nsISupportsArray of
+ // nsISupportsStrings to load, or a xul:tab of
+ // a tabbrowser, which will be replaced by this
+ // window (for this case, all other arguments are
+ // ignored).
+ if (!window.arguments || !window.arguments[0])
+ return null;
+
+ let uri = window.arguments[0];
+ let sessionStartup = Cc["@mozilla.org/browser/sessionstartup;1"]
+ .getService(Ci.nsISessionStartup);
+ let defaultArgs = Cc["@mozilla.org/browser/clh;1"]
+ .getService(Ci.nsIBrowserHandler)
+ .defaultArgs;
+
+ // If the given URI matches defaultArgs (the default homepage) we want
+ // to block its load if we're going to restore a session anyway.
+ if (uri == defaultArgs && sessionStartup.willOverrideHomepage)
+ return null;
+
+ return uri;
+ },
+
+ onUnload: function() {
+ // In certain scenarios it's possible for unload to be fired before onload,
+ // (e.g. if the window is being closed after browser.js loads but before the
+ // load completes). In that case, there's nothing to do here.
+ if (!this._loadHandled)
+ return;
+
+ gDevToolsBrowser.forgetBrowserWindow(window);
+
+ let desc = Object.getOwnPropertyDescriptor(window, "DeveloperToolbar");
+ if (desc && !desc.get) {
+ DeveloperToolbar.destroy();
+ }
+
+ // First clean up services initialized in gBrowserInit.onLoad (or those whose
+ // uninit methods don't depend on the services having been initialized).
+
+ allTabs.uninit();
+
+ CombinedStopReload.uninit();
+
+ gGestureSupport.init(false);
+
+ gHistorySwipeAnimation.uninit();
+
+ FullScreen.cleanup();
+
+ Services.obs.removeObserver(gPluginHandler.pluginCrashed, "plugin-crashed");
+
+ try {
+ gBrowser.removeProgressListener(window.XULBrowserWindow);
+ gBrowser.removeTabsProgressListener(window.TabsProgressListener);
+ } catch (ex) {
+ }
+
+ BookmarkingUI.uninit();
+
+ TabsOnTop.uninit();
+
+ TabsInTitlebar.uninit();
+
+ var enumerator = Services.wm.getEnumerator(null);
+ enumerator.getNext();
+ if (!enumerator.hasMoreElements()) {
+ document.persist("sidebar-box", "sidebarcommand");
+ document.persist("sidebar-box", "width");
+ document.persist("sidebar-box", "src");
+ document.persist("sidebar-title", "value");
+ }
+
+ // Now either cancel delayedStartup, or clean up the services initialized from
+ // it.
+ if (this._boundDelayedStartup) {
+ this._cancelDelayedStartup();
+ } else {
+ if (Win7Features)
+ Win7Features.onCloseWindow();
+
+ gPrefService.removeObserver(ctrlTab.prefName, ctrlTab);
+ gPrefService.removeObserver(allTabs.prefName, allTabs);
+ ctrlTab.uninit();
+ gBrowserThumbnails.uninit();
+ FullZoom.destroy();
+
+ Services.obs.removeObserver(gSessionHistoryObserver, "browser:purge-session-history");
+ Services.obs.removeObserver(gXPInstallObserver, "addon-install-disabled");
+ Services.obs.removeObserver(gXPInstallObserver, "addon-install-started");
+ Services.obs.removeObserver(gXPInstallObserver, "addon-install-blocked");
+ Services.obs.removeObserver(gXPInstallObserver, "addon-install-failed");
+ Services.obs.removeObserver(gXPInstallObserver, "addon-install-complete");
+ Services.obs.removeObserver(gFormSubmitObserver, "invalidformsubmit");
+
+ try {
+ gPrefService.removeObserver(gHomeButton.prefDomain, gHomeButton);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+
+#ifdef XP_WIN
+#ifdef MOZ_METRO
+ gMetroPrefs.prefDomain.forEach(function(prefName) {
+ Services.prefs.removeObserver(prefName, gMetroPrefs);
+ });
+#endif
+#endif
+
+ BrowserOffline.uninit();
+ OfflineApps.uninit();
+ IndexedDBPromptHelper.uninit();
+ AddonManager.removeAddonListener(AddonsMgrListener);
+ SocialUI.uninit();
+ }
+
+ // Final window teardown, do this last.
+ window.XULBrowserWindow.destroy();
+ window.XULBrowserWindow = null;
+ window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIXULWindow)
+ .XULBrowserWindow = null;
+ window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = null;
+ },
+
+#ifdef XP_MACOSX
+ // nonBrowserWindowStartup(), nonBrowserWindowDelayedStartup(), and
+ // nonBrowserWindowShutdown() are used for non-browser windows in
+ // macBrowserOverlay
+ nonBrowserWindowStartup: function() {
+ // Disable inappropriate commands / submenus
+ var disabledItems = ['Browser:SavePage',
+ 'Browser:SendLink', 'cmd_pageSetup', 'cmd_print', 'cmd_find', 'cmd_findAgain',
+ 'viewToolbarsMenu', 'viewSidebarMenuMenu', 'Browser:Reload',
+ 'viewFullZoomMenu', 'pageStyleMenu', 'charsetMenu', 'View:PageSource', 'View:FullScreen',
+ 'viewHistorySidebar', 'Browser:AddBookmarkAs', 'Browser:BookmarkAllTabs',
+ 'View:PageInfo', 'Browser:ToggleAddonBar'];
+ var element;
+
+ for (let disabledItem of disabledItems) {
+ element = document.getElementById(disabledItem);
+ if (element)
+ element.setAttribute("disabled", "true");
+ }
+
+ // If no windows are active (i.e. we're the hidden window), disable the close, minimize
+ // and zoom menu commands as well
+ if (window.location.href == "chrome://browser/content/hiddenWindow.xul") {
+ var hiddenWindowDisabledItems = ['cmd_close', 'minimizeWindow', 'zoomWindow'];
+ for (let hiddenWindowDisabledItem of hiddenWindowDisabledItems) {
+ element = document.getElementById(hiddenWindowDisabledItem);
+ if (element)
+ element.setAttribute("disabled", "true");
+ }
+
+ // also hide the window-list separator
+ element = document.getElementById("sep-window-list");
+ element.setAttribute("hidden", "true");
+
+ // Setup the dock menu.
+ let dockMenuElement = document.getElementById("menu_mac_dockmenu");
+ if (dockMenuElement != null) {
+ let nativeMenu = Cc["@mozilla.org/widget/standalonenativemenu;1"]
+ .createInstance(Ci.nsIStandaloneNativeMenu);
+
+ try {
+ nativeMenu.init(dockMenuElement);
+
+ let dockSupport = Cc["@mozilla.org/widget/macdocksupport;1"]
+ .getService(Ci.nsIMacDockSupport);
+ dockSupport.dockMenu = nativeMenu;
+ }
+ catch (e) {
+ }
+ }
+ }
+
+ SocialUI.nonBrowserWindowInit();
+
+ if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ document.getElementById("macDockMenuNewWindow").hidden = true;
+ }
+
+ this._delayedStartupTimeoutId = setTimeout(this.nonBrowserWindowDelayedStartup.bind(this), 0);
+ },
+
+ nonBrowserWindowDelayedStartup: function() {
+ this._delayedStartupTimeoutId = null;
+
+ // initialise the offline listener
+ BrowserOffline.init();
+
+ // Set up Sanitize Item
+ this._initializeSanitizer();
+
+ // initialize the private browsing UI
+ gPrivateBrowsingUI.init();
+
+#ifdef MOZ_SERVICES_SYNC
+ // initialize the sync UI
+ gSyncUI.init();
+#endif
+ },
+
+ nonBrowserWindowShutdown: function() {
+ // If nonBrowserWindowDelayedStartup hasn't run yet, we have no work to do -
+ // just cancel the pending timeout and return;
+ if (this._delayedStartupTimeoutId) {
+ clearTimeout(this._delayedStartupTimeoutId);
+ return;
+ }
+
+ BrowserOffline.uninit();
+ },
+#endif
+
+ _initializeSanitizer: function() {
+ const kDidSanitizeDomain = "privacy.sanitize.didShutdownSanitize";
+ if (gPrefService.prefHasUserValue(kDidSanitizeDomain)) {
+ gPrefService.clearUserPref(kDidSanitizeDomain);
+ // We need to persist this preference change, since we want to
+ // check it at next app start even if the browser exits abruptly
+ gPrefService.savePrefFile(null);
+ }
+
+ /**
+ * Migrate Firefox 3.0 privacy.item prefs under one of these conditions:
+ *
+ * a) User has customized any privacy.item prefs
+ * b) privacy.sanitize.sanitizeOnShutdown is set
+ */
+ if (!gPrefService.getBoolPref("privacy.sanitize.migrateFx3Prefs")) {
+ let itemBranch = gPrefService.getBranch("privacy.item.");
+ let itemArray = itemBranch.getChildList("");
+
+ // See if any privacy.item prefs are set
+ let doMigrate = itemArray.some(function (name) itemBranch.prefHasUserValue(name));
+ // Or if sanitizeOnShutdown is set
+ if (!doMigrate)
+ doMigrate = gPrefService.getBoolPref("privacy.sanitize.sanitizeOnShutdown");
+
+ if (doMigrate) {
+ let cpdBranch = gPrefService.getBranch("privacy.cpd.");
+ let clearOnShutdownBranch = gPrefService.getBranch("privacy.clearOnShutdown.");
+ for (let name of itemArray) {
+ try {
+ // don't migrate password or offlineApps clearing in the CRH dialog since
+ // there's no UI for those anymore. They default to false. bug 497656
+ if (name != "passwords" && name != "offlineApps")
+ cpdBranch.setBoolPref(name, itemBranch.getBoolPref(name));
+ clearOnShutdownBranch.setBoolPref(name, itemBranch.getBoolPref(name));
+ }
+ catch(e) {
+ Cu.reportError("Exception thrown during privacy pref migration: " + e);
+ }
+ }
+ }
+
+ gPrefService.setBoolPref("privacy.sanitize.migrateFx3Prefs", true);
+ }
+ },
+}
+
+
+/* Legacy global init functions */
+var BrowserStartup = gBrowserInit.onLoad.bind(gBrowserInit);
+var BrowserShutdown = gBrowserInit.onUnload.bind(gBrowserInit);
+#ifdef XP_MACOSX
+var nonBrowserWindowStartup = gBrowserInit.nonBrowserWindowStartup.bind(gBrowserInit);
+var nonBrowserWindowDelayedStartup = gBrowserInit.nonBrowserWindowDelayedStartup.bind(gBrowserInit);
+var nonBrowserWindowShutdown = gBrowserInit.nonBrowserWindowShutdown.bind(gBrowserInit);
+#endif
+
+function HandleAppCommandEvent(evt) {
+ switch (evt.command) {
+ case "Back":
+ BrowserBack();
+ break;
+ case "Forward":
+ BrowserForward();
+ break;
+ case "Reload":
+ BrowserReloadSkipCache();
+ break;
+ case "Stop":
+ if (XULBrowserWindow.stopCommand.getAttribute("disabled") != "true")
+ BrowserStop();
+ break;
+ case "Search":
+ BrowserSearch.webSearch();
+ break;
+ case "Bookmarks":
+ toggleSidebar('viewBookmarksSidebar');
+ break;
+ case "Home":
+ BrowserHome();
+ break;
+ case "New":
+ BrowserOpenTab();
+ break;
+ case "Close":
+ BrowserCloseTabOrWindow();
+ break;
+ case "Find":
+ gFindBar.onFindCommand();
+ break;
+ case "Help":
+ openHelpLink('firefox-help');
+ break;
+ case "Open":
+ BrowserOpenFileWindow();
+ break;
+ case "Print":
+ PrintUtils.print();
+ break;
+ case "Save":
+ saveDocument(window.content.document);
+ break;
+ case "SendMail":
+ MailIntegration.sendLinkForWindow(window.content);
+ break;
+ default:
+ return;
+ }
+ evt.stopPropagation();
+ evt.preventDefault();
+}
+
+function gotoHistoryIndex(aEvent) {
+ let index = aEvent.target.getAttribute("index");
+ if (!index)
+ return false;
+
+ let where = whereToOpenLink(aEvent);
+
+ if (where == "current") {
+ // Normal click. Go there in the current tab and update session history.
+
+ try {
+ gBrowser.gotoIndex(index);
+ }
+ catch(ex) {
+ return false;
+ }
+ return true;
+ }
+ // Modified click. Go there in a new tab/window.
+
+ duplicateTabIn(gBrowser.selectedTab, where, index - gBrowser.sessionHistory.index);
+ return true;
+}
+
+function BrowserForward(aEvent) {
+ let where = whereToOpenLink(aEvent, false, true);
+
+ if (where == "current") {
+ try {
+ gBrowser.goForward();
+ }
+ catch(ex) {
+ }
+ }
+ else {
+ duplicateTabIn(gBrowser.selectedTab, where, 1);
+ }
+}
+
+function BrowserBack(aEvent) {
+ let where = whereToOpenLink(aEvent, false, true);
+
+ if (where == "current") {
+ try {
+ gBrowser.goBack();
+ }
+ catch(ex) {
+ }
+ }
+ else {
+ duplicateTabIn(gBrowser.selectedTab, where, -1);
+ }
+}
+
+function BrowserHandleBackspace()
+{
+ switch (gPrefService.getIntPref("browser.backspace_action")) {
+ case 0:
+ BrowserBack();
+ break;
+ case 1:
+ goDoCommand("cmd_scrollPageUp");
+ break;
+ }
+}
+
+function BrowserHandleShiftBackspace()
+{
+ switch (gPrefService.getIntPref("browser.backspace_action")) {
+ case 0:
+ BrowserForward();
+ break;
+ case 1:
+ goDoCommand("cmd_scrollPageDown");
+ break;
+ }
+}
+
+function BrowserStop() {
+ const stopFlags = nsIWebNavigation.STOP_ALL;
+ gBrowser.webNavigation.stop(stopFlags);
+}
+
+function BrowserReloadOrDuplicate(aEvent) {
+ var backgroundTabModifier = aEvent.button == 1 ||
+#ifdef XP_MACOSX
+ aEvent.metaKey;
+#else
+ aEvent.ctrlKey;
+#endif
+ if (aEvent.shiftKey && !backgroundTabModifier) {
+ BrowserReloadSkipCache();
+ return;
+ }
+
+ let where = whereToOpenLink(aEvent, false, true);
+ if (where == "current")
+ BrowserReload();
+ else
+ duplicateTabIn(gBrowser.selectedTab, where);
+}
+
+function BrowserReload() {
+ const reloadFlags = nsIWebNavigation.LOAD_FLAGS_NONE;
+ BrowserReloadWithFlags(reloadFlags);
+}
+
+function BrowserReloadSkipCache() {
+ // Bypass proxy and cache.
+ const reloadFlags = nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY | nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
+ BrowserReloadWithFlags(reloadFlags);
+}
+
+var BrowserHome = BrowserGoHome;
+function BrowserGoHome(aEvent) {
+ if (aEvent && "button" in aEvent &&
+ aEvent.button == 2) // right-click: do nothing
+ return;
+
+ var homePage = gHomeButton.getHomePage();
+ var where = whereToOpenLink(aEvent, false, true);
+ var urls;
+
+ // Home page should open in a new tab when current tab is an app tab
+ if (where == "current" &&
+ gBrowser &&
+ gBrowser.selectedTab.pinned)
+ where = "tab";
+
+ // openUILinkIn in utilityOverlay.js doesn't handle loading multiple pages
+ switch (where) {
+ case "current":
+ loadOneOrMoreURIs(homePage);
+ break;
+ case "tabshifted":
+ case "tab":
+ urls = homePage.split("|");
+ var loadInBackground = getBoolPref("browser.tabs.loadBookmarksInBackground", false);
+ gBrowser.loadTabs(urls, loadInBackground);
+ break;
+ case "window":
+ OpenBrowserWindow();
+ break;
+ }
+}
+
+function loadOneOrMoreURIs(aURIString)
+{
+#ifdef XP_MACOSX
+ // we're not a browser window, pass the URI string to a new browser window
+ if (window.location.href != getBrowserURL())
+ {
+ window.openDialog(getBrowserURL(), "_blank", "all,dialog=no", aURIString);
+ return;
+ }
+#endif
+ // This function throws for certain malformed URIs, so use exception handling
+ // so that we don't disrupt startup
+ try {
+ gBrowser.loadTabs(aURIString.split("|"), false, true);
+ }
+ catch (e) {
+ }
+}
+
+function focusAndSelectUrlBar() {
+ if (gURLBar) {
+ if (window.fullScreen)
+ FullScreen.mouseoverToggle(true);
+
+ gURLBar.select();
+ if (document.activeElement == gURLBar.inputField)
+ return true;
+ }
+ return false;
+}
+
+function openLocation() {
+ if (focusAndSelectUrlBar())
+ return;
+
+#ifdef XP_MACOSX
+ if (window.location.href != getBrowserURL()) {
+ var win = getTopWin();
+ if (win) {
+ // If there's an open browser window, it should handle this command
+ win.focus()
+ win.openLocation();
+ }
+ else {
+ // If there are no open browser windows, open a new one
+ win = window.openDialog("chrome://browser/content/", "_blank",
+ "chrome,all,dialog=no", BROWSER_NEW_TAB_URL);
+ win.addEventListener("load", openLocationCallback, false);
+ }
+ return;
+ }
+#endif
+ openDialog("chrome://browser/content/openLocation.xul", "_blank",
+ "chrome,modal,titlebar", window);
+}
+
+function openLocationCallback()
+{
+ // make sure the DOM is ready
+ setTimeout(function() { this.openLocation(); }, 0);
+}
+
+function BrowserOpenTab()
+{
+ openUILinkIn(BROWSER_NEW_TAB_URL, "tab");
+}
+
+/* Called from the openLocation dialog. This allows that dialog to instruct
+ its opener to open a new window and then step completely out of the way.
+ Anything less byzantine is causing horrible crashes, rather believably,
+ though oddly only on Linux. */
+function delayedOpenWindow(chrome, flags, href, postData)
+{
+ // The other way to use setTimeout,
+ // setTimeout(openDialog, 10, chrome, "_blank", flags, url),
+ // doesn't work here. The extra "magic" extra argument setTimeout adds to
+ // the callback function would confuse gBrowserInit.onLoad() by making
+ // window.arguments[1] be an integer instead of null.
+ setTimeout(function() { openDialog(chrome, "_blank", flags, href, null, null, postData); }, 10);
+}
+
+/* Required because the tab needs time to set up its content viewers and get the load of
+ the URI kicked off before becoming the active content area. */
+function delayedOpenTab(aUrl, aReferrer, aCharset, aPostData, aAllowThirdPartyFixup)
+{
+ gBrowser.loadOneTab(aUrl, {
+ referrerURI: aReferrer,
+ charset: aCharset,
+ postData: aPostData,
+ inBackground: false,
+ allowThirdPartyFixup: aAllowThirdPartyFixup});
+}
+
+var gLastOpenDirectory = {
+ _lastDir: null,
+ get path() {
+ if (!this._lastDir || !this._lastDir.exists()) {
+ try {
+ this._lastDir = gPrefService.getComplexValue("browser.open.lastDir",
+ Ci.nsILocalFile);
+ if (!this._lastDir.exists())
+ this._lastDir = null;
+ }
+ catch(e) {}
+ }
+ return this._lastDir;
+ },
+ set path(val) {
+ try {
+ if (!val || !val.isDirectory())
+ return;
+ } catch(e) {
+ return;
+ }
+ this._lastDir = val.clone();
+
+ // Don't save the last open directory pref inside the Private Browsing mode
+ if (!PrivateBrowsingUtils.isWindowPrivate(window))
+ gPrefService.setComplexValue("browser.open.lastDir", Ci.nsILocalFile,
+ this._lastDir);
+ },
+ reset: function() {
+ this._lastDir = null;
+ }
+};
+
+function BrowserOpenFileWindow()
+{
+ // Get filepicker component.
+ try {
+ const nsIFilePicker = Ci.nsIFilePicker;
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
+ let fpCallback = function fpCallback_done(aResult) {
+ if (aResult == nsIFilePicker.returnOK) {
+ try {
+ if (fp.file) {
+ gLastOpenDirectory.path =
+ fp.file.parent.QueryInterface(Ci.nsILocalFile);
+ }
+ } catch (ex) {
+ }
+ openUILinkIn(fp.fileURL.spec, "current");
+ }
+ };
+
+ fp.init(window, gNavigatorBundle.getString("openFile"),
+ nsIFilePicker.modeOpen);
+ fp.appendFilters(nsIFilePicker.filterAll | nsIFilePicker.filterText |
+ nsIFilePicker.filterImages | nsIFilePicker.filterXML |
+ nsIFilePicker.filterHTML);
+ fp.displayDirectory = gLastOpenDirectory.path;
+ fp.open(fpCallback);
+ } catch (ex) {
+ }
+}
+
+function BrowserCloseTabOrWindow() {
+#ifdef XP_MACOSX
+ // If we're not a browser window, just close the window
+ if (window.location.href != getBrowserURL()) {
+ closeWindow(true);
+ return;
+ }
+#endif
+
+ // If the current tab is the last one, this will close the window.
+ gBrowser.removeCurrentTab({animate: true});
+}
+
+function BrowserTryToCloseWindow()
+{
+ if (WindowIsClosing())
+ window.close(); // WindowIsClosing does all the necessary checks
+}
+
+function loadURI(uri, referrer, postData, allowThirdPartyFixup) {
+ if (postData === undefined)
+ postData = null;
+
+ var flags = nsIWebNavigation.LOAD_FLAGS_NONE;
+ if (allowThirdPartyFixup)
+ flags |= nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+
+ try {
+ gBrowser.loadURIWithFlags(uri, flags, referrer, null, postData);
+ } catch (e) {}
+}
+
+function getShortcutOrURI(aURL, aPostDataRef, aMayInheritPrincipal) {
+ // Initialize outparam to false
+ if (aMayInheritPrincipal)
+ aMayInheritPrincipal.value = false;
+
+ var shortcutURL = null;
+ var keyword = aURL;
+ var param = "";
+
+ var offset = aURL.indexOf(" ");
+ if (offset > 0) {
+ keyword = aURL.substr(0, offset);
+ param = aURL.substr(offset + 1);
+ }
+
+ if (!aPostDataRef)
+ aPostDataRef = {};
+
+ var engine = Services.search.getEngineByAlias(keyword);
+ if (engine) {
+ var submission = engine.getSubmission(param);
+ aPostDataRef.value = submission.postData;
+ return submission.uri.spec;
+ }
+
+ [shortcutURL, aPostDataRef.value] =
+ PlacesUtils.getURLAndPostDataForKeyword(keyword);
+
+ if (!shortcutURL)
+ return aURL;
+
+ var postData = "";
+ if (aPostDataRef.value)
+ postData = unescape(aPostDataRef.value);
+
+ if (/%s/i.test(shortcutURL) || /%s/i.test(postData)) {
+ var charset = "";
+ const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/;
+ var matches = shortcutURL.match(re);
+ if (matches)
+ [, shortcutURL, charset] = matches;
+ else {
+ // Try to get the saved character-set.
+ try {
+ // makeURI throws if URI is invalid.
+ // Will return an empty string if character-set is not found.
+ charset = PlacesUtils.history.getCharsetForURI(makeURI(shortcutURL));
+ } catch (e) {}
+ }
+
+ // encodeURIComponent produces UTF-8, and cannot be used for other charsets.
+ // escape() works in those cases, but it doesn't uri-encode +, @, and /.
+ // Therefore we need to manually replace these ASCII characters by their
+ // encodeURIComponent result, to match the behavior of nsEscape() with
+ // url_XPAlphas
+ var encodedParam = "";
+ if (charset && charset != "UTF-8")
+ encodedParam = escape(convertFromUnicode(charset, param)).
+ replace(/[+@\/]+/g, encodeURIComponent);
+ else // Default charset is UTF-8
+ encodedParam = encodeURIComponent(param);
+
+ shortcutURL = shortcutURL.replace(/%s/g, encodedParam).replace(/%S/g, param);
+
+ if (/%s/i.test(postData)) // POST keyword
+ aPostDataRef.value = getPostDataStream(postData, param, encodedParam,
+ "application/x-www-form-urlencoded");
+ }
+ else if (param) {
+ // This keyword doesn't take a parameter, but one was provided. Just return
+ // the original URL.
+ aPostDataRef.value = null;
+
+ return aURL;
+ }
+
+ // This URL came from a bookmark, so it's safe to let it inherit the current
+ // document's principal.
+ if (aMayInheritPrincipal)
+ aMayInheritPrincipal.value = true;
+
+ return shortcutURL;
+}
+
+function getPostDataStream(aStringData, aKeyword, aEncKeyword, aType) {
+ var dataStream = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+ aStringData = aStringData.replace(/%s/g, aEncKeyword).replace(/%S/g, aKeyword);
+ dataStream.data = aStringData;
+
+ var mimeStream = Cc["@mozilla.org/network/mime-input-stream;1"].
+ createInstance(Ci.nsIMIMEInputStream);
+ mimeStream.addHeader("Content-Type", aType);
+ mimeStream.addContentLength = true;
+ mimeStream.setData(dataStream);
+ return mimeStream.QueryInterface(Ci.nsIInputStream);
+}
+
+function getLoadContext() {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext);
+}
+
+function readFromClipboard()
+{
+ var url;
+
+ try {
+ // Create transferable that will transfer the text.
+ var trans = Components.classes["@mozilla.org/widget/transferable;1"]
+ .createInstance(Components.interfaces.nsITransferable);
+ trans.init(getLoadContext());
+
+ trans.addDataFlavor("text/unicode");
+
+ // If available, use selection clipboard, otherwise global one
+ if (Services.clipboard.supportsSelectionClipboard())
+ Services.clipboard.getData(trans, Services.clipboard.kSelectionClipboard);
+ else
+ Services.clipboard.getData(trans, Services.clipboard.kGlobalClipboard);
+
+ var data = {};
+ var dataLen = {};
+ trans.getTransferData("text/unicode", data, dataLen);
+
+ if (data) {
+ data = data.value.QueryInterface(Components.interfaces.nsISupportsString);
+ url = data.data.substring(0, dataLen.value / 2);
+ }
+ } catch (ex) {
+ }
+
+ return url;
+}
+
+function BrowserViewSourceOfDocument(aDocument)
+{
+ var pageCookie;
+ var webNav;
+
+ // Get the document charset
+ var docCharset = "charset=" + aDocument.characterSet;
+
+ // Get the nsIWebNavigation associated with the document
+ try {
+ var win;
+ var ifRequestor;
+
+ // Get the DOMWindow for the requested document. If the DOMWindow
+ // cannot be found, then just use the content window...
+ //
+ // XXX: This is a bit of a hack...
+ win = aDocument.defaultView;
+ if (win == window) {
+ win = content;
+ }
+ ifRequestor = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
+
+ webNav = ifRequestor.getInterface(nsIWebNavigation);
+ } catch(err) {
+ // If nsIWebNavigation cannot be found, just get the one for the whole
+ // window...
+ webNav = gBrowser.webNavigation;
+ }
+ //
+ // Get the 'PageDescriptor' for the current document. This allows the
+ // view-source to access the cached copy of the content rather than
+ // refetching it from the network...
+ //
+ try{
+ var PageLoader = webNav.QueryInterface(Components.interfaces.nsIWebPageDescriptor);
+
+ pageCookie = PageLoader.currentDescriptor;
+ } catch(err) {
+ // If no page descriptor is available, just use the view-source URL...
+ }
+
+ top.gViewSourceUtils.viewSource(webNav.currentURI.spec, pageCookie, aDocument);
+}
+
+// doc - document to use for source, or null for this window's document
+// initialTab - name of the initial tab to display, or null for the first tab
+// imageElement - image to load in the Media Tab of the Page Info window; can be null/omitted
+function BrowserPageInfo(doc, initialTab, imageElement) {
+ var args = {doc: doc, initialTab: initialTab, imageElement: imageElement};
+ var windows = Services.wm.getEnumerator("Browser:page-info");
+
+ var documentURL = doc ? doc.location : window.content.document.location;
+
+ // Check for windows matching the url
+ while (windows.hasMoreElements()) {
+ var currentWindow = windows.getNext();
+ if (currentWindow.document.documentElement.getAttribute("relatedUrl") == documentURL) {
+ currentWindow.focus();
+ currentWindow.resetPageInfo(args);
+ return currentWindow;
+ }
+ }
+
+ // We didn't find a matching window, so open a new one.
+ return openDialog("chrome://browser/content/pageinfo/pageInfo.xul", "",
+ "chrome,toolbar,dialog=no,resizable", args);
+}
+
+function URLBarSetURI(aURI) {
+ var value = gBrowser.userTypedValue;
+ var valid = false;
+
+ if (value == null) {
+ let uri = aURI || gBrowser.currentURI;
+ // Strip off "wyciwyg://" and passwords for the location bar
+ try {
+ uri = Services.uriFixup.createExposableURI(uri);
+ } catch (e) {}
+
+ // Replace initial page URIs with an empty string
+ // only if there's no opener (bug 370555).
+ // Bug 863515 - Make content.opener checks work in electrolysis.
+ if (gInitialPages.indexOf(uri.spec) != -1)
+ value = !gMultiProcessBrowser && content.opener ? uri.spec : "";
+ else
+ value = losslessDecodeURI(uri);
+
+ valid = !isBlankPageURL(uri.spec);
+ }
+
+ gURLBar.value = value;
+ gURLBar.valueIsTyped = !valid;
+ SetPageProxyState(valid ? "valid" : "invalid");
+}
+
+function losslessDecodeURI(aURI) {
+ var value = aURI.spec;
+ // Try to decode as UTF-8 if there's no encoding sequence that we would break.
+ if (!/%25(?:3B|2F|3F|3A|40|26|3D|2B|24|2C|23)/i.test(value))
+ try {
+ value = decodeURI(value)
+ // 1. decodeURI decodes %25 to %, which creates unintended
+ // encoding sequences. Re-encode it, unless it's part of
+ // a sequence that survived decodeURI, i.e. one for:
+ // ';', '/', '?', ':', '@', '&', '=', '+', '$', ',', '#'
+ // (RFC 3987 section 3.2)
+ // 2. Re-encode whitespace so that it doesn't get eaten away
+ // by the location bar (bug 410726).
+ .replace(/%(?!3B|2F|3F|3A|40|26|3D|2B|24|2C|23)|[\r\n\t]/ig,
+ encodeURIComponent);
+ } catch (e) {}
+
+ // Encode invisible characters (line and paragraph separator,
+ // object replacement character) (bug 452979)
+ value = value.replace(/[\v\x0c\x1c\x1d\x1e\x1f\u2028\u2029\ufffc]/g,
+ encodeURIComponent);
+
+ // Encode default ignorable characters (bug 546013)
+ // except ZWNJ (U+200C) and ZWJ (U+200D) (bug 582186).
+ // This includes all bidirectional formatting characters.
+ // (RFC 3987 sections 3.2 and 4.1 paragraph 6)
+ value = value.replace(/[\u00ad\u034f\u115f-\u1160\u17b4-\u17b5\u180b-\u180d\u200b\u200e-\u200f\u202a-\u202e\u2060-\u206f\u3164\ufe00-\ufe0f\ufeff\uffa0\ufff0-\ufff8]|\ud834[\udd73-\udd7a]|[\udb40-\udb43][\udc00-\udfff]/g,
+ encodeURIComponent);
+ return value;
+}
+
+function UpdateUrlbarSearchSplitterState()
+{
+ var splitter = document.getElementById("urlbar-search-splitter");
+ var urlbar = document.getElementById("urlbar-container");
+ var searchbar = document.getElementById("search-container");
+ var stop = document.getElementById("stop-button");
+
+ var ibefore = null;
+ if (urlbar && searchbar) {
+ if (urlbar.nextSibling == searchbar ||
+ urlbar.getAttribute("combined") &&
+ stop && stop.nextSibling == searchbar)
+ ibefore = searchbar;
+ else if (searchbar.nextSibling == urlbar)
+ ibefore = urlbar;
+ }
+
+ if (ibefore) {
+ if (!splitter) {
+ splitter = document.createElement("splitter");
+ splitter.id = "urlbar-search-splitter";
+ splitter.setAttribute("resizebefore", "flex");
+ splitter.setAttribute("resizeafter", "flex");
+ splitter.setAttribute("skipintoolbarset", "true");
+ splitter.className = "chromeclass-toolbar-additional";
+ }
+ urlbar.parentNode.insertBefore(splitter, ibefore);
+ } else if (splitter)
+ splitter.parentNode.removeChild(splitter);
+}
+
+function setUrlAndSearchBarWidthForConditionalForwardButton() {
+ // Workaround for bug 694084: Showing/hiding the conditional forward button resizes
+ // the search bar when the url/search bar splitter hasn't been used.
+ var urlbarContainer = document.getElementById("urlbar-container");
+ var searchbarContainer = document.getElementById("search-container");
+ if (!urlbarContainer ||
+ !searchbarContainer ||
+ urlbarContainer.hasAttribute("width") ||
+ searchbarContainer.hasAttribute("width") ||
+ urlbarContainer.parentNode != searchbarContainer.parentNode)
+ return;
+ urlbarContainer.style.width = searchbarContainer.style.width = "";
+ var urlbarWidth = urlbarContainer.clientWidth;
+ var searchbarWidth = searchbarContainer.clientWidth;
+ urlbarContainer.style.width = urlbarWidth + "px";
+ searchbarContainer.style.width = searchbarWidth + "px";
+}
+
+function UpdatePageProxyState()
+{
+ if (gURLBar && gURLBar.value != gLastValidURLStr)
+ SetPageProxyState("invalid");
+}
+
+function SetPageProxyState(aState)
+{
+ BookmarkingUI.onPageProxyStateChanged(aState);
+
+ if (!gURLBar)
+ return;
+
+ if (!gProxyFavIcon)
+ gProxyFavIcon = document.getElementById("page-proxy-favicon");
+
+ gURLBar.setAttribute("pageproxystate", aState);
+ gProxyFavIcon.setAttribute("pageproxystate", aState);
+
+ // the page proxy state is set to valid via OnLocationChange, which
+ // gets called when we switch tabs.
+ if (aState == "valid") {
+ gLastValidURLStr = gURLBar.value;
+ gURLBar.addEventListener("input", UpdatePageProxyState, false);
+ PageProxySetIcon(gBrowser.getIcon());
+ } else if (aState == "invalid") {
+ gURLBar.removeEventListener("input", UpdatePageProxyState, false);
+ PageProxyClearIcon();
+ }
+}
+
+function PageProxySetIcon (aURL)
+{
+ if (!gProxyFavIcon)
+ return;
+
+ if (gBrowser.selectedBrowser.contentDocument instanceof ImageDocument) {
+ // PageProxyClearIcon();
+ gProxyFavIcon.setAttribute("src", "chrome://browser/content/imagedocument.png");
+ return;
+ }
+
+ if (!aURL)
+ PageProxyClearIcon();
+ else if (gProxyFavIcon.getAttribute("src") != aURL)
+ gProxyFavIcon.setAttribute("src", aURL);
+}
+
+function PageProxyClearIcon ()
+{
+ gProxyFavIcon.removeAttribute("src");
+}
+
+
+function PageProxyClickHandler(aEvent)
+{
+ if (aEvent.button == 1 && gPrefService.getBoolPref("middlemouse.paste"))
+ middleMousePaste(aEvent);
+}
+
+/**
+ * Handle load of some pages (about:*) so that we can make modifications
+ * to the DOM for unprivileged pages.
+ */
+function BrowserOnAboutPageLoad(doc) {
+ if (doc.documentURI.toLowerCase() == "about:home") {
+ // XXX bug 738646 - when Marketplace is launched, remove this statement and
+ // the hidden attribute set on the apps button in aboutHome.xhtml
+ if (getBoolPref("browser.aboutHome.apps", false))
+ doc.getElementById("apps").removeAttribute("hidden");
+
+ let ss = Components.classes["@mozilla.org/browser/sessionstore;1"].
+ getService(Components.interfaces.nsISessionStore);
+ if (ss.canRestoreLastSession &&
+ !PrivateBrowsingUtils.isWindowPrivate(window))
+ doc.getElementById("launcher").setAttribute("session", "true");
+
+ // Inject search engine and snippets URL.
+ let docElt = doc.documentElement;
+ // set the following attributes BEFORE searchEngineURL, which triggers to
+ // show the snippets when it's set.
+ docElt.setAttribute("snippetsURL", AboutHomeUtils.snippetsURL);
+ if (AboutHomeUtils.showKnowYourRights) {
+ docElt.setAttribute("showKnowYourRights", "true");
+ // Set pref to indicate we've shown the notification.
+ let currentVersion = Services.prefs.getIntPref("browser.rights.version");
+ Services.prefs.setBoolPref("browser.rights." + currentVersion + ".shown", true);
+ }
+ docElt.setAttribute("snippetsVersion", AboutHomeUtils.snippetsVersion);
+
+ function updateSearchEngine() {
+ let engine = AboutHomeUtils.defaultSearchEngine;
+ docElt.setAttribute("searchEngineName", engine.name);
+ docElt.setAttribute("searchEnginePostData", engine.postDataString || "");
+ docElt.setAttribute("searchEngineURL", engine.searchURL);
+ }
+ updateSearchEngine();
+
+ // Listen for the event that's triggered when the user changes search engine.
+ // At this point we simply reload about:home to reflect the change.
+ Services.obs.addObserver(updateSearchEngine, "browser-search-engine-modified", false);
+
+ // Remove the observer when the page is reloaded or closed.
+ doc.defaultView.addEventListener("pagehide", function removeObserver() {
+ doc.defaultView.removeEventListener("pagehide", removeObserver);
+ Services.obs.removeObserver(updateSearchEngine, "browser-search-engine-modified");
+ }, false);
+
+#ifdef MOZ_SERVICES_HEALTHREPORT
+ doc.addEventListener("AboutHomeSearchEvent", function onSearch(e) {
+ BrowserSearch.recordSearchInHealthReport(e.detail, "abouthome");
+ }, true, true);
+#endif
+ }
+}
+
+/**
+ * Handle command events bubbling up from error page content
+ */
+let BrowserOnClick = {
+ handleEvent: function BrowserOnClick_handleEvent(aEvent) {
+ if (!aEvent.isTrusted || // Don't trust synthetic events
+ aEvent.button == 2 || aEvent.target.localName != "button") {
+ return;
+ }
+
+ let originalTarget = aEvent.originalTarget;
+ let ownerDoc = originalTarget.ownerDocument;
+
+ // If the event came from an ssl error page, it is probably either the "Add
+ // Exception…" or "Get me out of here!" button
+ if (ownerDoc.documentURI.startsWith("about:certerror")) {
+ this.onAboutCertError(originalTarget, ownerDoc);
+ }
+ else if (ownerDoc.documentURI.startsWith("about:blocked")) {
+ this.onAboutBlocked(originalTarget, ownerDoc);
+ }
+ else if (ownerDoc.documentURI.startsWith("about:neterror")) {
+ this.onAboutNetError(originalTarget, ownerDoc);
+ }
+ else if (ownerDoc.documentURI.toLowerCase() == "about:home") {
+ this.onAboutHome(originalTarget, ownerDoc);
+ }
+ },
+
+ onAboutCertError: function BrowserOnClick_onAboutCertError(aTargetElm, aOwnerDoc) {
+ let elmId = aTargetElm.getAttribute("id");
+ let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
+ let isTopFrame = (aOwnerDoc.defaultView.parent === aOwnerDoc.defaultView);
+
+ switch (elmId) {
+ case "exceptionDialogButton":
+ if (isTopFrame) {
+ secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_CLICK_ADD_EXCEPTION);
+ }
+ let params = { exceptionAdded : false };
+
+ try {
+ switch (Services.prefs.getIntPref("browser.ssl_override_behavior")) {
+ case 2 : // Pre-fetch & pre-populate
+ params.prefetchCert = true;
+ case 1 : // Pre-populate
+ params.location = aOwnerDoc.location.href;
+ }
+ } catch (e) {
+ Components.utils.reportError("Couldn't get ssl_override pref: " + e);
+ }
+
+ window.openDialog('chrome://pippki/content/exceptionDialog.xul',
+ '','chrome,centerscreen,modal', params);
+
+ // If the user added the exception cert, attempt to reload the page
+ if (params.exceptionAdded) {
+ aOwnerDoc.location.reload();
+ }
+ break;
+
+ case "getMeOutOfHereButton":
+ if (isTopFrame) {
+ secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_GET_ME_OUT_OF_HERE);
+ }
+ getMeOutOfHere();
+ break;
+
+ case "technicalContent":
+ if (isTopFrame) {
+ secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_TECHNICAL_DETAILS);
+ }
+ break;
+
+ case "expertContent":
+ if (isTopFrame) {
+ secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_UNDERSTAND_RISKS);
+ }
+ break;
+
+ }
+ },
+
+ onAboutBlocked: function BrowserOnClick_onAboutBlocked(aTargetElm, aOwnerDoc) {
+ let elmId = aTargetElm.getAttribute("id");
+ let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
+
+ // The event came from a button on a malware/phishing block page
+ // First check whether it's malware or phishing, so that we can
+ // use the right strings/links
+ let isMalware = /e=malwareBlocked/.test(aOwnerDoc.documentURI);
+ let bucketName = isMalware ? "WARNING_MALWARE_PAGE_":"WARNING_PHISHING_PAGE_";
+ let nsISecTel = Ci.nsISecurityUITelemetry;
+ let isIframe = (aOwnerDoc.defaultView.parent === aOwnerDoc.defaultView);
+ bucketName += isIframe ? "TOP_" : "FRAME_";
+
+ switch (elmId) {
+ case "getMeOutButton":
+ secHistogram.add(nsISecTel[bucketName + "GET_ME_OUT_OF_HERE"]);
+ getMeOutOfHere();
+ break;
+
+ case "reportButton":
+ // This is the "Why is this site blocked" button. For malware,
+ // we can fetch a site-specific report, for phishing, we redirect
+ // to the generic page describing phishing protection.
+
+ // We log even if malware/phishing info URL couldn't be found:
+ // the measurement is for how many users clicked the WHY BLOCKED button
+ secHistogram.add(nsISecTel[bucketName + "WHY_BLOCKED"]);
+
+ if (isMalware) {
+ // Get the stop badware "why is this blocked" report url,
+ // append the current url, and go there.
+ try {
+ let reportURL = formatURL("browser.safebrowsing.malware.reportURL", true);
+ reportURL += aOwnerDoc.location.href;
+ content.location = reportURL;
+ } catch (e) {
+ Components.utils.reportError("Couldn't get malware report URL: " + e);
+ }
+ }
+ else { // It's a phishing site, not malware
+ try {
+ content.location = formatURL("browser.safebrowsing.warning.infoURL", true);
+ } catch (e) {
+ Components.utils.reportError("Couldn't get phishing info URL: " + e);
+ }
+ }
+ break;
+
+ case "ignoreWarningButton":
+ secHistogram.add(nsISecTel[bucketName + "IGNORE_WARNING"]);
+ this.ignoreWarningButton(isMalware);
+ break;
+ }
+ },
+
+ ignoreWarningButton: function BrowserOnClick_ignoreWarningButton(aIsMalware) {
+ // Allow users to override and continue through to the site,
+ // but add a notify bar as a reminder, so that they don't lose
+ // track after, e.g., tab switching.
+ gBrowser.loadURIWithFlags(content.location.href,
+ nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER,
+ null, null, null);
+
+ Services.perms.add(makeURI(content.location.href), "safe-browsing",
+ Ci.nsIPermissionManager.ALLOW_ACTION,
+ Ci.nsIPermissionManager.EXPIRE_SESSION);
+
+ let buttons = [{
+ label: gNavigatorBundle.getString("safebrowsing.getMeOutOfHereButton.label"),
+ accessKey: gNavigatorBundle.getString("safebrowsing.getMeOutOfHereButton.accessKey"),
+ callback: function() { getMeOutOfHere(); }
+ }];
+
+ let title;
+ if (aIsMalware) {
+ title = gNavigatorBundle.getString("safebrowsing.reportedAttackSite");
+ buttons[1] = {
+ label: gNavigatorBundle.getString("safebrowsing.notAnAttackButton.label"),
+ accessKey: gNavigatorBundle.getString("safebrowsing.notAnAttackButton.accessKey"),
+ callback: function() {
+ openUILinkIn(gSafeBrowsing.getReportURL('MalwareError'), 'tab');
+ }
+ };
+ } else {
+ title = gNavigatorBundle.getString("safebrowsing.reportedWebForgery");
+ buttons[1] = {
+ label: gNavigatorBundle.getString("safebrowsing.notAForgeryButton.label"),
+ accessKey: gNavigatorBundle.getString("safebrowsing.notAForgeryButton.accessKey"),
+ callback: function() {
+ openUILinkIn(gSafeBrowsing.getReportURL('Error'), 'tab');
+ }
+ };
+ }
+
+ let notificationBox = gBrowser.getNotificationBox();
+ let value = "blocked-badware-page";
+
+ let previousNotification = notificationBox.getNotificationWithValue(value);
+ if (previousNotification) {
+ notificationBox.removeNotification(previousNotification);
+ }
+
+ let notification = notificationBox.appendNotification(
+ title,
+ value,
+ "chrome://global/skin/icons/blacklist_favicon.png",
+ notificationBox.PRIORITY_CRITICAL_HIGH,
+ buttons
+ );
+ // Persist the notification until the user removes so it
+ // doesn't get removed on redirects.
+ notification.persistence = -1;
+ },
+
+ onAboutNetError: function BrowserOnClick_onAboutNetError(aTargetElm, aOwnerDoc) {
+ let elmId = aTargetElm.getAttribute("id");
+ if (elmId != "errorTryAgain" || !/e=netOffline/.test(aOwnerDoc.documentURI))
+ return;
+ Services.io.offline = false;
+ },
+
+ onAboutHome: function BrowserOnClick_onAboutHome(aTargetElm, aOwnerDoc) {
+ let elmId = aTargetElm.getAttribute("id");
+
+ switch (elmId) {
+ case "restorePreviousSession":
+ let ss = Cc["@mozilla.org/browser/sessionstore;1"].
+ getService(Ci.nsISessionStore);
+ if (ss.canRestoreLastSession) {
+ ss.restoreLastSession();
+ }
+ aOwnerDoc.getElementById("launcher").removeAttribute("session");
+ break;
+
+ case "downloads":
+ BrowserDownloadsUI();
+ break;
+
+ case "bookmarks":
+ PlacesCommandHook.showPlacesOrganizer("AllBookmarks");
+ break;
+
+ case "history":
+ PlacesCommandHook.showPlacesOrganizer("History");
+ break;
+
+ case "apps":
+ openUILinkIn("https://marketplace.mozilla.org/", "tab");
+ break;
+
+ case "addons":
+ BrowserOpenAddonsMgr();
+ break;
+
+ case "sync":
+ openPreferences("paneSync");
+ break;
+
+ case "settings":
+ openPreferences();
+ break;
+ }
+ },
+};
+
+/**
+ * Re-direct the browser to a known-safe page. This function is
+ * used when, for example, the user browses to a known malware page
+ * and is presented with about:blocked. The "Get me out of here!"
+ * button should take the user to the default start page so that even
+ * when their own homepage is infected, we can get them somewhere safe.
+ */
+function getMeOutOfHere() {
+ // Get the start page from the *default* pref branch, not the user's
+ var prefs = Services.prefs.getDefaultBranch(null);
+ var url = BROWSER_NEW_TAB_URL;
+ try {
+ url = prefs.getComplexValue("browser.startup.homepage",
+ Ci.nsIPrefLocalizedString).data;
+ // If url is a pipe-delimited set of pages, just take the first one.
+ if (url.contains("|"))
+ url = url.split("|")[0];
+ } catch(e) {
+ Components.utils.reportError("Couldn't get homepage pref: " + e);
+ }
+ content.location = url;
+}
+
+function BrowserFullScreen()
+{
+ window.fullScreen = !window.fullScreen;
+}
+
+function onFullScreen(event) {
+ FullScreen.toggle(event);
+}
+
+function onMozEnteredDomFullscreen(event) {
+ FullScreen.enterDomFullscreen(event);
+}
+
+function getWebNavigation()
+{
+ return gBrowser.webNavigation;
+}
+
+function BrowserReloadWithFlags(reloadFlags) {
+ /* First, we'll try to use the session history object to reload so
+ * that framesets are handled properly. If we're in a special
+ * window (such as view-source) that has no session history, fall
+ * back on using the web navigation's reload method.
+ */
+
+ var webNav = gBrowser.webNavigation;
+ try {
+ var sh = webNav.sessionHistory;
+ if (sh)
+ webNav = sh.QueryInterface(nsIWebNavigation);
+ } catch (e) {
+ }
+
+ try {
+ webNav.reload(reloadFlags);
+ } catch (e) {
+ }
+}
+
+var PrintPreviewListener = {
+ _printPreviewTab: null,
+ _tabBeforePrintPreview: null,
+
+ getPrintPreviewBrowser: function () {
+ if (!this._printPreviewTab) {
+ this._tabBeforePrintPreview = gBrowser.selectedTab;
+ this._printPreviewTab = gBrowser.loadOneTab("about:blank",
+ { inBackground: false });
+ gBrowser.selectedTab = this._printPreviewTab;
+ }
+ return gBrowser.getBrowserForTab(this._printPreviewTab);
+ },
+ getSourceBrowser: function () {
+ return this._tabBeforePrintPreview ?
+ this._tabBeforePrintPreview.linkedBrowser : gBrowser.selectedBrowser;
+ },
+ getNavToolbox: function () {
+ return gNavToolbox;
+ },
+ onEnter: function () {
+ gInPrintPreviewMode = true;
+ this._toggleAffectedChrome();
+ },
+ onExit: function () {
+ gBrowser.selectedTab = this._tabBeforePrintPreview;
+ this._tabBeforePrintPreview = null;
+ gInPrintPreviewMode = false;
+ this._toggleAffectedChrome();
+ gBrowser.removeTab(this._printPreviewTab);
+ this._printPreviewTab = null;
+ },
+ _toggleAffectedChrome: function () {
+ gNavToolbox.collapsed = gInPrintPreviewMode;
+
+ if (gInPrintPreviewMode)
+ this._hideChrome();
+ else
+ this._showChrome();
+
+ if (this._chromeState.sidebarOpen)
+ toggleSidebar(this._sidebarCommand);
+
+#ifdef MENUBAR_CAN_AUTOHIDE
+ updateAppButtonDisplay();
+#endif
+ },
+ _hideChrome: function () {
+ this._chromeState = {};
+
+ var sidebar = document.getElementById("sidebar-box");
+ this._chromeState.sidebarOpen = !sidebar.hidden;
+ this._sidebarCommand = sidebar.getAttribute("sidebarcommand");
+
+ var notificationBox = gBrowser.getNotificationBox();
+ this._chromeState.notificationsOpen = !notificationBox.notificationsHidden;
+ notificationBox.notificationsHidden = true;
+
+ document.getElementById("sidebar").setAttribute("src", "about:blank");
+ var addonBar = document.getElementById("addon-bar");
+ this._chromeState.addonBarOpen = !addonBar.collapsed;
+ addonBar.collapsed = true;
+ gBrowser.updateWindowResizers();
+
+ this._chromeState.findOpen = gFindBarInitialized && !gFindBar.hidden;
+ if (gFindBarInitialized)
+ gFindBar.close();
+
+ var globalNotificationBox = document.getElementById("global-notificationbox");
+ this._chromeState.globalNotificationsOpen = !globalNotificationBox.notificationsHidden;
+ globalNotificationBox.notificationsHidden = true;
+
+ this._chromeState.syncNotificationsOpen = false;
+ var syncNotifications = document.getElementById("sync-notifications");
+ if (syncNotifications) {
+ this._chromeState.syncNotificationsOpen = !syncNotifications.notificationsHidden;
+ syncNotifications.notificationsHidden = true;
+ }
+ },
+ _showChrome: function () {
+ if (this._chromeState.notificationsOpen)
+ gBrowser.getNotificationBox().notificationsHidden = false;
+
+ if (this._chromeState.addonBarOpen) {
+ document.getElementById("addon-bar").collapsed = false;
+ gBrowser.updateWindowResizers();
+ }
+
+ if (this._chromeState.findOpen)
+ gFindBar.open();
+
+ if (this._chromeState.globalNotificationsOpen)
+ document.getElementById("global-notificationbox").notificationsHidden = false;
+
+ if (this._chromeState.syncNotificationsOpen)
+ document.getElementById("sync-notifications").notificationsHidden = false;
+ }
+}
+
+function getMarkupDocumentViewer()
+{
+ return gBrowser.markupDocumentViewer;
+}
+
+// This function is obsolete. Newer code should use <tooltip page="true"/> instead.
+function FillInHTMLTooltip(tipElement)
+{
+ document.getElementById("aHTMLTooltip").fillInPageTooltip(tipElement);
+}
+
+var browserDragAndDrop = {
+ canDropLink: function (aEvent) Services.droppedLinkHandler.canDropLink(aEvent, true),
+
+ dragOver: function (aEvent)
+ {
+ if (this.canDropLink(aEvent)) {
+ aEvent.preventDefault();
+ }
+ },
+
+ drop: function (aEvent, aName, aDisallowInherit) {
+ return Services.droppedLinkHandler.dropLink(aEvent, aName, aDisallowInherit);
+ }
+};
+
+var homeButtonObserver = {
+ onDrop: function (aEvent)
+ {
+ // disallow setting home pages that inherit the principal
+ let url = browserDragAndDrop.drop(aEvent, {}, true);
+ setTimeout(openHomeDialog, 0, url);
+ },
+
+ onDragOver: function (aEvent)
+ {
+ browserDragAndDrop.dragOver(aEvent);
+ aEvent.dropEffect = "link";
+ },
+ onDragExit: function (aEvent)
+ {
+ }
+}
+
+function openHomeDialog(aURL)
+{
+ var promptTitle = gNavigatorBundle.getString("droponhometitle");
+ var promptMsg = gNavigatorBundle.getString("droponhomemsg");
+ var pressedVal = Services.prompt.confirmEx(window, promptTitle, promptMsg,
+ Services.prompt.STD_YES_NO_BUTTONS,
+ null, null, null, null, {value:0});
+
+ if (pressedVal == 0) {
+ try {
+ var str = Components.classes["@mozilla.org/supports-string;1"]
+ .createInstance(Components.interfaces.nsISupportsString);
+ str.data = aURL;
+ gPrefService.setComplexValue("browser.startup.homepage",
+ Components.interfaces.nsISupportsString, str);
+ } catch (ex) {
+ dump("Failed to set the home page.\n"+ex+"\n");
+ }
+ }
+}
+
+var bookmarksButtonObserver = {
+ onDrop: function (aEvent)
+ {
+ let name = { };
+ let url = browserDragAndDrop.drop(aEvent, name);
+ try {
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "bookmark"
+ , uri: makeURI(url)
+ , title: name
+ , hiddenRows: [ "description"
+ , "location"
+ , "loadInSidebar"
+ , "keyword" ]
+ }, window);
+ } catch(ex) { }
+ },
+
+ onDragOver: function (aEvent)
+ {
+ browserDragAndDrop.dragOver(aEvent);
+ aEvent.dropEffect = "link";
+ },
+
+ onDragExit: function (aEvent)
+ {
+ }
+}
+
+var newTabButtonObserver = {
+ onDragOver: function (aEvent)
+ {
+ browserDragAndDrop.dragOver(aEvent);
+ },
+
+ onDragExit: function (aEvent)
+ {
+ },
+
+ onDrop: function (aEvent)
+ {
+ let url = browserDragAndDrop.drop(aEvent, { });
+ var postData = {};
+ url = getShortcutOrURI(url, postData);
+ if (url) {
+ // allow third-party services to fixup this URL
+ openNewTabWith(url, null, postData.value, aEvent, true);
+ }
+ }
+}
+
+var newWindowButtonObserver = {
+ onDragOver: function (aEvent)
+ {
+ browserDragAndDrop.dragOver(aEvent);
+ },
+ onDragExit: function (aEvent)
+ {
+ },
+ onDrop: function (aEvent)
+ {
+ let url = browserDragAndDrop.drop(aEvent, { });
+ var postData = {};
+ url = getShortcutOrURI(url, postData);
+ if (url) {
+ // allow third-party services to fixup this URL
+ openNewWindowWith(url, null, postData.value, true);
+ }
+ }
+}
+
+const DOMLinkHandler = {
+ handleEvent: function (event) {
+ switch (event.type) {
+ case "DOMLinkAdded":
+ this.onLinkAdded(event);
+ break;
+ }
+ },
+ getLinkIconURI: function(aLink) {
+ let targetDoc = aLink.ownerDocument;
+ var uri = makeURI(aLink.href, targetDoc.characterSet);
+
+ // Verify that the load of this icon is legal.
+ // Some error or special pages can load their favicon.
+ // To be on the safe side, only allow chrome:// favicons.
+ var isAllowedPage = [
+ /^about:neterror\?/,
+ /^about:blocked\?/,
+ /^about:certerror\?/,
+ /^about:home$/,
+ ].some(function (re) re.test(targetDoc.documentURI));
+
+ if (!isAllowedPage || !uri.schemeIs("chrome")) {
+ var ssm = Services.scriptSecurityManager;
+ try {
+ ssm.checkLoadURIWithPrincipal(targetDoc.nodePrincipal, uri,
+ Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
+ } catch(e) {
+ return null;
+ }
+ }
+
+ try {
+ var contentPolicy = Cc["@mozilla.org/layout/content-policy;1"].
+ getService(Ci.nsIContentPolicy);
+ } catch(e) {
+ return null; // Refuse to load if we can't do a security check.
+ }
+
+ // Security says okay, now ask content policy
+ if (contentPolicy.shouldLoad(Ci.nsIContentPolicy.TYPE_IMAGE,
+ uri, targetDoc.documentURIObject,
+ aLink, aLink.type, null)
+ != Ci.nsIContentPolicy.ACCEPT)
+ return null;
+
+ try {
+ uri.userPass = "";
+ } catch(e) {
+ // some URIs are immutable
+ }
+ return uri;
+ },
+ onLinkAdded: function (event) {
+ var link = event.originalTarget;
+ var rel = link.rel && link.rel.toLowerCase();
+ if (!link || !link.ownerDocument || !rel || !link.href)
+ return;
+
+ var feedAdded = false;
+ var iconAdded = false;
+ var searchAdded = false;
+ var rels = {};
+ for (let relString of rel.split(/\s+/))
+ rels[relString] = true;
+
+ for (let relVal in rels) {
+ switch (relVal) {
+ case "feed":
+ case "alternate":
+ if (!feedAdded) {
+ if (!rels.feed && rels.alternate && rels.stylesheet)
+ break;
+
+ if (isValidFeed(link, link.ownerDocument.nodePrincipal, rels.feed)) {
+ FeedHandler.addFeed(link, link.ownerDocument);
+ feedAdded = true;
+ }
+ }
+ break;
+ case "icon":
+ if (!iconAdded) {
+ if (!gPrefService.getBoolPref("browser.chrome.site_icons"))
+ break;
+
+ var uri = this.getLinkIconURI(link);
+ if (!uri)
+ break;
+
+ if (gBrowser.isFailedIcon(uri))
+ break;
+
+ var browserIndex = gBrowser.getBrowserIndexForDocument(link.ownerDocument);
+ // no browser? no favicon.
+ if (browserIndex == -1)
+ break;
+
+ let tab = gBrowser.tabs[browserIndex];
+ gBrowser.setIcon(tab, uri.spec);
+ iconAdded = true;
+ }
+ break;
+ case "search":
+ if (!searchAdded) {
+ var type = link.type && link.type.toLowerCase();
+ type = type.replace(/^\s+|\s*(?:;.*)?$/g, "");
+
+ if (type == "application/opensearchdescription+xml" && link.title &&
+ /^(?:https?|ftp):/i.test(link.href) &&
+ !PrivateBrowsingUtils.isWindowPrivate(window)) {
+ var engine = { title: link.title, href: link.href };
+ BrowserSearch.addEngine(engine, link.ownerDocument);
+ searchAdded = true;
+ }
+ }
+ break;
+ }
+ }
+ }
+}
+
+const BrowserSearch = {
+ addEngine: function(engine, targetDoc) {
+ if (!this.searchBar)
+ return;
+
+ var browser = gBrowser.getBrowserForDocument(targetDoc);
+ // ignore search engines from subframes (see bug 479408)
+ if (!browser)
+ return;
+
+ // Check to see whether we've already added an engine with this title
+ if (browser.engines) {
+ if (browser.engines.some(function (e) e.title == engine.title))
+ return;
+ }
+
+ // Append the URI and an appropriate title to the browser data.
+ // Use documentURIObject in the check for shouldLoadFavIcon so that we
+ // do the right thing with about:-style error pages. Bug 453442
+ var iconURL = null;
+ if (gBrowser.shouldLoadFavIcon(targetDoc.documentURIObject))
+ iconURL = targetDoc.documentURIObject.prePath + "/favicon.ico";
+
+ var hidden = false;
+ // If this engine (identified by title) is already in the list, add it
+ // to the list of hidden engines rather than to the main list.
+ // XXX This will need to be changed when engines are identified by URL;
+ // see bug 335102.
+ if (Services.search.getEngineByName(engine.title))
+ hidden = true;
+
+ var engines = (hidden ? browser.hiddenEngines : browser.engines) || [];
+
+ engines.push({ uri: engine.href,
+ title: engine.title,
+ icon: iconURL });
+
+ if (hidden)
+ browser.hiddenEngines = engines;
+ else
+ browser.engines = engines;
+ },
+
+ /**
+ * Gives focus to the search bar, if it is present on the toolbar, or loads
+ * the default engine's search form otherwise. For Mac, opens a new window
+ * or focuses an existing window, if necessary.
+ */
+ webSearch: function BrowserSearch_webSearch() {
+#ifdef XP_MACOSX
+ if (window.location.href != getBrowserURL()) {
+ var win = getTopWin();
+ if (win) {
+ // If there's an open browser window, it should handle this command
+ win.focus();
+ win.BrowserSearch.webSearch();
+ } else {
+ // If there are no open browser windows, open a new one
+ var observer = function observer(subject, topic, data) {
+ if (subject == win) {
+ BrowserSearch.webSearch();
+ Services.obs.removeObserver(observer, "browser-delayed-startup-finished");
+ }
+ }
+ win = window.openDialog(getBrowserURL(), "_blank",
+ "chrome,all,dialog=no", "about:blank");
+ Services.obs.addObserver(observer, "browser-delayed-startup-finished", false);
+ }
+ return;
+ }
+#endif
+ var searchBar = this.searchBar;
+ if (searchBar && window.fullScreen)
+ FullScreen.mouseoverToggle(true);
+ if (searchBar)
+ searchBar.select();
+ if (!searchBar || document.activeElement != searchBar.textbox.inputField)
+ openUILinkIn(Services.search.defaultEngine.searchForm, "current");
+ },
+
+ /**
+ * Loads a search results page, given a set of search terms. Uses the current
+ * engine if the search bar is visible, or the default engine otherwise.
+ *
+ * @param searchText
+ * The search terms to use for the search.
+ *
+ * @param useNewTab
+ * Boolean indicating whether or not the search should load in a new
+ * tab.
+ *
+ * @param purpose [optional]
+ * A string meant to indicate the context of the search request. This
+ * allows the search service to provide a different nsISearchSubmission
+ * depending on e.g. where the search is triggered in the UI.
+ *
+ * @return string Name of the search engine used to perform a search or null
+ * if a search was not performed.
+ */
+ loadSearch: function BrowserSearch_search(searchText, useNewTab, purpose) {
+ var engine;
+
+ // If the search bar is visible, use the current engine, otherwise, fall
+ // back to the default engine.
+ if (isElementVisible(this.searchBar))
+ engine = Services.search.currentEngine;
+ else
+ engine = Services.search.defaultEngine;
+
+ var submission = engine.getSubmission(searchText, null, purpose); // HTML response
+
+ // getSubmission can return null if the engine doesn't have a URL
+ // with a text/html response type. This is unlikely (since
+ // SearchService._addEngineToStore() should fail for such an engine),
+ // but let's be on the safe side.
+ if (!submission) {
+ return null;
+ }
+
+ let inBackground = Services.prefs.getBoolPref("browser.search.context.loadInBackground");
+ openLinkIn(submission.uri.spec,
+ useNewTab ? "tab" : "current",
+ { postData: submission.postData,
+ inBackground: inBackground,
+ relatedToCurrent: true });
+
+ return engine.name;
+ },
+
+ /**
+ * Perform a search initiated from the context menu.
+ *
+ * This should only be called from the context menu. See
+ * BrowserSearch.loadSearch for the preferred API.
+ */
+ loadSearchFromContext: function (terms) {
+ let engine = BrowserSearch.loadSearch(terms, true, "contextmenu");
+ if (engine) {
+ BrowserSearch.recordSearchInHealthReport(engine, "contextmenu");
+ }
+ },
+
+ /**
+ * Returns the search bar element if it is present in the toolbar, null otherwise.
+ */
+ get searchBar() {
+ return document.getElementById("searchbar");
+ },
+
+ loadAddEngines: function BrowserSearch_loadAddEngines() {
+ var newWindowPref = gPrefService.getIntPref("browser.link.open_newwindow");
+ var where = newWindowPref == 3 ? "tab" : "window";
+ var searchEnginesURL = formatURL("browser.search.searchEnginesURL", true);
+ openUILinkIn(searchEnginesURL, where);
+ },
+
+ /**
+ * Helper to record a search with Firefox Health Report.
+ *
+ * FHR records only search counts and nothing pertaining to the search itself.
+ *
+ * @param engine
+ * (string) The name of the engine used to perform the search. This
+ * is typically nsISearchEngine.name.
+ * @param source
+ * (string) Where the search originated from. See the FHR
+ * SearchesProvider for allowed values.
+ */
+ recordSearchInHealthReport: function (engine, source) {
+#ifdef MOZ_SERVICES_HEALTHREPORT
+ let reporter = Cc["@mozilla.org/datareporting/service;1"]
+ .getService()
+ .wrappedJSObject
+ .healthReporter;
+
+ // This can happen if the FHR component of the data reporting service is
+ // disabled. This is controlled by a pref that most will never use.
+ if (!reporter) {
+ return;
+ }
+
+ reporter.onInit().then(function record() {
+ try {
+ reporter.getProvider("org.mozilla.searches").recordSearch(engine, source);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ });
+#endif
+ },
+};
+
+function FillHistoryMenu(aParent) {
+ // Lazily add the hover listeners on first showing and never remove them
+ if (!aParent.hasStatusListener) {
+ // Show history item's uri in the status bar when hovering, and clear on exit
+ aParent.addEventListener("DOMMenuItemActive", function(aEvent) {
+ // Only the current page should have the checked attribute, so skip it
+ if (!aEvent.target.hasAttribute("checked"))
+ XULBrowserWindow.setOverLink(aEvent.target.getAttribute("uri"));
+ }, false);
+ aParent.addEventListener("DOMMenuItemInactive", function() {
+ XULBrowserWindow.setOverLink("");
+ }, false);
+
+ aParent.hasStatusListener = true;
+ }
+
+ // Remove old entries if any
+ var children = aParent.childNodes;
+ for (var i = children.length - 1; i >= 0; --i) {
+ if (children[i].hasAttribute("index"))
+ aParent.removeChild(children[i]);
+ }
+
+ var webNav = gBrowser.webNavigation;
+ var sessionHistory = webNav.sessionHistory;
+
+ var count = sessionHistory.count;
+ if (count <= 1) // don't display the popup for a single item
+ return false;
+
+ const MAX_HISTORY_MENU_ITEMS = 15;
+ var index = sessionHistory.index;
+ var half_length = Math.floor(MAX_HISTORY_MENU_ITEMS / 2);
+ var start = Math.max(index - half_length, 0);
+ var end = Math.min(start == 0 ? MAX_HISTORY_MENU_ITEMS : index + half_length + 1, count);
+ if (end == count)
+ start = Math.max(count - MAX_HISTORY_MENU_ITEMS, 0);
+
+ var tooltipBack = gNavigatorBundle.getString("tabHistory.goBack");
+ var tooltipCurrent = gNavigatorBundle.getString("tabHistory.current");
+ var tooltipForward = gNavigatorBundle.getString("tabHistory.goForward");
+
+ for (var j = end - 1; j >= start; j--) {
+ let item = document.createElement("menuitem");
+ let entry = sessionHistory.getEntryAtIndex(j, false);
+ let uri = entry.URI.spec;
+
+ item.setAttribute("uri", uri);
+ item.setAttribute("label", entry.title || uri);
+ item.setAttribute("index", j);
+
+ if (j != index) {
+ PlacesUtils.favicons.getFaviconURLForPage(entry.URI, function (aURI) {
+ if (aURI) {
+ let iconURL = PlacesUtils.favicons.getFaviconLinkForIcon(aURI).spec;
+ item.style.listStyleImage = "url(" + iconURL + ")";
+ }
+ });
+ }
+
+ if (j < index) {
+ item.className = "unified-nav-back menuitem-iconic menuitem-with-favicon";
+ item.setAttribute("tooltiptext", tooltipBack);
+ } else if (j == index) {
+ item.setAttribute("type", "radio");
+ item.setAttribute("checked", "true");
+ item.className = "unified-nav-current";
+ item.setAttribute("tooltiptext", tooltipCurrent);
+ } else {
+ item.className = "unified-nav-forward menuitem-iconic menuitem-with-favicon";
+ item.setAttribute("tooltiptext", tooltipForward);
+ }
+
+ aParent.appendChild(item);
+ }
+ return true;
+}
+
+function addToUrlbarHistory(aUrlToAdd) {
+ if (!PrivateBrowsingUtils.isWindowPrivate(window) &&
+ aUrlToAdd &&
+ !aUrlToAdd.contains(" ") &&
+ !/[\x00-\x1F]/.test(aUrlToAdd))
+ PlacesUIUtils.markPageAsTyped(aUrlToAdd);
+}
+
+function toJavaScriptConsole()
+{
+ toOpenWindowByType("global:console", "chrome://global/content/console.xul");
+}
+
+function BrowserDownloadsUI()
+{
+ Cc["@mozilla.org/download-manager-ui;1"].
+ getService(Ci.nsIDownloadManagerUI).show(window);
+}
+
+function toOpenWindowByType(inType, uri, features)
+{
+ var topWindow = Services.wm.getMostRecentWindow(inType);
+
+ if (topWindow)
+ topWindow.focus();
+ else if (features)
+ window.open(uri, "_blank", features);
+ else
+ window.open(uri, "_blank", "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar");
+}
+
+function OpenBrowserWindow(options)
+{
+ var telemetryObj = {};
+ TelemetryStopwatch.start("FX_NEW_WINDOW_MS", telemetryObj);
+
+ function newDocumentShown(doc, topic, data) {
+ if (topic == "document-shown" &&
+ doc != document &&
+ doc.defaultView == win) {
+ Services.obs.removeObserver(newDocumentShown, "document-shown");
+ TelemetryStopwatch.finish("FX_NEW_WINDOW_MS", telemetryObj);
+ }
+ };
+ Services.obs.addObserver(newDocumentShown, "document-shown", false);
+
+ var charsetArg = new String();
+ var handler = Components.classes["@mozilla.org/browser/clh;1"]
+ .getService(Components.interfaces.nsIBrowserHandler);
+ var defaultArgs = handler.defaultArgs;
+ var wintype = document.documentElement.getAttribute('windowtype');
+
+ var extraFeatures = "";
+ if (options && options.private) {
+ extraFeatures = ",private";
+ if (!PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ // Force the new window to load about:privatebrowsing instead of the default home page
+ defaultArgs = "about:privatebrowsing";
+ }
+ } else {
+ extraFeatures = ",non-private";
+ }
+
+ // if and only if the current window is a browser window and it has a document with a character
+ // set, then extract the current charset menu setting from the current document and use it to
+ // initialize the new browser window...
+ var win;
+ if (window && (wintype == "navigator:browser") && window.content && window.content.document)
+ {
+ var DocCharset = window.content.document.characterSet;
+ charsetArg = "charset="+DocCharset;
+
+ //we should "inherit" the charset menu setting in a new window
+ win = window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no" + extraFeatures, defaultArgs, charsetArg);
+ }
+ else // forget about the charset information.
+ {
+ win = window.openDialog("chrome://browser/content/", "_blank", "chrome,all,dialog=no" + extraFeatures, defaultArgs);
+ }
+
+ return win;
+}
+
+var gCustomizeSheet = false;
+function BrowserCustomizeToolbar() {
+ // Disable the toolbar context menu items
+ var menubar = document.getElementById("main-menubar");
+ for (let childNode of menubar.childNodes)
+ childNode.setAttribute("disabled", true);
+
+ var cmd = document.getElementById("cmd_CustomizeToolbars");
+ cmd.setAttribute("disabled", "true");
+
+ var splitter = document.getElementById("urlbar-search-splitter");
+ if (splitter)
+ splitter.parentNode.removeChild(splitter);
+
+ CombinedStopReload.uninit();
+
+ PlacesToolbarHelper.customizeStart();
+ BookmarkingUI.customizeStart();
+ DownloadsButton.customizeStart();
+
+ TabsInTitlebar.allowedBy("customizing-toolbars", false);
+
+ var customizeURL = "chrome://global/content/customizeToolbar.xul";
+ gCustomizeSheet = getBoolPref("toolbar.customization.usesheet", false);
+
+ if (gCustomizeSheet) {
+ let sheetFrame = document.createElement("iframe");
+ let panel = document.getElementById("customizeToolbarSheetPopup");
+ sheetFrame.id = "customizeToolbarSheetIFrame";
+ sheetFrame.toolbox = gNavToolbox;
+ sheetFrame.panel = panel;
+ sheetFrame.setAttribute("style", panel.getAttribute("sheetstyle"));
+ panel.appendChild(sheetFrame);
+
+ // Open the panel, but make it invisible until the iframe has loaded so
+ // that the user doesn't see a white flash.
+ panel.style.visibility = "hidden";
+ gNavToolbox.addEventListener("beforecustomization", function onBeforeCustomization() {
+ gNavToolbox.removeEventListener("beforecustomization", onBeforeCustomization, false);
+ panel.style.removeProperty("visibility");
+ }, false);
+
+ sheetFrame.setAttribute("src", customizeURL);
+
+ panel.openPopup(gNavToolbox, "after_start", 0, 0);
+ } else {
+ window.openDialog(customizeURL,
+ "CustomizeToolbar",
+ "chrome,titlebar,toolbar,location,resizable,dependent",
+ gNavToolbox);
+ }
+}
+
+function BrowserToolboxCustomizeDone(aToolboxChanged) {
+ if (gCustomizeSheet) {
+ document.getElementById("customizeToolbarSheetPopup").hidePopup();
+ let iframe = document.getElementById("customizeToolbarSheetIFrame");
+ iframe.parentNode.removeChild(iframe);
+ }
+
+ // Update global UI elements that may have been added or removed
+ if (aToolboxChanged) {
+ gURLBar = document.getElementById("urlbar");
+
+ gProxyFavIcon = document.getElementById("page-proxy-favicon");
+ gHomeButton.updateTooltip();
+ gIdentityHandler._cacheElements();
+ window.XULBrowserWindow.init();
+
+#ifndef XP_MACOSX
+ updateEditUIVisibility();
+#endif
+
+ // Hacky: update the PopupNotifications' object's reference to the iconBox,
+ // if it already exists, since it may have changed if the URL bar was
+ // added/removed.
+ if (!window.__lookupGetter__("PopupNotifications"))
+ PopupNotifications.iconBox = document.getElementById("notification-popup-box");
+ }
+
+ PlacesToolbarHelper.customizeDone();
+ BookmarkingUI.customizeDone();
+ DownloadsButton.customizeDone();
+
+ // The url bar splitter state is dependent on whether stop/reload
+ // and the location bar are combined, so we need this ordering
+ CombinedStopReload.init();
+ UpdateUrlbarSearchSplitterState();
+ setUrlAndSearchBarWidthForConditionalForwardButton();
+
+ // Update the urlbar
+ if (gURLBar) {
+ URLBarSetURI();
+ XULBrowserWindow.asyncUpdateUI();
+ BookmarkingUI.updateStarState();
+ SocialMark.updateMarkState();
+ SocialShare.update();
+ }
+
+ TabsInTitlebar.allowedBy("customizing-toolbars", true);
+
+ // Re-enable parts of the UI we disabled during the dialog
+ var menubar = document.getElementById("main-menubar");
+ for (let childNode of menubar.childNodes)
+ childNode.setAttribute("disabled", false);
+ var cmd = document.getElementById("cmd_CustomizeToolbars");
+ cmd.removeAttribute("disabled");
+
+ // make sure to re-enable click-and-hold
+ if (!getBoolPref("ui.click_hold_context_menus", false))
+ SetClickAndHoldHandlers();
+
+ gBrowser.selectedBrowser.focus();
+}
+
+function BrowserToolboxCustomizeChange(aType) {
+ switch (aType) {
+ case "iconsize":
+ case "mode":
+ retrieveToolbarIconsizesFromTheme();
+ break;
+ default:
+ gHomeButton.updatePersonalToolbarStyle();
+ BookmarkingUI.customizeChange();
+ allTabs.readPref();
+ }
+}
+
+/**
+ * Allows themes to override the "iconsize" attribute on toolbars.
+ */
+function retrieveToolbarIconsizesFromTheme() {
+ function retrieveToolbarIconsize(aToolbar) {
+ if (aToolbar.localName != "toolbar")
+ return;
+
+ // The theme indicates that it wants to override the "iconsize" attribute
+ // by specifying a special value for the "counter-reset" property on the
+ // toolbar. A custom property cannot be used because getComputedStyle can
+ // only return the values of standard CSS properties.
+ let counterReset = getComputedStyle(aToolbar).counterReset;
+ if (counterReset == "smallicons 0")
+ aToolbar.setAttribute("iconsize", "small");
+ else if (counterReset == "largeicons 0")
+ aToolbar.setAttribute("iconsize", "large");
+ }
+
+ Array.forEach(gNavToolbox.childNodes, retrieveToolbarIconsize);
+ gNavToolbox.externalToolbars.forEach(retrieveToolbarIconsize);
+}
+
+/**
+ * Update the global flag that tracks whether or not any edit UI (the Edit menu,
+ * edit-related items in the context menu, and edit-related toolbar buttons
+ * is visible, then update the edit commands' enabled state accordingly. We use
+ * this flag to skip updating the edit commands on focus or selection changes
+ * when no UI is visible to improve performance (including pageload performance,
+ * since focus changes when you load a new page).
+ *
+ * If UI is visible, we use goUpdateGlobalEditMenuItems to set the commands'
+ * enabled state so the UI will reflect it appropriately.
+ *
+ * If the UI isn't visible, we enable all edit commands so keyboard shortcuts
+ * still work and just lazily disable them as needed when the user presses a
+ * shortcut.
+ *
+ * This doesn't work on Mac, since Mac menus flash when users press their
+ * keyboard shortcuts, so edit UI is essentially always visible on the Mac,
+ * and we need to always update the edit commands. Thus on Mac this function
+ * is a no op.
+ */
+function updateEditUIVisibility()
+{
+#ifndef XP_MACOSX
+ let editMenuPopupState = document.getElementById("menu_EditPopup").state;
+ let contextMenuPopupState = document.getElementById("contentAreaContextMenu").state;
+ let placesContextMenuPopupState = document.getElementById("placesContext").state;
+#ifdef MENUBAR_CAN_AUTOHIDE
+ let appMenuPopupState = document.getElementById("appmenu-popup").state;
+#endif
+
+ // The UI is visible if the Edit menu is opening or open, if the context menu
+ // is open, or if the toolbar has been customized to include the Cut, Copy,
+ // or Paste toolbar buttons.
+ gEditUIVisible = editMenuPopupState == "showing" ||
+ editMenuPopupState == "open" ||
+ contextMenuPopupState == "showing" ||
+ contextMenuPopupState == "open" ||
+ placesContextMenuPopupState == "showing" ||
+ placesContextMenuPopupState == "open" ||
+#ifdef MENUBAR_CAN_AUTOHIDE
+ appMenuPopupState == "showing" ||
+ appMenuPopupState == "open" ||
+#endif
+ document.getElementById("cut-button") ||
+ document.getElementById("copy-button") ||
+ document.getElementById("paste-button") ? true : false;
+
+ // If UI is visible, update the edit commands' enabled state to reflect
+ // whether or not they are actually enabled for the current focus/selection.
+ if (gEditUIVisible)
+ goUpdateGlobalEditMenuItems();
+
+ // Otherwise, enable all commands, so that keyboard shortcuts still work,
+ // then lazily determine their actual enabled state when the user presses
+ // a keyboard shortcut.
+ else {
+ goSetCommandEnabled("cmd_undo", true);
+ goSetCommandEnabled("cmd_redo", true);
+ goSetCommandEnabled("cmd_cut", true);
+ goSetCommandEnabled("cmd_copy", true);
+ goSetCommandEnabled("cmd_paste", true);
+ goSetCommandEnabled("cmd_selectAll", true);
+ goSetCommandEnabled("cmd_delete", true);
+ goSetCommandEnabled("cmd_switchTextDirection", true);
+ }
+#endif
+}
+
+/**
+ * Makes the Character Encoding menu enabled or disabled as appropriate.
+ * To be called when the View menu or the app menu is opened.
+ */
+function updateCharacterEncodingMenuState()
+{
+ let charsetMenu = document.getElementById("charsetMenu");
+ let appCharsetMenu = document.getElementById("appmenu_charsetMenu");
+ let appDevCharsetMenu =
+ document.getElementById("appmenu_developer_charsetMenu");
+ // gBrowser is null on Mac when the menubar shows in the context of
+ // non-browser windows. The above elements may be null depending on
+ // what parts of the menubar are present. E.g. no app menu on Mac.
+ if (gBrowser &&
+ gBrowser.docShell &&
+ gBrowser.docShell.mayEnableCharacterEncodingMenu) {
+ if (charsetMenu) {
+ charsetMenu.removeAttribute("disabled");
+ }
+ if (appCharsetMenu) {
+ appCharsetMenu.removeAttribute("disabled");
+ }
+ if (appDevCharsetMenu) {
+ appDevCharsetMenu.removeAttribute("disabled");
+ }
+ } else {
+ if (charsetMenu) {
+ charsetMenu.setAttribute("disabled", "true");
+ }
+ if (appCharsetMenu) {
+ appCharsetMenu.setAttribute("disabled", "true");
+ }
+ if (appDevCharsetMenu) {
+ appDevCharsetMenu.setAttribute("disabled", "true");
+ }
+ }
+}
+
+/**
+ * Returns true if |aMimeType| is text-based, false otherwise.
+ *
+ * @param aMimeType
+ * The MIME type to check.
+ *
+ * If adding types to this function, please also check the similar
+ * function in findbar.xml
+ */
+function mimeTypeIsTextBased(aMimeType)
+{
+ return aMimeType.startsWith("text/") ||
+ aMimeType.endsWith("+xml") ||
+ aMimeType == "application/x-javascript" ||
+ aMimeType == "application/javascript" ||
+ aMimeType == "application/json" ||
+ aMimeType == "application/xml" ||
+ aMimeType == "mozilla.application/cached-xul";
+}
+
+var XULBrowserWindow = {
+ // Stored Status, Link and Loading values
+ status: "",
+ defaultStatus: "",
+ overLink: "",
+ startTime: 0,
+ statusText: "",
+ isBusy: false,
+/* Pale Moon: Don't hide navigation controls and toolbars for "special" pages. SBaD, M!
+ inContentWhitelist: ["about:addons", "about:downloads", "about:permissions",
+ "about:sync-progress", "about:preferences"],*/
+ inContentWhitelist: [],
+
+ QueryInterface: function (aIID) {
+ if (aIID.equals(Ci.nsIWebProgressListener) ||
+ aIID.equals(Ci.nsIWebProgressListener2) ||
+ aIID.equals(Ci.nsISupportsWeakReference) ||
+ aIID.equals(Ci.nsIXULBrowserWindow) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_NOINTERFACE;
+ },
+
+ get stopCommand () {
+ delete this.stopCommand;
+ return this.stopCommand = document.getElementById("Browser:Stop");
+ },
+ get reloadCommand () {
+ delete this.reloadCommand;
+ return this.reloadCommand = document.getElementById("Browser:Reload");
+ },
+ get statusTextField () {
+ delete this.statusTextField;
+ return this.statusTextField = document.getElementById("statusbar-display");
+ },
+ get isImage () {
+ delete this.isImage;
+ return this.isImage = document.getElementById("isImage");
+ },
+
+ init: function () {
+ this.throbberElement = document.getElementById("navigator-throbber");
+
+ // Bug 666809 - SecurityUI support for e10s
+ if (gMultiProcessBrowser)
+ return;
+
+ // Initialize the security button's state and tooltip text. Remember to reset
+ // _hostChanged, otherwise onSecurityChange will short circuit.
+ var securityUI = gBrowser.securityUI;
+ this._hostChanged = true;
+ this.onSecurityChange(null, null, securityUI.state);
+ },
+
+ destroy: function () {
+ // XXXjag to avoid leaks :-/, see bug 60729
+ delete this.throbberElement;
+ delete this.stopCommand;
+ delete this.reloadCommand;
+ delete this.statusTextField;
+ delete this.statusText;
+ },
+
+ setJSStatus: function () {
+ // unsupported
+ },
+
+ setDefaultStatus: function (status) {
+ this.defaultStatus = status;
+ this.updateStatusField();
+ },
+
+ setOverLink: function (url, anchorElt) {
+ // Encode bidirectional formatting characters.
+ // (RFC 3987 sections 3.2 and 4.1 paragraph 6)
+ url = url.replace(/[\u200e\u200f\u202a\u202b\u202c\u202d\u202e]/g,
+ encodeURIComponent);
+
+ if (gURLBar && gURLBar._mayTrimURLs /* corresponds to browser.urlbar.trimURLs */)
+ url = trimURL(url);
+
+ this.overLink = url;
+ LinkTargetDisplay.update();
+ },
+
+ updateStatusField: function () {
+ var text, type, types = ["overLink"];
+ if (this._busyUI)
+ types.push("status");
+ types.push("defaultStatus");
+ for (type of types) {
+ text = this[type];
+ if (text)
+ break;
+ }
+
+ // check the current value so we don't trigger an attribute change
+ // and cause needless (slow!) UI updates
+ if (this.statusText != text) {
+ let field = this.statusTextField;
+ field.setAttribute("previoustype", field.getAttribute("type"));
+ field.setAttribute("type", type);
+ field.label = text;
+ field.setAttribute("crop", type == "overLink" ? "center" : "end");
+ this.statusText = text;
+ }
+ },
+
+ // Called before links are navigated to to allow us to retarget them if needed.
+ onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {
+ let target = this._onBeforeLinkTraversal(originalTarget, linkURI, linkNode, isAppTab);
+ SocialUI.closeSocialPanelForLinkTraversal(target, linkNode);
+ return target;
+ },
+
+ _onBeforeLinkTraversal: function(originalTarget, linkURI, linkNode, isAppTab) {
+ // Don't modify non-default targets or targets that aren't in top-level app
+ // tab docshells (isAppTab will be false for app tab subframes).
+ if (originalTarget != "" || !isAppTab)
+ return originalTarget;
+
+ // External links from within app tabs should always open in new tabs
+ // instead of replacing the app tab's page (Bug 575561)
+ let linkHost;
+ let docHost;
+ try {
+ linkHost = linkURI.host;
+ docHost = linkNode.ownerDocument.documentURIObject.host;
+ } catch(e) {
+ // nsIURI.host can throw for non-nsStandardURL nsIURIs.
+ // If we fail to get either host, just return originalTarget.
+ return originalTarget;
+ }
+
+ if (docHost == linkHost)
+ return originalTarget;
+
+ // Special case: ignore "www" prefix if it is part of host string
+ let [longHost, shortHost] =
+ linkHost.length > docHost.length ? [linkHost, docHost] : [docHost, linkHost];
+ if (longHost == "www." + shortHost)
+ return originalTarget;
+
+ return "_blank";
+ },
+
+ onLinkIconAvailable: function (aIconURL) {
+ if (gProxyFavIcon && gBrowser.userTypedValue === null) {
+ PageProxySetIcon(aIconURL); // update the favicon in the URL bar
+ }
+ },
+
+ onProgressChange: function (aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress) {
+ // Do nothing.
+ },
+
+ onProgressChange64: function (aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress) {
+ return this.onProgressChange(aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress,
+ aMaxTotalProgress);
+ },
+
+ // This function fires only for the currently selected tab.
+ onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) {
+ const nsIWebProgressListener = Ci.nsIWebProgressListener;
+ const nsIChannel = Ci.nsIChannel;
+
+ if (aStateFlags & nsIWebProgressListener.STATE_START &&
+ aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
+
+ if (aRequest && aWebProgress.isTopLevel) {
+ // clear out feed data
+ gBrowser.selectedBrowser.feeds = null;
+
+ // clear out search-engine data
+ gBrowser.selectedBrowser.engines = null;
+ }
+
+ this.isBusy = true;
+
+ if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
+ this._busyUI = true;
+
+ // Turn the throbber on.
+ if (this.throbberElement)
+ this.throbberElement.setAttribute("busy", "true");
+
+ // XXX: This needs to be based on window activity...
+ this.stopCommand.removeAttribute("disabled");
+ CombinedStopReload.switchToStop();
+ }
+ }
+ else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
+ // This (thanks to the filter) is a network stop or the last
+ // request stop outside of loading the document, stop throbbers
+ // and progress bars and such
+ if (aRequest) {
+ let msg = "";
+ let location;
+ // Get the URI either from a channel or a pseudo-object
+ if (aRequest instanceof nsIChannel || "URI" in aRequest) {
+ location = aRequest.URI;
+
+ // For keyword URIs clear the user typed value since they will be changed into real URIs
+ if (location.scheme == "keyword" && aWebProgress.isTopLevel)
+ gBrowser.userTypedValue = null;
+
+ if (location.spec != "about:blank") {
+ switch (aStatus) {
+ case Components.results.NS_ERROR_NET_TIMEOUT:
+ msg = gNavigatorBundle.getString("nv_timeout");
+ break;
+ }
+ }
+ }
+
+ this.status = "";
+ this.setDefaultStatus(msg);
+
+ // Disable menu entries for images, enable otherwise
+ if (!gMultiProcessBrowser && content.document && mimeTypeIsTextBased(content.document.contentType))
+ this.isImage.removeAttribute('disabled');
+ else
+ this.isImage.setAttribute('disabled', 'true');
+ }
+
+ this.isBusy = false;
+
+ if (this._busyUI) {
+ this._busyUI = false;
+
+ // Turn the throbber off.
+ if (this.throbberElement)
+ this.throbberElement.removeAttribute("busy");
+
+ this.stopCommand.setAttribute("disabled", "true");
+ CombinedStopReload.switchToReload(aRequest instanceof Ci.nsIRequest);
+ }
+ }
+ },
+
+ onLocationChange: function (aWebProgress, aRequest, aLocationURI, aFlags) {
+ var location = aLocationURI ? aLocationURI.spec : "";
+ this._hostChanged = true;
+
+ // Hide the form invalid popup.
+ if (gFormSubmitObserver.panel) {
+ gFormSubmitObserver.panel.hidePopup();
+ }
+
+ let pageTooltip = document.getElementById("aHTMLTooltip");
+ let tooltipNode = pageTooltip.triggerNode;
+ if (tooltipNode) {
+ // Optimise for the common case
+ if (aWebProgress.isTopLevel) {
+ pageTooltip.hidePopup();
+ }
+ else {
+ for (let tooltipWindow = tooltipNode.ownerDocument.defaultView;
+ tooltipWindow != tooltipWindow.parent;
+ tooltipWindow = tooltipWindow.parent) {
+ if (tooltipWindow == aWebProgress.DOMWindow) {
+ pageTooltip.hidePopup();
+ break;
+ }
+ }
+ }
+ }
+
+ // Disable menu entries for images, enable otherwise
+ if (!gMultiProcessBrowser && content.document && mimeTypeIsTextBased(content.document.contentType))
+ this.isImage.removeAttribute('disabled');
+ else
+ this.isImage.setAttribute('disabled', 'true');
+
+ this.hideOverLinkImmediately = true;
+ this.setOverLink("", null);
+ this.hideOverLinkImmediately = false;
+
+ // We should probably not do this if the value has changed since the user
+ // searched
+ // Update urlbar only if a new page was loaded on the primary content area
+ // Do not update urlbar if there was a subframe navigation
+
+ var browser = gBrowser.selectedBrowser;
+ if (aWebProgress.isTopLevel) {
+ if ((location == "about:blank" && (gMultiProcessBrowser || !content.opener)) ||
+ location == "") { // Second condition is for new tabs, otherwise
+ // reload function is enabled until tab is refreshed.
+ this.reloadCommand.setAttribute("disabled", "true");
+ } else {
+ this.reloadCommand.removeAttribute("disabled");
+ }
+
+ if (gURLBar) {
+ URLBarSetURI(aLocationURI);
+
+ // Update starring UI
+ BookmarkingUI.updateStarState();
+ SocialMark.updateMarkState();
+ SocialShare.update();
+ }
+
+ // Show or hide browser chrome based on the whitelist
+ if (this.hideChromeForLocation(location)) {
+ document.documentElement.setAttribute("disablechrome", "true");
+ } else {
+ let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
+ if (ss.getTabValue(gBrowser.selectedTab, "appOrigin"))
+ document.documentElement.setAttribute("disablechrome", "true");
+ else
+ document.documentElement.removeAttribute("disablechrome");
+ }
+
+ // Utility functions for disabling find
+ var shouldDisableFind = function shouldDisableFind(aDocument) {
+ let docElt = aDocument.documentElement;
+ return docElt && docElt.getAttribute("disablefastfind") == "true";
+ }
+
+ var disableFindCommands = function disableFindCommands(aDisable) {
+ let findCommands = [document.getElementById("cmd_find"),
+ document.getElementById("cmd_findAgain"),
+ document.getElementById("cmd_findPrevious")];
+ for (let elt of findCommands) {
+ if (aDisable)
+ elt.setAttribute("disabled", "true");
+ else
+ elt.removeAttribute("disabled");
+ }
+ if (gFindBarInitialized) {
+ if (!gFindBar.hidden && aDisable) {
+ gFindBar.hidden = true;
+ this._findbarTemporarilyHidden = true;
+ } else if (this._findbarTemporarilyHidden && !aDisable) {
+ gFindBar.hidden = false;
+ this._findbarTemporarilyHidden = false;
+ }
+ }
+ }.bind(this);
+
+ var onContentRSChange = function onContentRSChange(e) {
+ if (e.target.readyState != "interactive" && e.target.readyState != "complete")
+ return;
+
+ e.target.removeEventListener("readystatechange", onContentRSChange);
+ disableFindCommands(shouldDisableFind(e.target));
+ }
+
+ // Disable find commands in documents that ask for them to be disabled.
+ if (!gMultiProcessBrowser && aLocationURI &&
+ (aLocationURI.schemeIs("about") || aLocationURI.schemeIs("chrome"))) {
+ // Don't need to re-enable/disable find commands for same-document location changes
+ // (e.g. the replaceStates in about:addons)
+ if (!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) {
+ if (content.document.readyState == "interactive" || content.document.readyState == "complete")
+ disableFindCommands(shouldDisableFind(content.document));
+ else {
+ content.document.addEventListener("readystatechange", onContentRSChange);
+ }
+ }
+ } else
+ disableFindCommands(false);
+
+ if (gFindBarInitialized) {
+ if (gFindBar.findMode != gFindBar.FIND_NORMAL) {
+ // Close the Find toolbar if we're in old-style TAF mode
+ gFindBar.close();
+ }
+
+ // fix bug 253793 - turn off highlight when page changes
+ gFindBar.getElement("highlight").checked = false;
+ }
+ }
+ UpdateBackForwardCommands(gBrowser.webNavigation);
+
+ gGestureSupport.restoreRotationState();
+
+ // See bug 358202, when tabs are switched during a drag operation,
+ // timers don't fire on windows (bug 203573)
+ if (aRequest)
+ setTimeout(function () { XULBrowserWindow.asyncUpdateUI(); }, 0);
+ else
+ this.asyncUpdateUI();
+ },
+
+ asyncUpdateUI: function () {
+ FeedHandler.updateFeeds();
+ },
+
+ hideChromeForLocation: function(aLocation) {
+ aLocation = aLocation.toLowerCase();
+ return this.inContentWhitelist.some(function(aSpec) {
+ return aSpec == aLocation;
+ });
+ },
+
+ onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) {
+ this.status = aMessage;
+ this.updateStatusField();
+ },
+
+ // Properties used to cache security state used to update the UI
+ _state: null,
+ _hostChanged: false, // onLocationChange will flip this bit
+
+ onSecurityChange: function (aWebProgress, aRequest, aState) {
+ // Don't need to do anything if the data we use to update the UI hasn't
+ // changed
+ if (this._state == aState &&
+ !this._hostChanged) {
+#ifdef DEBUG
+ try {
+ var contentHost = gBrowser.contentWindow.location.host;
+ if (this._host !== undefined && this._host != contentHost) {
+ Components.utils.reportError(
+ "ASSERTION: browser.js host is inconsistent. Content window has " +
+ "<" + contentHost + "> but cached host is <" + this._host + ">.\n"
+ );
+ }
+ } catch (ex) {}
+#endif
+ return;
+ }
+ this._state = aState;
+
+#ifdef DEBUG
+ try {
+ this._host = gBrowser.contentWindow.location.host;
+ } catch(ex) {
+ this._host = null;
+ }
+#endif
+
+ this._hostChanged = false;
+
+ // aState is defined as a bitmask that may be extended in the future.
+ // We filter out any unknown bits before testing for known values.
+ const wpl = Components.interfaces.nsIWebProgressListener;
+ const wpl_security_bits = wpl.STATE_IS_SECURE |
+ wpl.STATE_IS_BROKEN |
+ wpl.STATE_IS_INSECURE;
+ var level;
+
+ switch (this._state & wpl_security_bits) {
+ case wpl.STATE_IS_SECURE:
+ level = "high";
+ break;
+ case wpl.STATE_IS_BROKEN:
+ level = "broken";
+ break;
+ }
+
+ if (level) {
+ // We don't style the Location Bar based on the the 'level' attribute
+ // anymore, but still set it for third-party themes.
+ if (gURLBar)
+ gURLBar.setAttribute("level", level);
+ } else {
+ if (gURLBar)
+ gURLBar.removeAttribute("level");
+ }
+
+ if (gMultiProcessBrowser)
+ return;
+
+ // Don't pass in the actual location object, since it can cause us to
+ // hold on to the window object too long. Just pass in the fields we
+ // care about. (bug 424829)
+ var location = gBrowser.contentWindow.location;
+ var locationObj = {};
+ try {
+ // about:blank can be used by webpages so pretend it is http
+ locationObj.protocol = location == "about:blank" ? "http:" : location.protocol;
+ locationObj.host = location.host;
+ locationObj.hostname = location.hostname;
+ locationObj.port = location.port;
+ } catch (ex) {
+ // Can sometimes throw if the URL being visited has no host/hostname,
+ // e.g. about:blank. The _state for these pages means we won't need these
+ // properties anyways, though.
+ }
+ gIdentityHandler.checkIdentity(this._state, locationObj);
+ },
+
+ // simulate all change notifications after switching tabs
+ onUpdateCurrentBrowser: function XWB_onUpdateCurrentBrowser(aStateFlags, aStatus, aMessage, aTotalProgress) {
+ if (FullZoom.updateBackgroundTabs)
+ FullZoom.onLocationChange(gBrowser.currentURI, true);
+ var nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
+ var loadingDone = aStateFlags & nsIWebProgressListener.STATE_STOP;
+ // use a pseudo-object instead of a (potentially nonexistent) channel for getting
+ // a correct error message - and make sure that the UI is always either in
+ // loading (STATE_START) or done (STATE_STOP) mode
+ this.onStateChange(
+ gBrowser.webProgress,
+ { URI: gBrowser.currentURI },
+ loadingDone ? nsIWebProgressListener.STATE_STOP : nsIWebProgressListener.STATE_START,
+ aStatus
+ );
+ // status message and progress value are undefined if we're done with loading
+ if (loadingDone)
+ return;
+ this.onStatusChange(gBrowser.webProgress, null, 0, aMessage);
+ }
+};
+
+var LinkTargetDisplay = {
+ get DELAY_SHOW() {
+ delete this.DELAY_SHOW;
+ return this.DELAY_SHOW = Services.prefs.getIntPref("browser.overlink-delay");
+ },
+
+ DELAY_HIDE: 250,
+ _timer: 0,
+
+ get _isVisible () XULBrowserWindow.statusTextField.label != "",
+
+ update: function () {
+ clearTimeout(this._timer);
+ window.removeEventListener("mousemove", this, true);
+
+ if (!XULBrowserWindow.overLink) {
+ if (XULBrowserWindow.hideOverLinkImmediately)
+ this._hide();
+ else
+ this._timer = setTimeout(this._hide.bind(this), this.DELAY_HIDE);
+ return;
+ }
+
+ if (this._isVisible) {
+ XULBrowserWindow.updateStatusField();
+ } else {
+ // Let the display appear when the mouse doesn't move within the delay
+ this._showDelayed();
+ window.addEventListener("mousemove", this, true);
+ }
+ },
+
+ handleEvent: function (event) {
+ switch (event.type) {
+ case "mousemove":
+ // Restart the delay since the mouse was moved
+ clearTimeout(this._timer);
+ this._showDelayed();
+ break;
+ }
+ },
+
+ _showDelayed: function () {
+ this._timer = setTimeout(function (self) {
+ XULBrowserWindow.updateStatusField();
+ window.removeEventListener("mousemove", self, true);
+ }, this.DELAY_SHOW, this);
+ },
+
+ _hide: function () {
+ clearTimeout(this._timer);
+
+ XULBrowserWindow.updateStatusField();
+ }
+};
+
+var CombinedStopReload = {
+ init: function () {
+ if (this._initialized)
+ return;
+
+ var urlbar = document.getElementById("urlbar-container");
+ var reload = document.getElementById("reload-button");
+ var stop = document.getElementById("stop-button");
+
+ if (urlbar) {
+ if (urlbar.parentNode.getAttribute("mode") != "icons" ||
+ !reload || urlbar.nextSibling != reload ||
+ !stop || reload.nextSibling != stop)
+ urlbar.removeAttribute("combined");
+ else {
+ urlbar.setAttribute("combined", "true");
+ reload = document.getElementById("urlbar-reload-button");
+ stop = document.getElementById("urlbar-stop-button");
+ }
+ }
+ if (!stop || !reload || reload.nextSibling != stop)
+ return;
+
+ this._initialized = true;
+ if (XULBrowserWindow.stopCommand.getAttribute("disabled") != "true")
+ reload.setAttribute("displaystop", "true");
+ stop.addEventListener("click", this, false);
+ this.reload = reload;
+ this.stop = stop;
+ },
+
+ uninit: function () {
+ if (!this._initialized)
+ return;
+
+ this._cancelTransition();
+ this._initialized = false;
+ this.stop.removeEventListener("click", this, false);
+ this.reload = null;
+ this.stop = null;
+ },
+
+ handleEvent: function (event) {
+ // the only event we listen to is "click" on the stop button
+ if (event.button == 0 &&
+ !this.stop.disabled)
+ this._stopClicked = true;
+ },
+
+ switchToStop: function () {
+ if (!this._initialized)
+ return;
+
+ this._cancelTransition();
+ this.reload.setAttribute("displaystop", "true");
+ },
+
+ switchToReload: function (aDelay) {
+ if (!this._initialized)
+ return;
+
+ this.reload.removeAttribute("displaystop");
+
+ if (!aDelay || this._stopClicked) {
+ this._stopClicked = false;
+ this._cancelTransition();
+ this.reload.disabled = XULBrowserWindow.reloadCommand
+ .getAttribute("disabled") == "true";
+ return;
+ }
+
+ if (this._timer)
+ return;
+
+ // Temporarily disable the reload button to prevent the user from
+ // accidentally reloading the page when intending to click the stop button
+ this.reload.disabled = true;
+ this._timer = setTimeout(function (self) {
+ self._timer = 0;
+ self.reload.disabled = XULBrowserWindow.reloadCommand
+ .getAttribute("disabled") == "true";
+ }, 650, this);
+ },
+
+ _cancelTransition: function () {
+ if (this._timer) {
+ clearTimeout(this._timer);
+ this._timer = 0;
+ }
+ }
+};
+
+var TabsProgressListener = {
+ onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
+#ifdef MOZ_CRASHREPORTER
+ if (aRequest instanceof Ci.nsIChannel &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_START &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_IS_DOCUMENT &&
+ gCrashReporter.enabled) {
+ gCrashReporter.annotateCrashReport("URL", aRequest.URI.spec);
+ }
+#endif
+
+ // Collect telemetry data about tab load times.
+ if (aWebProgress.isTopLevel) {
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_START)
+ TelemetryStopwatch.start("FX_PAGE_LOAD_MS", aBrowser);
+ else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP)
+ TelemetryStopwatch.finish("FX_PAGE_LOAD_MS", aBrowser);
+ } else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+ aStatus == Cr.NS_BINDING_ABORTED) {
+ TelemetryStopwatch.cancel("FX_PAGE_LOAD_MS", aBrowser);
+ }
+ }
+
+ // Attach a listener to watch for "click" events bubbling up from error
+ // pages and other similar page. This lets us fix bugs like 401575 which
+ // require error page UI to do privileged things, without letting error
+ // pages have any privilege themselves.
+ // We can't look for this during onLocationChange since at that point the
+ // document URI is not yet the about:-uri of the error page.
+
+ let doc = gMultiProcessBrowser ? null : aWebProgress.DOMWindow.document;
+ if (!gMultiProcessBrowser &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+ Components.isSuccessCode(aStatus) &&
+ doc.documentURI.startsWith("about:") &&
+ !doc.documentURI.toLowerCase().startsWith("about:blank") &&
+ !doc.documentElement.hasAttribute("hasBrowserHandlers")) {
+ // STATE_STOP may be received twice for documents, thus store an
+ // attribute to ensure handling it just once.
+ doc.documentElement.setAttribute("hasBrowserHandlers", "true");
+ aBrowser.addEventListener("click", BrowserOnClick, true);
+ aBrowser.addEventListener("pagehide", function onPageHide(event) {
+ if (event.target.defaultView.frameElement)
+ return;
+ aBrowser.removeEventListener("click", BrowserOnClick, true);
+ aBrowser.removeEventListener("pagehide", onPageHide, true);
+ if (event.target.documentElement)
+ event.target.documentElement.removeAttribute("hasBrowserHandlers");
+ }, true);
+
+ // We also want to make changes to page UI for unprivileged about pages.
+ BrowserOnAboutPageLoad(doc);
+ }
+ },
+
+ onLocationChange: function (aBrowser, aWebProgress, aRequest, aLocationURI,
+ aFlags) {
+ // Filter out location changes caused by anchor navigation
+ // or history.push/pop/replaceState.
+ if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)
+ return;
+
+ // Only need to call locationChange if the PopupNotifications object
+ // for this window has already been initialized (i.e. its getter no
+ // longer exists)
+ if (!Object.getOwnPropertyDescriptor(window, "PopupNotifications").get)
+ PopupNotifications.locationChange(aBrowser);
+
+ gBrowser.getNotificationBox(aBrowser).removeTransientNotifications();
+
+ // Filter out location changes in sub documents.
+ if (aWebProgress.isTopLevel) {
+ // Initialize the click-to-play state.
+ aBrowser._clickToPlayPluginsActivated = new Map();
+ aBrowser._clickToPlayAllPluginsActivated = false;
+ aBrowser._pluginScriptedState = gPluginHandler.PLUGIN_SCRIPTED_STATE_NONE;
+
+ FullZoom.onLocationChange(aLocationURI, false, aBrowser);
+ }
+ },
+
+ onRefreshAttempted: function (aBrowser, aWebProgress, aURI, aDelay, aSameURI) {
+ if (gPrefService.getBoolPref("accessibility.blockautorefresh")) {
+ let brandBundle = document.getElementById("bundle_brand");
+ let brandShortName = brandBundle.getString("brandShortName");
+ let refreshButtonText =
+ gNavigatorBundle.getString("refreshBlocked.goButton");
+ let refreshButtonAccesskey =
+ gNavigatorBundle.getString("refreshBlocked.goButton.accesskey");
+ let message =
+ gNavigatorBundle.getFormattedString(aSameURI ? "refreshBlocked.refreshLabel"
+ : "refreshBlocked.redirectLabel",
+ [brandShortName]);
+ let docShell = aWebProgress.DOMWindow
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell);
+ let notificationBox = gBrowser.getNotificationBox(aBrowser);
+ let notification = notificationBox.getNotificationWithValue("refresh-blocked");
+ if (notification) {
+ notification.label = message;
+ notification.refreshURI = aURI;
+ notification.delay = aDelay;
+ notification.docShell = docShell;
+ } else {
+ let buttons = [{
+ label: refreshButtonText,
+ accessKey: refreshButtonAccesskey,
+ callback: function (aNotification, aButton) {
+ var refreshURI = aNotification.docShell
+ .QueryInterface(Ci.nsIRefreshURI);
+ refreshURI.forceRefreshURI(aNotification.refreshURI,
+ aNotification.delay, true);
+ }
+ }];
+ notification =
+ notificationBox.appendNotification(message, "refresh-blocked",
+ "chrome://browser/skin/Info.png",
+ notificationBox.PRIORITY_INFO_MEDIUM,
+ buttons);
+ notification.refreshURI = aURI;
+ notification.delay = aDelay;
+ notification.docShell = docShell;
+ }
+ return false;
+ }
+ return true;
+ }
+}
+
+function nsBrowserAccess() { }
+
+nsBrowserAccess.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserDOMWindow, Ci.nsISupports]),
+
+ openURI: function (aURI, aOpener, aWhere, aContext) {
+ var newWindow = null;
+ var isExternal = (aContext == Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
+
+ if (isExternal && aURI && aURI.schemeIs("chrome")) {
+ dump("use -chrome command-line option to load external chrome urls\n");
+ return null;
+ }
+
+ if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) {
+ if (isExternal &&
+ gPrefService.prefHasUserValue("browser.link.open_newwindow.override.external"))
+ aWhere = gPrefService.getIntPref("browser.link.open_newwindow.override.external");
+ else
+ aWhere = gPrefService.getIntPref("browser.link.open_newwindow");
+ }
+ switch (aWhere) {
+ case Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW :
+ // FIXME: Bug 408379. So how come this doesn't send the
+ // referrer like the other loads do?
+ var url = aURI ? aURI.spec : "about:blank";
+ // Pass all params to openDialog to ensure that "url" isn't passed through
+ // loadOneOrMoreURIs, which splits based on "|"
+ newWindow = openDialog(getBrowserURL(), "_blank", "all,dialog=no", url, null, null, null);
+ break;
+ case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB :
+ let win, needToFocusWin;
+
+ // try the current window. if we're in a popup, fall back on the most recent browser window
+ if (window.toolbar.visible)
+ win = window;
+ else {
+ let isPrivate = PrivateBrowsingUtils.isWindowPrivate(aOpener || window);
+ win = RecentWindow.getMostRecentBrowserWindow({private: isPrivate});
+ needToFocusWin = true;
+ }
+
+ if (!win) {
+ // we couldn't find a suitable window, a new one needs to be opened.
+ return null;
+ }
+
+ if (isExternal && (!aURI || aURI.spec == "about:blank")) {
+ win.BrowserOpenTab(); // this also focuses the location bar
+ win.focus();
+ newWindow = win.content;
+ break;
+ }
+
+ let loadInBackground = gPrefService.getBoolPref("browser.tabs.loadDivertedInBackground");
+ let referrer = aOpener ? makeURI(aOpener.location.href) : null;
+
+ let tab = win.gBrowser.loadOneTab(aURI ? aURI.spec : "about:blank", {
+ referrerURI: referrer,
+ fromExternal: isExternal,
+ inBackground: loadInBackground});
+ let browser = win.gBrowser.getBrowserForTab(tab);
+
+ newWindow = browser.contentWindow;
+ if (needToFocusWin || (!loadInBackground && isExternal))
+ newWindow.focus();
+ break;
+ default : // OPEN_CURRENTWINDOW or an illegal value
+ newWindow = content;
+ if (aURI) {
+ let referrer = aOpener ? makeURI(aOpener.location.href) : null;
+ let loadflags = isExternal ?
+ Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL :
+ Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
+ gBrowser.loadURIWithFlags(aURI.spec, loadflags, referrer, null, null);
+ }
+ if (!gPrefService.getBoolPref("browser.tabs.loadDivertedInBackground"))
+ window.focus();
+ }
+ return newWindow;
+ },
+
+ isTabContentWindow: function (aWindow) {
+ return gBrowser.browsers.some(function (browser) browser.contentWindow == aWindow);
+ }
+}
+
+function onViewToolbarsPopupShowing(aEvent, aInsertPoint) {
+ var popup = aEvent.target;
+ if (popup != aEvent.currentTarget)
+ return;
+
+ // Empty the menu
+ for (var i = popup.childNodes.length-1; i >= 0; --i) {
+ var deadItem = popup.childNodes[i];
+ if (deadItem.hasAttribute("toolbarId"))
+ popup.removeChild(deadItem);
+ }
+
+ var firstMenuItem = aInsertPoint || popup.firstChild;
+
+ let toolbarNodes = Array.slice(gNavToolbox.childNodes);
+ toolbarNodes.push(document.getElementById("addon-bar"));
+
+ for (let toolbar of toolbarNodes) {
+ let toolbarName = toolbar.getAttribute("toolbarname");
+ if (toolbarName) {
+ let menuItem = document.createElement("menuitem");
+ let hidingAttribute = toolbar.getAttribute("type") == "menubar" ?
+ "autohide" : "collapsed";
+ menuItem.setAttribute("id", "toggle_" + toolbar.id);
+ menuItem.setAttribute("toolbarId", toolbar.id);
+ menuItem.setAttribute("type", "checkbox");
+ menuItem.setAttribute("label", toolbarName);
+ menuItem.setAttribute("checked", toolbar.getAttribute(hidingAttribute) != "true");
+ if (popup.id != "appmenu_customizeMenu")
+ menuItem.setAttribute("accesskey", toolbar.getAttribute("accesskey"));
+ if (popup.id != "toolbar-context-menu")
+ menuItem.setAttribute("key", toolbar.getAttribute("key"));
+
+ popup.insertBefore(menuItem, firstMenuItem);
+
+ menuItem.addEventListener("command", onViewToolbarCommand, false);
+ }
+ }
+}
+
+function onViewToolbarCommand(aEvent) {
+ var toolbarId = aEvent.originalTarget.getAttribute("toolbarId");
+ var toolbar = document.getElementById(toolbarId);
+ var isVisible = aEvent.originalTarget.getAttribute("checked") == "true";
+ setToolbarVisibility(toolbar, isVisible);
+}
+
+function setToolbarVisibility(toolbar, isVisible) {
+ var hidingAttribute = toolbar.getAttribute("type") == "menubar" ?
+ "autohide" : "collapsed";
+
+ toolbar.setAttribute(hidingAttribute, !isVisible);
+ document.persist(toolbar.id, hidingAttribute);
+
+ PlacesToolbarHelper.init();
+ BookmarkingUI.onToolbarVisibilityChange();
+ gBrowser.updateWindowResizers();
+
+#ifdef MENUBAR_CAN_AUTOHIDE
+ updateAppButtonDisplay();
+#endif
+}
+
+var TabsOnTop = {
+ init: function TabsOnTop_init() {
+ Services.prefs.addObserver(this._prefName, this, false);
+// Pale Moon: Stop Being a Derp, Mozilla (#3)
+ // Only show the toggle UI if the user disabled tabs on top.
+// if (Services.prefs.getBoolPref(this._prefName)) {
+// for (let item of document.querySelectorAll("menuitem[command=cmd_ToggleTabsOnTop]"))
+// item.parentNode.removeChild(item);
+// }
+ },
+
+ uninit: function TabsOnTop_uninit() {
+ Services.prefs.removeObserver(this._prefName, this);
+ },
+
+ toggle: function () {
+ this.enabled = !Services.prefs.getBoolPref(this._prefName);
+ },
+
+ syncUI: function () {
+ let userEnabled = Services.prefs.getBoolPref(this._prefName);
+ let enabled = userEnabled && gBrowser.tabContainer.visible;
+
+ document.getElementById("cmd_ToggleTabsOnTop")
+ .setAttribute("checked", userEnabled);
+
+ document.documentElement.setAttribute("tabsontop", enabled);
+ document.getElementById("navigator-toolbox").setAttribute("tabsontop", enabled);
+ document.getElementById("TabsToolbar").setAttribute("tabsontop", enabled);
+ document.getElementById("nav-bar").setAttribute("tabsontop", enabled);
+ gBrowser.tabContainer.setAttribute("tabsontop", enabled);
+ TabsInTitlebar.allowedBy("tabs-on-top", enabled);
+ },
+
+ get enabled () {
+ return gNavToolbox.getAttribute("tabsontop") == "true";
+ },
+
+ set enabled (val) {
+ Services.prefs.setBoolPref(this._prefName, !!val);
+ return val;
+ },
+
+ observe: function (subject, topic, data) {
+ if (topic == "nsPref:changed")
+ this.syncUI();
+ },
+
+ _prefName: "browser.tabs.onTop"
+}
+
+var TabsInTitlebar = {
+ init: function () {
+#ifdef CAN_DRAW_IN_TITLEBAR
+ this._readPref();
+ Services.prefs.addObserver(this._prefName, this, false);
+
+ // Don't trust the initial value of the sizemode attribute; wait for
+ // the resize event (handled in tabbrowser.xml).
+ this.allowedBy("sizemode", false);
+
+ this._initialized = true;
+#endif
+ },
+
+ allowedBy: function (condition, allow) {
+#ifdef CAN_DRAW_IN_TITLEBAR
+ if (allow) {
+ if (condition in this._disallowed) {
+ delete this._disallowed[condition];
+ this._update();
+ }
+ } else {
+ if (!(condition in this._disallowed)) {
+ this._disallowed[condition] = null;
+ this._update();
+ }
+ }
+#endif
+ },
+
+ get enabled() {
+ return document.documentElement.getAttribute("tabsintitlebar") == "true";
+ },
+
+#ifdef CAN_DRAW_IN_TITLEBAR
+ observe: function (subject, topic, data) {
+ if (topic == "nsPref:changed")
+ this._readPref();
+ },
+
+ _initialized: false,
+ _disallowed: {},
+ _prefName: "browser.tabs.drawInTitlebar",
+
+ _readPref: function () {
+ this.allowedBy("pref",
+ Services.prefs.getBoolPref(this._prefName));
+ },
+
+ _update: function () {
+ function $(id) document.getElementById(id);
+ function rect(ele) ele.getBoundingClientRect();
+
+ if (!this._initialized || window.fullScreen)
+ return;
+
+ let allowed = true;
+ for (let something in this._disallowed) {
+ allowed = false;
+ break;
+ }
+
+ if (allowed == this.enabled)
+ return;
+
+ let titlebar = $("titlebar");
+
+ if (allowed) {
+ let tabsToolbar = $("TabsToolbar");
+
+#ifdef MENUBAR_CAN_AUTOHIDE
+ let appmenuButtonBox = $("appmenu-button-container");
+ this._sizePlaceholder("appmenu-button", rect(appmenuButtonBox).width);
+#endif
+ let captionButtonsBox = $("titlebar-buttonbox");
+ this._sizePlaceholder("caption-buttons", rect(captionButtonsBox).width);
+
+ let tabsToolbarRect = rect(tabsToolbar);
+ let titlebarTop = rect($("titlebar-content")).top;
+ titlebar.style.marginBottom = - Math.min(tabsToolbarRect.top - titlebarTop,
+ tabsToolbarRect.height) + "px";
+
+ document.documentElement.setAttribute("tabsintitlebar", "true");
+
+ if (!this._draghandle) {
+ let tmp = {};
+ Components.utils.import("resource://gre/modules/WindowDraggingUtils.jsm", tmp);
+ this._draghandle = new tmp.WindowDraggingElement(tabsToolbar);
+ this._draghandle.mouseDownCheck = function () {
+ return !this._dragBindingAlive && TabsInTitlebar.enabled;
+ };
+ }
+ } else {
+ document.documentElement.removeAttribute("tabsintitlebar");
+
+ titlebar.style.marginBottom = "";
+ }
+ },
+
+ _sizePlaceholder: function (type, width) {
+ Array.forEach(document.querySelectorAll(".titlebar-placeholder[type='"+ type +"']"),
+ function (node) { node.width = width; });
+ },
+#endif
+
+ uninit: function () {
+#ifdef CAN_DRAW_IN_TITLEBAR
+ this._initialized = false;
+ Services.prefs.removeObserver(this._prefName, this);
+#endif
+ }
+};
+
+#ifdef MENUBAR_CAN_AUTOHIDE
+function updateAppButtonDisplay() {
+ var displayAppButton =
+ !gInPrintPreviewMode &&
+ window.menubar.visible &&
+ document.getElementById("toolbar-menubar").getAttribute("autohide") == "true";
+
+#ifdef CAN_DRAW_IN_TITLEBAR
+ document.getElementById("titlebar").hidden = !displayAppButton;
+
+ if (displayAppButton)
+ document.documentElement.setAttribute("chromemargin", "0,2,2,2");
+ else
+ document.documentElement.removeAttribute("chromemargin");
+
+ TabsInTitlebar.allowedBy("drawing-in-titlebar", displayAppButton);
+#else
+ document.getElementById("appmenu-toolbar-button").hidden =
+ !displayAppButton;
+#endif
+}
+#endif
+
+#ifdef CAN_DRAW_IN_TITLEBAR
+function onTitlebarMaxClick() {
+ if (window.windowState == window.STATE_MAXIMIZED)
+ window.restore();
+ else
+ window.maximize();
+}
+#endif
+
+function displaySecurityInfo()
+{
+ BrowserPageInfo(null, "securityTab");
+}
+
+/**
+ * Opens or closes the sidebar identified by commandID.
+ *
+ * @param commandID a string identifying the sidebar to toggle; see the
+ * note below. (Optional if a sidebar is already open.)
+ * @param forceOpen boolean indicating whether the sidebar should be
+ * opened regardless of its current state (optional).
+ * @note
+ * We expect to find a xul:broadcaster element with the specified ID.
+ * The following attributes on that element may be used and/or modified:
+ * - id (required) the string to match commandID. The convention
+ * is to use this naming scheme: 'view<sidebar-name>Sidebar'.
+ * - sidebarurl (required) specifies the URL to load in this sidebar.
+ * - sidebartitle or label (in that order) specify the title to
+ * display on the sidebar.
+ * - checked indicates whether the sidebar is currently displayed.
+ * Note that toggleSidebar updates this attribute when
+ * it changes the sidebar's visibility.
+ * - group this attribute must be set to "sidebar".
+ */
+function toggleSidebar(commandID, forceOpen) {
+
+ var sidebarBox = document.getElementById("sidebar-box");
+ if (!commandID)
+ commandID = sidebarBox.getAttribute("sidebarcommand");
+
+ var sidebarBroadcaster = document.getElementById(commandID);
+ var sidebar = document.getElementById("sidebar"); // xul:browser
+ var sidebarTitle = document.getElementById("sidebar-title");
+ var sidebarSplitter = document.getElementById("sidebar-splitter");
+
+ if (sidebarBroadcaster.getAttribute("checked") == "true") {
+ if (!forceOpen) {
+ // Replace the document currently displayed in the sidebar with about:blank
+ // so that we can free memory by unloading the page. We need to explicitly
+ // create a new content viewer because the old one doesn't get destroyed
+ // until about:blank has loaded (which does not happen as long as the
+ // element is hidden).
+ sidebar.setAttribute("src", "about:blank");
+ sidebar.docShell.createAboutBlankContentViewer(null);
+
+ sidebarBroadcaster.removeAttribute("checked");
+ sidebarBox.setAttribute("sidebarcommand", "");
+ sidebarTitle.value = "";
+ sidebarBox.hidden = true;
+ sidebarSplitter.hidden = true;
+ gBrowser.selectedBrowser.focus();
+ } else {
+ fireSidebarFocusedEvent();
+ }
+ return;
+ }
+
+ // now we need to show the specified sidebar
+
+ // ..but first update the 'checked' state of all sidebar broadcasters
+ var broadcasters = document.getElementsByAttribute("group", "sidebar");
+ for (let broadcaster of broadcasters) {
+ // skip elements that observe sidebar broadcasters and random
+ // other elements
+ if (broadcaster.localName != "broadcaster")
+ continue;
+
+ if (broadcaster != sidebarBroadcaster)
+ broadcaster.removeAttribute("checked");
+ else
+ sidebarBroadcaster.setAttribute("checked", "true");
+ }
+
+ sidebarBox.hidden = false;
+ sidebarSplitter.hidden = false;
+
+ var url = sidebarBroadcaster.getAttribute("sidebarurl");
+ var title = sidebarBroadcaster.getAttribute("sidebartitle");
+ if (!title)
+ title = sidebarBroadcaster.getAttribute("label");
+ sidebar.setAttribute("src", url); // kick off async load
+ sidebarBox.setAttribute("sidebarcommand", sidebarBroadcaster.id);
+ sidebarTitle.value = title;
+
+ // We set this attribute here in addition to setting it on the <browser>
+ // element itself, because the code in gBrowserInit.onUnload persists this
+ // attribute, not the "src" of the <browser id="sidebar">. The reason it
+ // does that is that we want to delay sidebar load a bit when a browser
+ // window opens. See delayedStartup().
+ sidebarBox.setAttribute("src", url);
+
+ if (sidebar.contentDocument.location.href != url)
+ sidebar.addEventListener("load", sidebarOnLoad, true);
+ else // older code handled this case, so we do it too
+ fireSidebarFocusedEvent();
+}
+
+function sidebarOnLoad(event) {
+ var sidebar = document.getElementById("sidebar");
+ sidebar.removeEventListener("load", sidebarOnLoad, true);
+ // We're handling the 'load' event before it bubbles up to the usual
+ // (non-capturing) event handlers. Let it bubble up before firing the
+ // SidebarFocused event.
+ setTimeout(fireSidebarFocusedEvent, 0);
+}
+
+/**
+ * Fire a "SidebarFocused" event on the sidebar's |window| to give the sidebar
+ * a chance to adjust focus as needed. An additional event is needed, because
+ * we don't want to focus the sidebar when it's opened on startup or in a new
+ * window, only when the user opens the sidebar.
+ */
+function fireSidebarFocusedEvent() {
+ var sidebar = document.getElementById("sidebar");
+ var event = document.createEvent("Events");
+ event.initEvent("SidebarFocused", true, false);
+ sidebar.contentWindow.dispatchEvent(event);
+}
+
+#ifdef XP_WIN
+#ifdef MOZ_METRO
+/**
+ * Some prefs that have consequences in both Metro and Desktop such as
+ * app-update prefs, are automatically pushed from Desktop here for use
+ * in Metro.
+ */
+var gMetroPrefs = {
+ prefDomain: ["app.update.auto", "app.update.enabled",
+ "app.update.service.enabled",
+ "app.update.metro.enabled"],
+ observe: function (aSubject, aTopic, aPrefName)
+ {
+ if (aTopic != "nsPref:changed")
+ return;
+
+ this.pushDesktopControlledPrefToMetro(aPrefName);
+ },
+
+ /**
+ * Writes the pref to HKCU in the registry and adds a pref-observer to keep
+ * the registry in sync with changes to the value.
+ */
+ pushDesktopControlledPrefToMetro: function(aPrefName) {
+ let registry = Cc["@mozilla.org/windows-registry-key;1"].
+ createInstance(Ci.nsIWindowsRegKey);
+ try {
+ var prefType = Services.prefs.getPrefType(aPrefName);
+ let prefFunc;
+ if (prefType == Components.interfaces.nsIPrefBranch.PREF_INT)
+ prefFunc = "getIntPref";
+ else if (prefType == Components.interfaces.nsIPrefBranch.PREF_BOOL)
+ prefFunc = "getBoolPref";
+ else if (prefType == Components.interfaces.nsIPrefBranch.PREF_STRING)
+ prefFunc = "getCharPref";
+ else
+ throw "Unsupported pref type";
+
+ let prefValue = Services.prefs[prefFunc](aPrefName);
+ registry.create(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ "Software\\Mozilla\\Firefox\\Metro\\Prefs\\" + prefType,
+ Ci.nsIWindowsRegKey.ACCESS_WRITE);
+ // Always write as string, but the registry subfolder will determine
+ // how Metro interprets that string value.
+ registry.writeStringValue(aPrefName, prefValue);
+ } catch (ex) {
+ Components.utils.reportError("Couldn't push pref " + aPrefName + ": " + ex);
+ } finally {
+ registry.close();
+ }
+ }
+};
+#endif
+#endif
+
+var gHomeButton = {
+ prefDomain: "browser.startup.homepage",
+ observe: function (aSubject, aTopic, aPrefName)
+ {
+ if (aTopic != "nsPref:changed" || aPrefName != this.prefDomain)
+ return;
+
+ this.updateTooltip();
+ },
+
+ updateTooltip: function (homeButton)
+ {
+ if (!homeButton)
+ homeButton = document.getElementById("home-button");
+ if (homeButton) {
+ var homePage = this.getHomePage();
+ homePage = homePage.replace(/\|/g,', ');
+ if (homePage.toLowerCase() == "about:home")
+ homeButton.setAttribute("tooltiptext", homeButton.getAttribute("aboutHomeOverrideTooltip"));
+ else
+ homeButton.setAttribute("tooltiptext", homePage);
+ }
+ },
+
+ getHomePage: function ()
+ {
+ var url;
+ try {
+ url = gPrefService.getComplexValue(this.prefDomain,
+ Components.interfaces.nsIPrefLocalizedString).data;
+ } catch (e) {
+ }
+
+ // use this if we can't find the pref
+ if (!url) {
+ var configBundle = Services.strings
+ .createBundle("chrome://branding/locale/browserconfig.properties");
+ url = configBundle.GetStringFromName(this.prefDomain);
+ }
+
+ return url;
+ },
+
+ updatePersonalToolbarStyle: function (homeButton)
+ {
+ if (!homeButton)
+ homeButton = document.getElementById("home-button");
+ if (homeButton)
+ homeButton.className = homeButton.parentNode.id == "PersonalToolbar"
+ || homeButton.parentNode.parentNode.id == "PersonalToolbar" ?
+ homeButton.className.replace("toolbarbutton-1", "bookmark-item") :
+ homeButton.className.replace("bookmark-item", "toolbarbutton-1");
+ }
+};
+
+/**
+ * Gets the selected text in the active browser. Leading and trailing
+ * whitespace is removed, and consecutive whitespace is replaced by a single
+ * space. A maximum of 150 characters will be returned, regardless of the value
+ * of aCharLen.
+ *
+ * @param aCharLen
+ * The maximum number of characters to return.
+ */
+function getBrowserSelection(aCharLen) {
+ // selections of more than 150 characters aren't useful
+ const kMaxSelectionLen = 150;
+ const charLen = Math.min(aCharLen || kMaxSelectionLen, kMaxSelectionLen);
+ let commandDispatcher = document.commandDispatcher;
+
+ var focusedWindow = commandDispatcher.focusedWindow;
+ var selection = focusedWindow.getSelection().toString();
+ // try getting a selected text in text input.
+ if (!selection) {
+ let element = commandDispatcher.focusedElement;
+ var isOnTextInput = function isOnTextInput(elem) {
+ // we avoid to return a value if a selection is in password field.
+ // ref. bug 565717
+ return elem instanceof HTMLTextAreaElement ||
+ (elem instanceof HTMLInputElement && elem.mozIsTextField(true));
+ };
+
+ if (isOnTextInput(element)) {
+ selection = element.QueryInterface(Ci.nsIDOMNSEditableElement)
+ .editor.selection.toString();
+ }
+ }
+
+ if (selection) {
+ if (selection.length > charLen) {
+ // only use the first charLen important chars. see bug 221361
+ var pattern = new RegExp("^(?:\\s*.){0," + charLen + "}");
+ pattern.test(selection);
+ selection = RegExp.lastMatch;
+ }
+
+ selection = selection.trim().replace(/\s+/g, " ");
+
+ if (selection.length > charLen)
+ selection = selection.substr(0, charLen);
+ }
+ return selection;
+}
+
+var gWebPanelURI;
+function openWebPanel(aTitle, aURI)
+{
+ // Ensure that the web panels sidebar is open.
+ toggleSidebar('viewWebPanelsSidebar', true);
+
+ // Set the title of the panel.
+ document.getElementById("sidebar-title").value = aTitle;
+
+ // Tell the Web Panels sidebar to load the bookmark.
+ var sidebar = document.getElementById("sidebar");
+ if (sidebar.docShell && sidebar.contentDocument && sidebar.contentDocument.getElementById('web-panels-browser')) {
+ sidebar.contentWindow.loadWebPanel(aURI);
+ if (gWebPanelURI) {
+ gWebPanelURI = "";
+ sidebar.removeEventListener("load", asyncOpenWebPanel, true);
+ }
+ }
+ else {
+ // The panel is still being constructed. Attach an onload handler.
+ if (!gWebPanelURI)
+ sidebar.addEventListener("load", asyncOpenWebPanel, true);
+ gWebPanelURI = aURI;
+ }
+}
+
+function asyncOpenWebPanel(event)
+{
+ var sidebar = document.getElementById("sidebar");
+ if (gWebPanelURI && sidebar.contentDocument && sidebar.contentDocument.getElementById('web-panels-browser'))
+ sidebar.contentWindow.loadWebPanel(gWebPanelURI);
+ gWebPanelURI = "";
+ sidebar.removeEventListener("load", asyncOpenWebPanel, true);
+}
+
+/*
+ * - [ Dependencies ] ---------------------------------------------------------
+ * utilityOverlay.js:
+ * - gatherTextUnder
+ */
+
+/**
+ * Extracts linkNode and href for the current click target.
+ *
+ * @param event
+ * The click event.
+ * @return [href, linkNode].
+ *
+ * @note linkNode will be null if the click wasn't on an anchor
+ * element (or XLink).
+ */
+function hrefAndLinkNodeForClickEvent(event)
+{
+ function isHTMLLink(aNode)
+ {
+ // Be consistent with what nsContextMenu.js does.
+ return ((aNode instanceof HTMLAnchorElement && aNode.href) ||
+ (aNode instanceof HTMLAreaElement && aNode.href) ||
+ aNode instanceof HTMLLinkElement);
+ }
+
+ let node = event.target;
+ while (node && !isHTMLLink(node)) {
+ node = node.parentNode;
+ }
+
+ if (node)
+ return [node.href, node];
+
+ // If there is no linkNode, try simple XLink.
+ let href, baseURI;
+ node = event.target;
+ while (node && !href) {
+ if (node.nodeType == Node.ELEMENT_NODE) {
+ href = node.getAttributeNS("http://www.w3.org/1999/xlink", "href");
+ if (href)
+ baseURI = node.baseURI;
+ }
+ node = node.parentNode;
+ }
+
+ // In case of XLink, we don't return the node we got href from since
+ // callers expect <a>-like elements.
+ return [href ? makeURLAbsolute(baseURI, href) : null, null];
+}
+
+/**
+ * Called whenever the user clicks in the content area.
+ *
+ * @param event
+ * The click event.
+ * @param isPanelClick
+ * Whether the event comes from a web panel.
+ * @note default event is prevented if the click is handled.
+ */
+function contentAreaClick(event, isPanelClick)
+{
+ if (!event.isTrusted || event.defaultPrevented || event.button == 2)
+ return;
+
+ let [href, linkNode] = hrefAndLinkNodeForClickEvent(event);
+ if (!href) {
+ // Not a link, handle middle mouse navigation.
+ if (event.button == 1 &&
+ gPrefService.getBoolPref("middlemouse.contentLoadURL") &&
+ !gPrefService.getBoolPref("general.autoScroll")) {
+ middleMousePaste(event);
+ event.preventDefault();
+ }
+ return;
+ }
+
+ // This code only applies if we have a linkNode (i.e. clicks on real anchor
+ // elements, as opposed to XLink).
+ if (linkNode && event.button == 0 &&
+ !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey) {
+ // A Web panel's links should target the main content area. Do this
+ // if no modifier keys are down and if there's no target or the target
+ // equals _main (the IE convention) or _content (the Mozilla convention).
+ let target = linkNode.target;
+ let mainTarget = !target || target == "_content" || target == "_main";
+ if (isPanelClick && mainTarget) {
+ // javascript and data links should be executed in the current browser.
+ if (linkNode.getAttribute("onclick") ||
+ href.startsWith("javascript:") ||
+ href.startsWith("data:"))
+ return;
+
+ try {
+ urlSecurityCheck(href, linkNode.ownerDocument.nodePrincipal);
+ }
+ catch(ex) {
+ // Prevent loading unsecure destinations.
+ event.preventDefault();
+ return;
+ }
+
+ loadURI(href, null, null, false);
+ event.preventDefault();
+ return;
+ }
+
+ if (linkNode.getAttribute("rel") == "sidebar") {
+ // This is the Opera convention for a special link that, when clicked,
+ // allows to add a sidebar panel. The link's title attribute contains
+ // the title that should be used for the sidebar panel.
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "bookmark"
+ , uri: makeURI(href)
+ , title: linkNode.getAttribute("title")
+ , loadBookmarkInSidebar: true
+ , hiddenRows: [ "description"
+ , "location"
+ , "keyword" ]
+ }, window);
+ event.preventDefault();
+ return;
+ }
+ }
+
+ handleLinkClick(event, href, linkNode);
+
+ // Mark the page as a user followed link. This is done so that history can
+ // distinguish automatic embed visits from user activated ones. For example
+ // pages loaded in frames are embed visits and lost with the session, while
+ // visits across frames should be preserved.
+ try {
+ if (!PrivateBrowsingUtils.isWindowPrivate(window))
+ PlacesUIUtils.markPageAsFollowedLink(href);
+ } catch (ex) { /* Skip invalid URIs. */ }
+}
+
+/**
+ * Handles clicks on links.
+ *
+ * @return true if the click event was handled, false otherwise.
+ */
+function handleLinkClick(event, href, linkNode) {
+ if (event.button == 2) // right click
+ return false;
+
+ var where = whereToOpenLink(event);
+ if (where == "current")
+ return false;
+
+ var doc = event.target.ownerDocument;
+
+ if (where == "save") {
+ saveURL(href, linkNode ? gatherTextUnder(linkNode) : "", null, true,
+ true, doc.documentURIObject, doc);
+ event.preventDefault();
+ return true;
+ }
+
+ urlSecurityCheck(href, doc.nodePrincipal);
+ openLinkIn(href, where, { referrerURI: doc.documentURIObject,
+ charset: doc.characterSet });
+ event.preventDefault();
+ return true;
+}
+
+function middleMousePaste(event) {
+ let clipboard = readFromClipboard();
+ if (!clipboard)
+ return;
+
+ // Strip embedded newlines and surrounding whitespace, to match the URL
+ // bar's behavior (stripsurroundingwhitespace)
+ clipboard = clipboard.replace(/\s*\n\s*/g, "");
+
+ let mayInheritPrincipal = { value: false };
+ let url = getShortcutOrURI(clipboard, mayInheritPrincipal);
+ try {
+ makeURI(url);
+ } catch (ex) {
+ // Not a valid URI.
+ return;
+ }
+
+ try {
+ addToUrlbarHistory(url);
+ } catch (ex) {
+ // Things may go wrong when adding url to session history,
+ // but don't let that interfere with the loading of the url.
+ Cu.reportError(ex);
+ }
+
+ openUILink(url, event,
+ { ignoreButton: true,
+ disallowInheritPrincipal: !mayInheritPrincipal.value });
+
+ event.stopPropagation();
+}
+
+function handleDroppedLink(event, url, name)
+{
+ let postData = { };
+ let uri = getShortcutOrURI(url, postData);
+ if (uri)
+ loadURI(uri, null, postData.value, false);
+
+ // Keep the event from being handled by the dragDrop listeners
+ // built-in to gecko if they happen to be above us.
+ event.preventDefault();
+};
+
+function MultiplexHandler(event)
+{ try {
+ var node = event.target;
+ var name = node.getAttribute('name');
+
+ if (name == 'detectorGroup') {
+ BrowserCharsetReload();
+ SelectDetector(event, false);
+ } else if (name == 'charsetGroup') {
+ var charset = node.getAttribute('id');
+ charset = charset.substring('charset.'.length, charset.length)
+ BrowserSetForcedCharacterSet(charset);
+ } else if (name == 'charsetCustomize') {
+ //do nothing - please remove this else statement, once the charset prefs moves to the pref window
+ } else {
+ BrowserSetForcedCharacterSet(node.getAttribute('id'));
+ }
+ } catch(ex) { alert(ex); }
+}
+
+function SelectDetector(event, doReload)
+{
+ var uri = event.target.getAttribute("id");
+ var prefvalue = uri.substring('chardet.'.length, uri.length);
+ if ("off" == prefvalue) { // "off" is special value to turn off the detectors
+ prefvalue = "";
+ }
+
+ try {
+ var str = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+
+ str.data = prefvalue;
+ gPrefService.setComplexValue("intl.charset.detector", Ci.nsISupportsString, str);
+ if (doReload)
+ window.content.location.reload();
+ }
+ catch (ex) {
+ dump("Failed to set the intl.charset.detector preference.\n");
+ }
+}
+
+function BrowserSetForcedCharacterSet(aCharset)
+{
+ gBrowser.docShell.gatherCharsetMenuTelemetry();
+ gBrowser.docShell.charset = aCharset;
+ // Save the forced character-set
+ if (!PrivateBrowsingUtils.isWindowPrivate(window))
+ PlacesUtils.setCharsetForURI(getWebNavigation().currentURI, aCharset);
+ BrowserCharsetReload();
+}
+
+function BrowserCharsetReload()
+{
+ BrowserReloadWithFlags(nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
+}
+
+function charsetMenuGetElement(parent, id) {
+ return parent.getElementsByAttribute("id", id)[0];
+}
+
+function UpdateCurrentCharset(target) {
+ // extract the charset from DOM
+ var wnd = document.commandDispatcher.focusedWindow;
+ if ((window == wnd) || (wnd == null)) wnd = window.content;
+
+ // Uncheck previous item
+ if (gPrevCharset) {
+ var pref_item = charsetMenuGetElement(target, "charset." + gPrevCharset);
+ if (pref_item)
+ pref_item.setAttribute('checked', 'false');
+ }
+
+ var menuitem = charsetMenuGetElement(target, "charset." + wnd.document.characterSet);
+ if (menuitem) {
+ menuitem.setAttribute('checked', 'true');
+ }
+}
+
+function UpdateCharsetDetector(target) {
+ var prefvalue;
+
+ try {
+ prefvalue = gPrefService.getComplexValue("intl.charset.detector", Ci.nsIPrefLocalizedString).data;
+ }
+ catch (ex) {}
+
+ if (!prefvalue)
+ prefvalue = "off";
+
+ var menuitem = charsetMenuGetElement(target, "chardet." + prefvalue);
+ if (menuitem)
+ menuitem.setAttribute("checked", "true");
+}
+
+function UpdateMenus(event) {
+ UpdateCurrentCharset(event.target);
+ UpdateCharsetDetector(event.target);
+}
+
+function CreateMenu(node) {
+ Services.obs.notifyObservers(null, "charsetmenu-selected", node);
+}
+
+function charsetLoadListener() {
+ var charset = window.content.document.characterSet;
+
+ if (charset.length > 0 && (charset != gLastBrowserCharset)) {
+ if (!gCharsetMenu)
+ gCharsetMenu = Cc['@mozilla.org/rdf/datasource;1?name=charset-menu'].getService(Ci.nsICurrentCharsetListener);
+ gCharsetMenu.SetCurrentCharset(charset);
+ gPrevCharset = gLastBrowserCharset;
+ gLastBrowserCharset = charset;
+ }
+}
+
+
+var gPageStyleMenu = {
+
+ _getAllStyleSheets: function (frameset) {
+ var styleSheetsArray = Array.slice(frameset.document.styleSheets);
+ for (let i = 0; i < frameset.frames.length; i++) {
+ let frameSheets = this._getAllStyleSheets(frameset.frames[i]);
+ styleSheetsArray = styleSheetsArray.concat(frameSheets);
+ }
+ return styleSheetsArray;
+ },
+
+ fillPopup: function (menuPopup) {
+ var noStyle = menuPopup.firstChild;
+ var persistentOnly = noStyle.nextSibling;
+ var sep = persistentOnly.nextSibling;
+ while (sep.nextSibling)
+ menuPopup.removeChild(sep.nextSibling);
+
+ var styleSheets = this._getAllStyleSheets(window.content);
+ var currentStyleSheets = {};
+ var styleDisabled = getMarkupDocumentViewer().authorStyleDisabled;
+ var haveAltSheets = false;
+ var altStyleSelected = false;
+
+ for (let currentStyleSheet of styleSheets) {
+ if (!currentStyleSheet.title)
+ continue;
+
+ // Skip any stylesheets whose media attribute doesn't match.
+ if (currentStyleSheet.media.length > 0) {
+ let mediaQueryList = currentStyleSheet.media.mediaText;
+ if (!window.content.matchMedia(mediaQueryList).matches)
+ continue;
+ }
+
+ if (!currentStyleSheet.disabled)
+ altStyleSelected = true;
+
+ haveAltSheets = true;
+
+ let lastWithSameTitle = null;
+ if (currentStyleSheet.title in currentStyleSheets)
+ lastWithSameTitle = currentStyleSheets[currentStyleSheet.title];
+
+ if (!lastWithSameTitle) {
+ let menuItem = document.createElement("menuitem");
+ menuItem.setAttribute("type", "radio");
+ menuItem.setAttribute("label", currentStyleSheet.title);
+ menuItem.setAttribute("data", currentStyleSheet.title);
+ menuItem.setAttribute("checked", !currentStyleSheet.disabled && !styleDisabled);
+ menuItem.setAttribute("oncommand", "gPageStyleMenu.switchStyleSheet(this.getAttribute('data'));");
+ menuPopup.appendChild(menuItem);
+ currentStyleSheets[currentStyleSheet.title] = menuItem;
+ } else if (currentStyleSheet.disabled) {
+ lastWithSameTitle.removeAttribute("checked");
+ }
+ }
+
+ noStyle.setAttribute("checked", styleDisabled);
+ persistentOnly.setAttribute("checked", !altStyleSelected && !styleDisabled);
+ persistentOnly.hidden = (window.content.document.preferredStyleSheetSet) ? haveAltSheets : false;
+ sep.hidden = (noStyle.hidden && persistentOnly.hidden) || !haveAltSheets;
+ },
+
+ _stylesheetInFrame: function (frame, title) {
+ return Array.some(frame.document.styleSheets,
+ function (stylesheet) stylesheet.title == title);
+ },
+
+ _stylesheetSwitchFrame: function (frame, title) {
+ var docStyleSheets = frame.document.styleSheets;
+
+ for (let i = 0; i < docStyleSheets.length; ++i) {
+ let docStyleSheet = docStyleSheets[i];
+
+ if (docStyleSheet.title)
+ docStyleSheet.disabled = (docStyleSheet.title != title);
+ else if (docStyleSheet.disabled)
+ docStyleSheet.disabled = false;
+ }
+ },
+
+ _stylesheetSwitchAll: function (frameset, title) {
+ if (!title || this._stylesheetInFrame(frameset, title))
+ this._stylesheetSwitchFrame(frameset, title);
+
+ for (let i = 0; i < frameset.frames.length; i++)
+ this._stylesheetSwitchAll(frameset.frames[i], title);
+ },
+
+ switchStyleSheet: function (title, contentWindow) {
+ getMarkupDocumentViewer().authorStyleDisabled = false;
+ this._stylesheetSwitchAll(contentWindow || content, title);
+ },
+
+ disableStyle: function () {
+ getMarkupDocumentViewer().authorStyleDisabled = true;
+ },
+};
+
+/* Legacy global page-style functions */
+var getAllStyleSheets = gPageStyleMenu._getAllStyleSheets.bind(gPageStyleMenu);
+var stylesheetFillPopup = gPageStyleMenu.fillPopup.bind(gPageStyleMenu);
+function stylesheetSwitchAll(contentWindow, title) {
+ gPageStyleMenu.switchStyleSheet(title, contentWindow);
+}
+function setStyleDisabled(disabled) {
+ if (disabled)
+ gPageStyleMenu.disableStyle();
+}
+
+
+var BrowserOffline = {
+ _inited: false,
+
+ /////////////////////////////////////////////////////////////////////////////
+ // BrowserOffline Public Methods
+ init: function ()
+ {
+ if (!this._uiElement)
+ this._uiElement = document.getElementById("workOfflineMenuitemState");
+
+ Services.obs.addObserver(this, "network:offline-status-changed", false);
+
+ this._updateOfflineUI(Services.io.offline);
+
+ this._inited = true;
+ },
+
+ uninit: function ()
+ {
+ if (this._inited) {
+ Services.obs.removeObserver(this, "network:offline-status-changed");
+ }
+ },
+
+ toggleOfflineStatus: function ()
+ {
+ var ioService = Services.io;
+
+ // Stop automatic management of the offline status
+ try {
+ ioService.manageOfflineStatus = false;
+ } catch (ex) {
+ }
+
+ if (!ioService.offline && !this._canGoOffline()) {
+ this._updateOfflineUI(false);
+ return;
+ }
+
+ ioService.offline = !ioService.offline;
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // nsIObserver
+ observe: function (aSubject, aTopic, aState)
+ {
+ if (aTopic != "network:offline-status-changed")
+ return;
+
+ this._updateOfflineUI(aState == "offline");
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // BrowserOffline Implementation Methods
+ _canGoOffline: function ()
+ {
+ try {
+ var cancelGoOffline = Cc["@mozilla.org/supports-PRBool;1"].createInstance(Ci.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelGoOffline, "offline-requested", null);
+
+ // Something aborted the quit process.
+ if (cancelGoOffline.data)
+ return false;
+ }
+ catch (ex) {
+ }
+
+ return true;
+ },
+
+ _uiElement: null,
+ _updateOfflineUI: function (aOffline)
+ {
+ var offlineLocked = gPrefService.prefIsLocked("network.online");
+ if (offlineLocked)
+ this._uiElement.setAttribute("disabled", "true");
+
+ this._uiElement.setAttribute("checked", aOffline);
+ }
+};
+
+var OfflineApps = {
+ /////////////////////////////////////////////////////////////////////////////
+ // OfflineApps Public Methods
+ init: function ()
+ {
+ Services.obs.addObserver(this, "offline-cache-update-completed", false);
+ },
+
+ uninit: function ()
+ {
+ Services.obs.removeObserver(this, "offline-cache-update-completed");
+ },
+
+ handleEvent: function(event) {
+ if (event.type == "MozApplicationManifest") {
+ this.offlineAppRequested(event.originalTarget.defaultView);
+ }
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // OfflineApps Implementation Methods
+
+ // XXX: _getBrowserWindowForContentWindow and _getBrowserForContentWindow
+ // were taken from browser/components/feeds/src/WebContentConverter.
+ _getBrowserWindowForContentWindow: function(aContentWindow) {
+ return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow)
+ .wrappedJSObject;
+ },
+
+ _getBrowserForContentWindow: function(aBrowserWindow, aContentWindow) {
+ // This depends on pseudo APIs of browser.js and tabbrowser.xml
+ aContentWindow = aContentWindow.top;
+ var browsers = aBrowserWindow.gBrowser.browsers;
+ for (let browser of browsers) {
+ if (browser.contentWindow == aContentWindow)
+ return browser;
+ }
+ // handle other browser/iframe elements that may need popupnotifications
+ let browser = aContentWindow
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+ if (browser.getAttribute("popupnotificationanchor"))
+ return browser;
+ return null;
+ },
+
+ _getManifestURI: function(aWindow) {
+ if (!aWindow.document.documentElement)
+ return null;
+
+ var attr = aWindow.document.documentElement.getAttribute("manifest");
+ if (!attr)
+ return null;
+
+ try {
+ var contentURI = makeURI(aWindow.location.href, null, null);
+ return makeURI(attr, aWindow.document.characterSet, contentURI);
+ } catch (e) {
+ return null;
+ }
+ },
+
+ // A cache update isn't tied to a specific window. Try to find
+ // the best browser in which to warn the user about space usage
+ _getBrowserForCacheUpdate: function(aCacheUpdate) {
+ // Prefer the current browser
+ var uri = this._getManifestURI(content);
+ if (uri && uri.equals(aCacheUpdate.manifestURI)) {
+ return gBrowser.selectedBrowser;
+ }
+
+ var browsers = gBrowser.browsers;
+ for (let browser of browsers) {
+ uri = this._getManifestURI(browser.contentWindow);
+ if (uri && uri.equals(aCacheUpdate.manifestURI)) {
+ return browser;
+ }
+ }
+
+ // is this from a non-tab browser/iframe?
+ browsers = document.querySelectorAll("iframe[popupnotificationanchor] | browser[popupnotificationanchor]");
+ for (let browser of browsers) {
+ uri = this._getManifestURI(browser.contentWindow);
+ if (uri && uri.equals(aCacheUpdate.manifestURI)) {
+ return browser;
+ }
+ }
+
+ return null;
+ },
+
+ _warnUsage: function(aBrowser, aURI) {
+ if (!aBrowser)
+ return;
+
+ let mainAction = {
+ label: gNavigatorBundle.getString("offlineApps.manageUsage"),
+ accessKey: gNavigatorBundle.getString("offlineApps.manageUsageAccessKey"),
+ callback: OfflineApps.manage
+ };
+
+ let warnQuota = gPrefService.getIntPref("offline-apps.quota.warn");
+ let message = gNavigatorBundle.getFormattedString("offlineApps.usage",
+ [ aURI.host,
+ warnQuota / 1024 ]);
+
+ let anchorID = "indexedDB-notification-icon";
+ PopupNotifications.show(aBrowser, "offline-app-usage", message,
+ anchorID, mainAction);
+
+ // Now that we've warned once, prevent the warning from showing up
+ // again.
+ Services.perms.add(aURI, "offline-app",
+ Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN);
+ },
+
+ // XXX: duplicated in preferences/advanced.js
+ _getOfflineAppUsage: function (host, groups)
+ {
+ var cacheService = Cc["@mozilla.org/network/application-cache-service;1"].
+ getService(Ci.nsIApplicationCacheService);
+ if (!groups)
+ groups = cacheService.getGroups();
+
+ var usage = 0;
+ for (let group of groups) {
+ var uri = Services.io.newURI(group, null, null);
+ if (uri.asciiHost == host) {
+ var cache = cacheService.getActiveCache(group);
+ usage += cache.usage;
+ }
+ }
+
+ return usage;
+ },
+
+ _checkUsage: function(aURI) {
+ // if the user has already allowed excessive usage, don't bother checking
+ if (Services.perms.testExactPermission(aURI, "offline-app") !=
+ Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN) {
+ var usage = this._getOfflineAppUsage(aURI.asciiHost);
+ var warnQuota = gPrefService.getIntPref("offline-apps.quota.warn");
+ if (usage >= warnQuota * 1024) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ offlineAppRequested: function(aContentWindow) {
+ if (!gPrefService.getBoolPref("browser.offline-apps.notify")) {
+ return;
+ }
+
+ let browserWindow = this._getBrowserWindowForContentWindow(aContentWindow);
+ let browser = this._getBrowserForContentWindow(browserWindow,
+ aContentWindow);
+
+ let currentURI = aContentWindow.document.documentURIObject;
+
+ // don't bother showing UI if the user has already made a decision
+ if (Services.perms.testExactPermission(currentURI, "offline-app") != Services.perms.UNKNOWN_ACTION)
+ return;
+
+ try {
+ if (gPrefService.getBoolPref("offline-apps.allow_by_default")) {
+ // all pages can use offline capabilities, no need to ask the user
+ return;
+ }
+ } catch(e) {
+ // this pref isn't set by default, ignore failures
+ }
+
+ let host = currentURI.asciiHost;
+ let notificationID = "offline-app-requested-" + host;
+ let notification = PopupNotifications.getNotification(notificationID, browser);
+
+ if (notification) {
+ notification.options.documents.push(aContentWindow.document);
+ } else {
+ let mainAction = {
+ label: gNavigatorBundle.getString("offlineApps.allow"),
+ accessKey: gNavigatorBundle.getString("offlineApps.allowAccessKey"),
+ callback: function() {
+ for (let document of notification.options.documents) {
+ OfflineApps.allowSite(document);
+ }
+ }
+ };
+ let secondaryActions = [{
+ label: gNavigatorBundle.getString("offlineApps.never"),
+ accessKey: gNavigatorBundle.getString("offlineApps.neverAccessKey"),
+ callback: function() {
+ for (let document of notification.options.documents) {
+ OfflineApps.disallowSite(document);
+ }
+ }
+ }];
+ let message = gNavigatorBundle.getFormattedString("offlineApps.available",
+ [ host ]);
+ let anchorID = "indexedDB-notification-icon";
+ let options= {
+ documents : [ aContentWindow.document ]
+ };
+ notification = PopupNotifications.show(browser, notificationID, message,
+ anchorID, mainAction,
+ secondaryActions, options);
+ }
+ },
+
+ allowSite: function(aDocument) {
+ Services.perms.add(aDocument.documentURIObject, "offline-app", Services.perms.ALLOW_ACTION);
+
+ // When a site is enabled while loading, manifest resources will
+ // start fetching immediately. This one time we need to do it
+ // ourselves.
+ this._startFetching(aDocument);
+ },
+
+ disallowSite: function(aDocument) {
+ Services.perms.add(aDocument.documentURIObject, "offline-app", Services.perms.DENY_ACTION);
+ },
+
+ manage: function() {
+ openAdvancedPreferences("networkTab");
+ },
+
+ _startFetching: function(aDocument) {
+ if (!aDocument.documentElement)
+ return;
+
+ var manifest = aDocument.documentElement.getAttribute("manifest");
+ if (!manifest)
+ return;
+
+ var manifestURI = makeURI(manifest, aDocument.characterSet,
+ aDocument.documentURIObject);
+
+ var updateService = Cc["@mozilla.org/offlinecacheupdate-service;1"].
+ getService(Ci.nsIOfflineCacheUpdateService);
+ updateService.scheduleUpdate(manifestURI, aDocument.documentURIObject, window);
+ },
+
+ /////////////////////////////////////////////////////////////////////////////
+ // nsIObserver
+ observe: function (aSubject, aTopic, aState)
+ {
+ if (aTopic == "offline-cache-update-completed") {
+ var cacheUpdate = aSubject.QueryInterface(Ci.nsIOfflineCacheUpdate);
+
+ var uri = cacheUpdate.manifestURI;
+ if (OfflineApps._checkUsage(uri)) {
+ var browser = this._getBrowserForCacheUpdate(cacheUpdate);
+ if (browser) {
+ OfflineApps._warnUsage(browser, cacheUpdate.manifestURI);
+ }
+ }
+ }
+ }
+};
+
+var IndexedDBPromptHelper = {
+ _permissionsPrompt: "indexedDB-permissions-prompt",
+ _permissionsResponse: "indexedDB-permissions-response",
+
+ _quotaPrompt: "indexedDB-quota-prompt",
+ _quotaResponse: "indexedDB-quota-response",
+ _quotaCancel: "indexedDB-quota-cancel",
+
+ _notificationIcon: "indexedDB-notification-icon",
+
+ init:
+ function IndexedDBPromptHelper_init() {
+ Services.obs.addObserver(this, this._permissionsPrompt, false);
+ Services.obs.addObserver(this, this._quotaPrompt, false);
+ Services.obs.addObserver(this, this._quotaCancel, false);
+ },
+
+ uninit:
+ function IndexedDBPromptHelper_uninit() {
+ Services.obs.removeObserver(this, this._permissionsPrompt);
+ Services.obs.removeObserver(this, this._quotaPrompt);
+ Services.obs.removeObserver(this, this._quotaCancel);
+ },
+
+ observe:
+ function IndexedDBPromptHelper_observe(subject, topic, data) {
+ if (topic != this._permissionsPrompt &&
+ topic != this._quotaPrompt &&
+ topic != this._quotaCancel) {
+ throw new Error("Unexpected topic!");
+ }
+
+ var requestor = subject.QueryInterface(Ci.nsIInterfaceRequestor);
+
+ var contentWindow = requestor.getInterface(Ci.nsIDOMWindow);
+ var contentDocument = contentWindow.document;
+ var browserWindow =
+ OfflineApps._getBrowserWindowForContentWindow(contentWindow);
+
+ if (browserWindow != window) {
+ // Must belong to some other window.
+ return;
+ }
+
+ var browser =
+ OfflineApps._getBrowserForContentWindow(browserWindow, contentWindow);
+
+ var host = contentDocument.documentURIObject.asciiHost;
+
+ var message;
+ var responseTopic;
+ if (topic == this._permissionsPrompt) {
+ message = gNavigatorBundle.getFormattedString("offlineApps.available",
+ [ host ]);
+ responseTopic = this._permissionsResponse;
+ }
+ else if (topic == this._quotaPrompt) {
+ message = gNavigatorBundle.getFormattedString("indexedDB.usage",
+ [ host, data ]);
+ responseTopic = this._quotaResponse;
+ }
+ else if (topic == this._quotaCancel) {
+ responseTopic = this._quotaResponse;
+ }
+
+ const hiddenTimeoutDuration = 30000; // 30 seconds
+ const firstTimeoutDuration = 300000; // 5 minutes
+
+ var timeoutId;
+
+ var observer = requestor.getInterface(Ci.nsIObserver);
+
+ var mainAction = {
+ label: gNavigatorBundle.getString("offlineApps.allow"),
+ accessKey: gNavigatorBundle.getString("offlineApps.allowAccessKey"),
+ callback: function() {
+ clearTimeout(timeoutId);
+ observer.observe(null, responseTopic,
+ Ci.nsIPermissionManager.ALLOW_ACTION);
+ }
+ };
+
+ var secondaryActions = [
+ {
+ label: gNavigatorBundle.getString("offlineApps.never"),
+ accessKey: gNavigatorBundle.getString("offlineApps.neverAccessKey"),
+ callback: function() {
+ clearTimeout(timeoutId);
+ observer.observe(null, responseTopic,
+ Ci.nsIPermissionManager.DENY_ACTION);
+ }
+ }
+ ];
+
+ // This will be set to the result of PopupNotifications.show() below, or to
+ // the result of PopupNotifications.getNotification() if this is a
+ // quotaCancel notification.
+ var notification;
+
+ function timeoutNotification() {
+ // Remove the notification.
+ if (notification) {
+ notification.remove();
+ }
+
+ // Clear all of our timeout stuff. We may be called directly, not just
+ // when the timeout actually elapses.
+ clearTimeout(timeoutId);
+
+ // And tell the page that the popup timed out.
+ observer.observe(null, responseTopic,
+ Ci.nsIPermissionManager.UNKNOWN_ACTION);
+ }
+
+ var options = {
+ eventCallback: function(state) {
+ // Don't do anything if the timeout has not been set yet.
+ if (!timeoutId) {
+ return;
+ }
+
+ // If the popup is being dismissed start the short timeout.
+ if (state == "dismissed") {
+ clearTimeout(timeoutId);
+ timeoutId = setTimeout(timeoutNotification, hiddenTimeoutDuration);
+ return;
+ }
+
+ // If the popup is being re-shown then clear the timeout allowing
+ // unlimited waiting.
+ if (state == "shown") {
+ clearTimeout(timeoutId);
+ }
+ }
+ };
+
+ if (topic == this._quotaCancel) {
+ notification = PopupNotifications.getNotification(this._quotaPrompt,
+ browser);
+ timeoutNotification();
+ return;
+ }
+
+ notification = PopupNotifications.show(browser, topic, message,
+ this._notificationIcon, mainAction,
+ secondaryActions, options);
+
+ // Set the timeoutId after the popup has been created, and use the long
+ // timeout value. If the user doesn't notice the popup after this amount of
+ // time then it is most likely not visible and we want to alert the page.
+ timeoutId = setTimeout(timeoutNotification, firstTimeoutDuration);
+ }
+};
+
+function WindowIsClosing()
+{
+ let event = document.createEvent("Events");
+ event.initEvent("WindowIsClosing", true, true);
+ if (!window.dispatchEvent(event))
+ return false;
+
+ if (!closeWindow(false, warnAboutClosingWindow))
+ return false;
+
+ for (let browser of gBrowser.browsers) {
+ let ds = browser.docShell;
+ if (ds.contentViewer && !ds.contentViewer.permitUnload())
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Checks if this is the last full *browser* window around. If it is, this will
+ * be communicated like quitting. Otherwise, we warn about closing multiple tabs.
+ * @returns true if closing can proceed, false if it got cancelled.
+ */
+function warnAboutClosingWindow() {
+ // Popups aren't considered full browser windows.
+ let isPBWindow = PrivateBrowsingUtils.isWindowPrivate(window);
+ if (!isPBWindow && !toolbar.visible)
+ return gBrowser.warnAboutClosingTabs(gBrowser.closingTabsEnum.ALL);
+
+ // Figure out if there's at least one other browser window around.
+ let e = Services.wm.getEnumerator("navigator:browser");
+ let otherPBWindowExists = false;
+ let nonPopupPresent = false;
+ while (e.hasMoreElements()) {
+ let win = e.getNext();
+ if (win != window) {
+ if (isPBWindow && PrivateBrowsingUtils.isWindowPrivate(win))
+ otherPBWindowExists = true;
+ if (win.toolbar.visible)
+ nonPopupPresent = true;
+ // If the current window is not in private browsing mode we don't need to
+ // look for other pb windows, we can leave the loop when finding the
+ // first non-popup window. If however the current window is in private
+ // browsing mode then we need at least one other pb and one non-popup
+ // window to break out early.
+ if ((!isPBWindow || otherPBWindowExists) && nonPopupPresent)
+ break;
+ }
+ }
+
+ if (isPBWindow && !otherPBWindowExists) {
+ let exitingCanceled = Cc["@mozilla.org/supports-PRBool;1"].
+ createInstance(Ci.nsISupportsPRBool);
+ exitingCanceled.data = false;
+ Services.obs.notifyObservers(exitingCanceled,
+ "last-pb-context-exiting",
+ null);
+ if (exitingCanceled.data)
+ return false;
+ }
+
+ if (nonPopupPresent) {
+ return isPBWindow || gBrowser.warnAboutClosingTabs(gBrowser.closingTabsEnum.ALL);
+ }
+
+ let os = Services.obs;
+
+ let closingCanceled = Cc["@mozilla.org/supports-PRBool;1"].
+ createInstance(Ci.nsISupportsPRBool);
+ os.notifyObservers(closingCanceled,
+ "browser-lastwindow-close-requested", null);
+ if (closingCanceled.data)
+ return false;
+
+ os.notifyObservers(null, "browser-lastwindow-close-granted", null);
+
+#ifdef XP_MACOSX
+ // OS X doesn't quit the application when the last window is closed, but keeps
+ // the session alive. Hence don't prompt users to save tabs, but warn about
+ // closing multiple tabs.
+ return isPBWindow || gBrowser.warnAboutClosingTabs(gBrowser.closingTabsEnum.ALL);
+#else
+ return true;
+#endif
+}
+
+var MailIntegration = {
+ sendLinkForWindow: function (aWindow) {
+ this.sendMessage(aWindow.location.href,
+ aWindow.document.title);
+ },
+
+ sendMessage: function (aBody, aSubject) {
+ // generate a mailto url based on the url and the url's title
+ var mailtoUrl = "mailto:";
+ if (aBody) {
+ mailtoUrl += "?body=" + encodeURIComponent(aBody);
+ mailtoUrl += "&subject=" + encodeURIComponent(aSubject);
+ }
+
+ var uri = makeURI(mailtoUrl);
+
+ // now pass this uri to the operating system
+ this._launchExternalUrl(uri);
+ },
+
+ // a generic method which can be used to pass arbitrary urls to the operating
+ // system.
+ // aURL --> a nsIURI which represents the url to launch
+ _launchExternalUrl: function (aURL) {
+ var extProtocolSvc =
+ Cc["@mozilla.org/uriloader/external-protocol-service;1"]
+ .getService(Ci.nsIExternalProtocolService);
+ if (extProtocolSvc)
+ extProtocolSvc.loadUrl(aURL);
+ }
+};
+
+function BrowserOpenAddonsMgr(aView) {
+ if (aView) {
+ let emWindow;
+ let browserWindow;
+
+ var receivePong = function receivePong(aSubject, aTopic, aData) {
+ let browserWin = aSubject.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ if (!emWindow || browserWin == window /* favor the current window */) {
+ emWindow = aSubject;
+ browserWindow = browserWin;
+ }
+ }
+ Services.obs.addObserver(receivePong, "EM-pong", false);
+ Services.obs.notifyObservers(null, "EM-ping", "");
+ Services.obs.removeObserver(receivePong, "EM-pong");
+
+ if (emWindow) {
+ emWindow.loadView(aView);
+ browserWindow.gBrowser.selectedTab =
+ browserWindow.gBrowser._getTabForContentWindow(emWindow);
+ emWindow.focus();
+ return;
+ }
+ }
+
+ var newLoad = !switchToTabHavingURI("about:addons", true);
+
+ if (aView) {
+ // This must be a new load, else the ping/pong would have
+ // found the window above.
+ Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
+ Services.obs.removeObserver(observer, aTopic);
+ aSubject.loadView(aView);
+ }, "EM-loaded", false);
+ }
+}
+
+function AddKeywordForSearchField() {
+ var node = document.popupNode;
+
+ var charset = node.ownerDocument.characterSet;
+
+ var docURI = makeURI(node.ownerDocument.URL,
+ charset);
+
+ var formURI = makeURI(node.form.getAttribute("action"),
+ charset,
+ docURI);
+
+ var spec = formURI.spec;
+
+ var isURLEncoded =
+ (node.form.method.toUpperCase() == "POST"
+ && (node.form.enctype == "application/x-www-form-urlencoded" ||
+ node.form.enctype == ""));
+
+ var title = gNavigatorBundle.getFormattedString("addKeywordTitleAutoFill",
+ [node.ownerDocument.title]);
+ var description = PlacesUIUtils.getDescriptionFromDocument(node.ownerDocument);
+
+ var formData = [];
+
+ function escapeNameValuePair(aName, aValue, aIsFormUrlEncoded) {
+ if (aIsFormUrlEncoded)
+ return escape(aName + "=" + aValue);
+ else
+ return escape(aName) + "=" + escape(aValue);
+ }
+
+ for (let el of node.form.elements) {
+ if (!el.type) // happens with fieldsets
+ continue;
+
+ if (el == node) {
+ formData.push((isURLEncoded) ? escapeNameValuePair(el.name, "%s", true) :
+ // Don't escape "%s", just append
+ escapeNameValuePair(el.name, "", false) + "%s");
+ continue;
+ }
+
+ let type = el.type.toLowerCase();
+
+ if (((el instanceof HTMLInputElement && el.mozIsTextField(true)) ||
+ type == "hidden" || type == "textarea") ||
+ ((type == "checkbox" || type == "radio") && el.checked)) {
+ formData.push(escapeNameValuePair(el.name, el.value, isURLEncoded));
+ } else if (el instanceof HTMLSelectElement && el.selectedIndex >= 0) {
+ for (var j=0; j < el.options.length; j++) {
+ if (el.options[j].selected)
+ formData.push(escapeNameValuePair(el.name, el.options[j].value,
+ isURLEncoded));
+ }
+ }
+ }
+
+ var postData;
+
+ if (isURLEncoded)
+ postData = formData.join("&");
+ else
+ spec += "?" + formData.join("&");
+
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "bookmark"
+ , uri: makeURI(spec)
+ , title: title
+ , description: description
+ , keyword: ""
+ , postData: postData
+ , charSet: charset
+ , hiddenRows: [ "location"
+ , "description"
+ , "tags"
+ , "loadInSidebar" ]
+ }, window);
+}
+
+function SwitchDocumentDirection(aWindow) {
+ // document.dir can also be "auto", in which case it won't change
+ if (aWindow.document.dir == "ltr" || aWindow.document.dir == "") {
+ aWindow.document.dir = "rtl";
+ } else if (aWindow.document.dir == "rtl") {
+ aWindow.document.dir = "ltr";
+ }
+ for (var run = 0; run < aWindow.frames.length; run++)
+ SwitchDocumentDirection(aWindow.frames[run]);
+}
+
+function convertFromUnicode(charset, str)
+{
+ try {
+ var unicodeConverter = Components
+ .classes["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
+ unicodeConverter.charset = charset;
+ str = unicodeConverter.ConvertFromUnicode(str);
+ return str + unicodeConverter.Finish();
+ } catch(ex) {
+ return null;
+ }
+}
+
+/**
+ * Re-open a closed tab.
+ * @param aIndex
+ * The index of the tab (via nsSessionStore.getClosedTabData)
+ * @returns a reference to the reopened tab.
+ */
+function undoCloseTab(aIndex) {
+ // wallpaper patch to prevent an unnecessary blank tab (bug 343895)
+ var blankTabToRemove = null;
+ if (gBrowser.tabs.length == 1 &&
+ !gPrefService.getBoolPref("browser.tabs.autoHide") &&
+ isTabEmpty(gBrowser.selectedTab))
+ blankTabToRemove = gBrowser.selectedTab;
+
+ var tab = null;
+ var ss = Cc["@mozilla.org/browser/sessionstore;1"].
+ getService(Ci.nsISessionStore);
+ if (ss.getClosedTabCount(window) > (aIndex || 0)) {
+ tab = ss.undoCloseTab(window, aIndex || 0);
+
+ if (blankTabToRemove)
+ gBrowser.removeTab(blankTabToRemove);
+ }
+
+ return tab;
+}
+
+/**
+ * Re-open a closed window.
+ * @param aIndex
+ * The index of the window (via nsSessionStore.getClosedWindowData)
+ * @returns a reference to the reopened window.
+ */
+function undoCloseWindow(aIndex) {
+ let ss = Cc["@mozilla.org/browser/sessionstore;1"].
+ getService(Ci.nsISessionStore);
+ let window = null;
+ if (ss.getClosedWindowCount() > (aIndex || 0))
+ window = ss.undoCloseWindow(aIndex || 0);
+
+ return window;
+}
+
+/*
+ * Determines if a tab is "empty", usually used in the context of determining
+ * if it's ok to close the tab.
+ */
+function isTabEmpty(aTab) {
+ if (aTab.hasAttribute("busy"))
+ return false;
+
+ let browser = aTab.linkedBrowser;
+ if (!isBlankPageURL(browser.currentURI.spec))
+ return false;
+
+ // Bug 863515 - Make content.opener checks work in electrolysis.
+ if (!gMultiProcessBrowser && browser.contentWindow.opener)
+ return false;
+
+ if (browser.sessionHistory && browser.sessionHistory.count >= 2)
+ return false;
+
+ return true;
+}
+
+#ifdef MOZ_SERVICES_SYNC
+function BrowserOpenSyncTabs() {
+ switchToTabHavingURI("about:sync-tabs", true);
+}
+#endif
+
+/**
+ * Format a URL
+ * eg:
+ * echo formatURL("https://addons.mozilla.org/%LOCALE%/%APP%/%VERSION%/");
+ * > https://addons.mozilla.org/en-US/firefox/3.0a1/
+ *
+ * Currently supported built-ins are LOCALE, APP, and any value from nsIXULAppInfo, uppercased.
+ */
+function formatURL(aFormat, aIsPref) {
+ var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter);
+ return aIsPref ? formatter.formatURLPref(aFormat) : formatter.formatURL(aFormat);
+}
+
+/**
+ * Utility object to handle manipulations of the identity indicators in the UI
+ */
+var gIdentityHandler = {
+ // Mode strings used to control CSS display
+ IDENTITY_MODE_IDENTIFIED : "verifiedIdentity", // High-quality identity information
+ IDENTITY_MODE_DOMAIN_VERIFIED : "verifiedDomain", // Minimal SSL CA-signed domain verification
+ IDENTITY_MODE_UNKNOWN : "unknownIdentity", // No trusted identity information
+ IDENTITY_MODE_MIXED_CONTENT : "unknownIdentity mixedContent", // SSL with unauthenticated content
+ IDENTITY_MODE_MIXED_ACTIVE_CONTENT : "unknownIdentity mixedContent mixedActiveContent", // SSL with unauthenticated content
+ IDENTITY_MODE_CHROMEUI : "chromeUI", // Part of the product's UI
+
+ // Cache the most recent SSLStatus and Location seen in checkIdentity
+ _lastStatus : null,
+ _lastLocation : null,
+ _mode : "unknownIdentity",
+
+ // smart getters
+ get _encryptionLabel () {
+ delete this._encryptionLabel;
+ this._encryptionLabel = {};
+ this._encryptionLabel[this.IDENTITY_MODE_DOMAIN_VERIFIED] =
+ gNavigatorBundle.getString("identity.encrypted");
+ this._encryptionLabel[this.IDENTITY_MODE_IDENTIFIED] =
+ gNavigatorBundle.getString("identity.encrypted");
+ this._encryptionLabel[this.IDENTITY_MODE_UNKNOWN] =
+ gNavigatorBundle.getString("identity.unencrypted");
+ this._encryptionLabel[this.IDENTITY_MODE_MIXED_CONTENT] =
+ gNavigatorBundle.getString("identity.mixed_content");
+ this._encryptionLabel[this.IDENTITY_MODE_MIXED_ACTIVE_CONTENT] =
+ gNavigatorBundle.getString("identity.mixed_content");
+ return this._encryptionLabel;
+ },
+ get _identityPopup () {
+ delete this._identityPopup;
+ return this._identityPopup = document.getElementById("identity-popup");
+ },
+ get _identityBox () {
+ delete this._identityBox;
+ return this._identityBox = document.getElementById("identity-box");
+ },
+ get _identityPopupContentBox () {
+ delete this._identityPopupContentBox;
+ return this._identityPopupContentBox =
+ document.getElementById("identity-popup-content-box");
+ },
+ get _identityPopupContentHost () {
+ delete this._identityPopupContentHost;
+ return this._identityPopupContentHost =
+ document.getElementById("identity-popup-content-host");
+ },
+ get _identityPopupContentOwner () {
+ delete this._identityPopupContentOwner;
+ return this._identityPopupContentOwner =
+ document.getElementById("identity-popup-content-owner");
+ },
+ get _identityPopupContentSupp () {
+ delete this._identityPopupContentSupp;
+ return this._identityPopupContentSupp =
+ document.getElementById("identity-popup-content-supplemental");
+ },
+ get _identityPopupContentVerif () {
+ delete this._identityPopupContentVerif;
+ return this._identityPopupContentVerif =
+ document.getElementById("identity-popup-content-verifier");
+ },
+ get _identityPopupEncLabel () {
+ delete this._identityPopupEncLabel;
+ return this._identityPopupEncLabel =
+ document.getElementById("identity-popup-encryption-label");
+ },
+ get _identityIconLabel () {
+ delete this._identityIconLabel;
+ return this._identityIconLabel = document.getElementById("identity-icon-label");
+ },
+ get _overrideService () {
+ delete this._overrideService;
+ return this._overrideService = Cc["@mozilla.org/security/certoverride;1"]
+ .getService(Ci.nsICertOverrideService);
+ },
+ get _identityIconCountryLabel () {
+ delete this._identityIconCountryLabel;
+ return this._identityIconCountryLabel = document.getElementById("identity-icon-country-label");
+ },
+ get _identityIcon () {
+ delete this._identityIcon;
+ return this._identityIcon = document.getElementById("page-proxy-favicon");
+ },
+
+ /**
+ * Rebuild cache of the elements that may or may not exist depending
+ * on whether there's a location bar.
+ */
+ _cacheElements : function() {
+ delete this._identityBox;
+ delete this._identityIconLabel;
+ delete this._identityIconCountryLabel;
+ delete this._identityIcon;
+ this._identityBox = document.getElementById("identity-box");
+ this._identityIconLabel = document.getElementById("identity-icon-label");
+ this._identityIconCountryLabel = document.getElementById("identity-icon-country-label");
+ this._identityIcon = document.getElementById("page-proxy-favicon");
+ },
+
+ /**
+ * Handler for mouseclicks on the "More Information" button in the
+ * "identity-popup" panel.
+ */
+ handleMoreInfoClick : function(event) {
+ displaySecurityInfo();
+ event.stopPropagation();
+ },
+
+ /**
+ * Helper to parse out the important parts of _lastStatus (of the SSL cert in
+ * particular) for use in constructing identity UI strings
+ */
+ getIdentityData : function() {
+ var result = {};
+ var status = this._lastStatus.QueryInterface(Components.interfaces.nsISSLStatus);
+ var cert = status.serverCert;
+
+ // Human readable name of Subject
+ result.subjectOrg = cert.organization;
+
+ // SubjectName fields, broken up for individual access
+ if (cert.subjectName) {
+ result.subjectNameFields = {};
+ cert.subjectName.split(",").forEach(function(v) {
+ var field = v.split("=");
+ this[field[0]] = field[1];
+ }, result.subjectNameFields);
+
+ // Call out city, state, and country specifically
+ result.city = result.subjectNameFields.L;
+ result.state = result.subjectNameFields.ST;
+ result.country = result.subjectNameFields.C;
+ }
+
+ // Human readable name of Certificate Authority
+ result.caOrg = cert.issuerOrganization || cert.issuerCommonName;
+ result.cert = cert;
+
+ return result;
+ },
+
+ /**
+ * Determine the identity of the page being displayed by examining its SSL cert
+ * (if available) and, if necessary, update the UI to reflect this. Intended to
+ * be called by onSecurityChange
+ *
+ * @param PRUint32 state
+ * @param JS Object location that mirrors an nsLocation (i.e. has .host and
+ * .hostname and .port)
+ */
+ checkIdentity : function(state, location) {
+ var currentStatus = gBrowser.securityUI
+ .QueryInterface(Components.interfaces.nsISSLStatusProvider)
+ .SSLStatus;
+ this._lastStatus = currentStatus;
+ this._lastLocation = location;
+
+ let nsIWebProgressListener = Ci.nsIWebProgressListener;
+ if (location.protocol == "chrome:" || location.protocol == "about:") {
+ this.setMode(this.IDENTITY_MODE_CHROMEUI);
+ } else if (state & nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL) {
+ this.setMode(this.IDENTITY_MODE_IDENTIFIED);
+ } else if (state & nsIWebProgressListener.STATE_IS_SECURE) {
+ this.setMode(this.IDENTITY_MODE_DOMAIN_VERIFIED);
+ } else if (state & nsIWebProgressListener.STATE_IS_BROKEN) {
+ if ((state & nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT) &&
+ gPrefService.getBoolPref("security.mixed_content.block_active_content")) {
+ this.setMode(this.IDENTITY_MODE_MIXED_ACTIVE_CONTENT);
+ } else {
+ this.setMode(this.IDENTITY_MODE_MIXED_CONTENT);
+ }
+ } else {
+ this.setMode(this.IDENTITY_MODE_UNKNOWN);
+ }
+
+ // Ensure the doorhanger is shown when mixed active content is blocked.
+ if (state & nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT)
+ this.showMixedContentDoorhanger();
+ },
+
+ /**
+ * Display the Mixed Content Blocker doohanger, providing an option
+ * to the user to override mixed content blocking
+ */
+ showMixedContentDoorhanger : function() {
+ // If we've already got an active notification, bail out to avoid showing it repeatedly.
+ if (PopupNotifications.getNotification("mixed-content-blocked", gBrowser.selectedBrowser))
+ return;
+
+ let helplink = document.getElementById("mixed-content-blocked-helplink");
+ helplink.href = Services.urlFormatter.formatURLPref("browser.mixedcontent.warning.infoURL");
+
+ let brandBundle = document.getElementById("bundle_brand");
+ let brandShortName = brandBundle.getString("brandShortName");
+ let messageString = gNavigatorBundle.getFormattedString("mixedContentBlocked.message", [brandShortName]);
+ let action = {
+ label: gNavigatorBundle.getString("mixedContentBlocked.keepBlockingButton.label"),
+ accessKey: gNavigatorBundle.getString("mixedContentBlocked.keepBlockingButton.accesskey"),
+ callback: function() { /* NOP */ }
+ };
+ let secondaryActions = [
+ {
+ label: gNavigatorBundle.getString("mixedContentBlocked.unblock.label"),
+ accessKey: gNavigatorBundle.getString("mixedContentBlocked.unblock.accesskey"),
+ callback: function() {
+ // Reload the page with the content unblocked
+ BrowserReloadWithFlags(nsIWebNavigation.LOAD_FLAGS_ALLOW_MIXED_CONTENT);
+ }
+ }
+ ];
+ let options = {
+ dismissed: true,
+ };
+ PopupNotifications.show(gBrowser.selectedBrowser, "mixed-content-blocked",
+ messageString, "mixed-content-blocked-notification-icon",
+ action, secondaryActions, options);
+ },
+
+ /**
+ * Return the eTLD+1 version of the current hostname
+ */
+ getEffectiveHost : function() {
+ if (!this._IDNService)
+ this._IDNService = Cc["@mozilla.org/network/idn-service;1"]
+ .getService(Ci.nsIIDNService);
+ try {
+ let baseDomain =
+ Services.eTLD.getBaseDomainFromHost(this._lastLocation.hostname);
+ return this._IDNService.convertToDisplayIDN(baseDomain, {});
+ } catch (e) {
+ // If something goes wrong (e.g. hostname is an IP address) just fail back
+ // to the full domain.
+ return this._lastLocation.hostname;
+ }
+ },
+
+ /**
+ * Update the UI to reflect the specified mode, which should be one of the
+ * IDENTITY_MODE_* constants.
+ */
+ setMode : function(newMode) {
+ if (!this._identityBox) {
+ // No identity box means the identity box is not visible, in which
+ // case there's nothing to do.
+ return;
+ }
+
+ this._identityBox.className = newMode;
+ this.setIdentityMessages(newMode);
+
+ // Update the popup too, if it's open
+ if (this._identityPopup.state == "open")
+ this.setPopupMessages(newMode);
+
+ this._mode = newMode;
+ },
+
+ /**
+ * Set up the messages for the primary identity UI based on the specified mode,
+ * and the details of the SSL cert, where applicable
+ *
+ * @param newMode The newly set identity mode. Should be one of the IDENTITY_MODE_* constants.
+ */
+ setIdentityMessages : function(newMode) {
+ let icon_label = "";
+ let tooltip = "";
+ let icon_country_label = "";
+ let icon_labels_dir = "ltr";
+
+ switch (newMode) {
+ case this.IDENTITY_MODE_DOMAIN_VERIFIED: {
+ let iData = this.getIdentityData();
+
+ //Pale Moon: honor browser.identity.ssl_domain_display!
+ switch (gPrefService.getIntPref("browser.identity.ssl_domain_display")) {
+ case 2 : // Show full domain
+ icon_label = this._lastLocation.hostname;
+ break;
+ case 1 : // Show eTLD.
+ icon_label = this.getEffectiveHost();
+ }
+
+ // Verifier is either the CA Org, for a normal cert, or a special string
+ // for certs that are trusted because of a security exception.
+ tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier",
+ [iData.caOrg]);
+
+ // Check whether this site is a security exception. XPConnect does the right
+ // thing here in terms of converting _lastLocation.port from string to int, but
+ // the overrideService doesn't like undefined ports, so make sure we have
+ // something in the default case (bug 432241).
+ // .hostname can return an empty string in some exceptional cases -
+ // hasMatchingOverride does not handle that, so avoid calling it.
+ // Updating the tooltip value in those cases isn't critical.
+ // FIXME: Fixing bug 646690 would probably makes this check unnecessary
+ if (this._lastLocation.hostname &&
+ this._overrideService.hasMatchingOverride(this._lastLocation.hostname,
+ (this._lastLocation.port || 443),
+ iData.cert, {}, {}))
+ tooltip = gNavigatorBundle.getString("identity.identified.verified_by_you");
+ break; }
+ case this.IDENTITY_MODE_IDENTIFIED: {
+ // If it's identified, then we can populate the dialog with credentials
+ let iData = this.getIdentityData();
+ tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier",
+ [iData.caOrg]);
+ icon_label = iData.subjectOrg;
+ if (iData.country)
+ icon_country_label = "(" + iData.country + ")";
+
+ // If the organization name starts with an RTL character, then
+ // swap the positions of the organization and country code labels.
+ // The Unicode ranges reflect the definition of the UCS2_CHAR_IS_BIDI
+ // macro in intl/unicharutil/util/nsBidiUtils.h. When bug 218823 gets
+ // fixed, this test should be replaced by one adhering to the
+ // Unicode Bidirectional Algorithm proper (at the paragraph level).
+ icon_labels_dir = /^[\u0590-\u08ff\ufb1d-\ufdff\ufe70-\ufefc]/.test(icon_label) ?
+ "rtl" : "ltr";
+ break; }
+ case this.IDENTITY_MODE_CHROMEUI:
+ break;
+ default:
+ tooltip = gNavigatorBundle.getString("identity.unknown.tooltip");
+ }
+
+ // Push the appropriate strings out to the UI
+ this._identityBox.tooltipText = tooltip;
+ this._identityIconLabel.value = icon_label;
+ this._identityIconCountryLabel.value = icon_country_label;
+ // Set cropping and direction
+ this._identityIconLabel.crop = icon_country_label ? "end" : "center";
+ this._identityIconLabel.parentNode.style.direction = icon_labels_dir;
+ // Hide completely if the organization label is empty
+ this._identityIconLabel.parentNode.collapsed = icon_label ? false : true;
+ },
+
+ /**
+ * Set up the title and content messages for the identity message popup,
+ * based on the specified mode, and the details of the SSL cert, where
+ * applicable
+ *
+ * @param newMode The newly set identity mode. Should be one of the IDENTITY_MODE_* constants.
+ */
+ setPopupMessages : function(newMode) {
+
+ this._identityPopup.className = newMode;
+ this._identityPopupContentBox.className = newMode;
+
+ // Set the static strings up front
+ this._identityPopupEncLabel.textContent = this._encryptionLabel[newMode];
+
+ // Initialize the optional strings to empty values
+ let supplemental = "";
+ let verifier = "";
+ let host = "";
+ let owner = "";
+
+ switch (newMode) {
+ case this.IDENTITY_MODE_DOMAIN_VERIFIED:
+ host = this.getEffectiveHost();
+ owner = gNavigatorBundle.getString("identity.ownerUnknown2");
+ verifier = this._identityBox.tooltipText;
+ break;
+ case this.IDENTITY_MODE_IDENTIFIED: {
+ // If it's identified, then we can populate the dialog with credentials
+ let iData = this.getIdentityData();
+ host = this.getEffectiveHost();
+ owner = iData.subjectOrg;
+ verifier = this._identityBox.tooltipText;
+
+ // Build an appropriate supplemental block out of whatever location data we have
+ if (iData.city)
+ supplemental += iData.city + "\n";
+ if (iData.state && iData.country)
+ supplemental += gNavigatorBundle.getFormattedString("identity.identified.state_and_country",
+ [iData.state, iData.country]);
+ else if (iData.state) // State only
+ supplemental += iData.state;
+ else if (iData.country) // Country only
+ supplemental += iData.country;
+ break; }
+ }
+
+ // Push the appropriate strings out to the UI
+ this._identityPopupContentHost.textContent = host;
+ this._identityPopupContentOwner.textContent = owner;
+ this._identityPopupContentSupp.textContent = supplemental;
+ this._identityPopupContentVerif.textContent = verifier;
+ },
+
+ hideIdentityPopup : function() {
+ this._identityPopup.hidePopup();
+ },
+
+ /**
+ * Click handler for the identity-box element in primary chrome.
+ */
+ handleIdentityButtonEvent : function(event) {
+ TelemetryStopwatch.start("FX_IDENTITY_POPUP_OPEN_MS");
+ event.stopPropagation();
+
+ if ((event.type == "click" && event.button != 0) ||
+ (event.type == "keypress" && event.charCode != KeyEvent.DOM_VK_SPACE &&
+ event.keyCode != KeyEvent.DOM_VK_RETURN)) {
+ TelemetryStopwatch.cancel("FX_IDENTITY_POPUP_OPEN_MS");
+ return; // Left click, space or enter only
+ }
+
+ // Don't allow left click, space or enter if the location
+ // is chrome UI or the location has been modified.
+ if (this._mode == this.IDENTITY_MODE_CHROMEUI ||
+ gURLBar.getAttribute("pageproxystate") != "valid") {
+ TelemetryStopwatch.cancel("FX_IDENTITY_POPUP_OPEN_MS");
+ return;
+ }
+
+ // Make sure that the display:none style we set in xul is removed now that
+ // the popup is actually needed
+ this._identityPopup.hidden = false;
+
+ // Update the popup strings
+ this.setPopupMessages(this._identityBox.className);
+
+ // Add the "open" attribute to the identity box for styling
+ this._identityBox.setAttribute("open", "true");
+ var self = this;
+ this._identityPopup.addEventListener("popuphidden", function onPopupHidden(e) {
+ e.currentTarget.removeEventListener("popuphidden", onPopupHidden, false);
+ self._identityBox.removeAttribute("open");
+ }, false);
+
+ // Now open the popup, anchored off the primary chrome element
+ this._identityPopup.openPopup(this._identityIcon, "bottomcenter topleft");
+ },
+
+ onPopupShown : function(event) {
+ TelemetryStopwatch.finish("FX_IDENTITY_POPUP_OPEN_MS");
+ document.getElementById('identity-popup-more-info-button').focus();
+ },
+
+ onDragStart: function (event) {
+ if (gURLBar.getAttribute("pageproxystate") != "valid")
+ return;
+
+ var value = content.location.href;
+ var urlString = value + "\n" + content.document.title;
+ var htmlString = "<a href=\"" + value + "\">" + value + "</a>";
+
+ var dt = event.dataTransfer;
+ dt.setData("text/x-moz-url", urlString);
+ dt.setData("text/uri-list", value);
+ dt.setData("text/plain", value);
+ dt.setData("text/html", htmlString);
+ dt.setDragImage(gProxyFavIcon, 16, 16);
+ }
+};
+
+function getNotificationBox(aWindow) {
+ var foundBrowser = gBrowser.getBrowserForDocument(aWindow.document);
+ if (foundBrowser)
+ return gBrowser.getNotificationBox(foundBrowser)
+ return null;
+};
+
+function getTabModalPromptBox(aWindow) {
+ var foundBrowser = gBrowser.getBrowserForDocument(aWindow.document);
+ if (foundBrowser)
+ return gBrowser.getTabModalPromptBox(foundBrowser);
+ return null;
+};
+
+/* DEPRECATED */
+function getBrowser() gBrowser;
+function getNavToolbox() gNavToolbox;
+
+let gPrivateBrowsingUI = {
+ init: function PBUI_init() {
+ // Do nothing for normal windows
+ if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
+ return;
+ }
+
+ // Disable the Clear Recent History... menu item when in PB mode
+ // temporary fix until bug 463607 is fixed
+ document.getElementById("Tools:Sanitize").setAttribute("disabled", "true");
+
+ if (window.location.href == getBrowserURL()) {
+#ifdef XP_MACOSX
+ if (!PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ document.documentElement.setAttribute("drawintitlebar", true);
+ }
+#endif
+
+ // Adjust the window's title
+ let docElement = document.documentElement;
+ if (!PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ docElement.setAttribute("title",
+ docElement.getAttribute("title_privatebrowsing"));
+ docElement.setAttribute("titlemodifier",
+ docElement.getAttribute("titlemodifier_privatebrowsing"));
+ }
+ docElement.setAttribute("privatebrowsingmode",
+ PrivateBrowsingUtils.permanentPrivateBrowsing ? "permanent" : "temporary");
+ gBrowser.updateTitlebar();
+
+ if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ // Adjust the New Window menu entries
+ [
+ { normal: "menu_newNavigator", private: "menu_newPrivateWindow" },
+ { normal: "appmenu_newNavigator", private: "appmenu_newPrivateWindow" },
+ ].forEach(function(menu) {
+ let newWindow = document.getElementById(menu.normal);
+ let newPrivateWindow = document.getElementById(menu.private);
+ if (newWindow && newPrivateWindow) {
+ newPrivateWindow.hidden = true;
+ newWindow.label = newPrivateWindow.label;
+ newWindow.accessKey = newPrivateWindow.accessKey;
+ newWindow.command = newPrivateWindow.command;
+ }
+ });
+ }
+ }
+
+ if (gURLBar &&
+ !PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ // Disable switch to tab autocompletion for private windows
+ // (not for "Always use private browsing" mode)
+ gURLBar.setAttribute("autocompletesearchparam", "");
+ }
+ }
+};
+
+
+/**
+ * Switch to a tab that has a given URI, and focusses its browser window.
+ * If a matching tab is in this window, it will be switched to. Otherwise, other
+ * windows will be searched.
+ *
+ * @param aURI
+ * URI to search for
+ * @param aOpenNew
+ * True to open a new tab and switch to it, if no existing tab is found.
+ * If no suitable window is found, a new one will be opened.
+ * @return True if an existing tab was found, false otherwise
+ */
+function switchToTabHavingURI(aURI, aOpenNew) {
+ // This will switch to the tab in aWindow having aURI, if present.
+ function switchIfURIInWindow(aWindow) {
+ // Only switch to the tab if neither the source and desination window are
+ // private and they are not in permanent private borwsing mode
+ if ((PrivateBrowsingUtils.isWindowPrivate(window) ||
+ PrivateBrowsingUtils.isWindowPrivate(aWindow)) &&
+ !PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ return false;
+ }
+
+ let browsers = aWindow.gBrowser.browsers;
+ for (let i = 0; i < browsers.length; i++) {
+ let browser = browsers[i];
+ if (browser.currentURI.equals(aURI)) {
+ // Focus the matching window & tab
+ aWindow.focus();
+ aWindow.gBrowser.tabContainer.selectedIndex = i;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // This can be passed either nsIURI or a string.
+ if (!(aURI instanceof Ci.nsIURI))
+ aURI = Services.io.newURI(aURI, null, null);
+
+ let isBrowserWindow = !!window.gBrowser;
+
+ // Prioritise this window.
+ if (isBrowserWindow && switchIfURIInWindow(window))
+ return true;
+
+ let winEnum = Services.wm.getEnumerator("navigator:browser");
+ while (winEnum.hasMoreElements()) {
+ let browserWin = winEnum.getNext();
+ // Skip closed (but not yet destroyed) windows,
+ // and the current window (which was checked earlier).
+ if (browserWin.closed || browserWin == window)
+ continue;
+ if (switchIfURIInWindow(browserWin))
+ return true;
+ }
+
+ // No opened tab has that url.
+ if (aOpenNew) {
+ if (isBrowserWindow && isTabEmpty(gBrowser.selectedTab))
+ gBrowser.selectedBrowser.loadURI(aURI.spec);
+ else
+ openUILinkIn(aURI.spec, "tab");
+ }
+
+ return false;
+}
+
+function restoreLastSession() {
+ let ss = Cc["@mozilla.org/browser/sessionstore;1"].
+ getService(Ci.nsISessionStore);
+ ss.restoreLastSession();
+}
+
+var TabContextMenu = {
+ contextTab: null,
+ updateContextMenu: function updateContextMenu(aPopupMenu) {
+ this.contextTab = aPopupMenu.triggerNode.localName == "tab" ?
+ aPopupMenu.triggerNode : gBrowser.selectedTab;
+ let disabled = gBrowser.tabs.length == 1;
+
+ // Enable the "Close Tab" menuitem when the window doesn't close with the last tab.
+ document.getElementById("context_closeTab").disabled =
+ disabled && gBrowser.tabContainer._closeWindowWithLastTab;
+
+ var menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple");
+ for (let menuItem of menuItems)
+ menuItem.disabled = disabled;
+
+ disabled = gBrowser.visibleTabs.length == 1;
+ menuItems = aPopupMenu.getElementsByAttribute("tbattr", "tabbrowser-multiple-visible");
+ for (let menuItem of menuItems)
+ menuItem.disabled = disabled;
+
+ // Session store
+ document.getElementById("context_undoCloseTab").disabled =
+ Cc["@mozilla.org/browser/sessionstore;1"].
+ getService(Ci.nsISessionStore).
+ getClosedTabCount(window) == 0;
+
+ // Only one of pin/unpin should be visible
+ document.getElementById("context_pinTab").hidden = this.contextTab.pinned;
+ document.getElementById("context_unpinTab").hidden = !this.contextTab.pinned;
+
+ // Disable "Close Tabs to the Right" if there are no tabs
+ // following it and hide it when the user rightclicked on a pinned
+ // tab.
+ document.getElementById("context_closeTabsToTheEnd").disabled =
+ gBrowser.getTabsToTheEndFrom(this.contextTab).length == 0;
+ document.getElementById("context_closeTabsToTheEnd").hidden = this.contextTab.pinned;
+
+ // Disable "Close other Tabs" if there is only one unpinned tab and
+ // hide it when the user rightclicked on a pinned tab.
+ let unpinnedTabs = gBrowser.visibleTabs.length - gBrowser._numPinnedTabs;
+ document.getElementById("context_closeOtherTabs").disabled = unpinnedTabs <= 1;
+ document.getElementById("context_closeOtherTabs").hidden = this.contextTab.pinned;
+
+ // Hide "Bookmark All Tabs" for a pinned tab. Update its state if visible.
+ let bookmarkAllTabs = document.getElementById("context_bookmarkAllTabs");
+ bookmarkAllTabs.hidden = this.contextTab.pinned;
+ if (!bookmarkAllTabs.hidden)
+ PlacesCommandHook.updateBookmarkAllTabsCommand();
+ }
+};
+
+XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
+ "resource:///modules/devtools/gDevTools.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "gDevToolsBrowser",
+ "resource:///modules/devtools/gDevTools.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "HUDConsoleUI", function () {
+ return Cu.import("resource:///modules/HUDService.jsm", {}).HUDService.consoleUI;
+});
+
+// Prompt user to restart the browser in safe mode
+function safeModeRestart()
+{
+ // prompt the user to confirm
+ let promptTitle = gNavigatorBundle.getString("safeModeRestartPromptTitle");
+ let promptMessage =
+ gNavigatorBundle.getString("safeModeRestartPromptMessage");
+ let restartText = gNavigatorBundle.getString("safeModeRestartButton");
+ let buttonFlags = (Services.prompt.BUTTON_POS_0 *
+ Services.prompt.BUTTON_TITLE_IS_STRING) +
+ (Services.prompt.BUTTON_POS_1 *
+ Services.prompt.BUTTON_TITLE_CANCEL) +
+ Services.prompt.BUTTON_POS_0_DEFAULT;
+
+ let rv = Services.prompt.confirmEx(window, promptTitle, promptMessage,
+ buttonFlags, restartText, null, null,
+ null, {});
+ if (rv == 0) {
+ Services.startup.restartInSafeMode(Ci.nsIAppStartup.eAttemptQuit);
+ }
+}
+
+/* duplicateTabIn duplicates tab in a place specified by the parameter |where|.
+ *
+ * |where| can be:
+ * "tab" new tab
+ * "tabshifted" same as "tab" but in background if default is to select new
+ * tabs, and vice versa
+ * "window" new window
+ *
+ * delta is the offset to the history entry that you want to load.
+ */
+function duplicateTabIn(aTab, where, delta) {
+ let newTab = Cc['@mozilla.org/browser/sessionstore;1']
+ .getService(Ci.nsISessionStore)
+ .duplicateTab(window, aTab, delta);
+
+ switch (where) {
+ case "window":
+ gBrowser.hideTab(newTab);
+ gBrowser.replaceTabWithWindow(newTab);
+ break;
+ case "tabshifted":
+ // A background tab has been opened, nothing else to do here.
+ break;
+ case "tab":
+ gBrowser.selectedTab = newTab;
+ break;
+ }
+}
+
+function toggleAddonBar() {
+ let addonBar = document.getElementById("addon-bar");
+ setToolbarVisibility(addonBar, addonBar.collapsed);
+}
+
+var Scratchpad = {
+ prefEnabledName: "devtools.scratchpad.enabled",
+
+ openScratchpad: function SP_openScratchpad() {
+ return this.ScratchpadManager.openScratchpad();
+ }
+};
+
+XPCOMUtils.defineLazyGetter(Scratchpad, "ScratchpadManager", function() {
+ let tmp = {};
+ Cu.import("resource:///modules/devtools/scratchpad-manager.jsm", tmp);
+ return tmp.ScratchpadManager;
+});
+
+var ResponsiveUI = {
+ toggle: function RUI_toggle() {
+ this.ResponsiveUIManager.toggle(window, gBrowser.selectedTab);
+ }
+};
+
+XPCOMUtils.defineLazyGetter(ResponsiveUI, "ResponsiveUIManager", function() {
+ let tmp = {};
+ Cu.import("resource:///modules/devtools/responsivedesign.jsm", tmp);
+ return tmp.ResponsiveUIManager;
+});
+
+XPCOMUtils.defineLazyGetter(window, "gShowPageResizers", function () {
+#ifdef XP_WIN
+ // Only show resizers on Windows 2000 and XP
+ return parseFloat(Services.sysinfo.getProperty("version")) < 6;
+#else
+ return false;
+#endif
+});
+
+var MousePosTracker = {
+ _listeners: [],
+ _x: 0,
+ _y: 0,
+ get _windowUtils() {
+ delete this._windowUtils;
+ return this._windowUtils = window.getInterface(Ci.nsIDOMWindowUtils);
+ },
+
+ addListener: function (listener) {
+ if (this._listeners.indexOf(listener) >= 0)
+ return;
+
+ listener._hover = false;
+ this._listeners.push(listener);
+
+ this._callListener(listener);
+ },
+
+ removeListener: function (listener) {
+ var index = this._listeners.indexOf(listener);
+ if (index < 0)
+ return;
+
+ this._listeners.splice(index, 1);
+ },
+
+ handleEvent: function (event) {
+ var fullZoom = this._windowUtils.fullZoom;
+ this._x = event.screenX / fullZoom - window.mozInnerScreenX;
+ this._y = event.screenY / fullZoom - window.mozInnerScreenY;
+
+ this._listeners.forEach(function (listener) {
+ try {
+ this._callListener(listener);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }, this);
+ },
+
+ _callListener: function (listener) {
+ let rect = listener.getMouseTargetRect();
+ let hover = this._x >= rect.left &&
+ this._x <= rect.right &&
+ this._y >= rect.top &&
+ this._y <= rect.bottom;
+
+ if (hover == listener._hover)
+ return;
+
+ listener._hover = hover;
+
+ if (hover) {
+ if (listener.onMouseEnter)
+ listener.onMouseEnter();
+ } else {
+ if (listener.onMouseLeave)
+ listener.onMouseLeave();
+ }
+ }
+};
+
+function focusNextFrame(event) {
+ let fm = Services.focus;
+ let dir = event.shiftKey ? fm.MOVEFOCUS_BACKWARDDOC : fm.MOVEFOCUS_FORWARDDOC;
+ let element = fm.moveFocus(window, null, dir, fm.FLAG_BYKEY);
+ if (element.ownerDocument == document)
+ focusAndSelectUrlBar();
+}
+let BrowserChromeTest = {
+ _cb: null,
+ _ready: false,
+ markAsReady: function () {
+ this._ready = true;
+ if (this._cb) {
+ this._cb();
+ this._cb = null;
+ }
+ },
+ runWhenReady: function (cb) {
+ if (this._ready)
+ cb();
+ else
+ this._cb = cb;
+ }
+};
diff --git a/browser/base/content/browser.xul b/browser/base/content/browser.xul
new file mode 100644
index 000000000..69bf474ca
--- /dev/null
+++ b/browser/base/content/browser.xul
@@ -0,0 +1,1116 @@
+#filter substitution
+<?xml version="1.0"?>
+# -*- Mode: HTML -*-
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?>
+
+# Pale Moon: Restore title to AppMenu windowed use
+<?xml-stylesheet href="chrome://browser/content/browser-title.css" type="text/css"?>
+
+<?xml-stylesheet href="chrome://browser/content/places/places.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/devtools/common.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/baseMenuOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+
+# Pale Moon: padlock feature
+<?xul-overlay href="chrome://browser/content/padlock.xul"?>
+
+# All DTD information is stored in a separate file so that it can be shared by
+# hiddenWindow.xul.
+#include browser-doctype.inc
+
+<window id="main-window"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="gBrowserInit.onLoad()" onunload="gBrowserInit.onUnload()" onclose="return WindowIsClosing();"
+ title="&mainWindow.title;@PRE_RELEASE_SUFFIX@"
+ title_normal="&mainWindow.title;@PRE_RELEASE_SUFFIX@"
+#ifdef XP_MACOSX
+ title_privatebrowsing="&mainWindow.title;@PRE_RELEASE_SUFFIX@&mainWindow.titlemodifiermenuseparator;&mainWindow.titlePrivateBrowsingSuffix;"
+ titledefault="&mainWindow.title;@PRE_RELEASE_SUFFIX@"
+ titlemodifier=""
+ titlemodifier_normal=""
+ titlemodifier_privatebrowsing="&mainWindow.titlePrivateBrowsingSuffix;"
+#else
+ title_privatebrowsing="&mainWindow.titlemodifier;@PRE_RELEASE_SUFFIX@ &mainWindow.titlePrivateBrowsingSuffix;"
+ titlemodifier="&mainWindow.titlemodifier;@PRE_RELEASE_SUFFIX@"
+ titlemodifier_normal="&mainWindow.titlemodifier;@PRE_RELEASE_SUFFIX@"
+ titlemodifier_privatebrowsing="&mainWindow.titlemodifier;@PRE_RELEASE_SUFFIX@ &mainWindow.titlePrivateBrowsingSuffix;"
+#endif
+ titlemenuseparator="&mainWindow.titlemodifiermenuseparator;"
+ lightweightthemes="true"
+ lightweightthemesfooter="browser-bottombox"
+ windowtype="navigator:browser"
+ macanimationtype="document"
+ screenX="4" screenY="4"
+ fullscreenbutton="true"
+ persist="screenX screenY width height sizemode">
+
+# All JS files which are not content (only) dependent that browser.xul
+# wishes to include *must* go into the global-scripts.inc file
+# so that they can be shared by macBrowserOverlay.xul.
+#include global-scripts.inc
+<script type="application/javascript" src="chrome://browser/content/nsContextMenu.js"/>
+
+<script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
+
+<script type="application/javascript" src="chrome://browser/content/places/editBookmarkOverlay.js"/>
+
+# All sets except for popupsets (commands, keys, stringbundles and broadcasters) *must* go into the
+# browser-sets.inc file for sharing with hiddenWindow.xul.
+#define FULL_BROWSER_WINDOW
+#include browser-sets.inc
+#undef FULL_BROWSER_WINDOW
+
+ <popupset id="mainPopupSet">
+ <menupopup id="tabContextMenu"
+ onpopupshowing="if (event.target == this) TabContextMenu.updateContextMenu(this);"
+ onpopuphidden="if (event.target == this) TabContextMenu.contextTab = null;">
+ <menuitem id="context_reloadTab" label="&reloadTab.label;" accesskey="&reloadTab.accesskey;"
+ oncommand="gBrowser.reloadTab(TabContextMenu.contextTab);"/>
+ <menuseparator/>
+ <menuitem id="context_pinTab" label="&pinTab.label;"
+ accesskey="&pinTab.accesskey;"
+ oncommand="gBrowser.pinTab(TabContextMenu.contextTab);"/>
+ <menuitem id="context_unpinTab" label="&unpinTab.label;" hidden="true"
+ accesskey="&unpinTab.accesskey;"
+ oncommand="gBrowser.unpinTab(TabContextMenu.contextTab);"/>
+ <menuitem id="context_openTabInWindow" label="&moveToNewWindow.label;"
+ accesskey="&moveToNewWindow.accesskey;"
+ tbattr="tabbrowser-multiple"
+ oncommand="gBrowser.replaceTabWithWindow(TabContextMenu.contextTab);"/>
+ <menuseparator/>
+ <menuitem id="context_reloadAllTabs" label="&reloadAllTabs.label;" accesskey="&reloadAllTabs.accesskey;"
+ tbattr="tabbrowser-multiple-visible"
+ oncommand="gBrowser.reloadAllTabs();"/>
+ <menuitem id="context_bookmarkAllTabs"
+ label="&bookmarkAllTabs.label;"
+ accesskey="&bookmarkAllTabs.accesskey;"
+ command="Browser:BookmarkAllTabs"/>
+ <menuitem id="context_closeTabsToTheEnd" label="&closeTabsToTheEnd.label;" accesskey="&closeTabsToTheEnd.accesskey;"
+ oncommand="gBrowser.removeTabsToTheEndFrom(TabContextMenu.contextTab);"/>
+ <menuitem id="context_closeOtherTabs" label="&closeOtherTabs.label;" accesskey="&closeOtherTabs.accesskey;"
+ oncommand="gBrowser.removeAllTabsBut(TabContextMenu.contextTab);"/>
+ <menuseparator/>
+ <menuitem id="context_undoCloseTab"
+ label="&undoCloseTab.label;"
+ accesskey="&undoCloseTab.accesskey;"
+ observes="History:UndoCloseTab"/>
+ <menuitem id="context_closeTab" label="&closeTab.label;" accesskey="&closeTab.accesskey;"
+ oncommand="gBrowser.removeTab(TabContextMenu.contextTab, { animate: true });"/>
+ </menupopup>
+
+ <!-- bug 415444/582485: event.stopPropagation is here for the cloned version
+ of this menupopup -->
+ <menupopup id="backForwardMenu"
+ onpopupshowing="return FillHistoryMenu(event.target);"
+ oncommand="gotoHistoryIndex(event); event.stopPropagation();"
+ onclick="checkForMiddleClick(this, event);"/>
+ <tooltip id="aHTMLTooltip" page="true"/>
+
+ <!-- for search and content formfill/pw manager -->
+ <panel type="autocomplete" id="PopupAutoComplete" noautofocus="true" hidden="true"/>
+
+ <!-- for url bar autocomplete -->
+ <panel type="autocomplete-richlistbox" id="PopupAutoCompleteRichResult" noautofocus="true" hidden="true"/>
+
+ <!-- for invalid form error message -->
+ <panel id="invalid-form-popup" type="arrow" orient="vertical" noautofocus="true" hidden="true" level="parent">
+ <description/>
+ </panel>
+
+ <panel id="editBookmarkPanel"
+ type="arrow"
+ footertype="promobox"
+ orient="vertical"
+ ignorekeys="true"
+ consumeoutsideclicks="true"
+ hidden="true"
+ onpopupshown="StarUI.panelShown(event);"
+ aria-labelledby="editBookmarkPanelTitle">
+ <row id="editBookmarkPanelHeader" align="center" hidden="true">
+ <vbox align="center">
+ <image id="editBookmarkPanelStarIcon"/>
+ </vbox>
+ <vbox>
+ <label id="editBookmarkPanelTitle"/>
+ <description id="editBookmarkPanelDescription"/>
+ <hbox>
+ <button id="editBookmarkPanelRemoveButton"
+ class="editBookmarkPanelHeaderButton"
+ oncommand="StarUI.removeBookmarkButtonCommand();"
+ accesskey="&editBookmark.removeBookmark.accessKey;"/>
+ </hbox>
+ </vbox>
+ </row>
+ <vbox id="editBookmarkPanelContent" flex="1" hidden="true"/>
+ <hbox id="editBookmarkPanelBottomButtons" pack="end">
+#ifndef XP_UNIX
+ <button id="editBookmarkPanelDoneButton"
+ class="editBookmarkPanelBottomButton"
+ label="&editBookmark.done.label;"
+ default="true"
+ oncommand="StarUI.panel.hidePopup();"/>
+ <button id="editBookmarkPanelDeleteButton"
+ class="editBookmarkPanelBottomButton"
+ label="&editBookmark.cancel.label;"
+ oncommand="StarUI.cancelButtonOnCommand();"/>
+#else
+ <button id="editBookmarkPanelDeleteButton"
+ class="editBookmarkPanelBottomButton"
+ label="&editBookmark.cancel.label;"
+ oncommand="StarUI.cancelButtonOnCommand();"/>
+ <button id="editBookmarkPanelDoneButton"
+ class="editBookmarkPanelBottomButton"
+ label="&editBookmark.done.label;"
+ default="true"
+ oncommand="StarUI.panel.hidePopup();"/>
+#endif
+ </hbox>
+ </panel>
+
+ <panel id="socialActivatedNotification"
+ type="arrow"
+ hidden="true"
+ consumeoutsideclicks="true"
+ align="start"
+ orient="horizontal"
+ role="alert">
+ <image id="social-activation-icon" class="popup-notification-icon"/>
+ <vbox flex="1">
+ <description id="social-activation-message" class="popup-notification-description">&social.activated.description;</description>
+ <spacer flex="1"/>
+ <hbox pack="start" align="center" class="popup-notification-button-container">
+ <label id="social-undoactivation-button"
+ class="text-link"
+ value="&social.activated.undo.label;"
+ accesskey="&social.activated.undo.accesskey;"
+ onclick="SocialUI.undoActivation(this);"/>
+ <spacer flex="1"/>
+ <button id="social-activation-button"
+ default="true"
+ autofocus="autofocus"
+ label="&social.ok.label;"
+ accesskey="&social.ok.accesskey;"
+ oncommand="SocialUI.activationPanel.hidePopup();"/>
+ </hbox>
+ </vbox>
+ </panel>
+
+ <panel id="social-share-panel"
+ class="social-panel"
+ type="arrow"
+ orient="horizontal"
+ onpopupshowing="SocialShare.onShowing()"
+ onpopuphidden="SocialShare.onHidden()"
+ consumeoutsideclicks="true"
+ hidden="true">
+ <vbox class="social-share-toolbar">
+ <vbox id="social-share-provider-buttons" flex="1"/>
+ </vbox>
+ </panel>
+
+ <panel id="social-notification-panel"
+ class="social-panel"
+ type="arrow"
+ hidden="true"
+ noautofocus="true"/>
+ <panel id="social-flyout-panel"
+ class="social-panel"
+ onpopupshown="SocialFlyout.onShown()"
+ onpopuphidden="SocialFlyout.onHidden()"
+ side="right"
+ type="arrow"
+ hidden="true"
+ flip="slide"
+ rolluponmousewheel="true"
+ consumeoutsideclicks="false"
+ noautofocus="true"
+ position="topcenter topright"/>
+
+ <menupopup id="toolbar-context-menu"
+ onpopupshowing="onViewToolbarsPopupShowing(event);">
+ <menuseparator/>
+ <menuitem command="cmd_ToggleTabsOnTop"
+ type="checkbox"
+ label="&viewTabsOnTop.label;"
+ accesskey="&viewTabsOnTop.accesskey;"/>
+ <menuitem command="cmd_CustomizeToolbars"
+ label="&viewCustomizeToolbar.label;"
+ accesskey="&viewCustomizeToolbar.accesskey;"/>
+ </menupopup>
+
+ <menupopup id="blockedPopupOptions"
+ onpopupshowing="gPopupBlockerObserver.fillPopupList(event);"
+ onpopuphiding="gPopupBlockerObserver.onPopupHiding(event);">
+ <menuitem observes="blockedPopupAllowSite"/>
+ <menuitem observes="blockedPopupEditSettings"/>
+ <menuitem observes="blockedPopupDontShowMessage"/>
+ <menuseparator observes="blockedPopupsSeparator"/>
+ </menupopup>
+
+ <menupopup id="autohide-context"
+ onpopupshowing="FullScreen.getAutohide(this.firstChild);">
+ <menuitem type="checkbox" label="&fullScreenAutohide.label;"
+ accesskey="&fullScreenAutohide.accesskey;"
+ oncommand="FullScreen.setAutohide();"/>
+ <menuseparator/>
+ <menuitem label="&fullScreenExit.label;"
+ accesskey="&fullScreenExit.accesskey;"
+ oncommand="BrowserFullScreen();"/>
+ </menupopup>
+
+ <menupopup id="contentAreaContextMenu" pagemenu="start"
+ onpopupshowing="if (event.target != this)
+ return true;
+ gContextMenu = new nsContextMenu(this, event.shiftKey);
+ if (gContextMenu.shouldDisplay)
+ updateEditUIVisibility();
+ return gContextMenu.shouldDisplay;"
+ onpopuphiding="if (event.target != this)
+ return;
+ gContextMenu.hiding();
+ gContextMenu = null;
+ updateEditUIVisibility();">
+#include browser-context.inc
+ </menupopup>
+
+ <menupopup id="placesContext"/>
+
+
+ <panel id="ctrlTab-panel" class="KUI-panel" hidden="true" norestorefocus="true" level="top">
+ <hbox>
+ <button class="ctrlTab-preview" flex="1"/>
+ <button class="ctrlTab-preview" flex="1"/>
+ <button class="ctrlTab-preview" flex="1"/>
+ <button class="ctrlTab-preview" flex="1"/>
+ <button class="ctrlTab-preview" flex="1"/>
+ <button class="ctrlTab-preview" flex="1"/>
+ </hbox>
+ <hbox pack="center">
+ <button id="ctrlTab-showAll" class="ctrlTab-preview" noicon="true"/>
+ </hbox>
+ </panel>
+
+ <panel id="allTabs-panel" hidden="true" norestorefocus="true" ignorekeys="true"
+ onmouseover="allTabs._updateTabCloseButton(event);">
+ <hbox id="allTabs-meta" align="center">
+ <spacer flex="1"/>
+ <textbox id="allTabs-filter"
+ tooltiptext="&allTabs.filter.emptyText;"
+ type="search"
+ oncommand="allTabs.filter();"/>
+ <spacer flex="1"/>
+ <toolbarbutton class="KUI-panel-closebutton"
+ oncommand="allTabs.close()"
+ tooltiptext="&closeCmd.label;"/>
+ </hbox>
+ <stack id="allTabs-stack">
+ <vbox id="allTabs-container"><hbox/></vbox>
+ <toolbarbutton id="allTabs-tab-close-button"
+ class="tabs-closebutton"
+ oncommand="allTabs.closeTab(event);"
+ tooltiptext="&closeCmd.label;"
+ style="visibility:hidden"/>
+ </stack>
+ </panel>
+
+ <!-- Bookmarks and history tooltip -->
+ <tooltip id="bhTooltip"/>
+
+ <panel id="customizeToolbarSheetPopup"
+ noautohide="true"
+ sheetstyle="&dialog.dimensions;"/>
+
+ <tooltip id="tabbrowser-tab-tooltip" onpopupshowing="gBrowser.createTooltip(event);"/>
+
+ <tooltip id="back-button-tooltip">
+ <label class="tooltip-label" value="&backButton.tooltip;"/>
+#ifdef XP_MACOSX
+ <label class="tooltip-label" value="&backForwardButtonMenuMac.tooltip;"/>
+#else
+ <label class="tooltip-label" value="&backForwardButtonMenu.tooltip;"/>
+#endif
+ </tooltip>
+
+ <tooltip id="forward-button-tooltip">
+ <label class="tooltip-label" value="&forwardButton.tooltip;"/>
+#ifdef XP_MACOSX
+ <label class="tooltip-label" value="&backForwardButtonMenuMac.tooltip;"/>
+#else
+ <label class="tooltip-label" value="&backForwardButtonMenu.tooltip;"/>
+#endif
+ </tooltip>
+
+#include popup-notifications.inc
+
+ </popupset>
+
+#ifdef CAN_DRAW_IN_TITLEBAR
+<vbox id="titlebar">
+ <hbox id="titlebar-content">
+#ifdef MENUBAR_CAN_AUTOHIDE
+ <hbox id="appmenu-button-container">
+ <button id="appmenu-button"
+ type="menu"
+ label="&brandShortName;"
+ style="-moz-user-focus: ignore;">
+#include browser-appmenu.inc
+ </button>
+ </hbox>
+#endif
+ <spacer id="titlebar-spacer" flex="1"/>
+ <hbox id="titlebar-buttonbox-container" align="start">
+ <hbox id="titlebar-buttonbox">
+ <toolbarbutton class="titlebar-button" id="titlebar-min" oncommand="window.minimize();"/>
+ <toolbarbutton class="titlebar-button" id="titlebar-max" oncommand="onTitlebarMaxClick();"/>
+ <toolbarbutton class="titlebar-button" id="titlebar-close" command="cmd_closeWindow"/>
+ </hbox>
+ </hbox>
+ </hbox>
+</vbox>
+#endif
+
+<deck flex="1" id="tab-view-deck">
+<vbox flex="1" id="browser-panel">
+
+ <toolbox id="navigator-toolbox"
+ defaultmode="icons" mode="icons"
+ iconsize="large">
+ <!-- Menu -->
+ <toolbar type="menubar" id="toolbar-menubar" class="chromeclass-menubar" customizable="true"
+ defaultset="menubar-items"
+ mode="icons" iconsize="small" defaulticonsize="small"
+ lockiconsize="true"
+#ifdef MENUBAR_CAN_AUTOHIDE
+ toolbarname="&menubarCmd.label;"
+ accesskey="&menubarCmd.accesskey;"
+#endif
+ context="toolbar-context-menu">
+ <toolbaritem id="menubar-items" align="center">
+# The entire main menubar is placed into browser-menubar.inc, so that it can be shared by
+# hiddenWindow.xul.
+#include browser-menubar.inc
+ </toolbaritem>
+
+#ifdef CAN_DRAW_IN_TITLEBAR
+ <hbox class="titlebar-placeholder" type="appmenu-button" ordinal="0"/>
+ <hbox class="titlebar-placeholder" type="caption-buttons" ordinal="1000"/>
+#endif
+ </toolbar>
+
+ <toolbar id="nav-bar" class="toolbar-primary chromeclass-toolbar"
+ toolbarname="&navbarCmd.label;" accesskey="&navbarCmd.accesskey;"
+ fullscreentoolbar="true" mode="icons" customizable="true"
+ iconsize="large"
+ defaultset="unified-back-forward-button,reload-button,stop-button,home-button,urlbar-container,search-container,bookmarks-menu-button,downloads-button,window-controls"
+ context="toolbar-context-menu">
+
+ <toolbaritem id="unified-back-forward-button" class="chromeclass-toolbar-additional"
+ context="backForwardMenu" removable="true"
+ forwarddisabled="true"
+ title="&backForwardItem.title;">
+ <toolbarbutton id="back-button" class="toolbarbutton-1"
+ label="&backCmd.label;"
+ command="Browser:BackOrBackDuplicate"
+ onclick="checkForMiddleClick(this, event);"
+ tooltip="back-button-tooltip"/>
+ <toolbarbutton id="forward-button" class="toolbarbutton-1"
+ label="&forwardCmd.label;"
+ command="Browser:ForwardOrForwardDuplicate"
+ onclick="checkForMiddleClick(this, event);"
+ tooltip="forward-button-tooltip"/>
+ <dummyobservertarget hidden="true"
+ onbroadcast="if (this.getAttribute('disabled') == 'true')
+ this.parentNode.setAttribute('forwarddisabled', 'true');
+ else
+ this.parentNode.removeAttribute('forwarddisabled');">
+ <observes element="Browser:ForwardOrForwardDuplicate" attribute="disabled"/>
+ </dummyobservertarget>
+ </toolbaritem>
+
+ <toolbarbutton id="reload-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&reloadCmd.label;" removable="true"
+ command="Browser:ReloadOrDuplicate"
+ onclick="checkForMiddleClick(this, event);"
+ tooltiptext="&reloadButton.tooltip;"/>
+
+ <toolbarbutton id="stop-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&stopCmd.label;" removable="true"
+ command="Browser:Stop"
+ tooltiptext="&stopButton.tooltip;"/>
+
+ <toolbarbutton id="home-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ persist="class" removable="true"
+ label="&homeButton.label;"
+ ondragover="homeButtonObserver.onDragOver(event)"
+ ondragenter="homeButtonObserver.onDragOver(event)"
+ ondrop="homeButtonObserver.onDrop(event)"
+ ondragexit="homeButtonObserver.onDragExit(event)"
+ onclick="BrowserGoHome(event);"
+ aboutHomeOverrideTooltip="&abouthome.pageTitle;"/>
+
+ <toolbaritem id="urlbar-container" align="center" flex="400" persist="width" combined="true"
+ title="&locationItem.title;" class="chromeclass-location" removable="true">
+ <textbox id="urlbar" flex="1"
+ placeholder="&urlbar.placeholder2;"
+ type="autocomplete"
+ autocompletesearch="urlinline history"
+ autocompletesearchparam="enable-actions"
+ autocompletepopup="PopupAutoCompleteRichResult"
+ completeselectedindex="true"
+ tabscrolling="true"
+ showcommentcolumn="true"
+ showimagecolumn="true"
+ enablehistory="true"
+ maxrows="6"
+ newlines="stripsurroundingwhitespace"
+ oninput="gBrowser.userTypedValue = this.value;"
+ ontextentered="this.handleCommand(param);"
+ ontextreverted="return this.handleRevert();"
+ pageproxystate="invalid"
+ onfocus="document.getElementById('identity-box').style.MozUserFocus= 'normal'"
+ onblur="setTimeout(function() document.getElementById('identity-box').style.MozUserFocus = '', 0);">
+ <box id="notification-popup-box" hidden="true" align="center">
+ <image id="default-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="identity-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="geo-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="addons-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="indexedDB-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="password-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="webapps-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="plugins-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="web-notifications-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="alert-plugins-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="blocked-plugins-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="plugin-install-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="mixed-content-blocked-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="webRTC-shareDevices-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="webRTC-sharingDevices-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="pointerLock-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="servicesInstall-notification-icon" class="notification-anchor-icon" role="button"/>
+ </box>
+ <!-- Use onclick instead of normal popup= syntax since the popup
+ code fires onmousedown, and hence eats our favicon drag events.
+ We only add the identity-box button to the tab order when the location bar
+ has focus, otherwise pressing F6 focuses it instead of the location bar -->
+ <box id="identity-box" role="button"
+ align="center"
+ onclick="gIdentityHandler.handleIdentityButtonEvent(event);"
+ onkeypress="gIdentityHandler.handleIdentityButtonEvent(event);"
+ ondragstart="gIdentityHandler.onDragStart(event);">
+ <image id="page-proxy-favicon"
+ onclick="PageProxyClickHandler(event);"
+ pageproxystate="invalid"/>
+ <hbox id="identity-icon-labels">
+ <label id="identity-icon-label" class="plain" flex="1"/>
+ <label id="identity-icon-country-label" class="plain"/>
+ </hbox>
+ </box>
+ <box id="urlbar-display-box" align="center">
+ <label id="urlbar-display" value="&urlbar.switchToTab.label;"/>
+ </box>
+ <hbox id="urlbar-icons">
+ <image id="page-report-button"
+ class="urlbar-icon"
+ hidden="true"
+ tooltiptext="&pageReportIcon.tooltip;"
+ onclick="gPopupBlockerObserver.onReportButtonClick(event);"/>
+ <button type="menu"
+ style="-moz-user-focus: none"
+ class="plain urlbar-icon"
+ id="ub-feed-button"
+ collapsed="true"
+ tooltiptext="&feedButton.tooltip;"
+ onclick="return FeedHandler.onFeedButtonPMClick(event);">
+ <menupopup position="after_end"
+ id="ub-feed-menu"
+ onpopupshowing="return FeedHandler.buildFeedList(this);"
+ oncommand="return FeedHandler.subscribeToFeed(null, event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ </button>
+ <image id="star-button"
+ class="urlbar-icon"
+ onclick="BookmarkingUI.onCommand(event);"/>
+ <image id="go-button"
+ class="urlbar-icon"
+ tooltiptext="&goEndCap.tooltip;"
+ onclick="gURLBar.handleCommand(event);"/>
+ </hbox>
+ <toolbarbutton id="urlbar-go-button"
+ class="chromeclass-toolbar-additional"
+ onclick="gURLBar.handleCommand(event);"
+ tooltiptext="&goEndCap.tooltip;"/>
+ <toolbarbutton id="urlbar-reload-button"
+ class="chromeclass-toolbar-additional"
+ command="Browser:ReloadOrDuplicate"
+ onclick="checkForMiddleClick(this, event);"
+ tooltiptext="&reloadButton.tooltip;"/>
+ <toolbarbutton id="urlbar-stop-button"
+ class="chromeclass-toolbar-additional"
+ command="Browser:Stop"
+ tooltiptext="&stopButton.tooltip;"/>
+ </textbox>
+ </toolbaritem>
+
+ <toolbaritem id="search-container" title="&searchItem.title;"
+ align="center" class="chromeclass-toolbar-additional"
+ flex="100" persist="width" removable="true">
+ <searchbar id="searchbar" flex="1"/>
+ </toolbaritem>
+
+ <toolbarbutton id="webrtc-status-button"
+ class="toolbarbutton-1 chromeclass-toolbar-additional"
+ type="menu"
+ hidden="true"
+ orient="horizontal"
+ label="&webrtcIndicatorButton.label;"
+ tooltiptext="&webrtcIndicatorButton.tooltip;">
+ <menupopup onpopupshowing="WebrtcIndicator.fillPopup(this);"
+ onpopuphiding="WebrtcIndicator.clearPopup(this);"
+ oncommand="WebrtcIndicator.menuCommand(event.target);"/>
+ </toolbarbutton>
+
+ <toolbarbutton id="bookmarks-menu-button"
+ class="toolbarbutton-1 chromeclass-toolbar-additional"
+ persist="class"
+ removable="true"
+ type="menu"
+ label="&bookmarksMenuButton.label;"
+ tooltiptext="&bookmarksMenuButton.tooltip;"
+ ondragenter="PlacesMenuDNDHandler.onDragEnter(event);"
+ ondragover="PlacesMenuDNDHandler.onDragOver(event);"
+ ondragleave="PlacesMenuDNDHandler.onDragLeave(event);"
+ ondrop="PlacesMenuDNDHandler.onDrop(event);">
+ <menupopup id="BMB_bookmarksPopup"
+ placespopup="true"
+ context="placesContext"
+ openInTabs="children"
+ oncommand="BookmarksEventHandler.onCommand(event, this.parentNode._placesView);"
+ onclick="BookmarksEventHandler.onClick(event, this.parentNode._placesView);"
+ onpopupshowing="BookmarkingUI.onPopupShowing(event);
+ if (!this.parentNode._placesView)
+ new PlacesMenu(event, 'place:folder=BOOKMARKS_MENU');"
+ tooltip="bhTooltip" popupsinherittooltip="true">
+ <menuitem id="BMB_viewBookmarksToolbar"
+ placesanonid="view-toolbar"
+ toolbarId="PersonalToolbar"
+ type="checkbox"
+ oncommand="onViewToolbarCommand(event)"
+ label="&viewBookmarksToolbar.label;"/>
+ <menuseparator/>
+ <menuitem id="BMB_bookmarksShowAll"
+ label="&palemoon.menu.allBookmarks.label;"
+ command="Browser:ShowAllBookmarks"
+ key="manBookmarkKb"/>
+ <menuseparator/>
+ <menuitem id="BMB_bookmarkThisPage"
+#ifndef XP_MACOSX
+ class="menuitem-iconic"
+#endif
+ label="&bookmarkThisPageCmd.label;"
+ command="Browser:AddBookmarkAs"
+ key="addBookmarkAsKb"/>
+ <menuitem id="BMB_subscribeToPageMenuitem"
+#ifndef XP_MACOSX
+ class="menuitem-iconic"
+#endif
+ label="&subscribeToPageMenuitem.label;"
+ oncommand="return FeedHandler.subscribeToFeed(null, event);"
+ onclick="checkForMiddleClick(this, event);"
+ observes="singleFeedMenuitemState"/>
+ <menu id="BMB_subscribeToPageMenupopup"
+#ifndef XP_MACOSX
+ class="menu-iconic"
+#endif
+ label="&subscribeToPageMenupopup.label;"
+ observes="multipleFeedsMenuState">
+ <menupopup id="BMB_subscribeToPageSubmenuMenupopup"
+ onpopupshowing="return FeedHandler.buildFeedList(event.target);"
+ oncommand="return FeedHandler.subscribeToFeed(null, event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ </menu>
+ <menuseparator/>
+ <menu id="BMB_bookmarksToolbar"
+ placesanonid="toolbar-autohide"
+ class="menu-iconic bookmark-item"
+ label="&personalbarCmd.label;"
+ container="true">
+ <menupopup id="BMB_bookmarksToolbarPopup"
+ placespopup="true"
+ context="placesContext"
+ onpopupshowing="if (!this.parentNode._placesView)
+ new PlacesMenu(event, 'place:folder=TOOLBAR');"/>
+ </menu>
+ <menuseparator/>
+ <!-- Bookmarks menu items -->
+ <menuseparator builder="end"
+ class="hide-if-empty-places-result"/>
+ <menuitem id="BMB_unsortedBookmarks"
+ label="&bookmarksMenuButton.unsorted.label;"
+ oncommand="PlacesCommandHook.showPlacesOrganizer('UnfiledBookmarks');"
+ class="menuitem-iconic"/>
+ </menupopup>
+ </toolbarbutton>
+
+ <toolbarbutton id="social-share-button"
+ class="toolbarbutton-1 chromeclass-toolbar-additional"
+ hidden="true"
+ label="&sharePageCmd.label;"
+ tooltiptext="&sharePageCmd.label;"
+ command="Social:SharePage"/>
+
+ <toolbaritem id="social-toolbar-item"
+ class="chromeclass-toolbar-additional"
+ removable="false"
+ title="&socialToolbar.title;"
+ hidden="true"
+ skipintoolbarset="true"
+ observes="socialActiveBroadcaster">
+ <toolbarbutton id="social-notification-icon" class="default-notification-icon toolbarbutton-1 notification-anchor-icon"
+ oncommand="PopupNotifications._reshowNotifications(this,
+ document.getElementById('social-sidebar-browser'));"/>
+ <toolbarbutton id="social-provider-button"
+ class="toolbarbutton-1"
+ type="menu">
+ <menupopup id="social-statusarea-popup">
+ <menuitem class="social-statusarea-user menuitem-iconic" pack="start" align="center"
+ observes="socialBroadcaster_userDetails"
+ oncommand="SocialUI.showProfile(); document.getElementById('social-statusarea-popup').hidePopup();">
+ <image class="social-statusarea-user-portrait"
+ observes="socialBroadcaster_userDetails"/>
+ <vbox>
+ <label class="social-statusarea-loggedInStatus"
+ observes="socialBroadcaster_userDetails"/>
+ </vbox>
+ </menuitem>
+#ifndef XP_WIN
+ <menuseparator class="social-statusarea-separator"/>
+#endif
+ <menuitem class="social-toggle-sidebar-menuitem"
+ type="checkbox"
+ autocheck="false"
+ command="Social:ToggleSidebar"
+ label="&social.toggleSidebar.label;"
+ accesskey="&social.toggleSidebar.accesskey;"/>
+ <menuitem class="social-toggle-notifications-menuitem"
+ type="checkbox"
+ autocheck="false"
+ command="Social:ToggleNotifications"
+ label="&social.toggleNotifications.label;"
+ accesskey="&social.toggleNotifications.accesskey;"/>
+ <menuitem class="social-toggle-menuitem" command="Social:Toggle"/>
+ <menuseparator/>
+ <menuseparator class="social-provider-menu" hidden="true"/>
+ <menuitem class="social-addons-menuitem" command="Social:Addons"
+ label="&social.addons.label;"/>
+ <menuitem label="&social.learnMore.label;"
+ accesskey="&social.learnMore.accesskey;"
+ oncommand="SocialUI.showLearnMore();"/>
+ </menupopup>
+ </toolbarbutton>
+ <toolbarbutton id="social-mark-button"
+ class="toolbarbutton-1"
+ hidden="true"
+ disabled="true"
+ command="Social:TogglePageMark"/>
+ </toolbaritem>
+
+ <hbox id="window-controls" hidden="true" pack="end">
+ <toolbarbutton id="minimize-button"
+ tooltiptext="&fullScreenMinimize.tooltip;"
+ oncommand="window.minimize();"/>
+
+ <toolbarbutton id="restore-button"
+ tooltiptext="&fullScreenRestore.tooltip;"
+ oncommand="BrowserFullScreen();"/>
+
+ <toolbarbutton id="close-button"
+ tooltiptext="&fullScreenClose.tooltip;"
+ oncommand="BrowserTryToCloseWindow();"/>
+ </hbox>
+ </toolbar>
+
+ <toolbarset id="customToolbars" context="toolbar-context-menu"/>
+
+ <toolbar id="PersonalToolbar"
+ mode="icons" iconsize="small" defaulticonsize="small"
+ lockiconsize="true"
+ class="chromeclass-directories"
+ context="toolbar-context-menu"
+ defaultset="personal-bookmarks"
+ toolbarname="&personalbarCmd.label;" accesskey="&personalbarCmd.accesskey;"
+ collapsed="false"
+ customizable="true">
+ <toolbaritem flex="1" id="personal-bookmarks" title="&bookmarksItem.title;"
+ removable="true">
+ <hbox flex="1"
+ id="PlacesToolbar"
+ context="placesContext"
+ onclick="BookmarksEventHandler.onClick(event, this._placesView);"
+ oncommand="BookmarksEventHandler.onCommand(event, this._placesView);"
+ tooltip="bhTooltip"
+ popupsinherittooltip="true">
+ <toolbarbutton class="bookmark-item bookmarks-toolbar-customize"
+ mousethrough="never"
+ label="&bookmarksToolbarItem.label;"/>
+ <hbox flex="1">
+ <hbox align="center">
+ <image id="PlacesToolbarDropIndicator"
+ mousethrough="always"
+ collapsed="true"/>
+ </hbox>
+ <scrollbox orient="horizontal"
+ id="PlacesToolbarItems"
+ flex="1"/>
+ <toolbarbutton type="menu"
+ id="PlacesChevron"
+ class="chevron"
+ mousethrough="never"
+ collapsed="true"
+ tooltiptext="&bookmarksToolbarChevron.tooltip;"
+ onpopupshowing="document.getElementById('PlacesToolbar')
+ ._placesView._onChevronPopupShowing(event);">
+ <menupopup id="PlacesChevronPopup"
+ placespopup="true"
+ tooltip="bhTooltip" popupsinherittooltip="true"
+ context="placesContext"/>
+ </toolbarbutton>
+ </hbox>
+ </hbox>
+ </toolbaritem>
+ </toolbar>
+
+#ifdef MENUBAR_CAN_AUTOHIDE
+#ifndef CAN_DRAW_IN_TITLEBAR
+#define APPMENU_ON_TABBAR
+#endif
+#endif
+
+
+ <toolbar id="TabsToolbar"
+ class="toolbar-primary"
+ fullscreentoolbar="true"
+ customizable="true"
+ mode="icons" lockmode="true"
+ iconsize="small" defaulticonsize="small" lockiconsize="true"
+ aria-label="&tabsToolbar.label;"
+ context="toolbar-context-menu"
+#ifdef APPMENU_ON_TABBAR
+ defaultset="appmenu-toolbar-button,tabbrowser-tabs,new-tab-button,alltabs-button,tabs-closebutton"
+#else
+ defaultset="tabbrowser-tabs,new-tab-button,alltabs-button,tabs-closebutton"
+#endif
+ collapsed="true">
+
+#ifdef APPMENU_ON_TABBAR
+ <toolbarbutton id="appmenu-toolbar-button"
+ class="chromeclass-toolbar-additional"
+ type="menu"
+ label="&brandShortName;"
+ tooltiptext="&appMenuButton.tooltip;">
+#include browser-appmenu.inc
+ </toolbarbutton>
+#endif
+
+ <tabs id="tabbrowser-tabs"
+ class="tabbrowser-tabs"
+ tabbrowser="content"
+ flex="1"
+ setfocus="false"
+ tooltip="tabbrowser-tab-tooltip"
+ stopwatchid="FX_TAB_CLICK_MS">
+ <tab class="tabbrowser-tab" selected="true" fadein="true"/>
+ </tabs>
+
+ <toolbarbutton id="new-tab-button"
+ class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&tabCmd.label;"
+ command="cmd_newNavigatorTab"
+ onclick="checkForMiddleClick(this, event);"
+ tooltiptext="&newTabButton.tooltip;"
+ ondrop="newTabButtonObserver.onDrop(event)"
+ ondragover="newTabButtonObserver.onDragOver(event)"
+ ondragenter="newTabButtonObserver.onDragOver(event)"
+ ondragexit="newTabButtonObserver.onDragExit(event)"
+ removable="true"/>
+
+ <toolbarbutton id="alltabs-button"
+ class="toolbarbutton-1 chromeclass-toolbar-additional tabs-alltabs-button"
+ type="menu"
+ label="&listAllTabs.label;"
+ tooltiptext="&listAllTabs.label;"
+ removable="true">
+ <menupopup id="alltabs-popup" position="after_end"/>
+ </toolbarbutton>
+
+ <toolbarbutton id="tabs-closebutton"
+ class="close-button tabs-closebutton"
+ command="cmd_close"
+ label="&closeTab.label;"
+ tooltiptext="&closeTab.label;"/>
+
+#ifdef CAN_DRAW_IN_TITLEBAR
+ <hbox class="titlebar-placeholder" type="appmenu-button" ordinal="0"/>
+ <hbox class="titlebar-placeholder" type="caption-buttons" ordinal="1000"/>
+#endif
+ </toolbar>
+
+ <toolbarpalette id="BrowserToolbarPalette">
+
+# Update primaryToolbarButtons in browser/themes/shared/browser.inc when adding
+# or removing default items with the toolbarbutton-1 class.
+
+ <toolbarbutton id="print-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&printButton.label;" command="cmd_print"
+ tooltiptext="&printButton.tooltip;"/>
+
+ <!-- This is a placeholder for the Downloads Indicator. It is visible
+ during the customization of the toolbar, in the palette, and before
+ the Downloads Indicator overlay is loaded. -->
+ <toolbarbutton id="downloads-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ oncommand="DownloadsIndicatorView.onCommand(event);"
+ ondrop="DownloadsIndicatorView.onDrop(event);"
+ ondragover="DownloadsIndicatorView.onDragOver(event);"
+ ondragenter="DownloadsIndicatorView.onDragOver(event);"
+ label="&downloads.label;"
+ tooltiptext="&downloads.tooltip;"/>
+
+ <toolbarbutton id="history-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ observes="viewHistorySidebar" label="&historyButton.label;"
+ tooltiptext="&historyButton.tooltip;"/>
+
+ <toolbarbutton id="bookmarks-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ observes="viewBookmarksSidebar"
+ tooltiptext="&bookmarksButton.tooltip;"
+ ondrop="bookmarksButtonObserver.onDrop(event)"
+ ondragover="bookmarksButtonObserver.onDragOver(event)"
+ ondragenter="bookmarksButtonObserver.onDragOver(event)"
+ ondragexit="bookmarksButtonObserver.onDragExit(event)"/>
+
+ <toolbarbutton id="new-window-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&newNavigatorCmd.label;"
+ command="key_newNavigator"
+ tooltiptext="&newWindowButton.tooltip;"
+ ondrop="newWindowButtonObserver.onDrop(event)"
+ ondragover="newWindowButtonObserver.onDragOver(event)"
+ ondragenter="newWindowButtonObserver.onDragOver(event)"
+ ondragexit="newWindowButtonObserver.onDragExit(event)"/>
+
+ <toolbarbutton id="fullscreen-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ observes="View:FullScreen"
+ type="checkbox"
+ label="&fullScreenCmd.label;"
+ tooltiptext="&fullScreenButton.tooltip;"/>
+
+ <toolbaritem id="zoom-controls" class="chromeclass-toolbar-additional"
+ title="&zoomControls.label;">
+ <toolbarbutton id="zoom-out-button" class="toolbarbutton-1"
+ label="&fullZoomReduceCmd.label;"
+ command="cmd_fullZoomReduce"
+ tooltiptext="&zoomOutButton.tooltip;"/>
+ <toolbarbutton id="zoom-in-button" class="toolbarbutton-1"
+ label="&fullZoomEnlargeCmd.label;"
+ command="cmd_fullZoomEnlarge"
+ tooltiptext="&zoomInButton.tooltip;"/>
+ </toolbaritem>
+
+ <toolbarbutton id="feed-button"
+ type="menu"
+ class="toolbarbutton-1 chromeclass-toolbar-additional"
+ disabled="true"
+ label="&feedButton.label;"
+ tooltiptext="&feedButton.tooltip;"
+ onclick="return FeedHandler.onFeedButtonClick(event);">
+ <menupopup position="after_end"
+ id="feed-menu"
+ onpopupshowing="return FeedHandler.buildFeedList(this);"
+ oncommand="return FeedHandler.subscribeToFeed(null, event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ </toolbarbutton>
+
+ <toolbarbutton id="cut-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&cutCmd.label;"
+ command="cmd_cut"
+ tooltiptext="&cutButton.tooltip;"/>
+
+ <toolbarbutton id="copy-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&copyCmd.label;"
+ command="cmd_copy"
+ tooltiptext="&copyButton.tooltip;"/>
+
+ <toolbarbutton id="paste-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&pasteCmd.label;"
+ command="cmd_paste"
+ tooltiptext="&pasteButton.tooltip;"/>
+
+#ifdef MOZ_SERVICES_SYNC
+ <toolbarbutton id="sync-button"
+ class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&syncToolbarButton.label;"
+ oncommand="gSyncUI.handleToolbarButton()"/>
+#endif
+
+ <toolbaritem id="navigator-throbber" title="&throbberItem.title;" align="center" pack="center"
+ mousethrough="always">
+ <image/>
+ </toolbaritem>
+ </toolbarpalette>
+ </toolbox>
+
+ <hbox id="fullscr-toggler" collapsed="true"/>
+
+ <hbox flex="1" id="browser">
+ <vbox id="browser-border-start" hidden="true" layer="true"/>
+ <vbox id="sidebar-box" hidden="true" class="chromeclass-extrachrome">
+ <sidebarheader id="sidebar-header" align="center">
+ <label id="sidebar-title" persist="value" flex="1" crop="end" control="sidebar"/>
+ <image id="sidebar-throbber"/>
+ <toolbarbutton class="tabs-closebutton" tooltiptext="&sidebarCloseButton.tooltip;" oncommand="toggleSidebar();"/>
+ </sidebarheader>
+ <browser id="sidebar" flex="1" autoscroll="false" disablehistory="true"
+ style="min-width: 14em; width: 18em; max-width: 36em;"/>
+ </vbox>
+
+ <splitter id="sidebar-splitter" class="chromeclass-extrachrome sidebar-splitter" hidden="true"/>
+ <vbox id="appcontent" flex="1">
+ <tabbrowser id="content" disablehistory="true"
+ flex="1" contenttooltip="aHTMLTooltip"
+ tabcontainer="tabbrowser-tabs"
+ contentcontextmenu="contentAreaContextMenu"
+ autocompletepopup="PopupAutoComplete"/>
+ <chatbar id="pinnedchats" layer="true" mousethrough="always" hidden="true"/>
+ <statuspanel id="statusbar-display" inactive="true"/>
+ </vbox>
+ <splitter id="social-sidebar-splitter"
+ class="chromeclass-extrachrome sidebar-splitter"
+ observes="socialSidebarBroadcaster"/>
+ <vbox id="social-sidebar-box"
+ class="chromeclass-extrachrome"
+ observes="socialSidebarBroadcaster"
+ persist="width">
+ <browser id="social-sidebar-browser"
+ type="content"
+ context="contentAreaContextMenu"
+ disableglobalhistory="true"
+ tooltip="aHTMLTooltip"
+ popupnotificationanchor="social-notification-icon"
+ flex="1"
+ style="min-width: 14em; width: 18em; max-width: 36em;"/>
+ </vbox>
+ <vbox id="browser-border-end" hidden="true" layer="true"/>
+ </hbox>
+
+ <hbox id="full-screen-warning-container" hidden="true" fadeout="true">
+ <hbox style="width: 100%;" pack="center"> <!-- Inner hbox needed due to bug 579776. -->
+ <vbox id="full-screen-warning-message" align="center">
+ <description id="full-screen-domain-text"/>
+ <description class="full-screen-description" value="&fullscreenExitHint.value;"/>
+ <vbox id="full-screen-approval-pane" align="center">
+ <description class="full-screen-description" value="&fullscreenApproval.value;"/>
+ <hbox>
+ <button label="&fullscreenAllowButton.label;"
+ oncommand="FullScreen.setFullscreenAllowed(true);"
+ class="full-screen-approval-button"/>
+ <button label="&fullscreenExitButton.label;"
+ oncommand="FullScreen.setFullscreenAllowed(false);"
+ class="full-screen-approval-button"/>
+ </hbox>
+ <checkbox id="full-screen-remember-decision"/>
+ </vbox>
+ </vbox>
+ </hbox>
+ </hbox>
+
+ <vbox id="browser-bottombox" layer="true">
+ <notificationbox id="global-notificationbox"/>
+ <toolbar id="developer-toolbar"
+ class="devtools-toolbar"
+ hidden="true">
+#ifdef XP_MACOSX
+ <toolbarbutton id="developer-toolbar-closebutton"
+ class="devtools-closebutton"
+ oncommand="DeveloperToolbar.hide();"
+ tooltiptext="&devToolbarCloseButton.tooltiptext;"/>
+#endif
+ <stack class="gclitoolbar-stack-node" flex="1">
+ <textbox class="gclitoolbar-input-node" rows="1"/>
+ <hbox class="gclitoolbar-complete-node"/>
+ </stack>
+ <toolbarbutton id="developer-toolbar-toolbox-button"
+ class="developer-toolbar-button"
+ observes="devtoolsMenuBroadcaster_DevToolbox"
+ tooltiptext="&devToolbarToolsButton.tooltip;"/>
+#ifndef XP_MACOSX
+ <toolbarbutton id="developer-toolbar-closebutton"
+ class="devtools-closebutton"
+ oncommand="DeveloperToolbar.hide();"
+ tooltiptext="&devToolbarCloseButton.tooltiptext;"/>
+#endif
+ </toolbar>
+
+ <toolbar id="addon-bar"
+ toolbarname="&palemoon.menu.statusBar.label;" accesskey="&palemoon.menu.statusBar.accesskey;"
+ collapsed="true"
+ class="toolbar-primary chromeclass-toolbar"
+ context="toolbar-context-menu" toolboxid="navigator-toolbox"
+ mode="icons" iconsize="small" defaulticonsize="small"
+ lockiconsize="true"
+ defaultset="addonbar-closebutton,spring,status-bar"
+ customizable="true"
+ key="key_toggleAddonBar">
+ <toolbarbutton id="addonbar-closebutton"
+ tooltiptext="&addonBarCloseButton.tooltip;"
+ oncommand="setToolbarVisibility(this.parentNode, false);"/>
+ <statusbar id="status-bar" ordinal="1000"/>
+ </toolbar>
+ </vbox>
+
+#ifndef XP_UNIX
+ <svg:svg height="0">
+ <svg:clipPath id="windows-keyhole-forward-clip-path" clipPathUnits="objectBoundingBox">
+ <svg:path d="M 0,0 C 0.16,0.11 0.28,0.29 0.28,0.5 0.28,0.71 0.16,0.89 0,1 L 1,1 1,0 0,0 z"/>
+ </svg:clipPath>
+ <svg:clipPath id="windows-urlbar-back-button-clip-path" clipPathUnits="userSpaceOnUse">
+ <svg:path d="M 0,0 0,7.8 C 2.5,11 4,14 4,18 4,22 2.5,25 0,28 l 0,22 10000,0 0,-50 L 0,0 z"/>
+ </svg:clipPath>
+ </svg:svg>
+#endif
+#ifdef XP_MACOSX
+ <svg:svg height="0">
+ <svg:clipPath id="osx-keyhole-forward-clip-path" clipPathUnits="objectBoundingBox">
+ <svg:path d="M 0,0 C 0.15,0.12 0.25,0.3 0.25,0.5 0.25,0.7 0.15,0.88 0,1 L 1,1 1,0 0,0 z"/>
+ </svg:clipPath>
+ <svg:clipPath id="osx-urlbar-back-button-clip-path" clipPathUnits="userSpaceOnUse">
+ <svg:path d="m 0,-5 0,4.03 C 3.6,1.8 6,6.1 6,11 6,16 3.6,20 0,23 l 0,27 10000,0 0,-55 L 0,-5 z"/>
+ </svg:clipPath>
+ <svg:clipPath id="osx-tab-ontop-left-curve-clip-path" clipPathUnits="userSpaceOnUse">
+ <svg:path d="M 9,0 C 7.3,0 6,1.3 6,3 l 0,14 c 0,3 -2.2,5 -5,5 l -1,0 0,1 12,0 0,-1 0,-19 0,-3 -3,0 z"/>
+ </svg:clipPath>
+ <svg:clipPath id="osx-tab-ontop-right-curve-clip-path" clipPathUnits="userSpaceOnUse">
+ <svg:path d="m 0,0 0,3 0,19 0,1 12,0 0,-1 -1,0 C 8.2,22 6,20 6,17 L 6,3 C 6,1.3 4.7,0 3,0 L 0,0 z"/>
+ </svg:clipPath>
+ <svg:clipPath id="osx-tab-onbottom-left-curve-clip-path" clipPathUnits="userSpaceOnUse">
+ <svg:path d="m 0,0 0,1 1,0 c 2.8,0 5,2.2 5,5 l 0,14 c 0,2 1.3,3 3,3 l 3,0 0,-3 L 12,1 12,0 0,0 z"/>
+ </svg:clipPath>
+ <svg:clipPath id="osx-tab-onbottom-right-curve-clip-path" clipPathUnits="userSpaceOnUse">
+ <svg:path d="m 0,0 0,1 0,19 0,3 3,0 c 1.7,0 3,-1 3,-3 L 6,6 C 6,3.2 8.2,1 11,1 L 12,1 12,0 0,0 z"/>
+ </svg:clipPath>
+ </svg:svg>
+#endif
+
+</vbox>
+# <iframe id="tab-view"> is dynamically appended as the 2nd child of #tab-view-deck.
+# Introducing the iframe dynamically, as needed, was found to be better than
+# starting with an empty iframe here in browser.xul from a Ts standpoint.
+</deck>
+
+</window>
diff --git a/browser/base/content/browserMountPoints.inc b/browser/base/content/browserMountPoints.inc
new file mode 100644
index 000000000..e4315b04a
--- /dev/null
+++ b/browser/base/content/browserMountPoints.inc
@@ -0,0 +1,12 @@
+<stringbundleset id="stringbundleset"/>
+
+<commandset id="mainCommandSet"/>
+<commandset id="baseMenuCommandSet"/>
+<commandset id="placesCommands"/>
+
+<broadcasterset id="mainBroadcasterSet"/>
+
+<keyset id="mainKeyset"/>
+<keyset id="baseMenuKeyset"/>
+
+<menubar id="main-menubar"/> \ No newline at end of file
diff --git a/browser/base/content/chatWindow.xul b/browser/base/content/chatWindow.xul
new file mode 100644
index 000000000..4b240acf6
--- /dev/null
+++ b/browser/base/content/chatWindow.xul
@@ -0,0 +1,109 @@
+#filter substitution
+<?xml version="1.0"?>
+
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://browser/content/browser.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<?xul-overlay href="chrome://browser/content/baseMenuOverlay.xul"?>
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+
+#include browser-doctype.inc
+
+<window id="chat-window"
+ windowtype="Social:Chat"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&mainWindow.title;@PRE_RELEASE_SUFFIX@"
+ onload="gChatWindow.onLoad();"
+ onunload="gChatWindow.onUnload();"
+ macanimationtype="document"
+ fullscreenbutton="true"
+# width and height are also used in socialchat.xml: chatbar dragend handler
+ width="400px"
+ height="420px"
+ persist="screenX screenY width height sizemode">
+
+ <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
+ <script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
+ <script type="application/javascript" src="chrome://browser/content/nsContextMenu.js"/>
+
+#include global-scripts.inc
+
+<script type="application/javascript">
+
+var gChatWindow = {
+ // cargo-culted from browser.js for nonBrowserStartup, but we're slightly
+ // different what what we need to leave enabled
+ onLoad: function() {
+ // Disable inappropriate commands / submenus
+ var disabledItems = ['Browser:SavePage',
+ 'Browser:SendLink', 'cmd_pageSetup', 'cmd_print', 'cmd_find', 'cmd_findAgain',
+ 'viewToolbarsMenu', 'viewSidebarMenuMenu',
+ 'viewFullZoomMenu', 'pageStyleMenu', 'charsetMenu',
+ 'viewHistorySidebar', 'Browser:AddBookmarkAs', 'Browser:BookmarkAllTabs',
+ 'Browser:ToggleTabView', 'Browser:ToggleAddonBar'];
+
+ for (let disabledItem of disabledItems) {
+ document.getElementById(disabledItem).setAttribute("disabled", "true");
+ }
+
+ // initialise the offline listener
+ BrowserOffline.init();
+ },
+
+ onUnload: function() {
+ BrowserOffline.uninit();
+ }
+}
+
+// define a popupnotifications handler for this window. we don't use
+// an iconbox here, and only support the browser frame for chat.
+XPCOMUtils.defineLazyGetter(this, "PopupNotifications", function () {
+ let tmp = {};
+ Cu.import("resource://gre/modules/PopupNotifications.jsm", tmp);
+ try {
+ return new tmp.PopupNotifications(document.getElementById("chatter").content,
+ document.getElementById("notification-popup"),
+ null);
+ } catch (ex) {
+ console.error(ex);
+ return null;
+ }
+});
+
+</script>
+
+#include browser-sets.inc
+
+#ifdef XP_MACOSX
+#include browser-menubar.inc
+#endif
+
+ <popupset id="mainPopupSet">
+ <tooltip id="aHTMLTooltip" page="true"/>
+ <menupopup id="contentAreaContextMenu" pagemenu="start"
+ onpopupshowing="if (event.target != this)
+ return true;
+ gContextMenu = new nsContextMenu(this, event.shiftKey);
+ if (gContextMenu.shouldDisplay)
+ document.popupNode = this.triggerNode;
+ return gContextMenu.shouldDisplay;"
+ onpopuphiding="if (event.target != this)
+ return;
+ gContextMenu.hiding();
+ gContextMenu = null;">
+#include browser-context.inc
+ </menupopup>
+
+#include popup-notifications.inc
+
+ </popupset>
+
+ <commandset id="editMenuCommands"/>
+ <chatbox id="chatter" flex="1"/>
+</window>
diff --git a/browser/base/content/content.js b/browser/base/content/content.js
new file mode 100644
index 000000000..314d9eb98
--- /dev/null
+++ b/browser/base/content/content.js
@@ -0,0 +1,45 @@
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+let Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this,
+ "LoginManagerContent", "resource://gre/modules/LoginManagerContent.jsm");
+
+// Bug 671101 - directly using webNavigation in this context
+// causes docshells to leak
+this.__defineGetter__("webNavigation", function () {
+ return docShell.QueryInterface(Ci.nsIWebNavigation);
+});
+
+addMessageListener("WebNavigation:LoadURI", function (message) {
+ let flags = message.json.flags || webNavigation.LOAD_FLAGS_NONE;
+
+ webNavigation.loadURI(message.json.uri, flags, null, null, null);
+});
+
+addMessageListener("Browser:HideSessionRestoreButton", function (message) {
+ // Hide session restore button on about:home
+ let doc = content.document;
+ let container;
+ if (doc.documentURI.toLowerCase() == "about:home" &&
+ (container = doc.getElementById("sessionRestoreContainer"))){
+ container.hidden = true;
+ }
+});
+
+addEventListener("DOMContentLoaded", function(event) {
+ LoginManagerContent.onContentLoaded(event);
+});
+addEventListener("DOMAutoComplete", function(event) {
+ LoginManagerContent.onUsernameInput(event);
+});
+addEventListener("blur", function(event) {
+ LoginManagerContent.onUsernameInput(event);
+});
diff --git a/browser/base/content/downloadManagerOverlay.xul b/browser/base/content/downloadManagerOverlay.xul
new file mode 100644
index 000000000..9987820cb
--- /dev/null
+++ b/browser/base/content/downloadManagerOverlay.xul
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?>
+
+<overlay id="downloadManagerOverlay"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<window id="downloadManager">
+
+#include browserMountPoints.inc
+
+<script type="application/javascript"><![CDATA[
+ window.addEventListener("load", function(event) {
+ // Bug 405696: Map Edit -> Find command to the download manager's command
+ var findMenuItem = document.getElementById("menu_find");
+ findMenuItem.setAttribute("command", "cmd_findDownload");
+ findMenuItem.setAttribute("key", "key_findDownload");
+
+ // Bug 429614: Map Edit -> Select All command to download manager's command
+ let selectAllMenuItem = document.getElementById("menu_selectAll");
+ selectAllMenuItem.setAttribute("command", "cmd_selectAllDownloads");
+ selectAllMenuItem.setAttribute("key", "key_selectAllDownloads");
+ }, false);
+]]></script>
+
+</window>
+
+</overlay>
diff --git a/browser/base/content/global-scripts.inc b/browser/base/content/global-scripts.inc
new file mode 100644
index 000000000..b4de574ae
--- /dev/null
+++ b/browser/base/content/global-scripts.inc
@@ -0,0 +1,13 @@
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<script type="application/javascript" src="chrome://global/content/printUtils.js"/>
+<script type="application/javascript" src="chrome://global/content/viewZoomOverlay.js"/>
+<script type="application/javascript" src="chrome://browser/content/places/browserPlacesViews.js"/>
+<script type="application/javascript" src="chrome://browser/content/browser.js"/>
+<script type="application/javascript" src="chrome://browser/content/downloads/downloads.js"/>
+<script type="application/javascript" src="chrome://browser/content/downloads/indicator.js"/>
+<script type="application/javascript" src="chrome://global/content/inlineSpellCheckUI.js"/>
+<script type="application/javascript" src="chrome://global/content/viewSourceUtils.js"/>
diff --git a/browser/base/content/hiddenWindow.xul b/browser/base/content/hiddenWindow.xul
new file mode 100644
index 000000000..bf201fd60
--- /dev/null
+++ b/browser/base/content/hiddenWindow.xul
@@ -0,0 +1,19 @@
+<?xml version="1.0"?>
+# -*- Mode: HTML -*-
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifdef XP_MACOSX
+<?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?>
+
+<window id="main-window"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+#include browserMountPoints.inc
+
+</window>
+
+#endif
diff --git a/browser/base/content/highlighter.css b/browser/base/content/highlighter.css
new file mode 100644
index 000000000..8fb9d8085
--- /dev/null
+++ b/browser/base/content/highlighter.css
@@ -0,0 +1,105 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+.highlighter-container {
+ pointer-events: none;
+}
+
+.highlighter-controls {
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+.highlighter-outline-container {
+ overflow: hidden;
+ position: relative;
+}
+
+.highlighter-outline {
+ position: absolute;
+}
+
+.highlighter-outline[hidden] {
+ opacity: 0;
+ pointer-events: none;
+ display: -moz-box;
+}
+
+.highlighter-outline:not([disable-transitions]) {
+ transition-property: opacity, top, left, width, height;
+ transition-duration: 0.1s;
+ transition-timing-function: linear;
+}
+
+/*
+ * Node Infobar
+ */
+
+.highlighter-nodeinfobar-container {
+ position: absolute;
+ max-width: 95%;
+}
+
+.highlighter-nodeinfobar-container[hidden] {
+ opacity: 0;
+ pointer-events: none;
+ display: -moz-box;
+}
+
+.highlighter-nodeinfobar-container:not([disable-transitions]),
+.highlighter-nodeinfobar-container[disable-transitions][force-transitions] {
+ transition-property: transform, opacity, top, left;
+ transition-duration: 0.1s;
+ transition-timing-function: linear;
+}
+
+.highlighter-nodeinfobar-text {
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ direction: ltr;
+}
+
+.highlighter-nodeinfobar-button > .toolbarbutton-text {
+ display: none;
+}
+
+.highlighter-nodeinfobar-container:not([locked]):not(:hover) > .highlighter-nodeinfobar > .highlighter-nodeinfobar-button {
+ visibility: hidden;
+}
+
+.highlighter-nodeinfobar-container[locked] > .highlighter-nodeinfobar,
+.highlighter-nodeinfobar-container:not([locked]):hover > .highlighter-nodeinfobar {
+ pointer-events: auto;
+}
+
+html|*.highlighter-nodeinfobar-id,
+html|*.highlighter-nodeinfobar-classes,
+html|*.highlighter-nodeinfobar-pseudo-classes,
+html|*.highlighter-nodeinfobar-tagname {
+ -moz-user-select: text;
+ -moz-user-focus: normal;
+ cursor: text;
+}
+
+.highlighter-nodeinfobar-arrow {
+ display: none;
+}
+
+.highlighter-nodeinfobar-container[position="top"]:not([hide-arrow]) > .highlighter-nodeinfobar-arrow-bottom {
+ display: block;
+}
+
+.highlighter-nodeinfobar-container[position="bottom"]:not([hide-arrow]) > .highlighter-nodeinfobar-arrow-top {
+ display: block;
+}
+
+.highlighter-nodeinfobar-container[disabled] {
+ visibility: hidden;
+}
+
+html|*.highlighter-nodeinfobar-tagname {
+ text-transform: lowercase;
+}
diff --git a/browser/base/content/imagedocument.png b/browser/base/content/imagedocument.png
new file mode 100644
index 000000000..ff4f21f9a
--- /dev/null
+++ b/browser/base/content/imagedocument.png
Binary files differ
diff --git a/browser/base/content/jsConsoleOverlay.xul b/browser/base/content/jsConsoleOverlay.xul
new file mode 100644
index 000000000..1bc518d4f
--- /dev/null
+++ b/browser/base/content/jsConsoleOverlay.xul
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?>
+
+<overlay id="jsConsoleOverlay"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<window id="JSConsoleWindow">
+
+#include browserMountPoints.inc
+
+</window>
+
+</overlay>
diff --git a/browser/base/content/macBrowserOverlay.xul b/browser/base/content/macBrowserOverlay.xul
new file mode 100644
index 000000000..a4d583e16
--- /dev/null
+++ b/browser/base/content/macBrowserOverlay.xul
@@ -0,0 +1,64 @@
+<?xml version="1.0"?>
+# -*- Mode: HTML -*-
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/places/places.css" type="text/css"?>
+
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/baseMenuOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+
+# All DTD information is stored in a separate file so that it can be shared by
+# hiddenWindow.xul.
+#include browser-doctype.inc
+
+<overlay id="hidden-overlay"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+# All JS files which are not content (only) dependent that browser.xul
+# wishes to include *must* go into the global-scripts.inc file
+# so that they can be shared by this overlay.
+#include global-scripts.inc
+
+<script type="application/javascript">
+ function OpenBrowserWindowFromDockMenu(options) {
+ let win = OpenBrowserWindow(options);
+ win.addEventListener("load", function listener() {
+ win.removeEventListener("load", listener);
+ let dockSupport = Cc["@mozilla.org/widget/macdocksupport;1"]
+ .getService(Ci.nsIMacDockSupport);
+ dockSupport.activateApplication(true);
+ });
+
+ return win;
+ }
+
+ addEventListener("load", function() { gBrowserInit.nonBrowserWindowStartup() }, false);
+ addEventListener("unload", function() { gBrowserInit.nonBrowserWindowShutdown() }, false);
+</script>
+
+# All sets except for popupsets (commands, keys, stringbundles and broadcasters) *must* go into the
+# browser-sets.inc file for sharing with hiddenWindow.xul.
+#include browser-sets.inc
+
+# The entire main menubar is placed into browser-menubar.inc, so that it can be shared by
+# hiddenWindow.xul.
+#include browser-menubar.inc
+
+<!-- Dock menu -->
+<popupset>
+ <menupopup id="menu_mac_dockmenu">
+ <!-- The command cannot be cmd_newNavigator because we need to activate
+ the application. -->
+ <menuitem label="&newNavigatorCmd.label;" oncommand="OpenBrowserWindowFromDockMenu();"
+ id="macDockMenuNewWindow" />
+ <menuitem label="&newPrivateWindow.label;" oncommand="OpenBrowserWindowFromDockMenu({private: true});" />
+ </menupopup>
+</popupset>
+
+</overlay>
diff --git a/browser/base/content/newtab/cells.js b/browser/base/content/newtab/cells.js
new file mode 100644
index 000000000..23cbd23bf
--- /dev/null
+++ b/browser/base/content/newtab/cells.js
@@ -0,0 +1,126 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * This class manages a cell's DOM node (not the actually cell content, a site).
+ * It's mostly read-only, i.e. all manipulation of both position and content
+ * aren't handled here.
+ */
+function Cell(aGrid, aNode) {
+ this._grid = aGrid;
+ this._node = aNode;
+ this._node._newtabCell = this;
+
+ // Register drag-and-drop event handlers.
+ ["dragenter", "dragover", "dragexit", "drop"].forEach(function (aType) {
+ this._node.addEventListener(aType, this, false);
+ }, this);
+}
+
+Cell.prototype = {
+ /**
+ * The grid.
+ */
+ _grid: null,
+
+ /**
+ * The cell's DOM node.
+ */
+ get node() this._node,
+
+ /**
+ * The cell's offset in the grid.
+ */
+ get index() {
+ let index = this._grid.cells.indexOf(this);
+
+ // Cache this value, overwrite the getter.
+ Object.defineProperty(this, "index", {value: index, enumerable: true});
+
+ return index;
+ },
+
+ /**
+ * The previous cell in the grid.
+ */
+ get previousSibling() {
+ let prev = this.node.previousElementSibling;
+ prev = prev && prev._newtabCell;
+
+ // Cache this value, overwrite the getter.
+ Object.defineProperty(this, "previousSibling", {value: prev, enumerable: true});
+
+ return prev;
+ },
+
+ /**
+ * The next cell in the grid.
+ */
+ get nextSibling() {
+ let next = this.node.nextElementSibling;
+ next = next && next._newtabCell;
+
+ // Cache this value, overwrite the getter.
+ Object.defineProperty(this, "nextSibling", {value: next, enumerable: true});
+
+ return next;
+ },
+
+ /**
+ * The site contained in the cell, if any.
+ */
+ get site() {
+ let firstChild = this.node.firstElementChild;
+ return firstChild && firstChild._newtabSite;
+ },
+
+ /**
+ * Checks whether the cell contains a pinned site.
+ * @return Whether the cell contains a pinned site.
+ */
+ containsPinnedSite: function Cell_containsPinnedSite() {
+ let site = this.site;
+ return site && site.isPinned();
+ },
+
+ /**
+ * Checks whether the cell contains a site (is empty).
+ * @return Whether the cell is empty.
+ */
+ isEmpty: function Cell_isEmpty() {
+ return !this.site;
+ },
+
+ /**
+ * Handles all cell events.
+ */
+ handleEvent: function Cell_handleEvent(aEvent) {
+ // We're not responding to external drag/drop events
+ // when our parent window is in private browsing mode.
+ if (inPrivateBrowsingMode() && !gDrag.draggedSite)
+ return;
+
+ if (aEvent.type != "dragexit" && !gDrag.isValid(aEvent))
+ return;
+
+ switch (aEvent.type) {
+ case "dragenter":
+ aEvent.preventDefault();
+ gDrop.enter(this, aEvent);
+ break;
+ case "dragover":
+ aEvent.preventDefault();
+ break;
+ case "dragexit":
+ gDrop.exit(this, aEvent);
+ break;
+ case "drop":
+ aEvent.preventDefault();
+ gDrop.drop(this, aEvent);
+ break;
+ }
+ }
+};
diff --git a/browser/base/content/newtab/drag.js b/browser/base/content/newtab/drag.js
new file mode 100644
index 000000000..dfbe8199a
--- /dev/null
+++ b/browser/base/content/newtab/drag.js
@@ -0,0 +1,151 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * This singleton implements site dragging functionality.
+ */
+let gDrag = {
+ /**
+ * The site offset to the drag start point.
+ */
+ _offsetX: null,
+ _offsetY: null,
+
+ /**
+ * The site that is dragged.
+ */
+ _draggedSite: null,
+ get draggedSite() this._draggedSite,
+
+ /**
+ * The cell width/height at the point the drag started.
+ */
+ _cellWidth: null,
+ _cellHeight: null,
+ get cellWidth() this._cellWidth,
+ get cellHeight() this._cellHeight,
+
+ /**
+ * Start a new drag operation.
+ * @param aSite The site that's being dragged.
+ * @param aEvent The 'dragstart' event.
+ */
+ start: function Drag_start(aSite, aEvent) {
+ this._draggedSite = aSite;
+
+ // Mark nodes as being dragged.
+ let selector = ".newtab-site, .newtab-control, .newtab-thumbnail";
+ let parentCell = aSite.node.parentNode;
+ let nodes = parentCell.querySelectorAll(selector);
+ for (let i = 0; i < nodes.length; i++)
+ nodes[i].setAttribute("dragged", "true");
+
+ parentCell.setAttribute("dragged", "true");
+
+ this._setDragData(aSite, aEvent);
+
+ // Store the cursor offset.
+ let node = aSite.node;
+ let rect = node.getBoundingClientRect();
+ this._offsetX = aEvent.clientX - rect.left;
+ this._offsetY = aEvent.clientY - rect.top;
+
+ // Store the cell dimensions.
+ let cellNode = aSite.cell.node;
+ this._cellWidth = cellNode.offsetWidth;
+ this._cellHeight = cellNode.offsetHeight;
+
+ gTransformation.freezeSitePosition(aSite);
+ },
+
+ /**
+ * Handles the 'drag' event.
+ * @param aSite The site that's being dragged.
+ * @param aEvent The 'drag' event.
+ */
+ drag: function Drag_drag(aSite, aEvent) {
+ // Get the viewport size.
+ let {clientWidth, clientHeight} = document.documentElement;
+
+ // We'll want a padding of 5px.
+ let border = 5;
+
+ // Enforce minimum constraints to keep the drag image inside the window.
+ let left = Math.max(scrollX + aEvent.clientX - this._offsetX, border);
+ let top = Math.max(scrollY + aEvent.clientY - this._offsetY, border);
+
+ // Enforce maximum constraints to keep the drag image inside the window.
+ left = Math.min(left, scrollX + clientWidth - this.cellWidth - border);
+ top = Math.min(top, scrollY + clientHeight - this.cellHeight - border);
+
+ // Update the drag image's position.
+ gTransformation.setSitePosition(aSite, {left: left, top: top});
+ },
+
+ /**
+ * Ends the current drag operation.
+ * @param aSite The site that's being dragged.
+ * @param aEvent The 'dragend' event.
+ */
+ end: function Drag_end(aSite, aEvent) {
+ let nodes = gGrid.node.querySelectorAll("[dragged]")
+ for (let i = 0; i < nodes.length; i++)
+ nodes[i].removeAttribute("dragged");
+
+ // Slide the dragged site back into its cell (may be the old or the new cell).
+ gTransformation.slideSiteTo(aSite, aSite.cell, {unfreeze: true});
+
+ this._draggedSite = null;
+ },
+
+ /**
+ * Checks whether we're responsible for a given drag event.
+ * @param aEvent The drag event to check.
+ * @return Whether we should handle this drag and drop operation.
+ */
+ isValid: function Drag_isValid(aEvent) {
+ let link = gDragDataHelper.getLinkFromDragEvent(aEvent);
+
+ // Check that the drag data is non-empty.
+ // Can happen when dragging places folders.
+ if (!link || !link.url) {
+ return false;
+ }
+
+ // Check that we're not accepting URLs which would inherit the caller's
+ // principal (such as javascript: or data:).
+ return gLinkChecker.checkLoadURI(link.url);
+ },
+
+ /**
+ * Initializes the drag data for the current drag operation.
+ * @param aSite The site that's being dragged.
+ * @param aEvent The 'dragstart' event.
+ */
+ _setDragData: function Drag_setDragData(aSite, aEvent) {
+ let {url, title} = aSite;
+
+ let dt = aEvent.dataTransfer;
+ dt.mozCursor = "default";
+ dt.effectAllowed = "move";
+ dt.setData("text/plain", url);
+ dt.setData("text/uri-list", url);
+ dt.setData("text/x-moz-url", url + "\n" + title);
+ dt.setData("text/html", "<a href=\"" + url + "\">" + url + "</a>");
+
+ // Create and use an empty drag element. We don't want to use the default
+ // drag image with its default opacity.
+ let dragElement = document.createElementNS(HTML_NAMESPACE, "div");
+ dragElement.classList.add("newtab-drag");
+ let scrollbox = document.getElementById("newtab-scrollbox");
+ scrollbox.appendChild(dragElement);
+ dt.setDragImage(dragElement, 0, 0);
+
+ // After the 'dragstart' event has been processed we can remove the
+ // temporary drag element from the DOM.
+ setTimeout(function () scrollbox.removeChild(dragElement), 0);
+ }
+};
diff --git a/browser/base/content/newtab/dragDataHelper.js b/browser/base/content/newtab/dragDataHelper.js
new file mode 100644
index 000000000..a66e4e87e
--- /dev/null
+++ b/browser/base/content/newtab/dragDataHelper.js
@@ -0,0 +1,22 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+let gDragDataHelper = {
+ get mimeType() {
+ return "text/x-moz-url";
+ },
+
+ getLinkFromDragEvent: function DragDataHelper_getLinkFromDragEvent(aEvent) {
+ let dt = aEvent.dataTransfer;
+ if (!dt || !dt.types.contains(this.mimeType)) {
+ return null;
+ }
+
+ let data = dt.getData(this.mimeType) || "";
+ let [url, title] = data.split(/[\r\n]+/);
+ return {url: url, title: title};
+ }
+};
diff --git a/browser/base/content/newtab/drop.js b/browser/base/content/newtab/drop.js
new file mode 100644
index 000000000..7363396e8
--- /dev/null
+++ b/browser/base/content/newtab/drop.js
@@ -0,0 +1,150 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+// A little delay that prevents the grid from being too sensitive when dragging
+// sites around.
+const DELAY_REARRANGE_MS = 100;
+
+/**
+ * This singleton implements site dropping functionality.
+ */
+let gDrop = {
+ /**
+ * The last drop target.
+ */
+ _lastDropTarget: null,
+
+ /**
+ * Handles the 'dragenter' event.
+ * @param aCell The drop target cell.
+ */
+ enter: function Drop_enter(aCell) {
+ this._delayedRearrange(aCell);
+ },
+
+ /**
+ * Handles the 'dragexit' event.
+ * @param aCell The drop target cell.
+ * @param aEvent The 'dragexit' event.
+ */
+ exit: function Drop_exit(aCell, aEvent) {
+ if (aEvent.dataTransfer && !aEvent.dataTransfer.mozUserCancelled) {
+ this._delayedRearrange();
+ } else {
+ // The drag operation has been cancelled.
+ this._cancelDelayedArrange();
+ this._rearrange();
+ }
+ },
+
+ /**
+ * Handles the 'drop' event.
+ * @param aCell The drop target cell.
+ * @param aEvent The 'dragexit' event.
+ */
+ drop: function Drop_drop(aCell, aEvent) {
+ // The cell that is the drop target could contain a pinned site. We need
+ // to find out where that site has gone and re-pin it there.
+ if (aCell.containsPinnedSite())
+ this._repinSitesAfterDrop(aCell);
+
+ // Pin the dragged or insert the new site.
+ this._pinDraggedSite(aCell, aEvent);
+
+ this._cancelDelayedArrange();
+
+ // Update the grid and move all sites to their new places.
+ gUpdater.updateGrid();
+ },
+
+ /**
+ * Re-pins all pinned sites in their (new) positions.
+ * @param aCell The drop target cell.
+ */
+ _repinSitesAfterDrop: function Drop_repinSitesAfterDrop(aCell) {
+ let sites = gDropPreview.rearrange(aCell);
+
+ // Filter out pinned sites.
+ let pinnedSites = sites.filter(function (aSite) {
+ return aSite && aSite.isPinned();
+ });
+
+ // Re-pin all shifted pinned cells.
+ pinnedSites.forEach(function (aSite) aSite.pin(sites.indexOf(aSite)), this);
+ },
+
+ /**
+ * Pins the dragged site in its new place.
+ * @param aCell The drop target cell.
+ * @param aEvent The 'dragexit' event.
+ */
+ _pinDraggedSite: function Drop_pinDraggedSite(aCell, aEvent) {
+ let index = aCell.index;
+ let draggedSite = gDrag.draggedSite;
+
+ if (draggedSite) {
+ // Pin the dragged site at its new place.
+ if (aCell != draggedSite.cell)
+ draggedSite.pin(index);
+ } else {
+ let link = gDragDataHelper.getLinkFromDragEvent(aEvent);
+ if (link) {
+ // A new link was dragged onto the grid. Create it by pinning its URL.
+ gPinnedLinks.pin(link, index);
+
+ // Make sure the newly added link is not blocked.
+ gBlockedLinks.unblock(link);
+ }
+ }
+ },
+
+ /**
+ * Time a rearrange with a little delay.
+ * @param aCell The drop target cell.
+ */
+ _delayedRearrange: function Drop_delayedRearrange(aCell) {
+ // The last drop target didn't change so there's no need to re-arrange.
+ if (this._lastDropTarget == aCell)
+ return;
+
+ let self = this;
+
+ function callback() {
+ self._rearrangeTimeout = null;
+ self._rearrange(aCell);
+ }
+
+ this._cancelDelayedArrange();
+ this._rearrangeTimeout = setTimeout(callback, DELAY_REARRANGE_MS);
+
+ // Store the last drop target.
+ this._lastDropTarget = aCell;
+ },
+
+ /**
+ * Cancels a timed rearrange, if any.
+ */
+ _cancelDelayedArrange: function Drop_cancelDelayedArrange() {
+ if (this._rearrangeTimeout) {
+ clearTimeout(this._rearrangeTimeout);
+ this._rearrangeTimeout = null;
+ }
+ },
+
+ /**
+ * Rearrange all sites in the grid depending on the current drop target.
+ * @param aCell The drop target cell.
+ */
+ _rearrange: function Drop_rearrange(aCell) {
+ let sites = gGrid.sites;
+
+ // We need to rearrange the grid only if there's a current drop target.
+ if (aCell)
+ sites = gDropPreview.rearrange(aCell);
+
+ gTransformation.rearrangeSites(sites, {unfreeze: !aCell});
+ }
+};
diff --git a/browser/base/content/newtab/dropPreview.js b/browser/base/content/newtab/dropPreview.js
new file mode 100644
index 000000000..903762345
--- /dev/null
+++ b/browser/base/content/newtab/dropPreview.js
@@ -0,0 +1,222 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * This singleton provides the ability to re-arrange the current grid to
+ * indicate the transformation that results from dropping a cell at a certain
+ * position.
+ */
+let gDropPreview = {
+ /**
+ * Rearranges the sites currently contained in the grid when a site would be
+ * dropped onto the given cell.
+ * @param aCell The drop target cell.
+ * @return The re-arranged array of sites.
+ */
+ rearrange: function DropPreview_rearrange(aCell) {
+ let sites = gGrid.sites;
+
+ // Insert the dragged site into the current grid.
+ this._insertDraggedSite(sites, aCell);
+
+ // After the new site has been inserted we need to correct the positions
+ // of all pinned tabs that have been moved around.
+ this._repositionPinnedSites(sites, aCell);
+
+ return sites;
+ },
+
+ /**
+ * Inserts the currently dragged site into the given array of sites.
+ * @param aSites The array of sites to insert into.
+ * @param aCell The drop target cell.
+ */
+ _insertDraggedSite: function DropPreview_insertDraggedSite(aSites, aCell) {
+ let dropIndex = aCell.index;
+ let draggedSite = gDrag.draggedSite;
+
+ // We're currently dragging a site.
+ if (draggedSite) {
+ let dragCell = draggedSite.cell;
+ let dragIndex = dragCell.index;
+
+ // Move the dragged site into its new position.
+ if (dragIndex != dropIndex) {
+ aSites.splice(dragIndex, 1);
+ aSites.splice(dropIndex, 0, draggedSite);
+ }
+ // We're handling an external drag item.
+ } else {
+ aSites.splice(dropIndex, 0, null);
+ }
+ },
+
+ /**
+ * Correct the position of all pinned sites that might have been moved to
+ * different positions after the dragged site has been inserted.
+ * @param aSites The array of sites containing the dragged site.
+ * @param aCell The drop target cell.
+ */
+ _repositionPinnedSites:
+ function DropPreview_repositionPinnedSites(aSites, aCell) {
+
+ // Collect all pinned sites.
+ let pinnedSites = this._filterPinnedSites(aSites, aCell);
+
+ // Correct pinned site positions.
+ pinnedSites.forEach(function (aSite) {
+ aSites[aSites.indexOf(aSite)] = aSites[aSite.cell.index];
+ aSites[aSite.cell.index] = aSite;
+ }, this);
+
+ // There might be a pinned cell that got pushed out of the grid, try to
+ // sneak it in by removing a lower-priority cell.
+ if (this._hasOverflowedPinnedSite(aSites, aCell))
+ this._repositionOverflowedPinnedSite(aSites, aCell);
+ },
+
+ /**
+ * Filter pinned sites out of the grid that are still on their old positions
+ * and have not moved.
+ * @param aSites The array of sites to filter.
+ * @param aCell The drop target cell.
+ * @return The filtered array of sites.
+ */
+ _filterPinnedSites: function DropPreview_filterPinnedSites(aSites, aCell) {
+ let draggedSite = gDrag.draggedSite;
+
+ // When dropping on a cell that contains a pinned site make sure that all
+ // pinned cells surrounding the drop target are moved as well.
+ let range = this._getPinnedRange(aCell);
+
+ return aSites.filter(function (aSite, aIndex) {
+ // The site must be valid, pinned and not the dragged site.
+ if (!aSite || aSite == draggedSite || !aSite.isPinned())
+ return false;
+
+ let index = aSite.cell.index;
+
+ // If it's not in the 'pinned range' it's a valid pinned site.
+ return (index > range.end || index < range.start);
+ });
+ },
+
+ /**
+ * Determines the range of pinned sites surrounding the drop target cell.
+ * @param aCell The drop target cell.
+ * @return The range of pinned cells.
+ */
+ _getPinnedRange: function DropPreview_getPinnedRange(aCell) {
+ let dropIndex = aCell.index;
+ let range = {start: dropIndex, end: dropIndex};
+
+ // We need a pinned range only when dropping on a pinned site.
+ if (aCell.containsPinnedSite()) {
+ let links = gPinnedLinks.links;
+
+ // Find all previous siblings of the drop target that are pinned as well.
+ while (range.start && links[range.start - 1])
+ range.start--;
+
+ let maxEnd = links.length - 1;
+
+ // Find all next siblings of the drop target that are pinned as well.
+ while (range.end < maxEnd && links[range.end + 1])
+ range.end++;
+ }
+
+ return range;
+ },
+
+ /**
+ * Checks if the given array of sites contains a pinned site that has
+ * been pushed out of the grid.
+ * @param aSites The array of sites to check.
+ * @param aCell The drop target cell.
+ * @return Whether there is an overflowed pinned cell.
+ */
+ _hasOverflowedPinnedSite:
+ function DropPreview_hasOverflowedPinnedSite(aSites, aCell) {
+
+ // If the drop target isn't pinned there's no way a pinned site has been
+ // pushed out of the grid so we can just exit here.
+ if (!aCell.containsPinnedSite())
+ return false;
+
+ let cells = gGrid.cells;
+
+ // No cells have been pushed out of the grid, nothing to do here.
+ if (aSites.length <= cells.length)
+ return false;
+
+ let overflowedSite = aSites[cells.length];
+
+ // Nothing to do if the site that got pushed out of the grid is not pinned.
+ return (overflowedSite && overflowedSite.isPinned());
+ },
+
+ /**
+ * We have a overflowed pinned site that we need to re-position so that it's
+ * visible again. We try to find a lower-priority cell (empty or containing
+ * an unpinned site) that we can move it to.
+ * @param aSites The array of sites.
+ * @param aCell The drop target cell.
+ */
+ _repositionOverflowedPinnedSite:
+ function DropPreview_repositionOverflowedPinnedSite(aSites, aCell) {
+
+ // Try to find a lower-priority cell (empty or containing an unpinned site).
+ let index = this._indexOfLowerPrioritySite(aSites, aCell);
+
+ if (index > -1) {
+ let cells = gGrid.cells;
+ let dropIndex = aCell.index;
+
+ // Move all pinned cells to their new positions to let the overflowed
+ // site fit into the grid.
+ for (let i = index + 1, lastPosition = index; i < aSites.length; i++) {
+ if (i != dropIndex) {
+ aSites[lastPosition] = aSites[i];
+ lastPosition = i;
+ }
+ }
+
+ // Finally, remove the overflowed site from its previous position.
+ aSites.splice(cells.length, 1);
+ }
+ },
+
+ /**
+ * Finds the index of the last cell that is empty or contains an unpinned
+ * site. These are considered to be of a lower priority.
+ * @param aSites The array of sites.
+ * @param aCell The drop target cell.
+ * @return The cell's index.
+ */
+ _indexOfLowerPrioritySite:
+ function DropPreview_indexOfLowerPrioritySite(aSites, aCell) {
+
+ let cells = gGrid.cells;
+ let dropIndex = aCell.index;
+
+ // Search (beginning with the last site in the grid) for a site that is
+ // empty or unpinned (an thus lower-priority) and can be pushed out of the
+ // grid instead of the pinned site.
+ for (let i = cells.length - 1; i >= 0; i--) {
+ // The cell that is our drop target is not a good choice.
+ if (i == dropIndex)
+ continue;
+
+ let site = aSites[i];
+
+ // We can use the cell only if it's empty or the site is un-pinned.
+ if (!site || !site.isPinned())
+ return i;
+ }
+
+ return -1;
+ }
+};
diff --git a/browser/base/content/newtab/dropTargetShim.js b/browser/base/content/newtab/dropTargetShim.js
new file mode 100644
index 000000000..a85a6ccd6
--- /dev/null
+++ b/browser/base/content/newtab/dropTargetShim.js
@@ -0,0 +1,188 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * This singleton provides a custom drop target detection. We need this because
+ * the default DnD target detection relies on the cursor's position. We want
+ * to pick a drop target based on the dragged site's position.
+ */
+let gDropTargetShim = {
+ /**
+ * Cache for the position of all cells, cleaned after drag finished.
+ */
+ _cellPositions: null,
+
+ /**
+ * The last drop target that was hovered.
+ */
+ _lastDropTarget: null,
+
+ /**
+ * Initializes the drop target shim.
+ */
+ init: function DropTargetShim_init() {
+ let node = gGrid.node;
+
+ // Add drag event handlers.
+ node.addEventListener("dragstart", this, true);
+ node.addEventListener("dragend", this, true);
+ },
+
+ /**
+ * Handles all shim events.
+ */
+ handleEvent: function DropTargetShim_handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "dragstart":
+ this._start(aEvent);
+ break;
+ case "dragover":
+ this._dragover(aEvent);
+ break;
+ case "dragend":
+ this._end(aEvent);
+ break;
+ }
+ },
+
+ /**
+ * Handles the 'dragstart' event.
+ * @param aEvent The 'dragstart' event.
+ */
+ _start: function DropTargetShim_start(aEvent) {
+ if (aEvent.target.classList.contains("newtab-link")) {
+ gGrid.lock();
+
+ // XXX bug 505521 - Listen for dragover on the document.
+ document.documentElement.addEventListener("dragover", this, false);
+ }
+ },
+
+ /**
+ * Handles the 'drag' event and determines the current drop target.
+ * @param aEvent The 'drag' event.
+ */
+ _drag: function DropTargetShim_drag(aEvent) {
+ // Let's see if we find a drop target.
+ let target = this._findDropTarget(aEvent);
+
+ if (target != this._lastDropTarget) {
+ if (this._lastDropTarget)
+ // We left the last drop target.
+ this._dispatchEvent(aEvent, "dragexit", this._lastDropTarget);
+
+ if (target)
+ // We're now hovering a (new) drop target.
+ this._dispatchEvent(aEvent, "dragenter", target);
+
+ if (this._lastDropTarget)
+ // We left the last drop target.
+ this._dispatchEvent(aEvent, "dragleave", this._lastDropTarget);
+
+ this._lastDropTarget = target;
+ }
+ },
+
+ /**
+ * Handles the 'dragover' event as long as bug 505521 isn't fixed to get
+ * current mouse cursor coordinates while dragging.
+ * @param aEvent The 'dragover' event.
+ */
+ _dragover: function DropTargetShim_dragover(aEvent) {
+ let sourceNode = aEvent.dataTransfer.mozSourceNode.parentNode;
+ gDrag.drag(sourceNode._newtabSite, aEvent);
+
+ this._drag(aEvent);
+ },
+
+ /**
+ * Handles the 'dragend' event.
+ * @param aEvent The 'dragend' event.
+ */
+ _end: function DropTargetShim_end(aEvent) {
+ // Make sure to determine the current drop target in case the dragenter
+ // event hasn't been fired.
+ this._drag(aEvent);
+
+ if (this._lastDropTarget) {
+ if (aEvent.dataTransfer.mozUserCancelled) {
+ // The drag operation was cancelled.
+ this._dispatchEvent(aEvent, "dragexit", this._lastDropTarget);
+ this._dispatchEvent(aEvent, "dragleave", this._lastDropTarget);
+ } else {
+ // A site was successfully dropped.
+ this._dispatchEvent(aEvent, "drop", this._lastDropTarget);
+ }
+
+ // Clean up.
+ this._lastDropTarget = null;
+ this._cellPositions = null;
+ }
+
+ gGrid.unlock();
+
+ // XXX bug 505521 - Remove the document's dragover listener.
+ document.documentElement.removeEventListener("dragover", this, false);
+ },
+
+ /**
+ * Determines the current drop target by matching the dragged site's position
+ * against all cells in the grid.
+ * @return The currently hovered drop target or null.
+ */
+ _findDropTarget: function DropTargetShim_findDropTarget() {
+ // These are the minimum intersection values - we want to use the cell if
+ // the site is >= 50% hovering its position.
+ let minWidth = gDrag.cellWidth / 2;
+ let minHeight = gDrag.cellHeight / 2;
+
+ let cellPositions = this._getCellPositions();
+ let rect = gTransformation.getNodePosition(gDrag.draggedSite.node);
+
+ // Compare each cell's position to the dragged site's position.
+ for (let i = 0; i < cellPositions.length; i++) {
+ let inter = rect.intersect(cellPositions[i].rect);
+
+ // If the intersection is big enough we found a drop target.
+ if (inter.width >= minWidth && inter.height >= minHeight)
+ return cellPositions[i].cell;
+ }
+
+ // No drop target found.
+ return null;
+ },
+
+ /**
+ * Gets the positions of all cell nodes.
+ * @return The (cached) cell positions.
+ */
+ _getCellPositions: function DropTargetShim_getCellPositions() {
+ if (this._cellPositions)
+ return this._cellPositions;
+
+ return this._cellPositions = gGrid.cells.map(function (cell) {
+ return {cell: cell, rect: gTransformation.getNodePosition(cell.node)};
+ });
+ },
+
+ /**
+ * Dispatches a custom DragEvent on the given target node.
+ * @param aEvent The source event.
+ * @param aType The event type.
+ * @param aTarget The target node that receives the event.
+ */
+ _dispatchEvent:
+ function DropTargetShim_dispatchEvent(aEvent, aType, aTarget) {
+
+ let node = aTarget.node;
+ let event = document.createEvent("DragEvents");
+
+ event.initDragEvent(aType, true, true, window, 0, 0, 0, 0, 0, false, false,
+ false, false, 0, node, aEvent.dataTransfer);
+
+ node.dispatchEvent(event);
+ }
+};
diff --git a/browser/base/content/newtab/grid.js b/browser/base/content/newtab/grid.js
new file mode 100644
index 000000000..120691951
--- /dev/null
+++ b/browser/base/content/newtab/grid.js
@@ -0,0 +1,171 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * This singleton represents the grid that contains all sites.
+ */
+let gGrid = {
+ /**
+ * The DOM node of the grid.
+ */
+ _node: null,
+ get node() this._node,
+
+ /**
+ * The cached DOM fragment for sites.
+ */
+ _siteFragment: null,
+
+ /**
+ * All cells contained in the grid.
+ */
+ _cells: null,
+ get cells() this._cells,
+
+ /**
+ * All sites contained in the grid's cells. Sites may be empty.
+ */
+ get sites() [cell.site for each (cell in this.cells)],
+
+ // Tells whether the grid has already been initialized.
+ get ready() !!this._node,
+
+ /**
+ * Initializes the grid.
+ * @param aSelector The query selector of the grid.
+ */
+ init: function Grid_init() {
+ this._node = document.getElementById("newtab-grid");
+ this._createSiteFragment();
+ this._render();
+ },
+
+ /**
+ * Creates a new site in the grid.
+ * @param aLink The new site's link.
+ * @param aCell The cell that will contain the new site.
+ * @return The newly created site.
+ */
+ createSite: function Grid_createSite(aLink, aCell) {
+ let node = aCell.node;
+ node.appendChild(this._siteFragment.cloneNode(true));
+ return new Site(node.firstElementChild, aLink);
+ },
+
+ /**
+ * Refreshes the grid and re-creates all sites.
+ */
+ refresh: function Grid_refresh() {
+ // Remove all sites.
+ this.cells.forEach(function (cell) {
+ let node = cell.node;
+ let child = node.firstElementChild;
+
+ if (child)
+ node.removeChild(child);
+ }, this);
+
+ // Render the grid again.
+ this._render();
+ },
+
+ /**
+ * Locks the grid to block all pointer events.
+ */
+ lock: function Grid_lock() {
+ this.node.setAttribute("locked", "true");
+ },
+
+ /**
+ * Unlocks the grid to allow all pointer events.
+ */
+ unlock: function Grid_unlock() {
+ this.node.removeAttribute("locked");
+ },
+
+ /**
+ * Creates the newtab grid.
+ */
+ _renderGrid: function Grid_renderGrid() {
+ let row = document.createElementNS(HTML_NAMESPACE, "div");
+ let cell = document.createElementNS(HTML_NAMESPACE, "div");
+ row.classList.add("newtab-row");
+ cell.classList.add("newtab-cell");
+
+ // Clear the grid
+ this._node.innerHTML = "";
+
+ // Creates the structure of one row
+ for (let i = 0; i < gGridPrefs.gridColumns; i++) {
+ row.appendChild(cell.cloneNode(true));
+ }
+ // Creates the grid
+ for (let j = 0; j < gGridPrefs.gridRows; j++) {
+ this._node.appendChild(row.cloneNode(true));
+ }
+
+ // (Re-)initialize all cells.
+ let cellElements = this.node.querySelectorAll(".newtab-cell");
+ this._cells = [new Cell(this, cell) for (cell of cellElements)];
+ },
+
+ /**
+ * Creates the DOM fragment that is re-used when creating sites.
+ */
+ _createSiteFragment: function Grid_createSiteFragment() {
+ let site = document.createElementNS(HTML_NAMESPACE, "div");
+ site.classList.add("newtab-site");
+ site.setAttribute("draggable", "true");
+
+ // Create the site's inner HTML code.
+ site.innerHTML =
+ '<a class="newtab-link">' +
+ ' <span class="newtab-thumbnail"/>' +
+ ' <span class="newtab-title"/>' +
+ '</a>' +
+ '<input type="button" title="' + newTabString("pin") + '"' +
+ ' class="newtab-control newtab-control-pin"/>' +
+ '<input type="button" title="' + newTabString("block") + '"' +
+ ' class="newtab-control newtab-control-block"/>';
+
+ this._siteFragment = document.createDocumentFragment();
+ this._siteFragment.appendChild(site);
+ },
+
+ /**
+ * Renders the sites, creates all sites and puts them into their cells.
+ */
+ _renderSites: function Grid_renderSites() {
+ let cells = this.cells;
+ // Put sites into the cells.
+ let links = gLinks.getLinks();
+ let length = Math.min(links.length, cells.length);
+
+ for (let i = 0; i < length; i++) {
+ if (links[i])
+ this.createSite(links[i], cells[i]);
+ }
+ },
+
+ /**
+ * Renders the grid.
+ */
+ _render: function Grid_render() {
+ if (this._shouldRenderGrid()) {
+ this._renderGrid();
+ }
+
+ this._renderSites();
+ },
+
+ _shouldRenderGrid : function Grid_shouldRenderGrid() {
+ let rowsLength = this._node.querySelectorAll(".newtab-row").length;
+ let cellsLength = this._node.querySelectorAll(".newtab-cell").length;
+
+ return (rowsLength != gGridPrefs.gridRows ||
+ cellsLength != (gGridPrefs.gridRows * gGridPrefs.gridColumns));
+ }
+};
diff --git a/browser/base/content/newtab/newTab.css b/browser/base/content/newtab/newTab.css
new file mode 100644
index 000000000..830e4a8c1
--- /dev/null
+++ b/browser/base/content/newtab/newTab.css
@@ -0,0 +1,209 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+input[type=button] {
+ cursor: pointer;
+}
+
+/* SCROLLBOX */
+#newtab-scrollbox {
+ display: -moz-box;
+ position: relative;
+ -moz-box-flex: 1;
+ -moz-user-focus: normal;
+}
+
+#newtab-scrollbox:not([page-disabled]) {
+ overflow: auto;
+}
+
+/* UNDO */
+#newtab-undo-container {
+ transition: opacity 100ms ease-out;
+ display: -moz-box;
+ -moz-box-align: center;
+ -moz-box-pack: center;
+}
+
+#newtab-undo-container[undo-disabled] {
+ opacity: 0;
+ pointer-events: none;
+}
+
+/* TOGGLE */
+#newtab-toggle {
+ position: absolute;
+ top: 12px;
+ right: 12px;
+}
+
+#newtab-toggle:-moz-locale-dir(rtl) {
+ left: 12px;
+ right: auto;
+}
+
+/* MARGINS */
+#newtab-vertical-margin {
+ display: -moz-box;
+ position: relative;
+ -moz-box-flex: 1;
+ -moz-box-orient: vertical;
+}
+
+#newtab-margin-top {
+ min-height: 50px;
+ max-height: 80px;
+ display: -moz-box;
+ -moz-box-flex: 1;
+ -moz-box-align: center;
+ -moz-box-pack: center;
+}
+
+#newtab-margin-bottom {
+ min-height: 40px;
+ max-height: 100px;
+ -moz-box-flex: 1;
+}
+
+#newtab-horizontal-margin {
+ display: -moz-box;
+ -moz-box-flex: 5;
+}
+
+.newtab-side-margin {
+ min-width: 40px;
+ max-width: 300px;
+ -moz-box-flex: 1;
+}
+
+/* GRID */
+#newtab-grid {
+ display: -moz-box;
+ -moz-box-flex: 5;
+ -moz-box-orient: vertical;
+ min-width: 600px;
+ min-height: 400px;
+ transition: 100ms ease-out;
+ transition-property: opacity;
+}
+
+#newtab-grid[page-disabled] {
+ opacity: 0;
+}
+
+#newtab-grid[locked],
+#newtab-grid[page-disabled] {
+ pointer-events: none;
+}
+
+/* ROWS */
+.newtab-row {
+ display: -moz-box;
+ -moz-box-orient: horizontal;
+ -moz-box-direction: normal;
+ -moz-box-flex: 1;
+}
+
+/* CELLS */
+.newtab-cell {
+ display: -moz-box;
+ -moz-box-flex: 1;
+}
+
+/* SITES */
+.newtab-site {
+ position: relative;
+ -moz-box-flex: 1;
+ transition: 100ms ease-out;
+ transition-property: top, left, opacity;
+}
+
+.newtab-site[frozen] {
+ position: absolute;
+ pointer-events: none;
+}
+
+.newtab-site[dragged] {
+ transition-property: none;
+ z-index: 10;
+}
+
+/* LINK + THUMBNAILS */
+.newtab-link,
+.newtab-thumbnail {
+ position: absolute;
+ left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+}
+
+.newtab-thumbnail {
+ opacity: .8;
+ transition: opacity 100ms ease-out;
+}
+
+.newtab-thumbnail[dragged],
+.newtab-link:-moz-focusring > .newtab-thumbnail,
+.newtab-site:hover > .newtab-link > .newtab-thumbnail {
+ opacity: 1;
+}
+
+/* TITLES */
+.newtab-title {
+ position: absolute;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+/* CONTROLS */
+.newtab-control {
+ position: absolute;
+ top: 4px;
+ opacity: 0;
+ transition: opacity 100ms ease-out;
+}
+
+.newtab-control:-moz-focusring,
+.newtab-site:hover > .newtab-control {
+ opacity: 1;
+}
+
+.newtab-control[dragged] {
+ opacity: 0 !important;
+}
+
+@media (-moz-touch-enabled) {
+ .newtab-control {
+ opacity: 1;
+ }
+}
+
+.newtab-control-pin:-moz-locale-dir(ltr),
+.newtab-control-block:-moz-locale-dir(rtl) {
+ left: 4px;
+}
+
+.newtab-control-block:-moz-locale-dir(ltr),
+.newtab-control-pin:-moz-locale-dir(rtl) {
+ right: 4px;
+}
+
+/* DRAG & DROP */
+
+/*
+ * This is just a temporary drag element used for dataTransfer.setDragImage()
+ * so that we can use custom drag images and elements. It needs an opacity of
+ * 0.01 so that the core code detects that it's in fact a visible element.
+ */
+.newtab-drag {
+ width: 1px;
+ height: 1px;
+ background-color: #fff;
+ opacity: 0.01;
+}
diff --git a/browser/base/content/newtab/newTab.js b/browser/base/content/newtab/newTab.js
new file mode 100644
index 000000000..42bbaf09f
--- /dev/null
+++ b/browser/base/content/newtab/newTab.js
@@ -0,0 +1,57 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+let Cu = Components.utils;
+let Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/PageThumbs.jsm");
+Cu.import("resource://gre/modules/NewTabUtils.jsm");
+Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Rect",
+ "resource://gre/modules/Geometry.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+let {
+ links: gLinks,
+ allPages: gAllPages,
+ linkChecker: gLinkChecker,
+ pinnedLinks: gPinnedLinks,
+ blockedLinks: gBlockedLinks,
+ gridPrefs: gGridPrefs
+} = NewTabUtils;
+
+XPCOMUtils.defineLazyGetter(this, "gStringBundle", function() {
+ return Services.strings.
+ createBundle("chrome://browser/locale/newTab.properties");
+});
+
+function newTabString(name) gStringBundle.GetStringFromName('newtab.' + name);
+
+function inPrivateBrowsingMode() {
+ return PrivateBrowsingUtils.isWindowPrivate(window);
+}
+
+const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
+
+#include transformations.js
+#include page.js
+#include grid.js
+#include cells.js
+#include sites.js
+#include drag.js
+#include dragDataHelper.js
+#include drop.js
+#include dropTargetShim.js
+#include dropPreview.js
+#include updater.js
+#include undo.js
+
+// Everything is loaded. Initialize the New Tab Page.
+gPage.init();
diff --git a/browser/base/content/newtab/newTab.xul b/browser/base/content/newtab/newTab.xul
new file mode 100644
index 000000000..bab2c28e7
--- /dev/null
+++ b/browser/base/content/newtab/newTab.xul
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/newtab/newTab.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/newtab/newTab.css" type="text/css"?>
+
+<!DOCTYPE window [
+ <!ENTITY % newTabDTD SYSTEM "chrome://browser/locale/newTab.dtd">
+ %newTabDTD;
+]>
+
+<xul:window id="newtab-window" xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&newtab.pageTitle;">
+
+ <div id="newtab-scrollbox">
+
+ <div id="newtab-vertical-margin">
+ <div id="newtab-margin-top">
+ <div id="newtab-undo-container" undo-disabled="true">
+ <xul:label id="newtab-undo-label"
+ value="&newtab.undo.removedLabel;" />
+ <xul:button id="newtab-undo-button" tabindex="-1"
+ label="&newtab.undo.undoButton;"
+ class="newtab-undo-button" />
+ <xul:button id="newtab-undo-restore-button" tabindex="-1"
+ label="&newtab.undo.restoreButton;"
+ class="newtab-undo-button" />
+ <xul:toolbarbutton id="newtab-undo-close-button" tabindex="-1"
+ tooltiptext="&newtab.undo.closeTooltip;" />
+ </div>
+ </div>
+
+ <div id="newtab-horizontal-margin">
+ <div class="newtab-side-margin"/>
+
+ <div id="newtab-grid">
+ </div>
+
+ <div class="newtab-side-margin"/>
+ </div>
+
+ <div id="newtab-margin-bottom"/>
+ </div>
+ <input id="newtab-toggle" type="button"/>
+ </div>
+
+ <xul:script type="text/javascript;version=1.8"
+ src="chrome://browser/content/newtab/newTab.js"/>
+</xul:window>
diff --git a/browser/base/content/newtab/page.js b/browser/base/content/newtab/page.js
new file mode 100644
index 000000000..afe5bfba8
--- /dev/null
+++ b/browser/base/content/newtab/page.js
@@ -0,0 +1,135 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * This singleton represents the whole 'New Tab Page' and takes care of
+ * initializing all its components.
+ */
+let gPage = {
+ /**
+ * Initializes the page.
+ */
+ init: function Page_init() {
+ // Add ourselves to the list of pages to receive notifications.
+ gAllPages.register(this);
+
+ // Listen for 'unload' to unregister this page.
+ addEventListener("unload", this, false);
+
+ // Listen for toggle button clicks.
+ let button = document.getElementById("newtab-toggle");
+ button.addEventListener("click", this, false);
+
+ // Check if the new tab feature is enabled.
+ let enabled = gAllPages.enabled;
+ if (enabled)
+ this._init();
+
+ this._updateAttributes(enabled);
+ },
+
+ /**
+ * Listens for notifications specific to this page.
+ */
+ observe: function Page_observe() {
+ let enabled = gAllPages.enabled;
+ this._updateAttributes(enabled);
+
+ // Initialize the whole page if we haven't done that, yet.
+ if (enabled) {
+ this._init();
+ } else {
+ gUndoDialog.hide();
+ }
+ },
+
+ /**
+ * Updates the whole page and the grid when the storage has changed.
+ */
+ update: function Page_update() {
+ // The grid might not be ready yet as we initialize it asynchronously.
+ if (gGrid.ready) {
+ gGrid.refresh();
+ }
+ },
+
+ /**
+ * Internally initializes the page. This runs only when/if the feature
+ * is/gets enabled.
+ */
+ _init: function Page_init() {
+ if (this._initialized)
+ return;
+
+ this._initialized = true;
+
+ gLinks.populateCache(function () {
+ // Initialize and render the grid.
+ gGrid.init();
+
+ // Initialize the drop target shim.
+ gDropTargetShim.init();
+
+#ifdef XP_MACOSX
+ // Workaround to prevent a delay on MacOSX due to a slow drop animation.
+ document.addEventListener("dragover", this, false);
+ document.addEventListener("drop", this, false);
+#endif
+ }.bind(this));
+ },
+
+ /**
+ * Updates the 'page-disabled' attributes of the respective DOM nodes.
+ * @param aValue Whether the New Tab Page is enabled or not.
+ */
+ _updateAttributes: function Page_updateAttributes(aValue) {
+ // Set the nodes' states.
+ let nodeSelector = "#newtab-scrollbox, #newtab-toggle, #newtab-grid";
+ for (let node of document.querySelectorAll(nodeSelector)) {
+ if (aValue)
+ node.removeAttribute("page-disabled");
+ else
+ node.setAttribute("page-disabled", "true");
+ }
+
+ // Enables/disables the control and link elements.
+ let inputSelector = ".newtab-control, .newtab-link";
+ for (let input of document.querySelectorAll(inputSelector)) {
+ if (aValue)
+ input.removeAttribute("tabindex");
+ else
+ input.setAttribute("tabindex", "-1");
+ }
+
+ // Update the toggle button's title.
+ let toggle = document.getElementById("newtab-toggle");
+ toggle.setAttribute("title", newTabString(aValue ? "hide" : "show"));
+ },
+
+ /**
+ * Handles all page events.
+ */
+ handleEvent: function Page_handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "unload":
+ gAllPages.unregister(this);
+ break;
+ case "click":
+ gAllPages.enabled = !gAllPages.enabled;
+ break;
+ case "dragover":
+ if (gDrag.isValid(aEvent) && gDrag.draggedSite)
+ aEvent.preventDefault();
+ break;
+ case "drop":
+ if (gDrag.isValid(aEvent) && gDrag.draggedSite) {
+ aEvent.preventDefault();
+ aEvent.stopPropagation();
+ }
+ break;
+ }
+ }
+};
diff --git a/browser/base/content/newtab/sites.js b/browser/base/content/newtab/sites.js
new file mode 100644
index 000000000..d1745aff9
--- /dev/null
+++ b/browser/base/content/newtab/sites.js
@@ -0,0 +1,192 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * This class represents a site that is contained in a cell and can be pinned,
+ * moved around or deleted.
+ */
+function Site(aNode, aLink) {
+ this._node = aNode;
+ this._node._newtabSite = this;
+
+ this._link = aLink;
+
+ this._render();
+ this._addEventHandlers();
+}
+
+Site.prototype = {
+ /**
+ * The site's DOM node.
+ */
+ get node() this._node,
+
+ /**
+ * The site's link.
+ */
+ get link() this._link,
+
+ /**
+ * The url of the site's link.
+ */
+ get url() this.link.url,
+
+ /**
+ * The title of the site's link.
+ */
+ get title() this.link.title,
+
+ /**
+ * The site's parent cell.
+ */
+ get cell() {
+ let parentNode = this.node.parentNode;
+ return parentNode && parentNode._newtabCell;
+ },
+
+ /**
+ * Pins the site on its current or a given index.
+ * @param aIndex The pinned index (optional).
+ */
+ pin: function Site_pin(aIndex) {
+ if (typeof aIndex == "undefined")
+ aIndex = this.cell.index;
+
+ this._updateAttributes(true);
+ gPinnedLinks.pin(this._link, aIndex);
+ },
+
+ /**
+ * Unpins the site and calls the given callback when done.
+ */
+ unpin: function Site_unpin() {
+ if (this.isPinned()) {
+ this._updateAttributes(false);
+ gPinnedLinks.unpin(this._link);
+ gUpdater.updateGrid();
+ }
+ },
+
+ /**
+ * Checks whether this site is pinned.
+ * @return Whether this site is pinned.
+ */
+ isPinned: function Site_isPinned() {
+ return gPinnedLinks.isPinned(this._link);
+ },
+
+ /**
+ * Blocks the site (removes it from the grid) and calls the given callback
+ * when done.
+ */
+ block: function Site_block() {
+ if (!gBlockedLinks.isBlocked(this._link)) {
+ gUndoDialog.show(this);
+ gBlockedLinks.block(this._link);
+ gUpdater.updateGrid();
+ }
+ },
+
+ /**
+ * Gets the DOM node specified by the given query selector.
+ * @param aSelector The query selector.
+ * @return The DOM node we found.
+ */
+ _querySelector: function Site_querySelector(aSelector) {
+ return this.node.querySelector(aSelector);
+ },
+
+ /**
+ * Updates attributes for all nodes which status depends on this site being
+ * pinned or unpinned.
+ * @param aPinned Whether this site is now pinned or unpinned.
+ */
+ _updateAttributes: function (aPinned) {
+ let control = this._querySelector(".newtab-control-pin");
+
+ if (aPinned) {
+ control.setAttribute("pinned", true);
+ control.setAttribute("title", newTabString("unpin"));
+ } else {
+ control.removeAttribute("pinned");
+ control.setAttribute("title", newTabString("pin"));
+ }
+ },
+
+ /**
+ * Renders the site's data (fills the HTML fragment).
+ */
+ _render: function Site_render() {
+ let url = this.url;
+ let title = this.title || url;
+ let tooltip = (title == url ? title : title + "\n" + url);
+
+ let link = this._querySelector(".newtab-link");
+ link.setAttribute("title", tooltip);
+ link.setAttribute("href", url);
+ this._querySelector(".newtab-title").textContent = title;
+
+ if (this.isPinned())
+ this._updateAttributes(true);
+
+ let thumbnailURL = PageThumbs.getThumbnailURL(this.url);
+ let thumbnail = this._querySelector(".newtab-thumbnail");
+ thumbnail.style.backgroundImage = "url(" + thumbnailURL + ")";
+ },
+
+ /**
+ * Adds event handlers for the site and its buttons.
+ */
+ _addEventHandlers: function Site_addEventHandlers() {
+ // Register drag-and-drop event handlers.
+ this._node.addEventListener("dragstart", this, false);
+ this._node.addEventListener("dragend", this, false);
+ this._node.addEventListener("mouseover", this, false);
+
+ let controls = this.node.querySelectorAll(".newtab-control");
+ for (let i = 0; i < controls.length; i++)
+ controls[i].addEventListener("click", this, false);
+ },
+
+ /**
+ * Speculatively opens a connection to the current site.
+ */
+ _speculativeConnect: function Site_speculativeConnect() {
+ let sc = Services.io.QueryInterface(Ci.nsISpeculativeConnect);
+ let uri = Services.io.newURI(this.url, null, null);
+ sc.speculativeConnect(uri, null);
+ },
+
+ /**
+ * Handles all site events.
+ */
+ handleEvent: function Site_handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "click":
+ aEvent.preventDefault();
+ if (aEvent.target.classList.contains("newtab-control-block"))
+ this.block();
+ else if (this.isPinned())
+ this.unpin();
+ else
+ this.pin();
+ break;
+ case "mouseover":
+ this._node.removeEventListener("mouseover", this, false);
+ this._speculativeConnect();
+ break;
+ case "dragstart":
+ gDrag.start(this, aEvent);
+ break;
+ case "drag":
+ gDrag.drag(this, aEvent);
+ break;
+ case "dragend":
+ gDrag.end(this, aEvent);
+ break;
+ }
+ }
+};
diff --git a/browser/base/content/newtab/transformations.js b/browser/base/content/newtab/transformations.js
new file mode 100644
index 000000000..6d1554f5f
--- /dev/null
+++ b/browser/base/content/newtab/transformations.js
@@ -0,0 +1,265 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * This singleton allows to transform the grid by repositioning a site's node
+ * in the DOM and by showing or hiding the node. It additionally provides
+ * convenience methods to work with a site's DOM node.
+ */
+let gTransformation = {
+ /**
+ * Returns the width of the left and top border of a cell. We need to take it
+ * into account when measuring and comparing site and cell positions.
+ */
+ get _cellBorderWidths() {
+ let cstyle = window.getComputedStyle(gGrid.cells[0].node, null);
+ let widths = {
+ left: parseInt(cstyle.getPropertyValue("border-left-width")),
+ top: parseInt(cstyle.getPropertyValue("border-top-width"))
+ };
+
+ // Cache this value, overwrite the getter.
+ Object.defineProperty(this, "_cellBorderWidths",
+ {value: widths, enumerable: true});
+
+ return widths;
+ },
+
+ /**
+ * Gets a DOM node's position.
+ * @param aNode The DOM node.
+ * @return A Rect instance with the position.
+ */
+ getNodePosition: function Transformation_getNodePosition(aNode) {
+ let {left, top, width, height} = aNode.getBoundingClientRect();
+ return new Rect(left + scrollX, top + scrollY, width, height);
+ },
+
+ /**
+ * Fades a given node from zero to full opacity.
+ * @param aNode The node to fade.
+ * @param aCallback The callback to call when finished.
+ */
+ fadeNodeIn: function Transformation_fadeNodeIn(aNode, aCallback) {
+ this._setNodeOpacity(aNode, 1, function () {
+ // Clear the style property.
+ aNode.style.opacity = "";
+
+ if (aCallback)
+ aCallback();
+ });
+ },
+
+ /**
+ * Fades a given node from full to zero opacity.
+ * @param aNode The node to fade.
+ * @param aCallback The callback to call when finished.
+ */
+ fadeNodeOut: function Transformation_fadeNodeOut(aNode, aCallback) {
+ this._setNodeOpacity(aNode, 0, aCallback);
+ },
+
+ /**
+ * Fades a given site from zero to full opacity.
+ * @param aSite The site to fade.
+ * @param aCallback The callback to call when finished.
+ */
+ showSite: function Transformation_showSite(aSite, aCallback) {
+ this.fadeNodeIn(aSite.node, aCallback);
+ },
+
+ /**
+ * Fades a given site from full to zero opacity.
+ * @param aSite The site to fade.
+ * @param aCallback The callback to call when finished.
+ */
+ hideSite: function Transformation_hideSite(aSite, aCallback) {
+ this.fadeNodeOut(aSite.node, aCallback);
+ },
+
+ /**
+ * Allows to set a site's position.
+ * @param aSite The site to re-position.
+ * @param aPosition The desired position for the given site.
+ */
+ setSitePosition: function Transformation_setSitePosition(aSite, aPosition) {
+ let style = aSite.node.style;
+ let {top, left} = aPosition;
+
+ style.top = top + "px";
+ style.left = left + "px";
+ },
+
+ /**
+ * Freezes a site in its current position by positioning it absolute.
+ * @param aSite The site to freeze.
+ */
+ freezeSitePosition: function Transformation_freezeSitePosition(aSite) {
+ if (this._isFrozen(aSite))
+ return;
+
+ let style = aSite.node.style;
+ let comp = getComputedStyle(aSite.node, null);
+ style.width = comp.getPropertyValue("width")
+ style.height = comp.getPropertyValue("height");
+
+ aSite.node.setAttribute("frozen", "true");
+ this.setSitePosition(aSite, this.getNodePosition(aSite.node));
+ },
+
+ /**
+ * Unfreezes a site by removing its absolute positioning.
+ * @param aSite The site to unfreeze.
+ */
+ unfreezeSitePosition: function Transformation_unfreezeSitePosition(aSite) {
+ if (!this._isFrozen(aSite))
+ return;
+
+ let style = aSite.node.style;
+ style.left = style.top = style.width = style.height = "";
+ aSite.node.removeAttribute("frozen");
+ },
+
+ /**
+ * Slides the given site to the target node's position.
+ * @param aSite The site to move.
+ * @param aTarget The slide target.
+ * @param aOptions Set of options (see below).
+ * unfreeze - unfreeze the site after sliding
+ * callback - the callback to call when finished
+ */
+ slideSiteTo: function Transformation_slideSiteTo(aSite, aTarget, aOptions) {
+ let currentPosition = this.getNodePosition(aSite.node);
+ let targetPosition = this.getNodePosition(aTarget.node)
+ let callback = aOptions && aOptions.callback;
+
+ let self = this;
+
+ function finish() {
+ if (aOptions && aOptions.unfreeze)
+ self.unfreezeSitePosition(aSite);
+
+ if (callback)
+ callback();
+ }
+
+ // We need to take the width of a cell's border into account.
+ targetPosition.left += this._cellBorderWidths.left;
+ targetPosition.top += this._cellBorderWidths.top;
+
+ // Nothing to do here if the positions already match.
+ if (currentPosition.left == targetPosition.left &&
+ currentPosition.top == targetPosition.top) {
+ finish();
+ } else {
+ this.setSitePosition(aSite, targetPosition);
+ this._whenTransitionEnded(aSite.node, finish);
+ }
+ },
+
+ /**
+ * Rearranges a given array of sites and moves them to their new positions or
+ * fades in/out new/removed sites.
+ * @param aSites An array of sites to rearrange.
+ * @param aOptions Set of options (see below).
+ * unfreeze - unfreeze the site after rearranging
+ * callback - the callback to call when finished
+ */
+ rearrangeSites: function Transformation_rearrangeSites(aSites, aOptions) {
+ let batch = [];
+ let cells = gGrid.cells;
+ let callback = aOptions && aOptions.callback;
+ let unfreeze = aOptions && aOptions.unfreeze;
+
+ aSites.forEach(function (aSite, aIndex) {
+ // Do not re-arrange empty cells or the dragged site.
+ if (!aSite || aSite == gDrag.draggedSite)
+ return;
+
+ let deferred = Promise.defer();
+ batch.push(deferred.promise);
+ let cb = function () deferred.resolve();
+
+ if (!cells[aIndex])
+ // The site disappeared from the grid, hide it.
+ this.hideSite(aSite, cb);
+ else if (this._getNodeOpacity(aSite.node) != 1)
+ // The site disappeared before but is now back, show it.
+ this.showSite(aSite, cb);
+ else
+ // The site's position has changed, move it around.
+ this._moveSite(aSite, aIndex, {unfreeze: unfreeze, callback: cb});
+ }, this);
+
+ let wait = Promise.promised(function () callback && callback());
+ wait.apply(null, batch);
+ },
+
+ /**
+ * Listens for the 'transitionend' event on a given node and calls the given
+ * callback.
+ * @param aNode The node that is transitioned.
+ * @param aCallback The callback to call when finished.
+ */
+ _whenTransitionEnded:
+ function Transformation_whenTransitionEnded(aNode, aCallback) {
+
+ aNode.addEventListener("transitionend", function onEnd() {
+ aNode.removeEventListener("transitionend", onEnd, false);
+ aCallback();
+ }, false);
+ },
+
+ /**
+ * Gets a given node's opacity value.
+ * @param aNode The node to get the opacity value from.
+ * @return The node's opacity value.
+ */
+ _getNodeOpacity: function Transformation_getNodeOpacity(aNode) {
+ let cstyle = window.getComputedStyle(aNode, null);
+ return cstyle.getPropertyValue("opacity");
+ },
+
+ /**
+ * Sets a given node's opacity.
+ * @param aNode The node to set the opacity value for.
+ * @param aOpacity The opacity value to set.
+ * @param aCallback The callback to call when finished.
+ */
+ _setNodeOpacity:
+ function Transformation_setNodeOpacity(aNode, aOpacity, aCallback) {
+
+ if (this._getNodeOpacity(aNode) == aOpacity) {
+ if (aCallback)
+ aCallback();
+ } else {
+ if (aCallback)
+ this._whenTransitionEnded(aNode, aCallback);
+
+ aNode.style.opacity = aOpacity;
+ }
+ },
+
+ /**
+ * Moves a site to the cell with the given index.
+ * @param aSite The site to move.
+ * @param aIndex The target cell's index.
+ * @param aOptions Options that are directly passed to slideSiteTo().
+ */
+ _moveSite: function Transformation_moveSite(aSite, aIndex, aOptions) {
+ this.freezeSitePosition(aSite);
+ this.slideSiteTo(aSite, gGrid.cells[aIndex], aOptions);
+ },
+
+ /**
+ * Checks whether a site is currently frozen.
+ * @param aSite The site to check.
+ * @return Whether the given site is frozen.
+ */
+ _isFrozen: function Transformation_isFrozen(aSite) {
+ return aSite.node.hasAttribute("frozen");
+ }
+};
diff --git a/browser/base/content/newtab/undo.js b/browser/base/content/newtab/undo.js
new file mode 100644
index 000000000..5f619e980
--- /dev/null
+++ b/browser/base/content/newtab/undo.js
@@ -0,0 +1,116 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * Dialog allowing to undo the removal of single site or to completely restore
+ * the grid's original state.
+ */
+let gUndoDialog = {
+ /**
+ * The undo dialog's timeout in miliseconds.
+ */
+ HIDE_TIMEOUT_MS: 15000,
+
+ /**
+ * Contains undo information.
+ */
+ _undoData: null,
+
+ /**
+ * Initializes the undo dialog.
+ */
+ init: function UndoDialog_init() {
+ this._undoContainer = document.getElementById("newtab-undo-container");
+ this._undoContainer.addEventListener("click", this, false);
+ this._undoButton = document.getElementById("newtab-undo-button");
+ this._undoCloseButton = document.getElementById("newtab-undo-close-button");
+ this._undoRestoreButton = document.getElementById("newtab-undo-restore-button");
+ },
+
+ /**
+ * Shows the undo dialog.
+ * @param aSite The site that just got removed.
+ */
+ show: function UndoDialog_show(aSite) {
+ if (this._undoData)
+ clearTimeout(this._undoData.timeout);
+
+ this._undoData = {
+ index: aSite.cell.index,
+ wasPinned: aSite.isPinned(),
+ blockedLink: aSite.link,
+ timeout: setTimeout(this.hide.bind(this), this.HIDE_TIMEOUT_MS)
+ };
+
+ this._undoContainer.removeAttribute("undo-disabled");
+ this._undoButton.removeAttribute("tabindex");
+ this._undoCloseButton.removeAttribute("tabindex");
+ this._undoRestoreButton.removeAttribute("tabindex");
+ },
+
+ /**
+ * Hides the undo dialog.
+ */
+ hide: function UndoDialog_hide() {
+ if (!this._undoData)
+ return;
+
+ clearTimeout(this._undoData.timeout);
+ this._undoData = null;
+ this._undoContainer.setAttribute("undo-disabled", "true");
+ this._undoButton.setAttribute("tabindex", "-1");
+ this._undoCloseButton.setAttribute("tabindex", "-1");
+ this._undoRestoreButton.setAttribute("tabindex", "-1");
+ },
+
+ /**
+ * The undo dialog event handler.
+ * @param aEvent The event to handle.
+ */
+ handleEvent: function UndoDialog_handleEvent(aEvent) {
+ switch (aEvent.target.id) {
+ case "newtab-undo-button":
+ this._undo();
+ break;
+ case "newtab-undo-restore-button":
+ this._undoAll();
+ break;
+ case "newtab-undo-close-button":
+ this.hide();
+ break;
+ }
+ },
+
+ /**
+ * Undo the last blocked site.
+ */
+ _undo: function UndoDialog_undo() {
+ if (!this._undoData)
+ return;
+
+ let {index, wasPinned, blockedLink} = this._undoData;
+ gBlockedLinks.unblock(blockedLink);
+
+ if (wasPinned) {
+ gPinnedLinks.pin(blockedLink, index);
+ }
+
+ gUpdater.updateGrid();
+ this.hide();
+ },
+
+ /**
+ * Undo all blocked sites.
+ */
+ _undoAll: function UndoDialog_undoAll() {
+ NewTabUtils.undoAll(function() {
+ gUpdater.updateGrid();
+ this.hide();
+ }.bind(this));
+ }
+};
+
+gUndoDialog.init();
diff --git a/browser/base/content/newtab/updater.js b/browser/base/content/newtab/updater.js
new file mode 100644
index 000000000..7b483e037
--- /dev/null
+++ b/browser/base/content/newtab/updater.js
@@ -0,0 +1,186 @@
+#ifdef 0
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+#endif
+
+/**
+ * This singleton provides functionality to update the current grid to a new
+ * set of pinned and blocked sites. It adds, moves and removes sites.
+ */
+let gUpdater = {
+ /**
+ * Updates the current grid according to its pinned and blocked sites.
+ * This removes old, moves existing and creates new sites to fill gaps.
+ * @param aCallback The callback to call when finished.
+ */
+ updateGrid: function Updater_updateGrid(aCallback) {
+ let links = gLinks.getLinks().slice(0, gGrid.cells.length);
+
+ // Find all sites that remain in the grid.
+ let sites = this._findRemainingSites(links);
+
+ let self = this;
+
+ // Remove sites that are no longer in the grid.
+ this._removeLegacySites(sites, function () {
+ // Freeze all site positions so that we can move their DOM nodes around
+ // without any visual impact.
+ self._freezeSitePositions(sites);
+
+ // Move the sites' DOM nodes to their new position in the DOM. This will
+ // have no visual effect as all the sites have been frozen and will
+ // remain in their current position.
+ self._moveSiteNodes(sites);
+
+ // Now it's time to animate the sites actually moving to their new
+ // positions.
+ self._rearrangeSites(sites, function () {
+ // Try to fill empty cells and finish.
+ self._fillEmptyCells(links, aCallback);
+
+ // Update other pages that might be open to keep them synced.
+ gAllPages.update(gPage);
+ });
+ });
+ },
+
+ /**
+ * Takes an array of links and tries to correlate them to sites contained in
+ * the current grid. If no corresponding site can be found (i.e. the link is
+ * new and a site will be created) then just set it to null.
+ * @param aLinks The array of links to find sites for.
+ * @return Array of sites mapped to the given links (can contain null values).
+ */
+ _findRemainingSites: function Updater_findRemainingSites(aLinks) {
+ let map = {};
+
+ // Create a map to easily retrieve the site for a given URL.
+ gGrid.sites.forEach(function (aSite) {
+ if (aSite)
+ map[aSite.url] = aSite;
+ });
+
+ // Map each link to its corresponding site, if any.
+ return aLinks.map(function (aLink) {
+ return aLink && (aLink.url in map) && map[aLink.url];
+ });
+ },
+
+ /**
+ * Freezes the given sites' positions.
+ * @param aSites The array of sites to freeze.
+ */
+ _freezeSitePositions: function Updater_freezeSitePositions(aSites) {
+ aSites.forEach(function (aSite) {
+ if (aSite)
+ gTransformation.freezeSitePosition(aSite);
+ });
+ },
+
+ /**
+ * Moves the given sites' DOM nodes to their new positions.
+ * @param aSites The array of sites to move.
+ */
+ _moveSiteNodes: function Updater_moveSiteNodes(aSites) {
+ let cells = gGrid.cells;
+
+ // Truncate the given array of sites to not have more sites than cells.
+ // This can happen when the user drags a bookmark (or any other new kind
+ // of link) onto the grid.
+ let sites = aSites.slice(0, cells.length);
+
+ sites.forEach(function (aSite, aIndex) {
+ let cell = cells[aIndex];
+ let cellSite = cell.site;
+
+ // The site's position didn't change.
+ if (!aSite || cellSite != aSite) {
+ let cellNode = cell.node;
+
+ // Empty the cell if necessary.
+ if (cellSite)
+ cellNode.removeChild(cellSite.node);
+
+ // Put the new site in place, if any.
+ if (aSite)
+ cellNode.appendChild(aSite.node);
+ }
+ }, this);
+ },
+
+ /**
+ * Rearranges the given sites and slides them to their new positions.
+ * @param aSites The array of sites to re-arrange.
+ * @param aCallback The callback to call when finished.
+ */
+ _rearrangeSites: function Updater_rearrangeSites(aSites, aCallback) {
+ let options = {callback: aCallback, unfreeze: true};
+ gTransformation.rearrangeSites(aSites, options);
+ },
+
+ /**
+ * Removes all sites from the grid that are not in the given links array or
+ * exceed the grid.
+ * @param aSites The array of sites remaining in the grid.
+ * @param aCallback The callback to call when finished.
+ */
+ _removeLegacySites: function Updater_removeLegacySites(aSites, aCallback) {
+ let batch = [];
+
+ // Delete sites that were removed from the grid.
+ gGrid.sites.forEach(function (aSite) {
+ // The site must be valid and not in the current grid.
+ if (!aSite || aSites.indexOf(aSite) != -1)
+ return;
+
+ let deferred = Promise.defer();
+ batch.push(deferred.promise);
+
+ // Fade out the to-be-removed site.
+ gTransformation.hideSite(aSite, function () {
+ let node = aSite.node;
+
+ // Remove the site from the DOM.
+ node.parentNode.removeChild(node);
+ deferred.resolve();
+ });
+ });
+
+ let wait = Promise.promised(aCallback);
+ wait.apply(null, batch);
+ },
+
+ /**
+ * Tries to fill empty cells with new links if available.
+ * @param aLinks The array of links.
+ * @param aCallback The callback to call when finished.
+ */
+ _fillEmptyCells: function Updater_fillEmptyCells(aLinks, aCallback) {
+ let {cells, sites} = gGrid;
+ let batch = [];
+
+ // Find empty cells and fill them.
+ sites.forEach(function (aSite, aIndex) {
+ if (aSite || !aLinks[aIndex])
+ return;
+
+ let deferred = Promise.defer();
+ batch.push(deferred.promise);
+
+ // Create the new site and fade it in.
+ let site = gGrid.createSite(aLinks[aIndex], cells[aIndex]);
+
+ // Set the site's initial opacity to zero.
+ site.node.style.opacity = 0;
+
+ // Flush all style changes for the dynamically inserted site to make
+ // the fade-in transition work.
+ window.getComputedStyle(site.node).opacity;
+ gTransformation.showSite(site, function () deferred.resolve());
+ });
+
+ let wait = Promise.promised(aCallback);
+ wait.apply(null, batch);
+ }
+};
diff --git a/browser/base/content/nsContextMenu.js b/browser/base/content/nsContextMenu.js
new file mode 100644
index 000000000..b6b894dff
--- /dev/null
+++ b/browser/base/content/nsContextMenu.js
@@ -0,0 +1,1623 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+function nsContextMenu(aXulMenu, aIsShift) {
+ this.shouldDisplay = true;
+ this.initMenu(aXulMenu, aIsShift);
+}
+
+// Prototype for nsContextMenu "class."
+nsContextMenu.prototype = {
+ initMenu: function CM_initMenu(aXulMenu, aIsShift) {
+ // Get contextual info.
+ this.setTarget(document.popupNode, document.popupRangeParent,
+ document.popupRangeOffset);
+ if (!this.shouldDisplay)
+ return;
+
+ this.hasPageMenu = false;
+ if (!aIsShift) {
+ this.hasPageMenu = PageMenu.maybeBuildAndAttachMenu(this.target,
+ aXulMenu);
+ }
+
+ this.isFrameImage = document.getElementById("isFrameImage");
+ this.ellipsis = "\u2026";
+ try {
+ this.ellipsis = gPrefService.getComplexValue("intl.ellipsis",
+ Ci.nsIPrefLocalizedString).data;
+ } catch (e) { }
+ this.isTextSelected = this.isTextSelection();
+ this.isContentSelected = this.isContentSelection();
+ this.onPlainTextLink = false;
+
+ // Initialize (disable/remove) menu items.
+ this.initItems();
+ },
+
+ hiding: function CM_hiding() {
+ InlineSpellCheckerUI.clearSuggestionsFromMenu();
+ InlineSpellCheckerUI.clearDictionaryListFromMenu();
+ InlineSpellCheckerUI.uninit();
+ },
+
+ initItems: function CM_initItems() {
+ this.initPageMenuSeparator();
+ this.initOpenItems();
+ this.initNavigationItems();
+ this.initViewItems();
+ this.initMiscItems();
+ this.initSpellingItems();
+ this.initSaveItems();
+ this.initClipboardItems();
+ this.initMediaPlayerItems();
+ this.initLeaveDOMFullScreenItems();
+ this.initClickToPlayItems();
+ },
+
+ initPageMenuSeparator: function CM_initPageMenuSeparator() {
+ this.showItem("page-menu-separator", this.hasPageMenu);
+ },
+
+ initOpenItems: function CM_initOpenItems() {
+ var isMailtoInternal = false;
+ if (this.onMailtoLink) {
+ var mailtoHandler = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+ getService(Ci.nsIExternalProtocolService).
+ getProtocolHandlerInfo("mailto");
+ isMailtoInternal = (!mailtoHandler.alwaysAskBeforeHandling &&
+ mailtoHandler.preferredAction == Ci.nsIHandlerInfo.useHelperApp &&
+ (mailtoHandler.preferredApplicationHandler instanceof Ci.nsIWebHandlerApp));
+ }
+
+ // Time to do some bad things and see if we've highlighted a URL that
+ // isn't actually linked.
+ if (this.isTextSelected && !this.onLink) {
+ // Ok, we have some text, let's figure out if it looks like a URL.
+ let selection = document.commandDispatcher.focusedWindow
+ .getSelection();
+ let linkText = selection.toString().trim();
+ let uri;
+ if (/^(?:https?|ftp):/i.test(linkText)) {
+ try {
+ uri = makeURI(linkText);
+ } catch (ex) {}
+ }
+ // Check if this could be a valid url, just missing the protocol.
+ else if (/^(?:[a-z\d-]+\.)+[a-z]+$/i.test(linkText)) {
+ // Now let's see if this is an intentional link selection. Our guess is
+ // based on whether the selection begins/ends with whitespace or is
+ // preceded/followed by a non-word character.
+
+ // selection.toString() trims trailing whitespace, so we look for
+ // that explicitly in the first and last ranges.
+ let beginRange = selection.getRangeAt(0);
+ let delimitedAtStart = /^\s/.test(beginRange);
+ if (!delimitedAtStart) {
+ let container = beginRange.startContainer;
+ let offset = beginRange.startOffset;
+ if (container.nodeType == Node.TEXT_NODE && offset > 0)
+ delimitedAtStart = /\W/.test(container.textContent[offset - 1]);
+ else
+ delimitedAtStart = true;
+ }
+
+ let delimitedAtEnd = false;
+ if (delimitedAtStart) {
+ let endRange = selection.getRangeAt(selection.rangeCount - 1);
+ delimitedAtEnd = /\s$/.test(endRange);
+ if (!delimitedAtEnd) {
+ let container = endRange.endContainer;
+ let offset = endRange.endOffset;
+ if (container.nodeType == Node.TEXT_NODE &&
+ offset < container.textContent.length)
+ delimitedAtEnd = /\W/.test(container.textContent[offset]);
+ else
+ delimitedAtEnd = true;
+ }
+ }
+
+ if (delimitedAtStart && delimitedAtEnd) {
+ let uriFixup = Cc["@mozilla.org/docshell/urifixup;1"]
+ .getService(Ci.nsIURIFixup);
+ try {
+ uri = uriFixup.createFixupURI(linkText, uriFixup.FIXUP_FLAG_NONE);
+ } catch (ex) {}
+ }
+ }
+
+ if (uri && uri.host) {
+ this.linkURI = uri;
+ this.linkURL = this.linkURI.spec;
+ this.onPlainTextLink = true;
+ }
+ }
+
+ var shouldShow = this.onSaveableLink || isMailtoInternal || this.onPlainTextLink;
+ var isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
+ this.showItem("context-openlink", shouldShow && !isWindowPrivate);
+ this.showItem("context-openlinkprivate", shouldShow);
+ this.showItem("context-openlinkintab", shouldShow);
+ this.showItem("context-openlinkincurrent", this.onPlainTextLink);
+ this.showItem("context-sep-open", shouldShow);
+ },
+
+ initNavigationItems: function CM_initNavigationItems() {
+ var shouldShow = !(this.isContentSelected || this.onLink || this.onImage ||
+ this.onCanvas || this.onVideo || this.onAudio ||
+ this.onTextInput || this.onSocial);
+ this.showItem("context-back", shouldShow);
+ this.showItem("context-forward", shouldShow);
+
+ let stopped = XULBrowserWindow.stopCommand.getAttribute("disabled") == "true";
+
+ let stopReloadItem = "";
+ if (shouldShow || this.onSocial) {
+ stopReloadItem = (stopped || this.onSocial) ? "reload" : "stop";
+ }
+
+ this.showItem("context-reload", stopReloadItem == "reload");
+ this.showItem("context-stop", stopReloadItem == "stop");
+ this.showItem("context-sep-stop", !!stopReloadItem);
+
+ // XXX: Stop is determined in browser.js; the canStop broadcaster is broken
+ //this.setItemAttrFromNode( "context-stop", "disabled", "canStop" );
+ },
+
+ initLeaveDOMFullScreenItems: function CM_initLeaveFullScreenItem() {
+ // only show the option if the user is in DOM fullscreen
+ var shouldShow = (this.target.ownerDocument.mozFullScreenElement != null);
+ this.showItem("context-leave-dom-fullscreen", shouldShow);
+
+ // Explicitly show if in DOM fullscreen, but do not hide it has already been shown
+ if (shouldShow)
+ this.showItem("context-media-sep-commands", true);
+ },
+
+ initSaveItems: function CM_initSaveItems() {
+ var shouldShow = !(this.onTextInput || this.onLink ||
+ this.isContentSelected || this.onImage ||
+ this.onCanvas || this.onVideo || this.onAudio);
+ this.showItem("context-savepage", shouldShow);
+ this.showItem("context-sendpage", shouldShow);
+
+ // Save+Send link depends on whether we're in a link, or selected text matches valid URL pattern.
+ this.showItem("context-savelink", this.onSaveableLink || this.onPlainTextLink);
+ this.showItem("context-sendlink", this.onSaveableLink || this.onPlainTextLink);
+
+ // Save image depends on having loaded its content, video and audio don't.
+ this.showItem("context-saveimage", this.onLoadedImage || this.onCanvas);
+ this.showItem("context-savevideo", this.onVideo);
+ this.showItem("context-saveaudio", this.onAudio);
+ this.showItem("context-video-saveimage", this.onVideo);
+ this.setItemAttr("context-savevideo", "disabled", !this.mediaURL);
+ this.setItemAttr("context-saveaudio", "disabled", !this.mediaURL);
+ // Send media URL (but not for canvas, since it's a big data: URL)
+ this.showItem("context-sendimage", this.onImage);
+ this.showItem("context-sendvideo", this.onVideo);
+ this.showItem("context-sendaudio", this.onAudio);
+ this.setItemAttr("context-sendvideo", "disabled", !this.mediaURL);
+ this.setItemAttr("context-sendaudio", "disabled", !this.mediaURL);
+ },
+
+ initViewItems: function CM_initViewItems() {
+ // View source is always OK, unless in directory listing.
+ this.showItem("context-viewpartialsource-selection",
+ this.isContentSelected);
+ this.showItem("context-viewpartialsource-mathml",
+ this.onMathML && !this.isContentSelected);
+
+ var shouldShow = !(this.isContentSelected ||
+ this.onImage || this.onCanvas ||
+ this.onVideo || this.onAudio ||
+ this.onLink || this.onTextInput);
+ var showInspect = !this.onSocial && gPrefService.getBoolPref("devtools.inspector.enabled");
+ this.showItem("context-viewsource", shouldShow);
+ this.showItem("context-viewinfo", shouldShow);
+ this.showItem("inspect-separator", showInspect);
+ this.showItem("context-inspect", showInspect);
+
+ this.showItem("context-sep-viewsource", shouldShow);
+
+ // Set as Desktop background depends on whether an image was clicked on,
+ // and only works if we have a shell service.
+ var haveSetDesktopBackground = false;
+#ifdef HAVE_SHELL_SERVICE
+ // Only enable Set as Desktop Background if we can get the shell service.
+ var shell = getShellService();
+ if (shell)
+ haveSetDesktopBackground = shell.canSetDesktopBackground;
+#endif
+ this.showItem("context-setDesktopBackground",
+ haveSetDesktopBackground && this.onLoadedImage);
+
+ if (haveSetDesktopBackground && this.onLoadedImage) {
+ document.getElementById("context-setDesktopBackground")
+ .disabled = this.disableSetDesktopBackground();
+ }
+
+ // Reload image depends on an image that's not fully loaded
+ this.showItem("context-reloadimage", (this.onImage && !this.onCompletedImage));
+
+ // View image depends on having an image that's not standalone
+ // (or is in a frame), or a canvas.
+ this.showItem("context-viewimage", (this.onImage &&
+ (!this.inSyntheticDoc || this.inFrame)) || this.onCanvas);
+
+ // View video depends on not having a standalone video.
+ this.showItem("context-viewvideo", this.onVideo && (!this.inSyntheticDoc || this.inFrame));
+ this.setItemAttr("context-viewvideo", "disabled", !this.mediaURL);
+
+ // View background image depends on whether there is one, but don't make
+ // background images of a stand-alone media document available.
+ this.showItem("context-viewbgimage", shouldShow &&
+ !this._hasMultipleBGImages &&
+ !this.inSyntheticDoc);
+ this.showItem("context-sep-viewbgimage", shouldShow &&
+ !this._hasMultipleBGImages &&
+ !this.inSyntheticDoc);
+ document.getElementById("context-viewbgimage")
+ .disabled = !this.hasBGImage;
+
+ this.showItem("context-viewimageinfo", this.onImage);
+ },
+
+ initMiscItems: function CM_initMiscItems() {
+ var isTextSelected = this.isTextSelected;
+
+ // Use "Bookmark This Link" if on a link.
+ this.showItem("context-bookmarkpage",
+ !(this.isContentSelected || this.onTextInput || this.onLink ||
+ this.onImage || this.onVideo || this.onAudio || this.onSocial));
+ this.showItem("context-bookmarklink", (this.onLink && !this.onMailtoLink &&
+ !this.onSocial) || this.onPlainTextLink);
+ this.showItem("context-searchselect", isTextSelected);
+ this.showItem("context-keywordfield",
+ this.onTextInput && this.onKeywordField);
+ this.showItem("frame", this.inFrame);
+ this.showItem("frame-sep", this.inFrame && isTextSelected);
+
+ // Hide menu entries for images, show otherwise
+ if (this.inFrame) {
+ if (mimeTypeIsTextBased(this.target.ownerDocument.contentType))
+ this.isFrameImage.removeAttribute('hidden');
+ else
+ this.isFrameImage.setAttribute('hidden', 'true');
+ }
+
+ // BiDi UI
+ this.showItem("context-sep-bidi", top.gBidiUI);
+ this.showItem("context-bidi-text-direction-toggle",
+ this.onTextInput && top.gBidiUI);
+ this.showItem("context-bidi-page-direction-toggle",
+ !this.onTextInput && top.gBidiUI);
+
+ // SocialMarks
+ let marksEnabled = SocialUI.enabled && Social.provider.pageMarkInfo;
+ let enablePageMark = marksEnabled && !(this.isContentSelected ||
+ this.onTextInput || this.onLink || this.onImage ||
+ this.onVideo || this.onAudio || this.onSocial);
+ let enableLinkMark = marksEnabled && ((this.onLink && !this.onMailtoLink &&
+ !this.onSocial) || this.onPlainTextLink);
+ if (enablePageMark) {
+ Social.isURIMarked(gBrowser.currentURI, function(marked) {
+ let label = marked ? "social.unmarkpage.label" : "social.markpage.label";
+ let provider = Social.provider || Social.defaultProvider;
+ let menuLabel = gNavigatorBundle.getFormattedString(label, [provider.name]);
+ this.setItemAttr("context-markpage", "label", menuLabel);
+ }.bind(this));
+ }
+ this.showItem("context-markpage", enablePageMark);
+ if (enableLinkMark) {
+ Social.isURIMarked(this.linkURI, function(marked) {
+ let label = marked ? "social.unmarklink.label" : "social.marklink.label";
+ let provider = Social.provider || Social.defaultProvider;
+ let menuLabel = gNavigatorBundle.getFormattedString(label, [provider.name]);
+ this.setItemAttr("context-marklink", "label", menuLabel);
+ }.bind(this));
+ }
+ this.showItem("context-marklink", enableLinkMark);
+
+ // SocialShare
+ let shareButton = SocialShare.shareButton;
+ let shareEnabled = shareButton && !shareButton.disabled && !this.onSocial;
+ let pageShare = shareEnabled && !(this.isContentSelected ||
+ this.onTextInput || this.onLink || this.onImage ||
+ this.onVideo || this.onAudio);
+ this.showItem("context-sharepage", pageShare);
+ this.showItem("context-shareselect", shareEnabled && this.isContentSelected);
+ this.showItem("context-sharelink", shareEnabled && (this.onLink || this.onPlainTextLink) && !this.onMailtoLink);
+ this.showItem("context-shareimage", shareEnabled && this.onImage);
+ this.showItem("context-sharevideo", shareEnabled && this.onVideo);
+ this.setItemAttr("context-sharevideo", "disabled", !this.mediaURL);
+ },
+
+ initSpellingItems: function() {
+ var canSpell = InlineSpellCheckerUI.canSpellCheck && this.canSpellCheck;
+ var onMisspelling = InlineSpellCheckerUI.overMisspelling;
+ var showUndo = canSpell && InlineSpellCheckerUI.canUndo();
+ this.showItem("spell-check-enabled", canSpell);
+ this.showItem("spell-separator", canSpell || this.onEditableArea);
+ document.getElementById("spell-check-enabled")
+ .setAttribute("checked", canSpell && InlineSpellCheckerUI.enabled);
+
+ this.showItem("spell-add-to-dictionary", onMisspelling);
+ this.showItem("spell-undo-add-to-dictionary", showUndo);
+
+ // suggestion list
+ this.showItem("spell-suggestions-separator", onMisspelling || showUndo);
+ if (onMisspelling) {
+ var suggestionsSeparator =
+ document.getElementById("spell-add-to-dictionary");
+ var numsug =
+ InlineSpellCheckerUI.addSuggestionsToMenu(suggestionsSeparator.parentNode,
+ suggestionsSeparator, 5);
+ this.showItem("spell-no-suggestions", numsug == 0);
+ }
+ else
+ this.showItem("spell-no-suggestions", false);
+
+ // dictionary list
+ this.showItem("spell-dictionaries", canSpell && InlineSpellCheckerUI.enabled);
+ if (canSpell) {
+ var dictMenu = document.getElementById("spell-dictionaries-menu");
+ var dictSep = document.getElementById("spell-language-separator");
+ InlineSpellCheckerUI.addDictionaryListToMenu(dictMenu, dictSep);
+ this.showItem("spell-add-dictionaries-main", false);
+ }
+ else if (this.onEditableArea) {
+ // when there is no spellchecker but we might be able to spellcheck
+ // add the add to dictionaries item. This will ensure that people
+ // with no dictionaries will be able to download them
+ this.showItem("spell-add-dictionaries-main", true);
+ }
+ else
+ this.showItem("spell-add-dictionaries-main", false);
+ },
+
+ initClipboardItems: function() {
+ // Copy depends on whether there is selected text.
+ // Enabling this context menu item is now done through the global
+ // command updating system
+ // this.setItemAttr( "context-copy", "disabled", !this.isTextSelected() );
+ goUpdateGlobalEditMenuItems();
+
+ this.showItem("context-undo", this.onTextInput);
+ this.showItem("context-sep-undo", this.onTextInput);
+ this.showItem("context-cut", this.onTextInput);
+ this.showItem("context-copy",
+ this.isContentSelected || this.onTextInput);
+ this.showItem("context-paste", this.onTextInput);
+ this.showItem("context-delete", this.onTextInput);
+ this.showItem("context-sep-paste", this.onTextInput);
+ this.showItem("context-selectall", !(this.onLink || this.onImage ||
+ this.onVideo || this.onAudio ||
+ this.inSyntheticDoc) ||
+ this.isDesignMode);
+ this.showItem("context-sep-selectall", this.isContentSelected );
+
+ // XXX dr
+ // ------
+ // nsDocumentViewer.cpp has code to determine whether we're
+ // on a link or an image. we really ought to be using that...
+
+ // Copy email link depends on whether we're on an email link.
+ this.showItem("context-copyemail", this.onMailtoLink);
+
+ // Copy link location depends on whether we're on a non-mailto link.
+ this.showItem("context-copylink", this.onLink && !this.onMailtoLink);
+ this.showItem("context-sep-copylink", this.onLink &&
+ (this.onImage || this.onVideo || this.onAudio));
+
+#ifdef CONTEXT_COPY_IMAGE_CONTENTS
+ // Copy image contents depends on whether we're on an image.
+ this.showItem("context-copyimage-contents", this.onImage);
+#endif
+ // Copy image location depends on whether we're on an image.
+ this.showItem("context-copyimage", this.onImage);
+ this.showItem("context-copyvideourl", this.onVideo);
+ this.showItem("context-copyaudiourl", this.onAudio);
+ this.setItemAttr("context-copyvideourl", "disabled", !this.mediaURL);
+ this.setItemAttr("context-copyaudiourl", "disabled", !this.mediaURL);
+ this.showItem("context-sep-copyimage", this.onImage ||
+ this.onVideo || this.onAudio);
+ },
+
+ initMediaPlayerItems: function() {
+ var onMedia = (this.onVideo || this.onAudio);
+ // Several mutually exclusive items... play/pause, mute/unmute, show/hide
+ this.showItem("context-media-play", onMedia && (this.target.paused || this.target.ended));
+ this.showItem("context-media-pause", onMedia && !this.target.paused && !this.target.ended);
+ this.showItem("context-media-mute", onMedia && !this.target.muted);
+ this.showItem("context-media-unmute", onMedia && this.target.muted);
+ this.showItem("context-media-playbackrate", onMedia);
+ this.showItem("context-media-showcontrols", onMedia && !this.target.controls);
+ this.showItem("context-media-hidecontrols", onMedia && this.target.controls);
+ this.showItem("context-video-fullscreen", this.onVideo && this.target.ownerDocument.mozFullScreenElement == null);
+ var statsShowing = this.onVideo && XPCNativeWrapper.unwrap(this.target).mozMediaStatisticsShowing;
+ this.showItem("context-video-showstats", this.onVideo && this.target.controls && !statsShowing);
+ this.showItem("context-video-hidestats", this.onVideo && this.target.controls && statsShowing);
+
+ // Disable them when there isn't a valid media source loaded.
+ if (onMedia) {
+ this.setItemAttr("context-media-playbackrate-050x", "checked", this.target.playbackRate == 0.5);
+ this.setItemAttr("context-media-playbackrate-100x", "checked", this.target.playbackRate == 1.0);
+ this.setItemAttr("context-media-playbackrate-150x", "checked", this.target.playbackRate == 1.5);
+ this.setItemAttr("context-media-playbackrate-200x", "checked", this.target.playbackRate == 2.0);
+ var hasError = this.target.error != null ||
+ this.target.networkState == this.target.NETWORK_NO_SOURCE;
+ this.setItemAttr("context-media-play", "disabled", hasError);
+ this.setItemAttr("context-media-pause", "disabled", hasError);
+ this.setItemAttr("context-media-mute", "disabled", hasError);
+ this.setItemAttr("context-media-unmute", "disabled", hasError);
+ this.setItemAttr("context-media-playbackrate", "disabled", hasError);
+ this.setItemAttr("context-media-playbackrate-050x", "disabled", hasError);
+ this.setItemAttr("context-media-playbackrate-100x", "disabled", hasError);
+ this.setItemAttr("context-media-playbackrate-150x", "disabled", hasError);
+ this.setItemAttr("context-media-playbackrate-200x", "disabled", hasError);
+ this.setItemAttr("context-media-showcontrols", "disabled", hasError);
+ this.setItemAttr("context-media-hidecontrols", "disabled", hasError);
+ if (this.onVideo) {
+ let canSaveSnapshot = this.target.readyState >= this.target.HAVE_CURRENT_DATA;
+ this.setItemAttr("context-video-saveimage", "disabled", !canSaveSnapshot);
+ this.setItemAttr("context-video-fullscreen", "disabled", hasError);
+ this.setItemAttr("context-video-showstats", "disabled", hasError);
+ this.setItemAttr("context-video-hidestats", "disabled", hasError);
+ }
+ }
+ this.showItem("context-media-sep-commands", onMedia);
+ },
+
+ initClickToPlayItems: function() {
+ this.showItem("context-ctp-play", this.onCTPPlugin);
+ this.showItem("context-ctp-hide", this.onCTPPlugin);
+ this.showItem("context-sep-ctp", this.onCTPPlugin);
+ },
+
+ inspectNode: function CM_inspectNode() {
+ let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+ let gBrowser = this.browser.ownerDocument.defaultView.gBrowser;
+ let tt = devtools.TargetFactory.forTab(gBrowser.selectedTab);
+ return gDevTools.showToolbox(tt, "inspector").then(function(toolbox) {
+ let inspector = toolbox.getCurrentPanel();
+ inspector.selection.setNode(this.target, "browser-context-menu");
+ }.bind(this));
+ },
+
+ // Set various context menu attributes based on the state of the world.
+ setTarget: function (aNode, aRangeParent, aRangeOffset) {
+ const xulNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ if (aNode.namespaceURI == xulNS ||
+ aNode.nodeType == Node.DOCUMENT_NODE ||
+ this.isDisabledForEvents(aNode)) {
+ this.shouldDisplay = false;
+ return;
+ }
+
+ // Initialize contextual info.
+ this.onImage = false;
+ this.onLoadedImage = false;
+ this.onCompletedImage = false;
+ this.onCanvas = false;
+ this.onVideo = false;
+ this.onAudio = false;
+ this.onTextInput = false;
+ this.onKeywordField = false;
+ this.mediaURL = "";
+ this.onLink = false;
+ this.onMailtoLink = false;
+ this.onSaveableLink = false;
+ this.link = null;
+ this.linkURL = "";
+ this.linkURI = null;
+ this.linkProtocol = "";
+ this.onMathML = false;
+ this.inFrame = false;
+ this.inSyntheticDoc = false;
+ this.hasBGImage = false;
+ this.bgImageURL = "";
+ this.onEditableArea = false;
+ this.isDesignMode = false;
+ this.onCTPPlugin = false;
+ this.canSpellCheck = false;
+
+ // Remember the node that was clicked.
+ this.target = aNode;
+
+ this.browser = this.target.ownerDocument.defaultView
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+ this.onSocial = !!this.browser.getAttribute("origin");
+
+ // Check if we are in a synthetic document (stand alone image, video, etc.).
+ this.inSyntheticDoc = this.target.ownerDocument.mozSyntheticDocument;
+ // First, do checks for nodes that never have children.
+ if (this.target.nodeType == Node.ELEMENT_NODE) {
+ // See if the user clicked on an image.
+ if (this.target instanceof Ci.nsIImageLoadingContent &&
+ this.target.currentURI) {
+ this.onImage = true;
+
+ var request =
+ this.target.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
+ if (request && (request.imageStatus & request.STATUS_SIZE_AVAILABLE))
+ this.onLoadedImage = true;
+ if (request && (request.imageStatus & request.STATUS_LOAD_COMPLETE))
+ this.onCompletedImage = true;
+
+ this.mediaURL = this.target.currentURI.spec;
+ }
+ else if (this.target instanceof HTMLCanvasElement) {
+ this.onCanvas = true;
+ }
+ else if (this.target instanceof HTMLVideoElement) {
+ this.mediaURL = this.target.currentSrc || this.target.src;
+ // Firefox always creates a HTMLVideoElement when loading an ogg file
+ // directly. If the media is actually audio, be smarter and provide a
+ // context menu with audio operations.
+ if (this.target.readyState >= this.target.HAVE_METADATA &&
+ (this.target.videoWidth == 0 || this.target.videoHeight == 0)) {
+ this.onAudio = true;
+ } else {
+ this.onVideo = true;
+ }
+ }
+ else if (this.target instanceof HTMLAudioElement) {
+ this.onAudio = true;
+ this.mediaURL = this.target.currentSrc || this.target.src;
+ }
+ else if (this.target instanceof HTMLInputElement ) {
+ this.onTextInput = this.isTargetATextBox(this.target);
+ // Allow spellchecking UI on all text and search inputs.
+ if (this.onTextInput && ! this.target.readOnly &&
+ (this.target.type == "text" || this.target.type == "search")) {
+ this.onEditableArea = true;
+ InlineSpellCheckerUI.init(this.target.QueryInterface(Ci.nsIDOMNSEditableElement).editor);
+ InlineSpellCheckerUI.initFromEvent(aRangeParent, aRangeOffset);
+ }
+ this.onKeywordField = this.isTargetAKeywordField(this.target);
+ }
+ else if (this.target instanceof HTMLTextAreaElement) {
+ this.onTextInput = true;
+ if (!this.target.readOnly) {
+ this.onEditableArea = true;
+ InlineSpellCheckerUI.init(this.target.QueryInterface(Ci.nsIDOMNSEditableElement).editor);
+ InlineSpellCheckerUI.initFromEvent(aRangeParent, aRangeOffset);
+ }
+ }
+ else if (this.target instanceof HTMLHtmlElement) {
+ var bodyElt = this.target.ownerDocument.body;
+ if (bodyElt) {
+ let computedURL;
+ try {
+ computedURL = this.getComputedURL(bodyElt, "background-image");
+ this._hasMultipleBGImages = false;
+ } catch (e) {
+ this._hasMultipleBGImages = true;
+ }
+ if (computedURL) {
+ this.hasBGImage = true;
+ this.bgImageURL = makeURLAbsolute(bodyElt.baseURI,
+ computedURL);
+ }
+ }
+ }
+ else if ((this.target instanceof HTMLEmbedElement ||
+ this.target instanceof HTMLObjectElement ||
+ this.target instanceof HTMLAppletElement) &&
+ this.target.mozMatchesSelector(":-moz-handler-clicktoplay")) {
+ this.onCTPPlugin = true;
+ }
+
+ this.canSpellCheck = this._isSpellCheckEnabled(this.target);
+ }
+ else if (this.target.nodeType == Node.TEXT_NODE) {
+ // For text nodes, look at the parent node to determine the spellcheck attribute.
+ this.canSpellCheck = this.target.parentNode &&
+ this._isSpellCheckEnabled(this.target);
+ }
+
+ // Second, bubble out, looking for items of interest that can have childen.
+ // Always pick the innermost link, background image, etc.
+ const XMLNS = "http://www.w3.org/XML/1998/namespace";
+ var elem = this.target;
+ while (elem) {
+ if (elem.nodeType == Node.ELEMENT_NODE) {
+ // Link?
+ if (!this.onLink &&
+ // Be consistent with what hrefAndLinkNodeForClickEvent
+ // does in browser.js
+ ((elem instanceof HTMLAnchorElement && elem.href) ||
+ (elem instanceof HTMLAreaElement && elem.href) ||
+ elem instanceof HTMLLinkElement ||
+ elem.getAttributeNS("http://www.w3.org/1999/xlink", "type") == "simple")) {
+
+ // Target is a link or a descendant of a link.
+ this.onLink = true;
+
+ // Remember corresponding element.
+ this.link = elem;
+ this.linkURL = this.getLinkURL();
+ this.linkURI = this.getLinkURI();
+ this.linkProtocol = this.getLinkProtocol();
+ this.onMailtoLink = (this.linkProtocol == "mailto");
+ this.onSaveableLink = this.isLinkSaveable( this.link );
+ }
+
+ // Background image? Don't bother if we've already found a
+ // background image further down the hierarchy. Otherwise,
+ // we look for the computed background-image style.
+ if (!this.hasBGImage &&
+ !this._hasMultipleBGImages) {
+ let bgImgUrl;
+ try {
+ bgImgUrl = this.getComputedURL(elem, "background-image");
+ this._hasMultipleBGImages = false;
+ } catch (e) {
+ this._hasMultipleBGImages = true;
+ }
+ if (bgImgUrl) {
+ this.hasBGImage = true;
+ this.bgImageURL = makeURLAbsolute(elem.baseURI,
+ bgImgUrl);
+ }
+ }
+ }
+
+ elem = elem.parentNode;
+ }
+
+ // See if the user clicked on MathML
+ const NS_MathML = "http://www.w3.org/1998/Math/MathML";
+ if ((this.target.nodeType == Node.TEXT_NODE &&
+ this.target.parentNode.namespaceURI == NS_MathML)
+ || (this.target.namespaceURI == NS_MathML))
+ this.onMathML = true;
+
+ // See if the user clicked in a frame.
+ var docDefaultView = this.target.ownerDocument.defaultView;
+ if (docDefaultView != docDefaultView.top)
+ this.inFrame = true;
+
+ // if the document is editable, show context menu like in text inputs
+ if (!this.onEditableArea) {
+ var win = this.target.ownerDocument.defaultView;
+ if (win) {
+ var isEditable = false;
+ try {
+ var editingSession = win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIEditingSession);
+ if (editingSession.windowIsEditable(win) &&
+ this.getComputedStyle(this.target, "-moz-user-modify") == "read-write") {
+ isEditable = true;
+ }
+ }
+ catch(ex) {
+ // If someone built with composer disabled, we can't get an editing session.
+ }
+
+ if (isEditable) {
+ this.onTextInput = true;
+ this.onKeywordField = false;
+ this.onImage = false;
+ this.onLoadedImage = false;
+ this.onCompletedImage = false;
+ this.onMathML = false;
+ this.inFrame = false;
+ this.hasBGImage = false;
+ this.isDesignMode = true;
+ this.onEditableArea = true;
+ InlineSpellCheckerUI.init(editingSession.getEditorForWindow(win));
+ var canSpell = InlineSpellCheckerUI.canSpellCheck && this.canSpellCheck;
+ InlineSpellCheckerUI.initFromEvent(aRangeParent, aRangeOffset);
+ this.showItem("spell-check-enabled", canSpell);
+ this.showItem("spell-separator", canSpell);
+ }
+ }
+ }
+ },
+
+ // Returns the computed style attribute for the given element.
+ getComputedStyle: function(aElem, aProp) {
+ return aElem.ownerDocument
+ .defaultView
+ .getComputedStyle(aElem, "").getPropertyValue(aProp);
+ },
+
+ // Returns a "url"-type computed style attribute value, with the url() stripped.
+ getComputedURL: function(aElem, aProp) {
+ var url = aElem.ownerDocument
+ .defaultView.getComputedStyle(aElem, "")
+ .getPropertyCSSValue(aProp);
+ if (url instanceof CSSValueList) {
+ if (url.length != 1)
+ throw "found multiple URLs";
+ url = url[0];
+ }
+ return url.primitiveType == CSSPrimitiveValue.CSS_URI ?
+ url.getStringValue() : null;
+ },
+
+ // Returns true if clicked-on link targets a resource that can be saved.
+ isLinkSaveable: function(aLink) {
+ // We don't do the Right Thing for news/snews yet, so turn them off
+ // until we do.
+ return this.linkProtocol && !(
+ this.linkProtocol == "mailto" ||
+ this.linkProtocol == "javascript" ||
+ this.linkProtocol == "news" ||
+ this.linkProtocol == "snews" );
+ },
+
+ _isSpellCheckEnabled: function(aNode) {
+ // We can always force-enable spellchecking on textboxes
+ if (this.isTargetATextBox(aNode)) {
+ return true;
+ }
+ // We can never spell check something which is not content editable
+ var editable = aNode.isContentEditable;
+ if (!editable && aNode.ownerDocument) {
+ editable = aNode.ownerDocument.designMode == "on";
+ }
+ if (!editable) {
+ return false;
+ }
+ // Otherwise make sure that nothing in the parent chain disables spellchecking
+ return aNode.spellcheck;
+ },
+
+ // Open linked-to URL in a new window.
+ openLink : function () {
+ var doc = this.target.ownerDocument;
+ urlSecurityCheck(this.linkURL, doc.nodePrincipal);
+ openLinkIn(this.linkURL, "window",
+ { charset: doc.characterSet,
+ referrerURI: doc.documentURIObject });
+ },
+
+ // Open linked-to URL in a new private window.
+ openLinkInPrivateWindow : function () {
+ var doc = this.target.ownerDocument;
+ urlSecurityCheck(this.linkURL, doc.nodePrincipal);
+ openLinkIn(this.linkURL, "window",
+ { charset: doc.characterSet,
+ referrerURI: doc.documentURIObject,
+ private: true });
+ },
+
+ // Open linked-to URL in a new tab.
+ openLinkInTab: function() {
+ var doc = this.target.ownerDocument;
+ urlSecurityCheck(this.linkURL, doc.nodePrincipal);
+ openLinkIn(this.linkURL, "tab",
+ { charset: doc.characterSet,
+ referrerURI: doc.documentURIObject });
+ },
+
+ // open URL in current tab
+ openLinkInCurrent: function() {
+ var doc = this.target.ownerDocument;
+ urlSecurityCheck(this.linkURL, doc.nodePrincipal);
+ openLinkIn(this.linkURL, "current",
+ { charset: doc.characterSet,
+ referrerURI: doc.documentURIObject });
+ },
+
+ // Open frame in a new tab.
+ openFrameInTab: function() {
+ var doc = this.target.ownerDocument;
+ var frameURL = doc.location.href;
+ var referrer = doc.referrer;
+ openLinkIn(frameURL, "tab",
+ { charset: doc.characterSet,
+ referrerURI: referrer ? makeURI(referrer) : null });
+ },
+
+ // Reload clicked-in frame.
+ reloadFrame: function() {
+ this.target.ownerDocument.location.reload();
+ },
+
+ // Open clicked-in frame in its own window.
+ openFrame: function() {
+ var doc = this.target.ownerDocument;
+ var frameURL = doc.location.href;
+ var referrer = doc.referrer;
+ openLinkIn(frameURL, "window",
+ { charset: doc.characterSet,
+ referrerURI: referrer ? makeURI(referrer) : null });
+ },
+
+ // Open clicked-in frame in the same window.
+ showOnlyThisFrame: function() {
+ var doc = this.target.ownerDocument;
+ var frameURL = doc.location.href;
+
+ urlSecurityCheck(frameURL, this.browser.contentPrincipal,
+ Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
+ var referrer = doc.referrer;
+ openUILinkIn(frameURL, "current", { disallowInheritPrincipal: true,
+ referrerURI: referrer ? makeURI(referrer) : null });
+ },
+
+ reload: function(event) {
+ if (this.onSocial) {
+ // full reload of social provider
+ Social.enabled = false;
+ Services.tm.mainThread.dispatch(function() {
+ Social.enabled = true;
+ }, Components.interfaces.nsIThread.DISPATCH_NORMAL);
+ } else {
+ BrowserReloadOrDuplicate(event);
+ }
+ },
+
+ // View Partial Source
+ viewPartialSource: function(aContext) {
+ var focusedWindow = document.commandDispatcher.focusedWindow;
+ if (focusedWindow == window)
+ focusedWindow = content;
+
+ var docCharset = null;
+ if (focusedWindow)
+ docCharset = "charset=" + focusedWindow.document.characterSet;
+
+ // "View Selection Source" and others such as "View MathML Source"
+ // are mutually exclusive, with the precedence given to the selection
+ // when there is one
+ var reference = null;
+ if (aContext == "selection")
+ reference = focusedWindow.getSelection();
+ else if (aContext == "mathml")
+ reference = this.target;
+ else
+ throw "not reached";
+
+ // unused (and play nice for fragments generated via XSLT too)
+ var docUrl = null;
+ window.openDialog("chrome://global/content/viewPartialSource.xul",
+ "_blank", "scrollbars,resizable,chrome,dialog=no",
+ docUrl, docCharset, reference, aContext);
+ },
+
+ // Open new "view source" window with the frame's URL.
+ viewFrameSource: function() {
+ BrowserViewSourceOfDocument(this.target.ownerDocument);
+ },
+
+ viewInfo: function() {
+ BrowserPageInfo(this.target.ownerDocument.defaultView.top.document);
+ },
+
+ viewImageInfo: function() {
+ BrowserPageInfo(this.target.ownerDocument.defaultView.top.document,
+ "mediaTab", this.target);
+ },
+
+ viewFrameInfo: function() {
+ BrowserPageInfo(this.target.ownerDocument);
+ },
+
+ reloadImage: function(e) {
+ urlSecurityCheck(this.mediaURL,
+ this.browser.contentPrincipal,
+ Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
+
+ if (this.target instanceof Ci.nsIImageLoadingContent)
+ this.target.forceReload();
+ },
+
+ // Change current window to the URL of the image, video, or audio.
+ viewMedia: function(e) {
+ var viewURL;
+
+ if (this.onCanvas)
+ viewURL = this.target.toDataURL();
+ else {
+ viewURL = this.mediaURL;
+ urlSecurityCheck(viewURL,
+ this.browser.contentPrincipal,
+ Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
+ }
+
+ var doc = this.target.ownerDocument;
+ openUILink(viewURL, e, { disallowInheritPrincipal: true,
+ referrerURI: doc.documentURIObject });
+ },
+
+ saveVideoFrameAsImage: function () {
+ urlSecurityCheck(this.mediaURL, this.browser.contentPrincipal,
+ Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
+ let name = "";
+ try {
+ let uri = makeURI(this.mediaURL);
+ let url = uri.QueryInterface(Ci.nsIURL);
+ if (url.fileBaseName)
+ name = decodeURI(url.fileBaseName) + ".jpg";
+ } catch (e) { }
+ if (!name)
+ name = "snapshot.jpg";
+ var video = this.target;
+ var canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+ canvas.width = video.videoWidth;
+ canvas.height = video.videoHeight;
+ var ctxDraw = canvas.getContext("2d");
+ ctxDraw.drawImage(video, 0, 0);
+ saveImageURL(canvas.toDataURL("image/jpeg", ""), name, "SaveImageTitle", true, false, document.documentURIObject, this.target.ownerDocument);
+ },
+
+ fullScreenVideo: function () {
+ let video = this.target;
+ if (document.mozFullScreenEnabled)
+ video.mozRequestFullScreen();
+ },
+
+ leaveDOMFullScreen: function() {
+ document.mozCancelFullScreen();
+ },
+
+ // Change current window to the URL of the background image.
+ viewBGImage: function(e) {
+ urlSecurityCheck(this.bgImageURL,
+ this.browser.contentPrincipal,
+ Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
+ var doc = this.target.ownerDocument;
+ openUILink(this.bgImageURL, e, { disallowInheritPrincipal: true,
+ referrerURI: doc.documentURIObject });
+ },
+
+ disableSetDesktopBackground: function() {
+ // Disable the Set as Desktop Background menu item if we're still trying
+ // to load the image or the load failed.
+ if (!(this.target instanceof Ci.nsIImageLoadingContent))
+ return true;
+
+ if (("complete" in this.target) && !this.target.complete)
+ return true;
+
+ if (this.target.currentURI.schemeIs("javascript"))
+ return true;
+
+ var request = this.target
+ .QueryInterface(Ci.nsIImageLoadingContent)
+ .getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
+ if (!request)
+ return true;
+
+ return false;
+ },
+
+ setDesktopBackground: function() {
+ // Paranoia: check disableSetDesktopBackground again, in case the
+ // image changed since the context menu was initiated.
+ if (this.disableSetDesktopBackground())
+ return;
+
+ urlSecurityCheck(this.target.currentURI.spec,
+ this.target.ownerDocument.nodePrincipal);
+
+ // Confirm since it's annoying if you hit this accidentally.
+ const kDesktopBackgroundURL =
+ "chrome://browser/content/setDesktopBackground.xul";
+#ifdef XP_MACOSX
+ // On Mac, the Set Desktop Background window is not modal.
+ // Don't open more than one Set Desktop Background window.
+ var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Components.interfaces.nsIWindowMediator);
+ var dbWin = wm.getMostRecentWindow("Shell:SetDesktopBackground");
+ if (dbWin) {
+ dbWin.gSetBackground.init(this.target);
+ dbWin.focus();
+ }
+ else {
+ openDialog(kDesktopBackgroundURL, "",
+ "centerscreen,chrome,dialog=no,dependent,resizable=no",
+ this.target);
+ }
+#else
+ // On non-Mac platforms, the Set Wallpaper dialog is modal.
+ openDialog(kDesktopBackgroundURL, "",
+ "centerscreen,chrome,dialog,modal,dependent",
+ this.target);
+#endif
+ },
+
+ // Save URL of clicked-on frame.
+ saveFrame: function () {
+ saveDocument(this.target.ownerDocument);
+ },
+
+ // Helper function to wait for appropriate MIME-type headers and
+ // then prompt the user with a file picker
+ saveHelper: function(linkURL, linkText, dialogTitle, bypassCache, doc) {
+ // canonical def in nsURILoader.h
+ const NS_ERROR_SAVE_LINK_AS_TIMEOUT = 0x805d0020;
+
+ // an object to proxy the data through to
+ // nsIExternalHelperAppService.doContent, which will wait for the
+ // appropriate MIME-type headers and then prompt the user with a
+ // file picker
+ function saveAsListener() {}
+ saveAsListener.prototype = {
+ extListener: null,
+
+ onStartRequest: function saveLinkAs_onStartRequest(aRequest, aContext) {
+
+ // if the timer fired, the error status will have been caused by that,
+ // and we'll be restarting in onStopRequest, so no reason to notify
+ // the user
+ if (aRequest.status == NS_ERROR_SAVE_LINK_AS_TIMEOUT)
+ return;
+
+ timer.cancel();
+
+ // some other error occured; notify the user...
+ if (!Components.isSuccessCode(aRequest.status)) {
+ try {
+ const sbs = Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService);
+ const bundle = sbs.createBundle(
+ "chrome://mozapps/locale/downloads/downloads.properties");
+
+ const title = bundle.GetStringFromName("downloadErrorAlertTitle");
+ const msg = bundle.GetStringFromName("downloadErrorGeneric");
+
+ const promptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"].
+ getService(Ci.nsIPromptService);
+ promptSvc.alert(doc.defaultView, title, msg);
+ } catch (ex) {}
+ return;
+ }
+
+ var extHelperAppSvc =
+ Cc["@mozilla.org/uriloader/external-helper-app-service;1"].
+ getService(Ci.nsIExternalHelperAppService);
+ var channel = aRequest.QueryInterface(Ci.nsIChannel);
+ this.extListener =
+ extHelperAppSvc.doContent(channel.contentType, aRequest,
+ doc.defaultView, true);
+ this.extListener.onStartRequest(aRequest, aContext);
+ },
+
+ onStopRequest: function saveLinkAs_onStopRequest(aRequest, aContext,
+ aStatusCode) {
+ if (aStatusCode == NS_ERROR_SAVE_LINK_AS_TIMEOUT) {
+ // do it the old fashioned way, which will pick the best filename
+ // it can without waiting.
+ saveURL(linkURL, linkText, dialogTitle, bypassCache, false, doc.documentURIObject, doc);
+ }
+ if (this.extListener)
+ this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
+ },
+
+ onDataAvailable: function saveLinkAs_onDataAvailable(aRequest, aContext,
+ aInputStream,
+ aOffset, aCount) {
+ this.extListener.onDataAvailable(aRequest, aContext, aInputStream,
+ aOffset, aCount);
+ }
+ }
+
+ function callbacks() {}
+ callbacks.prototype = {
+ getInterface: function sLA_callbacks_getInterface(aIID) {
+ if (aIID.equals(Ci.nsIAuthPrompt) || aIID.equals(Ci.nsIAuthPrompt2)) {
+ // If the channel demands authentication prompt, we must cancel it
+ // because the save-as-timer would expire and cancel the channel
+ // before we get credentials from user. Both authentication dialog
+ // and save as dialog would appear on the screen as we fall back to
+ // the old fashioned way after the timeout.
+ timer.cancel();
+ channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT);
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+ }
+
+ // if it we don't have the headers after a short time, the user
+ // won't have received any feedback from their click. that's bad. so
+ // we give up waiting for the filename.
+ function timerCallback() {}
+ timerCallback.prototype = {
+ notify: function sLA_timer_notify(aTimer) {
+ channel.cancel(NS_ERROR_SAVE_LINK_AS_TIMEOUT);
+ return;
+ }
+ }
+
+ // set up a channel to do the saving
+ var ioService = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ var channel = ioService.newChannelFromURI(makeURI(linkURL));
+ if (channel instanceof Ci.nsIPrivateBrowsingChannel) {
+ let docIsPrivate = PrivateBrowsingUtils.isWindowPrivate(doc.defaultView);
+ channel.setPrivate(docIsPrivate);
+ }
+ channel.notificationCallbacks = new callbacks();
+
+ let flags = Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS;
+
+ if (bypassCache)
+ flags |= Ci.nsIRequest.LOAD_BYPASS_CACHE;
+
+ if (channel instanceof Ci.nsICachingChannel)
+ flags |= Ci.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY;
+
+ channel.loadFlags |= flags;
+
+ if (channel instanceof Ci.nsIHttpChannel) {
+ channel.referrer = doc.documentURIObject;
+ if (channel instanceof Ci.nsIHttpChannelInternal)
+ channel.forceAllowThirdPartyCookie = true;
+ }
+
+ // fallback to the old way if we don't see the headers quickly
+ var timeToWait =
+ gPrefService.getIntPref("browser.download.saveLinkAsFilenameTimeout");
+ var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(new timerCallback(), timeToWait,
+ timer.TYPE_ONE_SHOT);
+
+ // kick off the channel with our proxy object as the listener
+ channel.asyncOpen(new saveAsListener(), null);
+ },
+
+ // Save URL of clicked-on link.
+ saveLink: function() {
+ var doc = this.target.ownerDocument;
+ var linkText;
+ // If selected text is found to match valid URL pattern.
+ if (this.onPlainTextLink)
+ linkText = document.commandDispatcher.focusedWindow.getSelection().toString().trim();
+ else
+ linkText = this.linkText();
+ urlSecurityCheck(this.linkURL, doc.nodePrincipal);
+
+ this.saveHelper(this.linkURL, linkText, null, true, doc);
+ },
+
+ sendLink: function() {
+ // we don't know the title of the link so pass in an empty string
+ MailIntegration.sendMessage( this.linkURL, "" );
+ },
+
+ // Backwards-compatibility wrapper
+ saveImage : function() {
+ if (this.onCanvas || this.onImage)
+ this.saveMedia();
+ },
+
+ // Save URL of the clicked upon image, video, or audio.
+ saveMedia: function() {
+ var doc = this.target.ownerDocument;
+ if (this.onCanvas) {
+ // Bypass cache, since it's a data: URL.
+ saveImageURL(this.target.toDataURL(), "canvas.png", "SaveImageTitle",
+ true, false, doc.documentURIObject, doc);
+ }
+ else if (this.onImage) {
+ urlSecurityCheck(this.mediaURL, doc.nodePrincipal);
+ saveImageURL(this.mediaURL, null, "SaveImageTitle", false,
+ false, doc.documentURIObject, doc);
+ }
+ else if (this.onVideo || this.onAudio) {
+ urlSecurityCheck(this.mediaURL, doc.nodePrincipal);
+ var dialogTitle = this.onVideo ? "SaveVideoTitle" : "SaveAudioTitle";
+ this.saveHelper(this.mediaURL, null, dialogTitle, false, doc);
+ }
+ },
+
+ // Backwards-compatibility wrapper
+ sendImage : function() {
+ if (this.onCanvas || this.onImage)
+ this.sendMedia();
+ },
+
+ sendMedia: function() {
+ MailIntegration.sendMessage(this.mediaURL, "");
+ },
+
+ playPlugin: function() {
+ gPluginHandler._showClickToPlayNotification(this.browser, this.target);
+ },
+
+ hidePlugin: function() {
+ gPluginHandler.hideClickToPlayOverlay(this.target);
+ },
+
+ // Generate email address and put it on clipboard.
+ copyEmail: function() {
+ // Copy the comma-separated list of email addresses only.
+ // There are other ways of embedding email addresses in a mailto:
+ // link, but such complex parsing is beyond us.
+ var url = this.linkURL;
+ var qmark = url.indexOf("?");
+ var addresses;
+
+ // 7 == length of "mailto:"
+ addresses = qmark > 7 ? url.substring(7, qmark) : url.substr(7);
+
+ // Let's try to unescape it using a character set
+ // in case the address is not ASCII.
+ try {
+ var characterSet = this.target.ownerDocument.characterSet;
+ const textToSubURI = Cc["@mozilla.org/intl/texttosuburi;1"].
+ getService(Ci.nsITextToSubURI);
+ addresses = textToSubURI.unEscapeURIForUI(characterSet, addresses);
+ }
+ catch(ex) {
+ // Do nothing.
+ }
+
+ var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
+ getService(Ci.nsIClipboardHelper);
+ clipboard.copyString(addresses, document);
+ },
+
+ ///////////////
+ // Utilities //
+ ///////////////
+
+ // Show/hide one item (specified via name or the item element itself).
+ showItem: function(aItemOrId, aShow) {
+ var item = aItemOrId.constructor == String ?
+ document.getElementById(aItemOrId) : aItemOrId;
+ if (item)
+ item.hidden = !aShow;
+ },
+
+ // Set given attribute of specified context-menu item. If the
+ // value is null, then it removes the attribute (which works
+ // nicely for the disabled attribute).
+ setItemAttr: function(aID, aAttr, aVal ) {
+ var elem = document.getElementById(aID);
+ if (elem) {
+ if (aVal == null) {
+ // null indicates attr should be removed.
+ elem.removeAttribute(aAttr);
+ }
+ else {
+ // Set attr=val.
+ elem.setAttribute(aAttr, aVal);
+ }
+ }
+ },
+
+ // Set context menu attribute according to like attribute of another node
+ // (such as a broadcaster).
+ setItemAttrFromNode: function(aItem_id, aAttr, aOther_id) {
+ var elem = document.getElementById(aOther_id);
+ if (elem && elem.getAttribute(aAttr) == "true")
+ this.setItemAttr(aItem_id, aAttr, "true");
+ else
+ this.setItemAttr(aItem_id, aAttr, null);
+ },
+
+ // Temporary workaround for DOM api not yet implemented by XUL nodes.
+ cloneNode: function(aItem) {
+ // Create another element like the one we're cloning.
+ var node = document.createElement(aItem.tagName);
+
+ // Copy attributes from argument item to the new one.
+ var attrs = aItem.attributes;
+ for (var i = 0; i < attrs.length; i++) {
+ var attr = attrs.item(i);
+ node.setAttribute(attr.nodeName, attr.nodeValue);
+ }
+
+ // Voila!
+ return node;
+ },
+
+ // Generate fully qualified URL for clicked-on link.
+ getLinkURL: function() {
+ var href = this.link.href;
+ if (href)
+ return href;
+
+ href = this.link.getAttributeNS("http://www.w3.org/1999/xlink",
+ "href");
+
+ if (!href || !href.match(/\S/)) {
+ // Without this we try to save as the current doc,
+ // for example, HTML case also throws if empty
+ throw "Empty href";
+ }
+
+ return makeURLAbsolute(this.link.baseURI, href);
+ },
+
+ getLinkURI: function() {
+ try {
+ return makeURI(this.linkURL);
+ }
+ catch (ex) {
+ // e.g. empty URL string
+ }
+
+ return null;
+ },
+
+ getLinkProtocol: function() {
+ if (this.linkURI)
+ return this.linkURI.scheme; // can be |undefined|
+
+ return null;
+ },
+
+ // Get text of link.
+ linkText: function() {
+ var text = gatherTextUnder(this.link);
+ if (!text || !text.match(/\S/)) {
+ text = this.link.getAttribute("title");
+ if (!text || !text.match(/\S/)) {
+ text = this.link.getAttribute("alt");
+ if (!text || !text.match(/\S/))
+ text = this.linkURL;
+ }
+ }
+
+ return text;
+ },
+
+ // Get selected text. Only display the first 15 chars.
+ isTextSelection: function() {
+ // Get 16 characters, so that we can trim the selection if it's greater
+ // than 15 chars
+ var selectedText = getBrowserSelection(16);
+
+ if (!selectedText)
+ return false;
+
+ if (selectedText.length > 15)
+ selectedText = selectedText.substr(0,15) + this.ellipsis;
+
+ // Use the current engine if the search bar is visible, the default
+ // engine otherwise.
+ var engineName = "";
+ var ss = Cc["@mozilla.org/browser/search-service;1"].
+ getService(Ci.nsIBrowserSearchService);
+ if (isElementVisible(BrowserSearch.searchBar))
+ engineName = ss.currentEngine.name;
+ else
+ engineName = ss.defaultEngine.name;
+
+ // format "Search <engine> for <selection>" string to show in menu
+ var menuLabel = gNavigatorBundle.getFormattedString("contextMenuSearch",
+ [engineName,
+ selectedText]);
+ document.getElementById("context-searchselect").label = menuLabel;
+ document.getElementById("context-searchselect").accessKey =
+ gNavigatorBundle.getString("contextMenuSearch.accesskey");
+
+ return true;
+ },
+
+ // Returns true if anything is selected.
+ isContentSelection: function() {
+ return !document.commandDispatcher.focusedWindow.getSelection().isCollapsed;
+ },
+
+ toString: function () {
+ return "contextMenu.target = " + this.target + "\n" +
+ "contextMenu.onImage = " + this.onImage + "\n" +
+ "contextMenu.onLink = " + this.onLink + "\n" +
+ "contextMenu.link = " + this.link + "\n" +
+ "contextMenu.inFrame = " + this.inFrame + "\n" +
+ "contextMenu.hasBGImage = " + this.hasBGImage + "\n";
+ },
+
+ isDisabledForEvents: function(aNode) {
+ let ownerDoc = aNode.ownerDocument;
+ return
+ ownerDoc.defaultView &&
+ ownerDoc.defaultView
+ .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils)
+ .isNodeDisabledForEvents(aNode);
+ },
+
+ isTargetATextBox: function(node) {
+ if (node instanceof HTMLInputElement)
+ return node.mozIsTextField(false);
+
+ return (node instanceof HTMLTextAreaElement);
+ },
+
+ isTargetAKeywordField: function(aNode) {
+ if (!(aNode instanceof HTMLInputElement))
+ return false;
+
+ var form = aNode.form;
+ if (!form || aNode.type == "password")
+ return false;
+
+ var method = form.method.toUpperCase();
+
+ // These are the following types of forms we can create keywords for:
+ //
+ // method encoding type can create keyword
+ // GET * YES
+ // * YES
+ // POST YES
+ // POST application/x-www-form-urlencoded YES
+ // POST text/plain NO (a little tricky to do)
+ // POST multipart/form-data NO
+ // POST everything else YES
+ return (method == "GET" || method == "") ||
+ (form.enctype != "text/plain") && (form.enctype != "multipart/form-data");
+ },
+
+ // Determines whether or not the separator with the specified ID should be
+ // shown or not by determining if there are any non-hidden items between it
+ // and the previous separator.
+ shouldShowSeparator: function (aSeparatorID) {
+ var separator = document.getElementById(aSeparatorID);
+ if (separator) {
+ var sibling = separator.previousSibling;
+ while (sibling && sibling.localName != "menuseparator") {
+ if (!sibling.hidden)
+ return true;
+ sibling = sibling.previousSibling;
+ }
+ }
+ return false;
+ },
+
+ addDictionaries: function() {
+ var uri = formatURL("browser.dictionaries.download.url", true);
+
+ var locale = "-";
+ try {
+ locale = gPrefService.getComplexValue("intl.accept_languages",
+ Ci.nsIPrefLocalizedString).data;
+ }
+ catch (e) { }
+
+ var version = "-";
+ try {
+ version = Cc["@mozilla.org/xre/app-info;1"].
+ getService(Ci.nsIXULAppInfo).version;
+ }
+ catch (e) { }
+
+ uri = uri.replace(/%LOCALE%/, escape(locale)).replace(/%VERSION%/, version);
+
+ var newWindowPref = gPrefService.getIntPref("browser.link.open_newwindow");
+ var where = newWindowPref == 3 ? "tab" : "window";
+
+ openUILinkIn(uri, where);
+ },
+
+ bookmarkThisPage: function CM_bookmarkThisPage() {
+ window.top.PlacesCommandHook.bookmarkPage(this.browser, PlacesUtils.bookmarksMenuFolderId, true);
+ },
+
+ bookmarkLink: function CM_bookmarkLink() {
+ var linkText;
+ // If selected text is found to match valid URL pattern.
+ if (this.onPlainTextLink)
+ linkText = document.commandDispatcher.focusedWindow.getSelection().toString().trim();
+ else
+ linkText = this.linkText();
+ window.top.PlacesCommandHook.bookmarkLink(PlacesUtils.bookmarksMenuFolderId, this.linkURL,
+ linkText);
+ },
+
+ addBookmarkForFrame: function CM_addBookmarkForFrame() {
+ var doc = this.target.ownerDocument;
+ var uri = doc.documentURIObject;
+
+ var itemId = PlacesUtils.getMostRecentBookmarkForURI(uri);
+ if (itemId == -1) {
+ var title = doc.title;
+ var description = PlacesUIUtils.getDescriptionFromDocument(doc);
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "bookmark"
+ , uri: uri
+ , title: title
+ , description: description
+ , hiddenRows: [ "description"
+ , "location"
+ , "loadInSidebar"
+ , "keyword" ]
+ }, window.top);
+ }
+ else {
+ PlacesUIUtils.showBookmarkDialog({ action: "edit"
+ , type: "bookmark"
+ , itemId: itemId
+ }, window.top);
+ }
+ },
+
+ markLink: function CM_markLink() {
+ // send link to social
+ SocialMark.toggleURIMark(this.linkURI);
+ },
+
+ shareLink: function CM_shareLink() {
+ SocialShare.sharePage(null, { url: this.linkURI.spec });
+ },
+
+ shareImage: function CM_shareImage() {
+ SocialShare.sharePage(null, { url: this.imageURL, previews: [ this.mediaURL ] });
+ },
+
+ shareVideo: function CM_shareVideo() {
+ SocialShare.sharePage(null, { url: this.mediaURL, source: this.mediaURL });
+ },
+
+ shareSelect: function CM_shareSelect(selection) {
+ SocialShare.sharePage(null, { url: this.browser.currentURI.spec, text: selection });
+ },
+
+ savePageAs: function CM_savePageAs() {
+ saveDocument(this.browser.contentDocument);
+ },
+
+ sendPage: function CM_sendPage() {
+ MailIntegration.sendLinkForWindow(this.browser.contentWindow);
+ },
+
+ printFrame: function CM_printFrame() {
+ PrintUtils.print(this.target.ownerDocument.defaultView);
+ },
+
+ switchPageDirection: function CM_switchPageDirection() {
+ SwitchDocumentDirection(this.browser.contentWindow);
+ },
+
+ mediaCommand : function CM_mediaCommand(command, data) {
+ var media = this.target;
+
+ switch (command) {
+ case "play":
+ media.play();
+ break;
+ case "pause":
+ media.pause();
+ break;
+ case "mute":
+ media.muted = true;
+ break;
+ case "unmute":
+ media.muted = false;
+ break;
+ case "playbackRate":
+ media.playbackRate = data;
+ break;
+ case "hidecontrols":
+ media.removeAttribute("controls");
+ break;
+ case "showcontrols":
+ media.setAttribute("controls", "true");
+ break;
+ case "hidestats":
+ case "showstats":
+ var event = media.ownerDocument.createEvent("CustomEvent");
+ event.initCustomEvent("media-showStatistics", false, true, command == "showstats");
+ media.dispatchEvent(event);
+ break;
+ }
+ },
+
+ copyMediaLocation : function () {
+ var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
+ getService(Ci.nsIClipboardHelper);
+ clipboard.copyString(this.mediaURL, document);
+ },
+
+ get imageURL() {
+ if (this.onImage)
+ return this.mediaURL;
+ return "";
+ }
+};
diff --git a/browser/base/content/openLocation.js b/browser/base/content/openLocation.js
new file mode 100644
index 000000000..06f769fca
--- /dev/null
+++ b/browser/base/content/openLocation.js
@@ -0,0 +1,133 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var browser;
+var dialog = {};
+var pref = null;
+let openLocationModule = {};
+try {
+ pref = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+} catch (ex) {
+ // not critical, remain silent
+}
+
+Components.utils.import("resource:///modules/openLocationLastURL.jsm", openLocationModule);
+let gOpenLocationLastURL = new openLocationModule.OpenLocationLastURL(window.opener);
+
+function onLoad()
+{
+ dialog.input = document.getElementById("dialog.input");
+ dialog.open = document.documentElement.getButton("accept");
+ dialog.openWhereList = document.getElementById("openWhereList");
+ dialog.openTopWindow = document.getElementById("currentWindow");
+ dialog.bundle = document.getElementById("openLocationBundle");
+
+ if ("arguments" in window && window.arguments.length >= 1)
+ browser = window.arguments[0];
+
+ dialog.openWhereList.selectedItem = dialog.openTopWindow;
+
+ if (pref) {
+ try {
+ var useAutoFill = pref.getBoolPref("browser.urlbar.autoFill");
+ if (useAutoFill)
+ dialog.input.setAttribute("completedefaultindex", "true");
+ } catch (ex) {}
+
+ try {
+ var value = pref.getIntPref("general.open_location.last_window_choice");
+ var element = dialog.openWhereList.getElementsByAttribute("value", value)[0];
+ if (element)
+ dialog.openWhereList.selectedItem = element;
+ dialog.input.value = gOpenLocationLastURL.value;
+ }
+ catch(ex) {
+ }
+ if (dialog.input.value)
+ dialog.input.select(); // XXX should probably be done automatically
+ }
+
+ doEnabling();
+}
+
+function doEnabling()
+{
+ dialog.open.disabled = !dialog.input.value;
+}
+
+function open()
+{
+ var url;
+ var postData = {};
+ var mayInheritPrincipal = {value: false};
+ if (browser)
+ url = browser.getShortcutOrURI(dialog.input.value, postData, mayInheritPrincipal);
+ else
+ url = dialog.input.value;
+
+ try {
+ // Whichever target we use for the load, we allow third-party services to
+ // fixup the URI
+ switch (dialog.openWhereList.value) {
+ case "0":
+ var webNav = Components.interfaces.nsIWebNavigation;
+ var flags = webNav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ if (!mayInheritPrincipal.value)
+ flags |= webNav.LOAD_FLAGS_DISALLOW_INHERIT_OWNER;
+ browser.gBrowser.loadURIWithFlags(url, flags, null, null, postData.value);
+ break;
+ case "1":
+ window.opener.delayedOpenWindow(getBrowserURL(), "all,dialog=no",
+ url, postData.value, null, null, true);
+ break;
+ case "3":
+ browser.delayedOpenTab(url, null, null, postData.value, true);
+ break;
+ }
+ }
+ catch(exception) {
+ }
+
+ if (pref) {
+ gOpenLocationLastURL.value = dialog.input.value;
+ pref.setIntPref("general.open_location.last_window_choice", dialog.openWhereList.value);
+ }
+
+ // Delay closing slightly to avoid timing bug on Linux.
+ window.close();
+ return false;
+}
+
+function createInstance(contractid, iidName)
+{
+ var iid = Components.interfaces[iidName];
+ return Components.classes[contractid].createInstance(iid);
+}
+
+const nsIFilePicker = Components.interfaces.nsIFilePicker;
+function onChooseFile()
+{
+ try {
+ let fp = Components.classes["@mozilla.org/filepicker;1"].
+ createInstance(nsIFilePicker);
+ let fpCallback = function fpCallback_done(aResult) {
+ if (aResult == nsIFilePicker.returnOK && fp.fileURL.spec &&
+ fp.fileURL.spec.length > 0) {
+ dialog.input.value = fp.fileURL.spec;
+ }
+ doEnabling();
+ };
+
+ fp.init(window, dialog.bundle.getString("chooseFileDialogTitle"),
+ nsIFilePicker.modeOpen);
+ fp.appendFilters(nsIFilePicker.filterAll | nsIFilePicker.filterText |
+ nsIFilePicker.filterImages | nsIFilePicker.filterXML |
+ nsIFilePicker.filterHTML);
+ fp.open(fpCallback);
+ } catch (ex) {
+ }
+}
diff --git a/browser/base/content/openLocation.xul b/browser/base/content/openLocation.xul
new file mode 100644
index 000000000..7bafed0fe
--- /dev/null
+++ b/browser/base/content/openLocation.xul
@@ -0,0 +1,57 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://browser/locale/openLocation.dtd">
+
+<dialog id="openLocation"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&caption.label;"
+ onload="onLoad()"
+ buttonlabelaccept="&openBtn.label;"
+ buttoniconaccept="open"
+ ondialogaccept="open()"
+ style="width: 40em;"
+ persist="screenX screenY"
+ screenX="24" screenY="24">
+
+ <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
+ <script type="application/javascript" src="chrome://browser/content/openLocation.js"/>
+ <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
+
+ <stringbundle id="openLocationBundle" src="chrome://browser/locale/openLocation.properties"/>
+
+ <hbox>
+ <separator orient="vertical" class="thin"/>
+ <vbox flex="1">
+ <description>&enter.label;</description>
+ <separator class="thin"/>
+
+ <hbox align="center">
+ <textbox id="dialog.input" flex="1" type="autocomplete"
+ completeselectedindex="true"
+ autocompletesearch="urlinline history"
+ enablehistory="true"
+ class="uri-element"
+ oninput="doEnabling();"/>
+ <button label="&chooseFile.label;" oncommand="onChooseFile();"/>
+ </hbox>
+ <hbox align="center">
+ <label value="&openWhere.label;"/>
+ <menulist id="openWhereList">
+ <menupopup>
+ <menuitem value="0" id="currentWindow" label="&topTab.label;"/>
+ <menuitem value="3" label="&newTab.label;"/>
+ <menuitem value="1" label="&newWindow.label;"/>
+ </menupopup>
+ </menulist>
+ <spacer flex="1"/>
+ </hbox>
+ </vbox>
+ </hbox>
+
+</dialog>
diff --git a/browser/base/content/overrides/app-license.html b/browser/base/content/overrides/app-license.html
new file mode 100644
index 000000000..e7a158c79
--- /dev/null
+++ b/browser/base/content/overrides/app-license.html
@@ -0,0 +1,6 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+ <p><b>Binaries</b> of this product have been made available to you by the
+ <a href="http://www.mozilla.org/">Mozilla Project</a> under the Mozilla
+ Public License 2.0 (MPL). <a href="about:rights">Know your rights</a>.</p>
diff --git a/browser/base/content/padlock.css b/browser/base/content/padlock.css
new file mode 100644
index 000000000..9c4e978de
--- /dev/null
+++ b/browser/base/content/padlock.css
@@ -0,0 +1,193 @@
+#padlock-ib {
+ -moz-appearance: none;
+ min-width: 0px;
+ margin-right: 1px !important;
+ background-repeat: no-repeat;
+ background-position: center;
+ z-index: 1000 !important;
+ padding: 0px;
+ margin: 0px;
+ border: 0px;
+}
+
+#padlock-ib[padshow="ib-trans-bg"][level="ev"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_ev.png");
+ background-color: transparent;
+}
+
+#padlock-ib[padshow="ib-trans-bg"][level="high"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_https.png");
+ background-color: transparent;
+}
+
+#padlock-ib[padshow="ib-trans-bg"][level="low"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_low.png");
+ background-color: transparent;
+}
+
+#padlock-ib[padshow="ib-trans-bg"][level="broken"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_broken.png");
+ background-color: transparent;
+}
+
+#padlock-ib-left {
+ -moz-appearance: none;
+ min-width: 0px;
+ margin-right: 1px !important;
+ background-repeat: no-repeat;
+ background-position: center;
+ z-index: 1000 !important;
+ padding: 0px;
+ margin: 0px;
+ border: 0px;
+}
+
+#padlock-ib-left[padshow="ib-left"][level="ev"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_ev.png");
+ padding: 2px;
+ background-color: transparent;
+}
+
+#padlock-ib-left[padshow="ib-left"][level="high"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_https.png");
+ padding: 2px;
+ background-color: transparent;
+}
+
+#padlock-ib-left[padshow="ib-left"][level="low"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_low.png");
+ padding: 2px;
+ background-color: transparent;
+}
+
+#padlock-ib-left[padshow="ib-left"][level="broken"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_broken.png");
+ padding: 2px;
+ background-color: transparent;
+}
+
+#padlock-ub-right {
+ -moz-appearance: none;
+ min-width: 0px;
+ margin-right: 1px !important;
+ background-repeat: no-repeat;
+ background-position: center;
+ z-index: 1000 !important;
+ padding: 0px;
+ margin: 0px;
+ border: 0px;
+}
+
+#padlock-ub-right[padshow="ub-right"][level="ev"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_ev.png");
+ background-color: transparent;
+}
+
+#padlock-ub-right[padshow="ub-right"][level="high"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_https.png");
+ background-color: transparent;
+}
+
+#padlock-ub-right[padshow="ub-right"][level="low"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_low.png");
+ background-color: transparent;
+}
+
+#padlock-ub-right[padshow="ub-right"][level="broken"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_broken.png");
+ background-color: transparent;
+}
+
+#padlock-sb {
+ -moz-appearance: none;
+ background-repeat: no-repeat;
+ background-position: center;
+}
+
+#padlock-sb[padshow="statbar"][level="ev"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_ev.png");
+ background-color: transparent;
+}
+
+#padlock-sb[padshow="statbar"][level="high"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_https.png");
+ background-color: transparent;
+}
+
+#padlock-sb[padshow="statbar"][level="low"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_low.png");
+ background-color: transparent;
+}
+
+#padlock-sb[padshow="statbar"][level="broken"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_broken.png");
+ background-color: transparent;
+}
+
+#padlock-tab {
+ -moz-appearance: none;
+ background-repeat: no-repeat;
+ background-position: center;
+}
+
+#padlock-tab[padshow="tabs-bar"][level="ev"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_ev.png");
+ background-color: transparent;
+}
+
+#padlock-tab[padshow="tabs-bar"][level="high"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_https.png");
+ background-color: transparent;
+}
+
+#padlock-tab[padshow="tabs-bar"][level="low"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_low.png");
+ background-color: transparent;
+}
+
+#padlock-tab[padshow="tabs-bar"][level="broken"] {
+ list-style-image: url("chrome://browser/content/padlock_mod_broken.png");
+ background-color: transparent;
+}
+
+/* Classic style */
+#padlock-ib[padshow="ib-trans-bg"][padstyle="classic"][level="ev"],
+#padlock-ib-left[padshow="ib-left"][padstyle="classic"][level="ev"],
+#padlock-ub-right[padshow="ub-right"][padstyle="classic"][level="ev"],
+#padlock-sb[padshow="statbar"][padstyle="classic"][level="ev"],
+#padlock-tab[padshow="tabs-bar"][padstyle="classic"][level="ev"] {
+ list-style-image: url("chrome://browser/content/padlock_classic_ev.png");
+}
+
+#padlock-ib[padshow="ib-trans-bg"][padstyle="classic"][level="high"],
+#padlock-ib-left[padshow="ib-left"][padstyle="classic"][level="high"],
+#padlock-ub-right[padshow="ub-right"][padstyle="classic"][level="high"],
+#padlock-sb[padshow="statbar"][padstyle="classic"][level="high"],
+#padlock-tab[padshow="tabs-bar"][padstyle="classic"][level="high"] {
+ list-style-image: url("chrome://browser/content/padlock_classic_https.png");
+}
+
+#padlock-ib[padshow="ib-trans-bg"][padstyle="classic"][level="low"],
+#padlock-ib-left[padshow="ib-left"][padstyle="classic"][level="low"],
+#padlock-ub-right[padshow="ub-right"][padstyle="classic"][level="low"],
+#padlock-sb[padshow="statbar"][padstyle="classic"][level="low"],
+#padlock-tab[padshow="tabs-bar"][padstyle="classic"][level="low"] {
+ list-style-image: url("chrome://browser/content/padlock_classic_https.png");
+}
+
+#padlock-ib[padshow="ib-trans-bg"][padstyle="classic"][level="broken"],
+#padlock-ib-left[padshow="ib-left"][padstyle="classic"][level="broken"],
+#padlock-ub-right[padshow="ub-right"][padstyle="classic"][level="broken"],
+#padlock-sb[padshow="statbar"][padstyle="classic"][level="broken"],
+#padlock-tab[padshow="tabs-bar"][padstyle="classic"][level="broken"] {
+ list-style-image: url("chrome://browser/content/padlock_classic_broken.png");
+}
+
+/* Remove a few px of dead space for disabled locations */
+#padlock-ib:not([padshow="ib-trans-bg"]),
+#padlock-ib-left:not([padshow="ib-left"]),
+#padlock-ub-right:not([padshow="ub-right"]),
+#padlock-sb:not([padshow="statbar"]),
+#padlock-tab:not([padshow="tabs-bar"]) {
+ visibility: collapse;
+} \ No newline at end of file
diff --git a/browser/base/content/padlock.js b/browser/base/content/padlock.js
new file mode 100644
index 000000000..6ba610ac7
--- /dev/null
+++ b/browser/base/content/padlock.js
@@ -0,0 +1,193 @@
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+let Cu = Components.utils;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var padlock_PadLock =
+{
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference]),
+ onButtonClick: function(event) {
+ event.stopPropagation();
+ gIdentityHandler.handleMoreInfoClick(event);
+ },
+ onStateChange: function() {},
+ onProgressChange: function() {},
+ onLocationChange: function() {},
+ onStatusChange: function() {},
+ onSecurityChange: function(aCallerWebProgress, aRequestWithState, aState) {
+ // aState is defined as a bitmask that may be extended in the future.
+ // We filter out any unknown bits before testing for known values.
+ const wpl = Ci.nsIWebProgressListener;
+ const wpl_security_bits = wpl.STATE_IS_SECURE |
+ wpl.STATE_IS_BROKEN |
+ wpl.STATE_IS_INSECURE |
+ wpl.STATE_IDENTITY_EV_TOPLEVEL |
+ wpl.STATE_SECURE_HIGH |
+ wpl.STATE_SECURE_MED |
+ wpl.STATE_SECURE_LOW;
+ var level;
+ var is_insecure;
+ var highlight_urlbar = false;
+
+ switch (aState & wpl_security_bits) {
+ case wpl.STATE_IS_SECURE | wpl.STATE_SECURE_HIGH | wpl.STATE_IDENTITY_EV_TOPLEVEL:
+ level = "ev";
+ is_insecure = "";
+ highlight_urlbar = true;
+ break;
+ case wpl.STATE_IS_SECURE | wpl.STATE_SECURE_HIGH:
+ level = "high";
+ is_insecure = "";
+ highlight_urlbar = true;
+ break;
+ case wpl.STATE_IS_SECURE | wpl.STATE_SECURE_MED:
+ case wpl.STATE_IS_SECURE | wpl.STATE_SECURE_LOW:
+ level = "low";
+ is_insecure = "insecure";
+ break;
+ case wpl.STATE_IS_BROKEN:
+ level = "broken";
+ is_insecure = "insecure";
+ highlight_urlbar = true;
+ break;
+ default: // should not be reached
+ level = null;
+ is_insecure = "insecure";
+ }
+
+ try {
+ var proto = gBrowser.contentWindow.location.protocol;
+ if (proto == "about:" || proto == "chrome:" || proto == "file:" ) {
+ // do not warn when using local protocols
+ is_insecure = false;
+ }
+ }
+ catch (ex) {}
+
+ let ub = document.getElementById("urlbar");
+ if (highlight_urlbar) {
+ ub.setAttribute("security_level", level);
+ } else {
+ ub.removeAttribute("security_level");
+ }
+
+ padlock_PadLock.setPadlockLevel("padlock-ib", level);
+ padlock_PadLock.setPadlockLevel("padlock-ib-left", level);
+ padlock_PadLock.setPadlockLevel("padlock-ub-right", level);
+ padlock_PadLock.setPadlockLevel("padlock-sb", level);
+ padlock_PadLock.setPadlockLevel("padlock-tab", level);
+ },
+ setPadlockLevel: function(item, level) {
+ let secbut = document.getElementById(item);
+
+ if (level) {
+ secbut.setAttribute("level", level);
+ secbut.hidden = false;
+ } else {
+ secbut.hidden = true;
+ secbut.removeAttribute("level");
+ }
+
+ secbut.setAttribute("tooltiptext", level);
+ },
+ prefbranch : null,
+ onLoad: function() {
+ gBrowser.addProgressListener(padlock_PadLock);
+
+ var prefService = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService);
+ padlock_PadLock.prefbranch = prefService.getBranch("browser.padlock.");
+ padlock_PadLock.prefbranch.QueryInterface(Components.interfaces.nsIPrefBranch2);
+ padlock_PadLock.usePrefs();
+ padlock_PadLock.prefbranch.addObserver("", padlock_PadLock, false);
+ },
+ onUnLoad: function() {
+ padlock_PadLock.prefbranch.removeObserver("", this);
+ },
+ observe: function(subject, topic, data)
+ {
+ if (topic != "nsPref:changed")
+ return;
+ if (data != "style" && data != "urlbar_background" && data != "shown")
+ return;
+ padlock_PadLock.usePrefs();
+ },
+ usePrefs: function() {
+ var prefval = padlock_PadLock.prefbranch.getIntPref("style");
+ var position;
+ var padstyle;
+ if (prefval == 2) {
+ position = "ib-left";
+ padstyle = "modern";
+ }
+ else if (prefval == 3) {
+ position = "ub-right";
+ padstyle = "modern";
+ }
+ else if (prefval == 4) {
+ position = "statbar";
+ padstyle = "modern";
+ }
+ else if (prefval == 5) {
+ position = "tabs-bar";
+ padstyle = "modern";
+ }
+ else if (prefval == 6) {
+ position = "ib-trans-bg";
+ padstyle = "classic";
+ }
+ else if (prefval == 7) {
+ position = "ib-left";
+ padstyle = "classic";
+ }
+ else if (prefval == 8) {
+ position = "ub-right";
+ padstyle = "classic";
+ }
+ else if (prefval == 9) {
+ position = "statbar";
+ padstyle = "classic";
+ }
+ else if (prefval == 10) {
+ position = "tabs-bar";
+ padstyle = "classic";
+ }
+ else { // 1 or anything else_ default
+ position = "ib-trans-bg";
+ padstyle = "modern";
+ }
+
+ var colshow;
+ var colprefval = padlock_PadLock.prefbranch.getIntPref("urlbar_background");
+ if (colprefval == 1) {
+ colshow = "y";
+ }
+ else { // 0 or anything else_ default
+ colshow = "";
+ }
+
+ var lockenabled;
+ var lockenabled = padlock_PadLock.prefbranch.getBoolPref("shown");
+ if (lockenabled)
+ padshow = position;
+ else
+ padshow = "";
+
+ document.getElementById("padlock-ib").setAttribute("padshow", padshow);
+ document.getElementById("padlock-ib-left").setAttribute("padshow", padshow);
+ document.getElementById("padlock-ub-right").setAttribute("padshow", padshow);
+ document.getElementById("padlock-sb").setAttribute("padshow", padshow);
+ document.getElementById("padlock-tab").setAttribute("padshow", padshow);
+
+ document.getElementById("padlock-ib").setAttribute("padstyle", padstyle);
+ document.getElementById("padlock-ib-left").setAttribute("padstyle", padstyle);
+ document.getElementById("padlock-ub-right").setAttribute("padstyle", padstyle);
+ document.getElementById("padlock-sb").setAttribute("padstyle", padstyle);
+ document.getElementById("padlock-tab").setAttribute("padstyle", padstyle);
+
+ document.getElementById("urlbar").setAttribute("https_color", colshow);
+ }
+};
+
+window.addEventListener("load", padlock_PadLock.onLoad, false );
+window.addEventListener("unload", padlock_PadLock.onUnLoad, false );
diff --git a/browser/base/content/padlock.xul b/browser/base/content/padlock.xul
new file mode 100644
index 000000000..e820c19c7
--- /dev/null
+++ b/browser/base/content/padlock.xul
@@ -0,0 +1,63 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://browser/content/padlock.css" type="text/css"?>
+
+<overlay
+ id="padlock"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script type="application/x-javascript" src="chrome://browser/content/padlock.js"/>
+
+ <hbox id="identity-box">
+ <image id="padlock-ib" insertafter="identity-icon-labels"
+ class="urlbar-icon"
+ style="-moz-user-focus: none;"
+ hidden="false"
+ tooltiptext=""
+ onclick="return padlock_PadLock.onButtonClick(event);"/>
+ </hbox>
+
+ <hbox id="identity-box">
+ <image id="padlock-ib-left" insertbefore="identity-icon-labels"
+ class="urlbar-icon"
+ style="-moz-user-focus: none;"
+ hidden="false"
+ tooltiptext=""
+ onclick="return padlock_PadLock.onButtonClick(event);"/>
+ </hbox>
+
+ <hbox id="urlbar-icons">
+ <image id="padlock-ub-right" insertbefore="star-button"
+ class="urlbar-icon"
+ style="-moz-user-focus: none;"
+ hidden="false"
+ tooltiptext=""
+ onclick="return padlock_PadLock.onButtonClick(event);"/>
+ </hbox>
+
+ <statusbar id="status-bar">
+ <statusbarpanel insertafter="security-button"
+ id="padlock-sb-panel"
+ class="statusbar-iconic-text">
+ <image id="padlock-sb" insertbefore="star-button"
+ class="urlbar-icon"
+ style="-moz-user-focus: none;"
+ hidden="false"
+ tooltiptext=""
+ onclick="return padlock_PadLock.onButtonClick(event);"/>
+ </statusbarpanel>
+ </statusbar>
+
+ <toolbar id="TabsToolbar">
+ <toolbaritem insertafter="tabs-closebutton" id="tabs-padlock-tbitem"
+ align="center" pack="center">
+ <image id="padlock-tab"
+ class="urlbar-icon"
+ style="-moz-user-focus: none;"
+ hidden="false"
+ tooltiptext=""
+ onclick="return padlock_PadLock.onButtonClick(event);"/>
+ </toolbaritem>
+ </toolbar>
+
+
+</overlay>
diff --git a/browser/base/content/padlock_classic_broken.png b/browser/base/content/padlock_classic_broken.png
new file mode 100644
index 000000000..437036fe8
--- /dev/null
+++ b/browser/base/content/padlock_classic_broken.png
Binary files differ
diff --git a/browser/base/content/padlock_classic_ev.png b/browser/base/content/padlock_classic_ev.png
new file mode 100644
index 000000000..b3f80c0da
--- /dev/null
+++ b/browser/base/content/padlock_classic_ev.png
Binary files differ
diff --git a/browser/base/content/padlock_classic_https.png b/browser/base/content/padlock_classic_https.png
new file mode 100644
index 000000000..86026c04f
--- /dev/null
+++ b/browser/base/content/padlock_classic_https.png
Binary files differ
diff --git a/browser/base/content/padlock_mod_broken.png b/browser/base/content/padlock_mod_broken.png
new file mode 100644
index 000000000..33a6c0645
--- /dev/null
+++ b/browser/base/content/padlock_mod_broken.png
Binary files differ
diff --git a/browser/base/content/padlock_mod_ev.png b/browser/base/content/padlock_mod_ev.png
new file mode 100644
index 000000000..3dfdcbde5
--- /dev/null
+++ b/browser/base/content/padlock_mod_ev.png
Binary files differ
diff --git a/browser/base/content/padlock_mod_https.png b/browser/base/content/padlock_mod_https.png
new file mode 100644
index 000000000..d494b42b0
--- /dev/null
+++ b/browser/base/content/padlock_mod_https.png
Binary files differ
diff --git a/browser/base/content/padlock_mod_low.png b/browser/base/content/padlock_mod_low.png
new file mode 100644
index 000000000..fc60fcca6
--- /dev/null
+++ b/browser/base/content/padlock_mod_low.png
Binary files differ
diff --git a/browser/base/content/pageinfo/feeds.js b/browser/base/content/pageinfo/feeds.js
new file mode 100644
index 000000000..a15516bf7
--- /dev/null
+++ b/browser/base/content/pageinfo/feeds.js
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function initFeedTab()
+{
+ const feedTypes = {
+ "application/rss+xml": gBundle.getString("feedRss"),
+ "application/atom+xml": gBundle.getString("feedAtom"),
+ "text/xml": gBundle.getString("feedXML"),
+ "application/xml": gBundle.getString("feedXML"),
+ "application/rdf+xml": gBundle.getString("feedXML")
+ };
+
+ // get the feeds
+ var linkNodes = gDocument.getElementsByTagName("link");
+ var length = linkNodes.length;
+ for (var i = 0; i < length; i++) {
+ var link = linkNodes[i];
+ if (!link.href)
+ continue;
+
+ var rel = link.rel && link.rel.toLowerCase();
+ var rels = {};
+ if (rel) {
+ for each (let relVal in rel.split(/\s+/))
+ rels[relVal] = true;
+ }
+
+ if (rels.feed || (link.type && rels.alternate && !rels.stylesheet)) {
+ var type = isValidFeed(link, gDocument.nodePrincipal, rels.feed);
+ if (type) {
+ type = feedTypes[type] || feedTypes["application/rss+xml"];
+ addRow(link.title, type, link.href);
+ }
+ }
+ }
+
+ var feedListbox = document.getElementById("feedListbox");
+ document.getElementById("feedTab").hidden = feedListbox.getRowCount() == 0;
+}
+
+function onSubscribeFeed()
+{
+ var listbox = document.getElementById("feedListbox");
+ openUILinkIn(listbox.selectedItem.getAttribute("feedURL"), "current",
+ { ignoreAlt: true });
+}
+
+function addRow(name, type, url)
+{
+ var item = document.createElement("richlistitem");
+ item.setAttribute("feed", "true");
+ item.setAttribute("name", name);
+ item.setAttribute("type", type);
+ item.setAttribute("feedURL", url);
+ document.getElementById("feedListbox").appendChild(item);
+}
diff --git a/browser/base/content/pageinfo/feeds.xml b/browser/base/content/pageinfo/feeds.xml
new file mode 100644
index 000000000..782c05a73
--- /dev/null
+++ b/browser/base/content/pageinfo/feeds.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE bindings [
+ <!ENTITY % pageInfoDTD SYSTEM "chrome://browser/locale/pageInfo.dtd">
+ %pageInfoDTD;
+]>
+
+<bindings id="feedBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="feed" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+ <content>
+ <xul:vbox flex="1">
+ <xul:hbox flex="1">
+ <xul:textbox flex="1" readonly="true" xbl:inherits="value=name"
+ class="feedTitle"/>
+ <xul:label xbl:inherits="value=type"/>
+ </xul:hbox>
+ <xul:vbox>
+ <xul:vbox align="start">
+ <xul:hbox>
+ <xul:label xbl:inherits="value=feedURL,tooltiptext=feedURL" class="text-link" flex="1"
+ onclick="openUILink(this.value, event);" crop="end"/>
+ </xul:hbox>
+ </xul:vbox>
+ </xul:vbox>
+ <xul:hbox flex="1" class="feed-subscribe">
+ <xul:spacer flex="1"/>
+ <xul:button label="&feedSubscribe;" accesskey="&feedSubscribe.accesskey;"
+ oncommand="onSubscribeFeed()"/>
+ </xul:hbox>
+ </xul:vbox>
+ </content>
+ </binding>
+</bindings>
diff --git a/browser/base/content/pageinfo/pageInfo.css b/browser/base/content/pageinfo/pageInfo.css
new file mode 100644
index 000000000..622b56bb5
--- /dev/null
+++ b/browser/base/content/pageinfo/pageInfo.css
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+#viewGroup > radio {
+ -moz-binding: url("chrome://browser/content/pageinfo/pageInfo.xml#viewbutton");
+}
+
+richlistitem[feed] {
+ -moz-binding: url("chrome://browser/content/pageinfo/feeds.xml#feed");
+}
+
+richlistitem[feed]:not([selected="true"]) .feed-subscribe {
+ display: none;
+}
+
+groupbox[closed="true"] > .groupbox-body {
+ visibility: collapse;
+}
+
+#thepreviewimage {
+ display: block;
+/* This following entry can be removed when Bug 522850 is fixed. */
+ min-width: 1px;
+}
diff --git a/browser/base/content/pageinfo/pageInfo.js b/browser/base/content/pageinfo/pageInfo.js
new file mode 100644
index 000000000..7846bd36b
--- /dev/null
+++ b/browser/base/content/pageinfo/pageInfo.js
@@ -0,0 +1,1276 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//******** define a js object to implement nsITreeView
+function pageInfoTreeView(treeid, copycol)
+{
+ // copycol is the index number for the column that we want to add to
+ // the copy-n-paste buffer when the user hits accel-c
+ this.treeid = treeid;
+ this.copycol = copycol;
+ this.rows = 0;
+ this.tree = null;
+ this.data = [ ];
+ this.selection = null;
+ this.sortcol = -1;
+ this.sortdir = false;
+}
+
+pageInfoTreeView.prototype = {
+ set rowCount(c) { throw "rowCount is a readonly property"; },
+ get rowCount() { return this.rows; },
+
+ setTree: function(tree)
+ {
+ this.tree = tree;
+ },
+
+ getCellText: function(row, column)
+ {
+ // row can be null, but js arrays are 0-indexed.
+ // colidx cannot be null, but can be larger than the number
+ // of columns in the array. In this case it's the fault of
+ // whoever typoed while calling this function.
+ return this.data[row][column.index] || "";
+ },
+
+ setCellValue: function(row, column, value)
+ {
+ },
+
+ setCellText: function(row, column, value)
+ {
+ this.data[row][column.index] = value;
+ },
+
+ addRow: function(row)
+ {
+ this.rows = this.data.push(row);
+ this.rowCountChanged(this.rows - 1, 1);
+ if (this.selection.count == 0 && this.rowCount && !gImageElement)
+ this.selection.select(0);
+ },
+
+ rowCountChanged: function(index, count)
+ {
+ this.tree.rowCountChanged(index, count);
+ },
+
+ invalidate: function()
+ {
+ this.tree.invalidate();
+ },
+
+ clear: function()
+ {
+ if (this.tree)
+ this.tree.rowCountChanged(0, -this.rows);
+ this.rows = 0;
+ this.data = [ ];
+ },
+
+ handleCopy: function(row)
+ {
+ return (row < 0 || this.copycol < 0) ? "" : (this.data[row][this.copycol] || "");
+ },
+
+ performActionOnRow: function(action, row)
+ {
+ if (action == "copy") {
+ var data = this.handleCopy(row)
+ this.tree.treeBody.parentNode.setAttribute("copybuffer", data);
+ }
+ },
+
+ onPageMediaSort : function(columnname)
+ {
+ var tree = document.getElementById(this.treeid);
+ var treecol = tree.columns.getNamedColumn(columnname);
+
+ this.sortdir =
+ gTreeUtils.sort(
+ tree,
+ this,
+ this.data,
+ treecol.index,
+ function textComparator(a, b) { return a.toLowerCase().localeCompare(b.toLowerCase()); },
+ this.sortcol,
+ this.sortdir
+ );
+
+ this.sortcol = treecol.index;
+ },
+
+ getRowProperties: function(row) { return ""; },
+ getCellProperties: function(row, column) { return ""; },
+ getColumnProperties: function(column) { return ""; },
+ isContainer: function(index) { return false; },
+ isContainerOpen: function(index) { return false; },
+ isSeparator: function(index) { return false; },
+ isSorted: function() { },
+ canDrop: function(index, orientation) { return false; },
+ drop: function(row, orientation) { return false; },
+ getParentIndex: function(index) { return 0; },
+ hasNextSibling: function(index, after) { return false; },
+ getLevel: function(index) { return 0; },
+ getImageSrc: function(row, column) { },
+ getProgressMode: function(row, column) { },
+ getCellValue: function(row, column) { },
+ toggleOpenState: function(index) { },
+ cycleHeader: function(col) { },
+ selectionChanged: function() { },
+ cycleCell: function(row, column) { },
+ isEditable: function(row, column) { return false; },
+ isSelectable: function(row, column) { return false; },
+ performAction: function(action) { },
+ performActionOnCell: function(action, row, column) { }
+};
+
+// mmm, yummy. global variables.
+var gWindow = null;
+var gDocument = null;
+var gImageElement = null;
+
+// column number to help using the data array
+const COL_IMAGE_ADDRESS = 0;
+const COL_IMAGE_TYPE = 1;
+const COL_IMAGE_SIZE = 2;
+const COL_IMAGE_ALT = 3;
+const COL_IMAGE_COUNT = 4;
+const COL_IMAGE_NODE = 5;
+const COL_IMAGE_BG = 6;
+
+// column number to copy from, second argument to pageInfoTreeView's constructor
+const COPYCOL_NONE = -1;
+const COPYCOL_META_CONTENT = 1;
+const COPYCOL_IMAGE = COL_IMAGE_ADDRESS;
+
+// one nsITreeView for each tree in the window
+var gMetaView = new pageInfoTreeView('metatree', COPYCOL_META_CONTENT);
+var gImageView = new pageInfoTreeView('imagetree', COPYCOL_IMAGE);
+
+gImageView.getCellProperties = function(row, col) {
+ var data = gImageView.data[row];
+ var item = gImageView.data[row][COL_IMAGE_NODE];
+ var props = "";
+ if (!checkProtocol(data) ||
+ item instanceof HTMLEmbedElement ||
+ (item instanceof HTMLObjectElement && !item.type.startsWith("image/")))
+ props += "broken";
+
+ if (col.element.id == "image-address")
+ props += " ltr";
+
+ return props;
+};
+
+gImageView.getCellText = function(row, column) {
+ var value = this.data[row][column.index];
+ if (column.index == COL_IMAGE_SIZE) {
+ if (value == -1) {
+ return gStrings.unknown;
+ } else {
+ var kbSize = Number(Math.round(value / 1024 * 100) / 100);
+ return gBundle.getFormattedString("mediaFileSize", [kbSize]);
+ }
+ }
+ return value || "";
+};
+
+gImageView.onPageMediaSort = function(columnname) {
+ var tree = document.getElementById(this.treeid);
+ var treecol = tree.columns.getNamedColumn(columnname);
+
+ var comparator;
+ if (treecol.index == COL_IMAGE_SIZE) {
+ comparator = function numComparator(a, b) { return a - b; };
+ } else {
+ comparator = function textComparator(a, b) { return a.toLowerCase().localeCompare(b.toLowerCase()); };
+ }
+
+ this.sortdir =
+ gTreeUtils.sort(
+ tree,
+ this,
+ this.data,
+ treecol.index,
+ comparator,
+ this.sortcol,
+ this.sortdir
+ );
+
+ this.sortcol = treecol.index;
+};
+
+var gImageHash = { };
+
+// localized strings (will be filled in when the document is loaded)
+// this isn't all of them, these are just the ones that would otherwise have been loaded inside a loop
+var gStrings = { };
+var gBundle;
+
+const PERMISSION_CONTRACTID = "@mozilla.org/permissionmanager;1";
+const PREFERENCES_CONTRACTID = "@mozilla.org/preferences-service;1";
+const ATOM_CONTRACTID = "@mozilla.org/atom-service;1";
+
+// a number of services I'll need later
+// the cache services
+const nsICacheService = Components.interfaces.nsICacheService;
+const ACCESS_READ = Components.interfaces.nsICache.ACCESS_READ;
+const cacheService = Components.classes["@mozilla.org/network/cache-service;1"].getService(nsICacheService);
+var httpCacheSession = cacheService.createSession("HTTP", 0, true);
+httpCacheSession.doomEntriesIfExpired = false;
+var ftpCacheSession = cacheService.createSession("FTP", 0, true);
+ftpCacheSession.doomEntriesIfExpired = false;
+
+const nsICookiePermission = Components.interfaces.nsICookiePermission;
+const nsIPermissionManager = Components.interfaces.nsIPermissionManager;
+
+const nsICertificateDialogs = Components.interfaces.nsICertificateDialogs;
+const CERTIFICATEDIALOGS_CONTRACTID = "@mozilla.org/nsCertificateDialogs;1"
+
+// clipboard helper
+try {
+ const gClipboardHelper = Components.classes["@mozilla.org/widget/clipboardhelper;1"].getService(Components.interfaces.nsIClipboardHelper);
+}
+catch(e) {
+ // do nothing, later code will handle the error
+}
+
+// Interface for image loading content
+const nsIImageLoadingContent = Components.interfaces.nsIImageLoadingContent;
+
+// namespaces, don't need all of these yet...
+const XLinkNS = "http://www.w3.org/1999/xlink";
+const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const XMLNS = "http://www.w3.org/XML/1998/namespace";
+const XHTMLNS = "http://www.w3.org/1999/xhtml";
+const XHTML2NS = "http://www.w3.org/2002/06/xhtml2"
+
+const XHTMLNSre = "^http\:\/\/www\.w3\.org\/1999\/xhtml$";
+const XHTML2NSre = "^http\:\/\/www\.w3\.org\/2002\/06\/xhtml2$";
+const XHTMLre = RegExp(XHTMLNSre + "|" + XHTML2NSre, "");
+
+/* Overlays register functions here.
+ * These arrays are used to hold callbacks that Page Info will call at
+ * various stages. Use them by simply appending a function to them.
+ * For example, add a function to onLoadRegistry by invoking
+ * "onLoadRegistry.push(XXXLoadFunc);"
+ * The XXXLoadFunc should be unique to the overlay module, and will be
+ * invoked as "XXXLoadFunc();"
+ */
+
+// These functions are called to build the data displayed in the Page
+// Info window. The global variables gDocument and gWindow are set.
+var onLoadRegistry = [ ];
+
+// These functions are called to remove old data still displayed in
+// the window when the document whose information is displayed
+// changes. For example, at this time, the list of images of the Media
+// tab is cleared.
+var onResetRegistry = [ ];
+
+// These are called once for each subframe of the target document and
+// the target document itself. The frame is passed as an argument.
+var onProcessFrame = [ ];
+
+// These functions are called once for each element (in all subframes, if any)
+// in the target document. The element is passed as an argument.
+var onProcessElement = [ ];
+
+// These functions are called once when all the elements in all of the target
+// document (and all of its subframes, if any) have been processed
+var onFinished = [ ];
+
+// These functions are called once when the Page Info window is closed.
+var onUnloadRegistry = [ ];
+
+// These functions are called once when an image preview is shown.
+var onImagePreviewShown = [ ];
+
+/* Called when PageInfo window is loaded. Arguments are:
+ * window.arguments[0] - (optional) an object consisting of
+ * - doc: (optional) document to use for source. if not provided,
+ * the calling window's document will be used
+ * - initialTab: (optional) id of the inital tab to display
+ */
+function onLoadPageInfo()
+{
+ gBundle = document.getElementById("pageinfobundle");
+ gStrings.unknown = gBundle.getString("unknown");
+ gStrings.notSet = gBundle.getString("notset");
+ gStrings.mediaImg = gBundle.getString("mediaImg");
+ gStrings.mediaBGImg = gBundle.getString("mediaBGImg");
+ gStrings.mediaBorderImg = gBundle.getString("mediaBorderImg");
+ gStrings.mediaListImg = gBundle.getString("mediaListImg");
+ gStrings.mediaCursor = gBundle.getString("mediaCursor");
+ gStrings.mediaObject = gBundle.getString("mediaObject");
+ gStrings.mediaEmbed = gBundle.getString("mediaEmbed");
+ gStrings.mediaLink = gBundle.getString("mediaLink");
+ gStrings.mediaInput = gBundle.getString("mediaInput");
+ gStrings.mediaVideo = gBundle.getString("mediaVideo");
+ gStrings.mediaAudio = gBundle.getString("mediaAudio");
+
+ var args = "arguments" in window &&
+ window.arguments.length >= 1 &&
+ window.arguments[0];
+
+ if (!args || !args.doc) {
+ gWindow = window.opener.content;
+ gDocument = gWindow.document;
+ }
+
+ // init media view
+ var imageTree = document.getElementById("imagetree");
+ imageTree.view = gImageView;
+
+ /* Select the requested tab, if the name is specified */
+ loadTab(args);
+ Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService)
+ .notifyObservers(window, "page-info-dialog-loaded", null);
+}
+
+function loadPageInfo()
+{
+ var titleFormat = gWindow != gWindow.top ? "pageInfo.frame.title"
+ : "pageInfo.page.title";
+ document.title = gBundle.getFormattedString(titleFormat, [gDocument.location]);
+
+ document.getElementById("main-window").setAttribute("relatedUrl", gDocument.location);
+
+ // do the easy stuff first
+ makeGeneralTab();
+
+ // and then the hard stuff
+ makeTabs(gDocument, gWindow);
+
+ initFeedTab();
+ onLoadPermission();
+
+ /* Call registered overlay init functions */
+ onLoadRegistry.forEach(function(func) { func(); });
+}
+
+function resetPageInfo(args)
+{
+ /* Reset Meta tags part */
+ gMetaView.clear();
+
+ /* Reset Media tab */
+ var mediaTab = document.getElementById("mediaTab");
+ if (!mediaTab.hidden) {
+ Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService)
+ .removeObserver(imagePermissionObserver, "perm-changed");
+ mediaTab.hidden = true;
+ }
+ gImageView.clear();
+ gImageHash = {};
+
+ /* Reset Feeds Tab */
+ var feedListbox = document.getElementById("feedListbox");
+ while (feedListbox.firstChild)
+ feedListbox.removeChild(feedListbox.firstChild);
+
+ /* Call registered overlay reset functions */
+ onResetRegistry.forEach(function(func) { func(); });
+
+ /* Rebuild the data */
+ loadTab(args);
+}
+
+function onUnloadPageInfo()
+{
+ // Remove the observer, only if there is at least 1 image.
+ if (!document.getElementById("mediaTab").hidden) {
+ Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService)
+ .removeObserver(imagePermissionObserver, "perm-changed");
+ }
+
+ /* Call registered overlay unload functions */
+ onUnloadRegistry.forEach(function(func) { func(); });
+}
+
+function doHelpButton()
+{
+ const helpTopics = {
+ "generalPanel": "pageinfo_general",
+ "mediaPanel": "pageinfo_media",
+ "feedPanel": "pageinfo_feed",
+ "permPanel": "pageinfo_permissions",
+ "securityPanel": "pageinfo_security"
+ };
+
+ var deck = document.getElementById("mainDeck");
+ var helpdoc = helpTopics[deck.selectedPanel.id] || "pageinfo_general";
+ openHelpLink(helpdoc);
+}
+
+function showTab(id)
+{
+ var deck = document.getElementById("mainDeck");
+ var pagel = document.getElementById(id + "Panel");
+ deck.selectedPanel = pagel;
+}
+
+function loadTab(args)
+{
+ if (args && args.doc) {
+ gDocument = args.doc;
+ gWindow = gDocument.defaultView;
+ }
+
+ gImageElement = args && args.imageElement;
+
+ /* Load the page info */
+ loadPageInfo();
+
+ var initialTab = (args && args.initialTab) || "generalTab";
+ var radioGroup = document.getElementById("viewGroup");
+ initialTab = document.getElementById(initialTab) || document.getElementById("generalTab");
+ radioGroup.selectedItem = initialTab;
+ radioGroup.selectedItem.doCommand();
+ radioGroup.focus();
+}
+
+function onClickMore()
+{
+ var radioGrp = document.getElementById("viewGroup");
+ var radioElt = document.getElementById("securityTab");
+ radioGrp.selectedItem = radioElt;
+ showTab('security');
+}
+
+function toggleGroupbox(id)
+{
+ var elt = document.getElementById(id);
+ if (elt.hasAttribute("closed")) {
+ elt.removeAttribute("closed");
+ if (elt.flexWhenOpened)
+ elt.flex = elt.flexWhenOpened;
+ }
+ else {
+ elt.setAttribute("closed", "true");
+ if (elt.flex) {
+ elt.flexWhenOpened = elt.flex;
+ elt.flex = 0;
+ }
+ }
+}
+
+function openCacheEntry(key, cb)
+{
+ var tries = 0;
+ var checkCacheListener = {
+ onCacheEntryAvailable: function(entry, access, status) {
+ if (entry || tries == 1) {
+ cb(entry);
+ }
+ else {
+ tries++;
+ ftpCacheSession.asyncOpenCacheEntry(key, ACCESS_READ, this, true);
+ }
+ }
+ };
+ httpCacheSession.asyncOpenCacheEntry(key, ACCESS_READ, checkCacheListener, true);
+}
+
+function makeGeneralTab()
+{
+ var title = (gDocument.title) ? gBundle.getFormattedString("pageTitle", [gDocument.title]) : gBundle.getString("noPageTitle");
+ document.getElementById("titletext").value = title;
+
+ var url = gDocument.location.toString();
+ setItemValue("urltext", url);
+
+ var referrer = ("referrer" in gDocument && gDocument.referrer);
+ setItemValue("refertext", referrer);
+
+ var mode = ("compatMode" in gDocument && gDocument.compatMode == "BackCompat") ? "generalQuirksMode" : "generalStrictMode";
+ document.getElementById("modetext").value = gBundle.getString(mode);
+
+ // find out the mime type
+ var mimeType = gDocument.contentType;
+ setItemValue("typetext", mimeType);
+
+ // get the document characterset
+ var encoding = gDocument.characterSet;
+ document.getElementById("encodingtext").value = encoding;
+
+ // get the meta tags
+ var metaNodes = gDocument.getElementsByTagName("meta");
+ var length = metaNodes.length;
+
+ var metaGroup = document.getElementById("metaTags");
+ if (!length)
+ metaGroup.collapsed = true;
+ else {
+ var metaTagsCaption = document.getElementById("metaTagsCaption");
+ if (length == 1)
+ metaTagsCaption.label = gBundle.getString("generalMetaTag");
+ else
+ metaTagsCaption.label = gBundle.getFormattedString("generalMetaTags", [length]);
+ var metaTree = document.getElementById("metatree");
+ metaTree.treeBoxObject.view = gMetaView;
+
+ for (var i = 0; i < length; i++)
+ gMetaView.addRow([metaNodes[i].name || metaNodes[i].httpEquiv, metaNodes[i].content]);
+
+ metaGroup.collapsed = false;
+ }
+
+ // get the date of last modification
+ var modifiedText = formatDate(gDocument.lastModified, gStrings.notSet);
+ document.getElementById("modifiedtext").value = modifiedText;
+
+ // get cache info
+ var cacheKey = url.replace(/#.*$/, "");
+ openCacheEntry(cacheKey, function(cacheEntry) {
+ var sizeText;
+ if (cacheEntry) {
+ var pageSize = cacheEntry.dataSize;
+ var kbSize = formatNumber(Math.round(pageSize / 1024 * 100) / 100);
+ sizeText = gBundle.getFormattedString("generalSize", [kbSize, formatNumber(pageSize)]);
+ }
+ setItemValue("sizetext", sizeText);
+ });
+
+ securityOnLoad();
+}
+
+//******** Generic Build-a-tab
+// Assumes the views are empty. Only called once to build the tabs, and
+// does so by farming the task off to another thread via setTimeout().
+// The actual work is done with a TreeWalker that calls doGrab() once for
+// each element node in the document.
+
+var gFrameList = [ ];
+
+function makeTabs(aDocument, aWindow)
+{
+ goThroughFrames(aDocument, aWindow);
+ processFrames();
+}
+
+function goThroughFrames(aDocument, aWindow)
+{
+ gFrameList.push(aDocument);
+ if (aWindow && aWindow.frames.length > 0) {
+ var num = aWindow.frames.length;
+ for (var i = 0; i < num; i++)
+ goThroughFrames(aWindow.frames[i].document, aWindow.frames[i]); // recurse through the frames
+ }
+}
+
+function processFrames()
+{
+ if (gFrameList.length) {
+ var doc = gFrameList[0];
+ onProcessFrame.forEach(function(func) { func(doc); });
+ var iterator = doc.createTreeWalker(doc, NodeFilter.SHOW_ELEMENT, grabAll);
+ gFrameList.shift();
+ setTimeout(doGrab, 10, iterator);
+ onFinished.push(selectImage);
+ }
+ else
+ onFinished.forEach(function(func) { func(); });
+}
+
+function doGrab(iterator)
+{
+ for (var i = 0; i < 500; ++i)
+ if (!iterator.nextNode()) {
+ processFrames();
+ return;
+ }
+
+ setTimeout(doGrab, 10, iterator);
+}
+
+function addImage(url, type, alt, elem, isBg)
+{
+ if (!url)
+ return;
+
+ if (!gImageHash.hasOwnProperty(url))
+ gImageHash[url] = { };
+ if (!gImageHash[url].hasOwnProperty(type))
+ gImageHash[url][type] = { };
+ if (!gImageHash[url][type].hasOwnProperty(alt)) {
+ gImageHash[url][type][alt] = gImageView.data.length;
+ var row = [url, type, -1, alt, 1, elem, isBg];
+ gImageView.addRow(row);
+
+ // Fill in cache data asynchronously
+ openCacheEntry(url, function(cacheEntry) {
+ // The data at row[2] corresponds to the data size.
+ if (cacheEntry) {
+ row[2] = cacheEntry.dataSize;
+ // Invalidate the row to trigger a repaint.
+ gImageView.tree.invalidateRow(gImageView.data.indexOf(row));
+ }
+ });
+
+ // Add the observer, only once.
+ if (gImageView.data.length == 1) {
+ document.getElementById("mediaTab").hidden = false;
+ Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService)
+ .addObserver(imagePermissionObserver, "perm-changed", false);
+ }
+ }
+ else {
+ var i = gImageHash[url][type][alt];
+ gImageView.data[i][COL_IMAGE_COUNT]++;
+ if (elem == gImageElement)
+ gImageView.data[i][COL_IMAGE_NODE] = elem;
+ }
+}
+
+function grabAll(elem)
+{
+ // check for images defined in CSS (e.g. background, borders), any node may have multiple
+ var computedStyle = elem.ownerDocument.defaultView.getComputedStyle(elem, "");
+
+ if (computedStyle) {
+ var addImgFunc = function (label, val) {
+ if (val.primitiveType == CSSPrimitiveValue.CSS_URI) {
+ addImage(val.getStringValue(), label, gStrings.notSet, elem, true);
+ }
+ else if (val.primitiveType == CSSPrimitiveValue.CSS_STRING) {
+ // This is for -moz-image-rect.
+ // TODO: Reimplement once bug 714757 is fixed
+ var strVal = val.getStringValue();
+ if (strVal.search(/^.*url\(\"?/) > -1) {
+ url = strVal.replace(/^.*url\(\"?/,"").replace(/\"?\).*$/,"");
+ addImage(url, label, gStrings.notSet, elem, true);
+ }
+ }
+ else if (val.cssValueType == CSSValue.CSS_VALUE_LIST) {
+ // recursively resolve multiple nested CSS value lists
+ for (var i = 0; i < val.length; i++)
+ addImgFunc(label, val.item(i));
+ }
+ };
+
+ addImgFunc(gStrings.mediaBGImg, computedStyle.getPropertyCSSValue("background-image"));
+ addImgFunc(gStrings.mediaBorderImg, computedStyle.getPropertyCSSValue("border-image-source"));
+ addImgFunc(gStrings.mediaListImg, computedStyle.getPropertyCSSValue("list-style-image"));
+ addImgFunc(gStrings.mediaCursor, computedStyle.getPropertyCSSValue("cursor"));
+ }
+
+ // one swi^H^H^Hif-else to rule them all
+ if (elem instanceof HTMLImageElement)
+ addImage(elem.src, gStrings.mediaImg,
+ (elem.hasAttribute("alt")) ? elem.alt : gStrings.notSet, elem, false);
+ else if (elem instanceof SVGImageElement) {
+ try {
+ // Note: makeURLAbsolute will throw if either the baseURI is not a valid URI
+ // or the URI formed from the baseURI and the URL is not a valid URI
+ var href = makeURLAbsolute(elem.baseURI, elem.href.baseVal);
+ addImage(href, gStrings.mediaImg, "", elem, false);
+ } catch (e) { }
+ }
+ else if (elem instanceof HTMLVideoElement) {
+ addImage(elem.currentSrc, gStrings.mediaVideo, "", elem, false);
+ }
+ else if (elem instanceof HTMLAudioElement) {
+ addImage(elem.currentSrc, gStrings.mediaAudio, "", elem, false);
+ }
+ else if (elem instanceof HTMLLinkElement) {
+ if (elem.rel && /\bicon\b/i.test(elem.rel))
+ addImage(elem.href, gStrings.mediaLink, "", elem, false);
+ }
+ else if (elem instanceof HTMLInputElement || elem instanceof HTMLButtonElement) {
+ if (elem.type.toLowerCase() == "image")
+ addImage(elem.src, gStrings.mediaInput,
+ (elem.hasAttribute("alt")) ? elem.alt : gStrings.notSet, elem, false);
+ }
+ else if (elem instanceof HTMLObjectElement)
+ addImage(elem.data, gStrings.mediaObject, getValueText(elem), elem, false);
+ else if (elem instanceof HTMLEmbedElement)
+ addImage(elem.src, gStrings.mediaEmbed, "", elem, false);
+
+ onProcessElement.forEach(function(func) { func(elem); });
+
+ return NodeFilter.FILTER_ACCEPT;
+}
+
+//******** Link Stuff
+function openURL(target)
+{
+ var url = target.parentNode.childNodes[2].value;
+ window.open(url, "_blank", "chrome");
+}
+
+function onBeginLinkDrag(event,urlField,descField)
+{
+ if (event.originalTarget.localName != "treechildren")
+ return;
+
+ var tree = event.target;
+ if (!("treeBoxObject" in tree))
+ tree = tree.parentNode;
+
+ var row = tree.treeBoxObject.getRowAt(event.clientX, event.clientY);
+ if (row == -1)
+ return;
+
+ // Adding URL flavor
+ var col = tree.columns[urlField];
+ var url = tree.view.getCellText(row, col);
+ col = tree.columns[descField];
+ var desc = tree.view.getCellText(row, col);
+
+ var dt = event.dataTransfer;
+ dt.setData("text/x-moz-url", url + "\n" + desc);
+ dt.setData("text/url-list", url);
+ dt.setData("text/plain", url);
+}
+
+//******** Image Stuff
+function getSelectedRows(tree)
+{
+ var start = { };
+ var end = { };
+ var numRanges = tree.view.selection.getRangeCount();
+
+ var rowArray = [ ];
+ for (var t = 0; t < numRanges; t++) {
+ tree.view.selection.getRangeAt(t, start, end);
+ for (var v = start.value; v <= end.value; v++)
+ rowArray.push(v);
+ }
+
+ return rowArray;
+}
+
+function getSelectedRow(tree)
+{
+ var rows = getSelectedRows(tree);
+ return (rows.length == 1) ? rows[0] : -1;
+}
+
+function selectSaveFolder(aCallback)
+{
+ const nsILocalFile = Components.interfaces.nsILocalFile;
+ const nsIFilePicker = Components.interfaces.nsIFilePicker;
+ let titleText = gBundle.getString("mediaSelectFolder");
+ let fp = Components.classes["@mozilla.org/filepicker;1"].
+ createInstance(nsIFilePicker);
+ let fpCallback = function fpCallback_done(aResult) {
+ if (aResult == nsIFilePicker.returnOK) {
+ aCallback(fp.file.QueryInterface(nsILocalFile));
+ } else {
+ aCallback(null);
+ }
+ };
+
+ fp.init(window, titleText, nsIFilePicker.modeGetFolder);
+ fp.appendFilters(nsIFilePicker.filterAll);
+ try {
+ let prefs = Components.classes[PREFERENCES_CONTRACTID].
+ getService(Components.interfaces.nsIPrefBranch);
+ let initialDir = prefs.getComplexValue("browser.download.dir", nsILocalFile);
+ if (initialDir) {
+ fp.displayDirectory = initialDir;
+ }
+ } catch (ex) {
+ }
+ fp.open(fpCallback);
+}
+
+function saveMedia()
+{
+ var tree = document.getElementById("imagetree");
+ var rowArray = getSelectedRows(tree);
+ if (rowArray.length == 1) {
+ var row = rowArray[0];
+ var item = gImageView.data[row][COL_IMAGE_NODE];
+ var url = gImageView.data[row][COL_IMAGE_ADDRESS];
+
+ if (url) {
+ var titleKey = "SaveImageTitle";
+
+ if (item instanceof HTMLVideoElement)
+ titleKey = "SaveVideoTitle";
+ else if (item instanceof HTMLAudioElement)
+ titleKey = "SaveAudioTitle";
+
+ saveURL(url, null, titleKey, false, false, makeURI(item.baseURI), gDocument);
+ }
+ } else {
+ selectSaveFolder(function(aDirectory) {
+ if (aDirectory) {
+ var saveAnImage = function(aURIString, aChosenData, aBaseURI) {
+ internalSave(aURIString, null, null, null, null, false, "SaveImageTitle",
+ aChosenData, aBaseURI, gDocument);
+ };
+
+ for (var i = 0; i < rowArray.length; i++) {
+ var v = rowArray[i];
+ var dir = aDirectory.clone();
+ var item = gImageView.data[v][COL_IMAGE_NODE];
+ var uriString = gImageView.data[v][COL_IMAGE_ADDRESS];
+ var uri = makeURI(uriString);
+
+ try {
+ uri.QueryInterface(Components.interfaces.nsIURL);
+ dir.append(decodeURIComponent(uri.fileName));
+ } catch(ex) {
+ /* data: uris */
+ }
+
+ if (i == 0) {
+ saveAnImage(uriString, new AutoChosen(dir, uri), makeURI(item.baseURI));
+ } else {
+ // This delay is a hack which prevents the download manager
+ // from opening many times. See bug 377339.
+ setTimeout(saveAnImage, 200, uriString, new AutoChosen(dir, uri),
+ makeURI(item.baseURI));
+ }
+ }
+ }
+ });
+ }
+}
+
+function onBlockImage()
+{
+ var permissionManager = Components.classes[PERMISSION_CONTRACTID]
+ .getService(nsIPermissionManager);
+
+ var checkbox = document.getElementById("blockImage");
+ var uri = makeURI(document.getElementById("imageurltext").value);
+ if (checkbox.checked)
+ permissionManager.add(uri, "image", nsIPermissionManager.DENY_ACTION);
+ else
+ permissionManager.remove(uri.host, "image");
+}
+
+function onImageSelect()
+{
+ var previewBox = document.getElementById("mediaPreviewBox");
+ var mediaSaveBox = document.getElementById("mediaSaveBox");
+ var splitter = document.getElementById("mediaSplitter");
+ var tree = document.getElementById("imagetree");
+ var count = tree.view.selection.count;
+ if (count == 0) {
+ previewBox.collapsed = true;
+ mediaSaveBox.collapsed = true;
+ splitter.collapsed = true;
+ tree.flex = 1;
+ }
+ else if (count > 1) {
+ splitter.collapsed = true;
+ previewBox.collapsed = true;
+ mediaSaveBox.collapsed = false;
+ tree.flex = 1;
+ }
+ else {
+ mediaSaveBox.collapsed = true;
+ splitter.collapsed = false;
+ previewBox.collapsed = false;
+ tree.flex = 0;
+ makePreview(getSelectedRows(tree)[0]);
+ }
+}
+
+function makePreview(row)
+{
+ var imageTree = document.getElementById("imagetree");
+ var item = gImageView.data[row][COL_IMAGE_NODE];
+ var url = gImageView.data[row][COL_IMAGE_ADDRESS];
+ var isBG = gImageView.data[row][COL_IMAGE_BG];
+ var isAudio = false;
+
+ setItemValue("imageurltext", url);
+
+ var imageText;
+ if (!isBG &&
+ !(item instanceof SVGImageElement) &&
+ !(gDocument instanceof ImageDocument)) {
+ imageText = item.title || item.alt;
+
+ if (!imageText && !(item instanceof HTMLImageElement))
+ imageText = getValueText(item);
+ }
+ setItemValue("imagetext", imageText);
+
+ setItemValue("imagelongdesctext", item.longDesc);
+
+ // get cache info
+ var cacheKey = url.replace(/#.*$/, "");
+ openCacheEntry(cacheKey, function(cacheEntry) {
+ // find out the file size
+ var sizeText;
+ if (cacheEntry) {
+ var imageSize = cacheEntry.dataSize;
+ var kbSize = Math.round(imageSize / 1024 * 100) / 100;
+ sizeText = gBundle.getFormattedString("generalSize",
+ [formatNumber(kbSize), formatNumber(imageSize)]);
+ }
+ else
+ sizeText = gBundle.getString("mediaUnknownNotCached");
+ setItemValue("imagesizetext", sizeText);
+
+ var mimeType;
+ var numFrames = 1;
+ if (item instanceof HTMLObjectElement ||
+ item instanceof HTMLEmbedElement ||
+ item instanceof HTMLLinkElement)
+ mimeType = item.type;
+
+ if (!mimeType && !isBG && item instanceof nsIImageLoadingContent) {
+ var imageRequest = item.getRequest(nsIImageLoadingContent.CURRENT_REQUEST);
+ if (imageRequest) {
+ mimeType = imageRequest.mimeType;
+ var image = imageRequest.image;
+ if (image)
+ numFrames = image.numFrames;
+ }
+ }
+
+ if (!mimeType)
+ mimeType = getContentTypeFromHeaders(cacheEntry);
+
+ // if we have a data url, get the MIME type from the url
+ if (!mimeType && url.startsWith("data:")) {
+ let dataMimeType = /^data:(image\/[^;,]+)/i.exec(url);
+ if (dataMimeType)
+ mimeType = dataMimeType[1].toLowerCase();
+ }
+
+ var imageType;
+ if (mimeType) {
+ // We found the type, try to display it nicely
+ let imageMimeType = /^image\/(.*)/i.exec(mimeType);
+ if (imageMimeType) {
+ imageType = imageMimeType[1].toUpperCase();
+ if (numFrames > 1)
+ imageType = gBundle.getFormattedString("mediaAnimatedImageType",
+ [imageType, numFrames]);
+ else
+ imageType = gBundle.getFormattedString("mediaImageType", [imageType]);
+ }
+ else {
+ // the MIME type doesn't begin with image/, display the raw type
+ imageType = mimeType;
+ }
+ }
+ else {
+ // We couldn't find the type, fall back to the value in the treeview
+ imageType = gImageView.data[row][COL_IMAGE_TYPE];
+ }
+ setItemValue("imagetypetext", imageType);
+
+ var imageContainer = document.getElementById("theimagecontainer");
+ var oldImage = document.getElementById("thepreviewimage");
+
+ var isProtocolAllowed = checkProtocol(gImageView.data[row]);
+
+ var newImage = new Image;
+ newImage.id = "thepreviewimage";
+ var physWidth = 0, physHeight = 0;
+ var width = 0, height = 0;
+
+ if ((item instanceof HTMLLinkElement || item instanceof HTMLInputElement ||
+ item instanceof HTMLImageElement ||
+ item instanceof SVGImageElement ||
+ (item instanceof HTMLObjectElement && mimeType && mimeType.startsWith("image/")) || isBG) && isProtocolAllowed) {
+ newImage.setAttribute("src", url);
+ physWidth = newImage.width || 0;
+ physHeight = newImage.height || 0;
+
+ // "width" and "height" attributes must be set to newImage,
+ // even if there is no "width" or "height attribute in item;
+ // otherwise, the preview image cannot be displayed correctly.
+ if (!isBG) {
+ newImage.width = ("width" in item && item.width) || newImage.naturalWidth;
+ newImage.height = ("height" in item && item.height) || newImage.naturalHeight;
+ }
+ else {
+ // the Width and Height of an HTML tag should not be used for its background image
+ // (for example, "table" can have "width" or "height" attributes)
+ newImage.width = newImage.naturalWidth;
+ newImage.height = newImage.naturalHeight;
+ }
+
+ if (item instanceof SVGImageElement) {
+ newImage.width = item.width.baseVal.value;
+ newImage.height = item.height.baseVal.value;
+ }
+
+ width = newImage.width;
+ height = newImage.height;
+
+ document.getElementById("theimagecontainer").collapsed = false
+ document.getElementById("brokenimagecontainer").collapsed = true;
+ }
+ else if (item instanceof HTMLVideoElement && isProtocolAllowed) {
+ newImage = document.createElementNS("http://www.w3.org/1999/xhtml", "video");
+ newImage.id = "thepreviewimage";
+ newImage.src = url;
+ newImage.controls = true;
+ width = physWidth = item.videoWidth;
+ height = physHeight = item.videoHeight;
+
+ document.getElementById("theimagecontainer").collapsed = false;
+ document.getElementById("brokenimagecontainer").collapsed = true;
+ }
+ else if (item instanceof HTMLAudioElement && isProtocolAllowed) {
+ newImage = new Audio;
+ newImage.id = "thepreviewimage";
+ newImage.src = url;
+ newImage.controls = true;
+ isAudio = true;
+
+ document.getElementById("theimagecontainer").collapsed = false;
+ document.getElementById("brokenimagecontainer").collapsed = true;
+ }
+ else {
+ // fallback image for protocols not allowed (e.g., javascript:)
+ // or elements not [yet] handled (e.g., object, embed).
+ document.getElementById("brokenimagecontainer").collapsed = false;
+ document.getElementById("theimagecontainer").collapsed = true;
+ }
+
+ var imageSize = "";
+ if (url && !isAudio) {
+ if (width != physWidth || height != physHeight) {
+ imageSize = gBundle.getFormattedString("mediaDimensionsScaled",
+ [formatNumber(physWidth),
+ formatNumber(physHeight),
+ formatNumber(width),
+ formatNumber(height)]);
+ }
+ else {
+ imageSize = gBundle.getFormattedString("mediaDimensions",
+ [formatNumber(width),
+ formatNumber(height)]);
+ }
+ }
+ setItemValue("imagedimensiontext", imageSize);
+
+ makeBlockImage(url);
+
+ imageContainer.removeChild(oldImage);
+ imageContainer.appendChild(newImage);
+
+ onImagePreviewShown.forEach(function(func) { func(); });
+ });
+}
+
+function makeBlockImage(url)
+{
+ var permissionManager = Components.classes[PERMISSION_CONTRACTID]
+ .getService(nsIPermissionManager);
+ var prefs = Components.classes[PREFERENCES_CONTRACTID]
+ .getService(Components.interfaces.nsIPrefBranch);
+
+ var checkbox = document.getElementById("blockImage");
+ var imagePref = prefs.getIntPref("permissions.default.image");
+ if (!(/^https?:/.test(url)) || imagePref == 2)
+ // We can't block the images from this host because either is is not
+ // for http(s) or we don't load images at all
+ checkbox.hidden = true;
+ else {
+ var uri = makeURI(url);
+ if (uri.host) {
+ checkbox.hidden = false;
+ checkbox.label = gBundle.getFormattedString("mediaBlockImage", [uri.host]);
+ var perm = permissionManager.testPermission(uri, "image");
+ checkbox.checked = perm == nsIPermissionManager.DENY_ACTION;
+ }
+ else
+ checkbox.hidden = true;
+ }
+}
+
+var imagePermissionObserver = {
+ observe: function (aSubject, aTopic, aData)
+ {
+ if (document.getElementById("mediaPreviewBox").collapsed)
+ return;
+
+ if (aTopic == "perm-changed") {
+ var permission = aSubject.QueryInterface(Components.interfaces.nsIPermission);
+ if (permission.type == "image") {
+ var imageTree = document.getElementById("imagetree");
+ var row = getSelectedRow(imageTree);
+ var item = gImageView.data[row][COL_IMAGE_NODE];
+ var url = gImageView.data[row][COL_IMAGE_ADDRESS];
+ if (makeURI(url).host == permission.host)
+ makeBlockImage(url);
+ }
+ }
+ }
+}
+
+function getContentTypeFromHeaders(cacheEntryDescriptor)
+{
+ if (!cacheEntryDescriptor)
+ return null;
+
+ return (/^Content-Type:\s*(.*?)\s*(?:\;|$)/mi
+ .exec(cacheEntryDescriptor.getMetaDataElement("response-head")))[1];
+}
+
+//******** Other Misc Stuff
+// Modified from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html
+// parse a node to extract the contents of the node
+function getValueText(node)
+{
+ var valueText = "";
+
+ // form input elements don't generally contain information that is useful to our callers, so return nothing
+ if (node instanceof HTMLInputElement ||
+ node instanceof HTMLSelectElement ||
+ node instanceof HTMLTextAreaElement)
+ return valueText;
+
+ // otherwise recurse for each child
+ var length = node.childNodes.length;
+ for (var i = 0; i < length; i++) {
+ var childNode = node.childNodes[i];
+ var nodeType = childNode.nodeType;
+
+ // text nodes are where the goods are
+ if (nodeType == Node.TEXT_NODE)
+ valueText += " " + childNode.nodeValue;
+ // and elements can have more text inside them
+ else if (nodeType == Node.ELEMENT_NODE) {
+ // images are special, we want to capture the alt text as if the image weren't there
+ if (childNode instanceof HTMLImageElement)
+ valueText += " " + getAltText(childNode);
+ else
+ valueText += " " + getValueText(childNode);
+ }
+ }
+
+ return stripWS(valueText);
+}
+
+// Copied from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html
+// traverse the tree in search of an img or area element and grab its alt tag
+function getAltText(node)
+{
+ var altText = "";
+
+ if (node.alt)
+ return node.alt;
+ var length = node.childNodes.length;
+ for (var i = 0; i < length; i++)
+ if ((altText = getAltText(node.childNodes[i]) != undefined)) // stupid js warning...
+ return altText;
+ return "";
+}
+
+// Copied from the Links Panel v2.3, http://segment7.net/mozilla/links/links.html
+// strip leading and trailing whitespace, and replace multiple consecutive whitespace characters with a single space
+function stripWS(text)
+{
+ var middleRE = /\s+/g;
+ var endRE = /(^\s+)|(\s+$)/g;
+
+ text = text.replace(middleRE, " ");
+ return text.replace(endRE, "");
+}
+
+function setItemValue(id, value)
+{
+ var item = document.getElementById(id);
+ if (value) {
+ item.parentNode.collapsed = false;
+ item.value = value;
+ }
+ else
+ item.parentNode.collapsed = true;
+}
+
+function formatNumber(number)
+{
+ return (+number).toLocaleString(); // coerce number to a numeric value before calling toLocaleString()
+}
+
+function formatDate(datestr, unknown)
+{
+ // scriptable date formatter, for pretty printing dates
+ var dateService = Components.classes["@mozilla.org/intl/scriptabledateformat;1"]
+ .getService(Components.interfaces.nsIScriptableDateFormat);
+
+ var date = new Date(datestr);
+ if (!date.valueOf())
+ return unknown;
+
+ return dateService.FormatDateTime("", dateService.dateFormatLong,
+ dateService.timeFormatSeconds,
+ date.getFullYear(), date.getMonth()+1, date.getDate(),
+ date.getHours(), date.getMinutes(), date.getSeconds());
+}
+
+function doCopy()
+{
+ if (!gClipboardHelper)
+ return;
+
+ var elem = document.commandDispatcher.focusedElement;
+
+ if (elem && "treeBoxObject" in elem) {
+ var view = elem.view;
+ var selection = view.selection;
+ var text = [], tmp = '';
+ var min = {}, max = {};
+
+ var count = selection.getRangeCount();
+
+ for (var i = 0; i < count; i++) {
+ selection.getRangeAt(i, min, max);
+
+ for (var row = min.value; row <= max.value; row++) {
+ view.performActionOnRow("copy", row);
+
+ tmp = elem.getAttribute("copybuffer");
+ if (tmp)
+ text.push(tmp);
+ elem.removeAttribute("copybuffer");
+ }
+ }
+ gClipboardHelper.copyString(text.join("\n"), document);
+ }
+}
+
+function doSelectAll()
+{
+ var elem = document.commandDispatcher.focusedElement;
+
+ if (elem && "treeBoxObject" in elem)
+ elem.view.selection.selectAll();
+}
+
+function selectImage()
+{
+ if (!gImageElement)
+ return;
+
+ var tree = document.getElementById("imagetree");
+ for (var i = 0; i < tree.view.rowCount; i++) {
+ if (gImageElement == gImageView.data[i][COL_IMAGE_NODE] &&
+ !gImageView.data[i][COL_IMAGE_BG]) {
+ tree.view.selection.select(i);
+ tree.treeBoxObject.ensureRowIsVisible(i);
+ tree.focus();
+ return;
+ }
+ }
+}
+
+function checkProtocol(img)
+{
+ var url = img[COL_IMAGE_ADDRESS];
+ return /^data:image\//i.test(url) ||
+ /^(https?|ftp|file|about|chrome|resource):/.test(url);
+}
diff --git a/browser/base/content/pageinfo/pageInfo.xml b/browser/base/content/pageinfo/pageInfo.xml
new file mode 100644
index 000000000..20d330046
--- /dev/null
+++ b/browser/base/content/pageinfo/pageInfo.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+
+<bindings id="pageInfoBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <!-- based on preferences.xml paneButton -->
+ <binding id="viewbutton" extends="chrome://global/content/bindings/radio.xml#radio">
+ <content>
+ <xul:image class="viewButtonIcon" xbl:inherits="src"/>
+ <xul:label class="viewButtonLabel" xbl:inherits="value=label"/>
+ </content>
+ <implementation implements="nsIAccessibleProvider">
+ <property name="accessibleType" readonly="true">
+ <getter>
+ <![CDATA[
+ return Components.interfaces.nsIAccessibleProvider.XULListitem;
+ ]]>
+ </getter>
+ </property>
+ </implementation>
+ </binding>
+
+</bindings>
diff --git a/browser/base/content/pageinfo/pageInfo.xul b/browser/base/content/pageinfo/pageInfo.xul
new file mode 100644
index 000000000..b1c480413
--- /dev/null
+++ b/browser/base/content/pageinfo/pageInfo.xul
@@ -0,0 +1,558 @@
+<?xml version="1.0"?>
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://browser/content/pageinfo/pageInfo.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/pageInfo.css" type="text/css"?>
+
+<!DOCTYPE window [
+ <!ENTITY % pageInfoDTD SYSTEM "chrome://browser/locale/pageInfo.dtd">
+ %pageInfoDTD;
+]>
+
+#ifdef XP_MACOSX
+<?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?>
+#endif
+
+<window id="main-window"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ windowtype="Browser:page-info"
+ onload="onLoadPageInfo()"
+ onunload="onUnloadPageInfo()"
+ align="stretch"
+ screenX="10" screenY="10"
+ width="&pageInfoWindow.width;" height="&pageInfoWindow.height;"
+ persist="screenX screenY width height sizemode">
+
+ <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
+ <script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
+ <script type="application/javascript" src="chrome://global/content/treeUtils.js"/>
+ <script type="application/javascript" src="chrome://browser/content/pageinfo/pageInfo.js"/>
+ <script type="application/javascript" src="chrome://browser/content/pageinfo/feeds.js"/>
+ <script type="application/javascript" src="chrome://browser/content/pageinfo/permissions.js"/>
+ <script type="application/javascript" src="chrome://browser/content/pageinfo/security.js"/>
+ <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
+
+ <stringbundleset id="pageinfobundleset">
+ <stringbundle id="pageinfobundle" src="chrome://browser/locale/pageInfo.properties"/>
+ <stringbundle id="pkiBundle" src="chrome://pippki/locale/pippki.properties"/>
+ <stringbundle id="browserBundle" src="chrome://browser/locale/browser.properties"/>
+ </stringbundleset>
+
+ <commandset id="pageInfoCommandSet">
+ <command id="cmd_close" oncommand="window.close();"/>
+ <command id="cmd_help" oncommand="doHelpButton();"/>
+ <command id="cmd_copy" oncommand="doCopy();"/>
+ <command id="cmd_selectall" oncommand="doSelectAll();"/>
+
+ <!-- permissions tab -->
+ <command id="cmd_imageDef" oncommand="onCheckboxClick('image');"/>
+ <command id="cmd_popupDef" oncommand="onCheckboxClick('popup');"/>
+ <command id="cmd_cookieDef" oncommand="onCheckboxClick('cookie');"/>
+ <command id="cmd_desktop-notificationDef" oncommand="onCheckboxClick('desktop-notification');"/>
+ <command id="cmd_installDef" oncommand="onCheckboxClick('install');"/>
+ <command id="cmd_fullscreenDef" oncommand="onCheckboxClick('fullscreen');"/>
+ <command id="cmd_geoDef" oncommand="onCheckboxClick('geo');"/>
+ <command id="cmd_indexedDBDef" oncommand="onCheckboxClick('indexedDB');"/>
+ <command id="cmd_pluginsDef" oncommand="onCheckboxClick('plugins');"/>
+ <command id="cmd_imageToggle" oncommand="onRadioClick('image');"/>
+ <command id="cmd_popupToggle" oncommand="onRadioClick('popup');"/>
+ <command id="cmd_cookieToggle" oncommand="onRadioClick('cookie');"/>
+ <command id="cmd_desktop-notificationToggle" oncommand="onRadioClick('desktop-notification');"/>
+ <command id="cmd_installToggle" oncommand="onRadioClick('install');"/>
+ <command id="cmd_fullscreenToggle" oncommand="onRadioClick('fullscreen');"/>
+ <command id="cmd_geoToggle" oncommand="onRadioClick('geo');"/>
+ <command id="cmd_indexedDBToggle" oncommand="onRadioClick('indexedDB');"/>
+ <command id="cmd_pluginsToggle" oncommand="onPluginRadioClick(event);"/>
+ <command id="cmd_pointerLockDef" oncommand="onCheckboxClick('pointerLock');"/>
+ <command id="cmd_pointerLockToggle" oncommand="onRadioClick('pointerLock');"/>
+ </commandset>
+
+ <keyset id="pageInfoKeySet">
+ <key key="&closeWindow.key;" modifiers="accel" command="cmd_close"/>
+ <key keycode="VK_ESCAPE" command="cmd_close"/>
+#ifdef XP_MACOSX
+ <key key="." modifiers="meta" command="cmd_close"/>
+#else
+ <key keycode="VK_F1" command="cmd_help"/>
+#endif
+ <key key="&copy.key;" modifiers="accel" command="cmd_copy"/>
+ <key key="&selectall.key;" modifiers="accel" command="cmd_selectall"/>
+ <key key="&selectall.key;" modifiers="alt" command="cmd_selectall"/>
+ </keyset>
+
+ <menupopup id="picontext">
+ <menuitem id="menu_selectall" label="&selectall.label;" command="cmd_selectall" accesskey="&selectall.accesskey;"/>
+ <menuitem id="menu_copy" label="&copy.label;" command="cmd_copy" accesskey="&copy.accesskey;"/>
+ </menupopup>
+
+ <windowdragbox id="topBar" class="viewGroupWrapper">
+ <radiogroup id="viewGroup" class="chromeclass-toolbar" orient="horizontal">
+ <radio id="generalTab" label="&generalTab;" accesskey="&generalTab.accesskey;"
+ oncommand="showTab('general');"/>
+ <radio id="mediaTab" label="&mediaTab;" accesskey="&mediaTab.accesskey;"
+ oncommand="showTab('media');" hidden="true"/>
+ <radio id="feedTab" label="&feedTab;" accesskey="&feedTab.accesskey;"
+ oncommand="showTab('feed');" hidden="true"/>
+ <radio id="permTab" label="&permTab;" accesskey="&permTab.accesskey;"
+ oncommand="showTab('perm');"/>
+ <radio id="securityTab" label="&securityTab;" accesskey="&securityTab.accesskey;"
+ oncommand="showTab('security');"/>
+ <!-- Others added by overlay -->
+ </radiogroup>
+ </windowdragbox>
+
+ <deck id="mainDeck" flex="1">
+ <!-- General page information -->
+ <vbox id="generalPanel">
+ <textbox class="header" readonly="true" id="titletext"/>
+ <grid id="generalGrid">
+ <columns>
+ <column/>
+ <column class="gridSeparator"/>
+ <column flex="1"/>
+ </columns>
+ <rows id="generalRows">
+ <row id="generalURLRow">
+ <label control="urltext" value="&generalURL;"/>
+ <separator/>
+ <textbox readonly="true" id="urltext"/>
+ </row>
+ <row id="generalSeparatorRow1">
+ <separator class="thin"/>
+ </row>
+ <row id="generalTypeRow">
+ <label control="typetext" value="&generalType;"/>
+ <separator/>
+ <textbox readonly="true" id="typetext"/>
+ </row>
+ <row id="generalModeRow">
+ <label control="modetext" value="&generalMode;"/>
+ <separator/>
+ <textbox readonly="true" crop="end" id="modetext"/>
+ </row>
+ <row id="generalEncodingRow">
+ <label control="encodingtext" value="&generalEncoding;"/>
+ <separator/>
+ <textbox readonly="true" id="encodingtext"/>
+ </row>
+ <row id="generalSizeRow">
+ <label control="sizetext" value="&generalSize;"/>
+ <separator/>
+ <textbox readonly="true" id="sizetext"/>
+ </row>
+ <row id="generalReferrerRow">
+ <label control="refertext" value="&generalReferrer;"/>
+ <separator/>
+ <textbox readonly="true" id="refertext"/>
+ </row>
+ <row id="generalSeparatorRow2">
+ <separator class="thin"/>
+ </row>
+ <row id="generalModifiedRow">
+ <label control="modifiedtext" value="&generalModified;"/>
+ <separator/>
+ <textbox readonly="true" id="modifiedtext"/>
+ </row>
+ </rows>
+ </grid>
+ <separator class="thin"/>
+ <groupbox id="metaTags" flex="1" class="collapsable treebox">
+ <caption id="metaTagsCaption" onclick="toggleGroupbox('metaTags');"/>
+ <tree id="metatree" flex="1" hidecolumnpicker="true" contextmenu="picontext">
+ <treecols>
+ <treecol id="meta-name" label="&generalMetaName;"
+ persist="width" flex="1"
+ onclick="gMetaView.onPageMediaSort('meta-name');"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="meta-content" label="&generalMetaContent;"
+ persist="width" flex="4"
+ onclick="gMetaView.onPageMediaSort('meta-content');"/>
+ </treecols>
+ <treechildren id="metatreechildren" flex="1"/>
+ </tree>
+ </groupbox>
+ <groupbox id="securityBox">
+ <caption id="securityBoxCaption" label="&securityHeader;"/>
+ <description id="general-security-identity" class="header"/>
+ <description id="general-security-privacy" class="header"/>
+ <hbox id="securityDetailsButtonBox" align="right">
+ <button id="security-view-details" label="&generalSecurityDetails;"
+ accesskey="&generalSecurityDetails.accesskey;"
+ oncommand="onClickMore();"/>
+ </hbox>
+ </groupbox>
+ </vbox>
+
+ <!-- Media information -->
+ <vbox id="mediaPanel">
+ <tree id="imagetree" onselect="onImageSelect();" contextmenu="picontext"
+ ondragstart="onBeginLinkDrag(event,'image-address','image-alt')">
+ <treecols>
+ <treecol sortSeparators="true" primary="true" persist="width" flex="10"
+ width="10" id="image-address" label="&mediaAddress;"
+ onclick="gImageView.onPageMediaSort('image-address');"/>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" persist="hidden width" flex="2"
+ width="2" id="image-type" label="&mediaType;"
+ onclick="gImageView.onPageMediaSort('image-type');"/>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" hidden="true" persist="hidden width" flex="2"
+ width="2" id="image-size" label="&mediaSize;" value="size"
+ onclick="gImageView.onPageMediaSort('image-size');"/>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" hidden="true" persist="hidden width" flex="4"
+ width="4" id="image-alt" label="&mediaAltHeader;"
+ onclick="gImageView.onPageMediaSort('image-alt');"/>
+ <splitter class="tree-splitter"/>
+ <treecol sortSeparators="true" hidden="true" persist="hidden width" flex="1"
+ width="1" id="image-count" label="&mediaCount;"
+ onclick="gImageView.onPageMediaSort('image-count');"/>
+ </treecols>
+ <treechildren id="imagetreechildren" flex="1"/>
+ </tree>
+ <splitter orient="vertical" id="mediaSplitter"/>
+ <vbox flex="1" id="mediaPreviewBox" collapsed="true">
+ <grid id="mediaGrid">
+ <columns>
+ <column id="mediaLabelColumn"/>
+ <column class="gridSeparator"/>
+ <column flex="1"/>
+ </columns>
+ <rows id="mediaRows">
+ <row id="mediaLocationRow">
+ <label control="imageurltext" value="&mediaLocation;"/>
+ <separator/>
+ <textbox readonly="true" id="imageurltext"/>
+ </row>
+ <row id="mediaTypeRow">
+ <label control="imagetypetext" value="&generalType;"/>
+ <separator/>
+ <textbox readonly="true" id="imagetypetext"/>
+ </row>
+ <row id="mediaSizeRow">
+ <label control="imagesizetext" value="&generalSize;"/>
+ <separator/>
+ <textbox readonly="true" id="imagesizetext"/>
+ </row>
+ <row id="mediaDimensionRow">
+ <label control="imagedimensiontext" value="&mediaDimension;"/>
+ <separator/>
+ <textbox readonly="true" id="imagedimensiontext"/>
+ </row>
+ <row id="mediaTextRow">
+ <label control="imagetext" value="&mediaText;"/>
+ <separator/>
+ <textbox readonly="true" id="imagetext"/>
+ </row>
+ <row id="mediaLongdescRow">
+ <label control="imagelongdesctext" value="&mediaLongdesc;"/>
+ <separator/>
+ <textbox readonly="true" id="imagelongdesctext"/>
+ </row>
+ </rows>
+ </grid>
+ <hbox id="imageSaveBox" align="end">
+ <vbox id="blockImageBox">
+ <checkbox id="blockImage" hidden="true" oncommand="onBlockImage()"
+ accesskey="&mediaBlockImage.accesskey;"/>
+ <label control="thepreviewimage" value="&mediaPreview;" class="header"/>
+ </vbox>
+ <spacer id="imageSaveBoxSpacer" flex="1"/>
+ <button label="&mediaSaveAs;" accesskey="&mediaSaveAs.accesskey;"
+ icon="save" id="imagesaveasbutton"
+ oncommand="saveMedia();"/>
+ </hbox>
+ <vbox id="imagecontainerbox" class="inset iframe" flex="1" pack="center">
+ <hbox id="theimagecontainer" pack="center">
+ <image id="thepreviewimage"/>
+ </hbox>
+ <hbox id="brokenimagecontainer" pack="center" collapsed="true">
+ <image id="brokenimage" src="resource://gre-resources/broken-image.png"/>
+ </hbox>
+ </vbox>
+ </vbox>
+ <hbox id="mediaSaveBox" collapsed="true">
+ <spacer id="mediaSaveBoxSpacer" flex="1"/>
+ <button label="&mediaSaveAs;" accesskey="&mediaSaveAs2.accesskey;"
+ icon="save" id="mediasaveasbutton"
+ oncommand="saveMedia();"/>
+ </hbox>
+ </vbox>
+
+ <!-- Feeds -->
+ <vbox id="feedPanel">
+ <richlistbox id="feedListbox" flex="1"/>
+ </vbox>
+
+ <!-- Permissions -->
+ <vbox id="permPanel">
+ <hbox id="permHostBox">
+ <label value="&permissionsFor;" control="hostText" />
+ <textbox id="hostText" class="header" readonly="true"
+ crop="end" flex="1"/>
+ </hbox>
+
+ <vbox id="permList" flex="1">
+ <vbox class="permission" id="permImageRow">
+ <label class="permissionLabel" id="permImageLabel"
+ value="&permImage;" control="imageRadioGroup"/>
+ <hbox id="permImageBox" role="group" aria-labelledby="permImageLabel">
+ <checkbox id="imageDef" command="cmd_imageDef" label="&permUseDefault;"/>
+ <spacer flex="1"/>
+ <radiogroup id="imageRadioGroup" orient="horizontal">
+ <radio id="image#1" command="cmd_imageToggle" label="&permAllow;"/>
+ <radio id="image#2" command="cmd_imageToggle" label="&permBlock;"/>
+ </radiogroup>
+ </hbox>
+ </vbox>
+ <vbox class="permission" id="permPopupRow">
+ <label class="permissionLabel" id="permPopupLabel"
+ value="&permPopup;" control="popupRadioGroup"/>
+ <hbox id="permPopupBox" role="group" aria-labelledby="permPopupLabel">
+ <checkbox id="popupDef" command="cmd_popupDef" label="&permUseDefault;"/>
+ <spacer flex="1"/>
+ <radiogroup id="popupRadioGroup" orient="horizontal">
+ <radio id="popup#1" command="cmd_popupToggle" label="&permAllow;"/>
+ <radio id="popup#2" command="cmd_popupToggle" label="&permBlock;"/>
+ </radiogroup>
+ </hbox>
+ </vbox>
+ <vbox class="permission" id="permCookieRow">
+ <label class="permissionLabel" id="permCookieLabel"
+ value="&permCookie;" control="cookieRadioGroup"/>
+ <hbox id="permCookieBox" role="group" aria-labelledby="permCookieLabel">
+ <checkbox id="cookieDef" command="cmd_cookieDef" label="&permUseDefault;"/>
+ <spacer flex="1"/>
+ <radiogroup id="cookieRadioGroup" orient="horizontal">
+ <radio id="cookie#1" command="cmd_cookieToggle" label="&permAllow;"/>
+ <radio id="cookie#8" command="cmd_cookieToggle" label="&permAllowSession;"/>
+ <radio id="cookie#2" command="cmd_cookieToggle" label="&permBlock;"/>
+ </radiogroup>
+ </hbox>
+ </vbox>
+ <vbox class="permission" id="permNotificationRow">
+ <label class="permissionLabel" id="permNotificationLabel"
+ value="&permNotifications;" control="desktop-notificationRadioGroup"/>
+ <hbox role="group" aria-labelledby="permNotificationLabel">
+ <checkbox id="desktop-notificationDef" command="cmd_desktop-notificationDef" label="&permUseDefault;"/>
+ <spacer flex="1"/>
+ <radiogroup id="desktop-notificationRadioGroup" orient="horizontal">
+ <radio id="desktop-notification#1" command="cmd_desktop-notificationToggle" label="&permAllow;"/>
+ <radio id="desktop-notification#2" command="cmd_desktop-notificationToggle" label="&permBlock;"/>
+ </radiogroup>
+ </hbox>
+ </vbox>
+ <vbox class="permission" id="permInstallRow">
+ <label class="permissionLabel" id="permInstallLabel"
+ value="&permInstall;" control="installRadioGroup"/>
+ <hbox id="permInstallBox" role="group" aria-labelledby="permInstallLabel">
+ <checkbox id="installDef" command="cmd_installDef" label="&permUseDefault;"/>
+ <spacer flex="1"/>
+ <radiogroup id="installRadioGroup" orient="horizontal">
+ <radio id="install#1" command="cmd_installToggle" label="&permAllow;"/>
+ <radio id="install#2" command="cmd_installToggle" label="&permBlock;"/>
+ </radiogroup>
+ </hbox>
+ </vbox>
+ <vbox class="permission" id="permGeoRow" >
+ <label class="permissionLabel" id="permGeoLabel"
+ value="&permGeo;" control="geoRadioGroup"/>
+ <hbox id="permGeoBox" role="group" aria-labelledby="permGeoLabel">
+ <checkbox id="geoDef" command="cmd_geoDef" label="&permAskAlways;"/>
+ <spacer flex="1"/>
+ <radiogroup id="geoRadioGroup" orient="horizontal">
+ <radio id="geo#1" command="cmd_geoToggle" label="&permAllow;"/>
+ <radio id="geo#2" command="cmd_geoToggle" label="&permBlock;"/>
+ </radiogroup>
+ </hbox>
+ </vbox>
+ <vbox class="permission" id="permIndexedDBRow">
+ <label class="permissionLabel" id="permIndexedDBLabel"
+ value="&permIndexedDB;" control="indexedDBRadioGroup"/>
+ <hbox id="permIndexedDBBox" role="group" aria-labelledby="permIndexedDBLabel">
+ <checkbox id="indexedDBDef" command="cmd_indexedDBDef" label="&permUseDefault;"/>
+ <spacer flex="1"/>
+ <radiogroup id="indexedDBRadioGroup" orient="horizontal">
+ <!-- Ask and Allow are purposefully reversed here! -->
+ <radio id="indexedDB#1" command="cmd_indexedDBToggle" label="&permAskAlways;"/>
+ <radio id="indexedDB#0" command="cmd_indexedDBToggle" label="&permAllow;"/>
+ <radio id="indexedDB#2" command="cmd_indexedDBToggle" label="&permBlock;"/>
+ </radiogroup>
+ </hbox>
+ <hbox id="permIndexedDBBox2">
+ <spacer flex="1"/>
+ <vbox id="permIndexedDBStatusBox" pack="center">
+ <label id="indexedDBStatus" control="indexedDBClear" hidden="true"/>
+ </vbox>
+ <button id="indexedDBClear" label="&permClearStorage;" hidden="true"
+ accesskey="&permClearStorage.accesskey;" onclick="onIndexedDBClear();"/>
+ </hbox>
+ </vbox>
+ <vbox class="permission" id="permPluginsRow">
+ <label class="permissionLabel" id="permPluginsLabel"
+ value="&permPlugins;" control="pluginsRadioGroup"/>
+ <hbox id="permPluginTemplate" role="group" aria-labelledby="permPluginsLabel" align="baseline">
+ <label class="permPluginTemplateLabel"/>
+ <spacer flex="1"/>
+ <radiogroup class="permPluginTemplateRadioGroup" orient="horizontal" command="cmd_pluginsToggle">
+ <radio class="permPluginTemplateRadioDefault" label="&permUseDefault;"/>
+ <radio class="permPluginTemplateRadioAsk" label="&permAskAlways;"/>
+ <radio class="permPluginTemplateRadioAllow" label="&permAllow;"/>
+ <radio class="permPluginTemplateRadioBlock" label="&permBlock;"/>
+ </radiogroup>
+ </hbox>
+ </vbox>
+ <vbox class="permission" id="permFullscreenRow">
+ <label class="permissionLabel" id="permFullscreenLabel"
+ value="&permFullscreen;" control="fullscreenRadioGroup"/>
+ <hbox id="permFullscreenBox" role="group" aria-labelledby="permFullscreenLabel">
+ <checkbox id="fullscreenDef" command="cmd_fullscreenDef" label="&permUseDefault;"/>
+ <spacer flex="1"/>
+ <radiogroup id="fullscreenRadioGroup" orient="horizontal">
+ <radio id="fullscreen#0" command="cmd_fullscreenToggle" label="&permAskAlways;"/>
+ <radio id="fullscreen#1" command="cmd_fullscreenToggle" label="&permAllow;"/>
+ <radio id="fullscreen#2" command="cmd_fullscreenToggle" label="&permBlock;"/>
+ </radiogroup>
+ </hbox>
+ </vbox>
+ <vbox class="permission" id="permPointerLockRow" >
+ <label class="permissionLabel" id="permPointerLockLabel"
+ value="&permPointerLock2;" control="pointerLockRadioGroup"/>
+ <hbox id="permPointerLockBox" role="group" aria-labelledby="permPointerLockLabel">
+ <checkbox id="pointerLockDef" command="cmd_pointerLockDef" label="&permAskAlways;"/>
+ <spacer flex="1"/>
+ <radiogroup id="pointerLockRadioGroup" orient="horizontal">
+ <radio id="pointerLock#1" command="cmd_pointerLockToggle" label="&permAllow;"/>
+ <radio id="pointerLock#2" command="cmd_pointerLockToggle" label="&permBlock;"/>
+ </radiogroup>
+ </hbox>
+ </vbox>
+ </vbox>
+ </vbox>
+
+ <!-- Security & Privacy -->
+ <vbox id="securityPanel">
+ <!-- Identity Section -->
+ <groupbox id="security-identity-groupbox" flex="1">
+ <caption id="security-identity" label="&securityView.identity.header;"/>
+ <grid id="security-identity-grid" flex="1">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows id="security-identity-rows">
+ <!-- Domain -->
+ <row id="security-identity-domain-row">
+ <label id="security-identity-domain-label"
+ class="fieldLabel"
+ value="&securityView.identity.domain;"
+ control="security-identity-domain-value"/>
+ <textbox id="security-identity-domain-value"
+ class="fieldValue" readonly="true"/>
+ </row>
+ <!-- Owner -->
+ <row id="security-identity-owner-row">
+ <label id="security-identity-owner-label"
+ class="fieldLabel"
+ value="&securityView.identity.owner;"
+ control="security-identity-owner-value"/>
+ <textbox id="security-identity-owner-value"
+ class="fieldValue" readonly="true"/>
+ </row>
+ <!-- Verifier -->
+ <row id="security-identity-verifier-row">
+ <label id="security-identity-verifier-label"
+ class="fieldLabel"
+ value="&securityView.identity.verifier;"
+ control="security-identity-verifier-value"/>
+ <textbox id="security-identity-verifier-value"
+ class="fieldValue" readonly="true" />
+ </row>
+ </rows>
+ </grid>
+ <spacer flex="1"/>
+ <!-- Cert button -->
+ <hbox id="security-view-cert-box" pack="end">
+ <button id="security-view-cert" label="&securityView.certView;"
+ accesskey="&securityView.accesskey;"
+ oncommand="security.viewCert();"/>
+ </hbox>
+ </groupbox>
+
+ <!-- Privacy & History section -->
+ <groupbox id="security-privacy-groupbox" flex="1">
+ <caption id="security-privacy" label="&securityView.privacy.header;" />
+ <grid id="security-privacy-grid">
+ <columns>
+ <column flex="1"/>
+ <column flex="1"/>
+ </columns>
+ <rows id="security-privacy-rows">
+ <!-- History -->
+ <row id="security-privacy-history-row">
+ <label id="security-privacy-history-label"
+ control="security-privacy-history-value"
+ class="fieldLabel">&securityView.privacy.history;</label>
+ <textbox id="security-privacy-history-value"
+ class="fieldValue"
+ value="&securityView.unknown;"
+ readonly="true"/>
+ </row>
+ <!-- Cookies -->
+ <row id="security-privacy-cookies-row">
+ <label id="security-privacy-cookies-label"
+ control="security-privacy-cookies-value"
+ class="fieldLabel">&securityView.privacy.cookies;</label>
+ <hbox id="security-privacy-cookies-box" align="center">
+ <textbox id="security-privacy-cookies-value"
+ class="fieldValue"
+ value="&securityView.unknown;"
+ flex="1"
+ readonly="true"/>
+ <button id="security-view-cookies"
+ label="&securityView.privacy.viewCookies;"
+ accesskey="&securityView.privacy.viewCookies.accessKey;"
+ oncommand="security.viewCookies();"/>
+ </hbox>
+ </row>
+ <!-- Passwords -->
+ <row id="security-privacy-passwords-row">
+ <label id="security-privacy-passwords-label"
+ control="security-privacy-passwords-value"
+ class="fieldLabel">&securityView.privacy.passwords;</label>
+ <hbox id="security-privacy-passwords-box" align="center">
+ <textbox id="security-privacy-passwords-value"
+ class="fieldValue"
+ value="&securityView.unknown;"
+ flex="1"
+ readonly="true"/>
+ <button id="security-view-password"
+ label="&securityView.privacy.viewPasswords;"
+ accesskey="&securityView.privacy.viewPasswords.accessKey;"
+ oncommand="security.viewPasswords();"/>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <!-- Technical Details section -->
+ <groupbox id="security-technical-groupbox" flex="1">
+ <caption id="security-technical" label="&securityView.technical.header;" />
+ <vbox id="security-technical-box" flex="1">
+ <label id="security-technical-shortform" class="fieldValue"/>
+ <description id="security-technical-longform1" class="fieldLabel"/>
+ <description id="security-technical-longform2" class="fieldLabel"/>
+ </vbox>
+ </groupbox>
+ </vbox>
+ <!-- Others added by overlay -->
+ </deck>
+
+#ifdef XP_MACOSX
+#include ../browserMountPoints.inc
+#endif
+
+</window>
diff --git a/browser/base/content/pageinfo/permissions.js b/browser/base/content/pageinfo/permissions.js
new file mode 100644
index 000000000..dc53ef449
--- /dev/null
+++ b/browser/base/content/pageinfo/permissions.js
@@ -0,0 +1,357 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const UNKNOWN = nsIPermissionManager.UNKNOWN_ACTION; // 0
+const ALLOW = nsIPermissionManager.ALLOW_ACTION; // 1
+const BLOCK = nsIPermissionManager.DENY_ACTION; // 2
+const SESSION = nsICookiePermission.ACCESS_SESSION; // 8
+
+const nsIQuotaManager = Components.interfaces.nsIQuotaManager;
+
+var gPermURI;
+var gPrefs;
+var gUsageRequest;
+
+var gPermObj = {
+ image: function getImageDefaultPermission()
+ {
+ if (gPrefs.getIntPref("permissions.default.image") == 2)
+ return BLOCK;
+ return ALLOW;
+ },
+ cookie: function getCookieDefaultPermission()
+ {
+ if (gPrefs.getIntPref("network.cookie.cookieBehavior") == 2)
+ return BLOCK;
+
+ if (gPrefs.getIntPref("network.cookie.lifetimePolicy") == 2)
+ return SESSION;
+ return ALLOW;
+ },
+ "desktop-notification": function getNotificationDefaultPermission()
+ {
+ return BLOCK;
+ },
+ popup: function getPopupDefaultPermission()
+ {
+ if (gPrefs.getBoolPref("dom.disable_open_during_load"))
+ return BLOCK;
+ return ALLOW;
+ },
+ install: function getInstallDefaultPermission()
+ {
+ try {
+ if (!gPrefs.getBoolPref("xpinstall.whitelist.required"))
+ return ALLOW;
+ }
+ catch (e) {
+ }
+ return BLOCK;
+ },
+ geo: function getGeoDefaultPermissions()
+ {
+ return BLOCK;
+ },
+ indexedDB: function getIndexedDBDefaultPermissions()
+ {
+ return UNKNOWN;
+ },
+ plugins: function getPluginsDefaultPermissions()
+ {
+ return UNKNOWN;
+ },
+ fullscreen: function getFullscreenDefaultPermissions()
+ {
+ return UNKNOWN;
+ },
+ pointerLock: function getPointerLockPermissions()
+ {
+ return BLOCK;
+ },
+};
+
+var permissionObserver = {
+ observe: function (aSubject, aTopic, aData)
+ {
+ if (aTopic == "perm-changed") {
+ var permission = aSubject.QueryInterface(Components.interfaces.nsIPermission);
+ if (permission.host == gPermURI.host) {
+ if (permission.type in gPermObj)
+ initRow(permission.type);
+ else if (permission.type.startsWith("plugin"))
+ setPluginsRadioState();
+ }
+ }
+ }
+};
+
+function onLoadPermission()
+{
+ gPrefs = Components.classes[PREFERENCES_CONTRACTID]
+ .getService(Components.interfaces.nsIPrefBranch);
+
+ var uri = gDocument.documentURIObject;
+ var permTab = document.getElementById("permTab");
+ if (/^https?$/.test(uri.scheme)) {
+ gPermURI = uri;
+ var hostText = document.getElementById("hostText");
+ hostText.value = gPermURI.host;
+
+ for (var i in gPermObj)
+ initRow(i);
+ var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ os.addObserver(permissionObserver, "perm-changed", false);
+ onUnloadRegistry.push(onUnloadPermission);
+ permTab.hidden = false;
+ }
+ else
+ permTab.hidden = true;
+}
+
+function onUnloadPermission()
+{
+ var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ os.removeObserver(permissionObserver, "perm-changed");
+
+ if (gUsageRequest) {
+ gUsageRequest.cancel();
+ gUsageRequest = null;
+ }
+}
+
+function initRow(aPartId)
+{
+ if (aPartId == "plugins") {
+ initPluginsRow();
+ return;
+ }
+
+ var permissionManager = Components.classes[PERMISSION_CONTRACTID]
+ .getService(nsIPermissionManager);
+
+ var checkbox = document.getElementById(aPartId + "Def");
+ var command = document.getElementById("cmd_" + aPartId + "Toggle");
+ // Geolocation and PointerLock permission consumers use testExactPermission, not testPermission.
+ var perm;
+ if (aPartId == "geo" || aPartId == "pointerLock")
+ perm = permissionManager.testExactPermission(gPermURI, aPartId);
+ else
+ perm = permissionManager.testPermission(gPermURI, aPartId);
+
+ if (perm) {
+ checkbox.checked = false;
+ command.removeAttribute("disabled");
+ }
+ else {
+ checkbox.checked = true;
+ command.setAttribute("disabled", "true");
+ perm = gPermObj[aPartId]();
+ }
+ setRadioState(aPartId, perm);
+
+ if (aPartId == "indexedDB") {
+ initIndexedDBRow();
+ }
+}
+
+function onCheckboxClick(aPartId)
+{
+ var permissionManager = Components.classes[PERMISSION_CONTRACTID]
+ .getService(nsIPermissionManager);
+
+ var command = document.getElementById("cmd_" + aPartId + "Toggle");
+ var checkbox = document.getElementById(aPartId + "Def");
+ if (checkbox.checked) {
+ permissionManager.remove(gPermURI.host, aPartId);
+ command.setAttribute("disabled", "true");
+ var perm = gPermObj[aPartId]();
+ setRadioState(aPartId, perm);
+ }
+ else {
+ onRadioClick(aPartId);
+ command.removeAttribute("disabled");
+ }
+}
+
+function onPluginRadioClick(aEvent) {
+ onRadioClick(aEvent.originalTarget.getAttribute("id").split('#')[0]);
+}
+
+function onRadioClick(aPartId)
+{
+ var permissionManager = Components.classes[PERMISSION_CONTRACTID]
+ .getService(nsIPermissionManager);
+
+ var radioGroup = document.getElementById(aPartId + "RadioGroup");
+ var id = radioGroup.selectedItem.id;
+ var permission = id.split('#')[1];
+ if (permission == UNKNOWN) {
+ permissionManager.remove(gPermURI.host, aPartId);
+ } else {
+ permissionManager.add(gPermURI, aPartId, permission);
+ }
+ if (aPartId == "indexedDB" &&
+ (permission == ALLOW || permission == BLOCK)) {
+ permissionManager.remove(gPermURI.host, "indexedDB-unlimited");
+ }
+}
+
+function setRadioState(aPartId, aValue)
+{
+ var radio = document.getElementById(aPartId + "#" + aValue);
+ radio.radioGroup.selectedItem = radio;
+}
+
+function initIndexedDBRow()
+{
+ var quotaManager = Components.classes["@mozilla.org/dom/quota/manager;1"]
+ .getService(nsIQuotaManager);
+ gUsageRequest =
+ quotaManager.getUsageForURI(gPermURI, onIndexedDBUsageCallback);
+
+ var status = document.getElementById("indexedDBStatus");
+ var button = document.getElementById("indexedDBClear");
+
+ status.value = "";
+ status.setAttribute("hidden", "true");
+ button.setAttribute("hidden", "true");
+}
+
+function onIndexedDBClear()
+{
+ Components.classes["@mozilla.org/dom/quota/manager;1"]
+ .getService(nsIQuotaManager)
+ .clearStoragesForURI(gPermURI);
+
+ var permissionManager = Components.classes[PERMISSION_CONTRACTID]
+ .getService(nsIPermissionManager);
+ permissionManager.remove(gPermURI.host, "indexedDB-unlimited");
+ initIndexedDBRow();
+}
+
+function onIndexedDBUsageCallback(uri, usage, fileUsage)
+{
+ if (!uri.equals(gPermURI)) {
+ throw new Error("Callback received for bad URI: " + uri);
+ }
+
+ if (usage) {
+ if (!("DownloadUtils" in window)) {
+ Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
+ }
+
+ var status = document.getElementById("indexedDBStatus");
+ var button = document.getElementById("indexedDBClear");
+
+ status.value =
+ gBundle.getFormattedString("indexedDBUsage",
+ DownloadUtils.convertByteUnits(usage));
+ status.removeAttribute("hidden");
+ button.removeAttribute("hidden");
+ }
+}
+
+// XXX copied this from browser-plugins.js - is there a way to share?
+function makeNicePluginName(aName) {
+ if (aName == "Shockwave Flash")
+ return "Adobe Flash";
+
+ // Clean up the plugin name by stripping off any trailing version numbers
+ // or "plugin". EG, "Foo Bar Plugin 1.23_02" --> "Foo Bar"
+ // Do this by first stripping the numbers, etc. off the end, and then
+ // removing "Plugin" (and then trimming to get rid of any whitespace).
+ // (Otherwise, something like "Java(TM) Plug-in 1.7.0_07" gets mangled)
+ let newName = aName.replace(/[\s\d\.\-\_\(\)]+$/, "").replace(/\bplug-?in\b/i, "").trim();
+ return newName;
+}
+
+function fillInPluginPermissionTemplate(aPluginName, aPermissionString) {
+ let permPluginTemplate = document.getElementById("permPluginTemplate").cloneNode(true);
+ permPluginTemplate.setAttribute("permString", aPermissionString);
+ let attrs = [
+ [ ".permPluginTemplateLabel", "value", aPluginName ],
+ [ ".permPluginTemplateRadioGroup", "id", aPermissionString + "RadioGroup" ],
+ [ ".permPluginTemplateRadioDefault", "id", aPermissionString + "#0" ],
+ [ ".permPluginTemplateRadioAsk", "id", aPermissionString + "#3" ],
+ [ ".permPluginTemplateRadioAllow", "id", aPermissionString + "#1" ],
+ [ ".permPluginTemplateRadioBlock", "id", aPermissionString + "#2" ]
+ ];
+
+ for (let attr of attrs) {
+ permPluginTemplate.querySelector(attr[0]).setAttribute(attr[1], attr[2]);
+ }
+
+ return permPluginTemplate;
+}
+
+function clearPluginPermissionTemplate() {
+ let permPluginTemplate = document.getElementById("permPluginTemplate");
+ permPluginTemplate.hidden = true;
+ permPluginTemplate.removeAttribute("permString");
+ document.querySelector(".permPluginTemplateLabel").removeAttribute("value");
+ document.querySelector(".permPluginTemplateRadioGroup").removeAttribute("id");
+ document.querySelector(".permPluginTemplateRadioAsk").removeAttribute("id");
+ document.querySelector(".permPluginTemplateRadioAllow").removeAttribute("id");
+ document.querySelector(".permPluginTemplateRadioBlock").removeAttribute("id");
+}
+
+function initPluginsRow() {
+ let vulnerableLabel = document.getElementById("browserBundle").getString("pluginActivateVulnerable.label");
+ let pluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+
+ let permissionMap = Map();
+
+ for (let plugin of pluginHost.getPluginTags()) {
+ if (plugin.disabled) {
+ continue;
+ }
+ for (let mimeType of plugin.getMimeTypes()) {
+ let permString = pluginHost.getPermissionStringForType(mimeType);
+ if (!permissionMap.has(permString)) {
+ var name = makeNicePluginName(plugin.name);
+ if (permString.startsWith("plugin-vulnerable:")) {
+ name += " \u2014 " + vulnerableLabel;
+ }
+ permissionMap.set(permString, name);
+ }
+ }
+ }
+
+ let entries = [{name: item[1], permission: item[0]} for (item of permissionMap)];
+ entries.sort(function(a, b) {
+ return a.name < b.name ? -1 : (a.name == b.name ? 0 : 1);
+ });
+
+ let permissionEntries = [
+ fillInPluginPermissionTemplate(p.name, p.permission) for (p of entries)
+ ];
+
+ let permPluginsRow = document.getElementById("permPluginsRow");
+ clearPluginPermissionTemplate();
+ if (permissionEntries.length < 1) {
+ permPluginsRow.hidden = true;
+ return;
+ }
+
+ for (let permissionEntry of permissionEntries) {
+ permPluginsRow.appendChild(permissionEntry);
+ }
+
+ setPluginsRadioState();
+}
+
+function setPluginsRadioState() {
+ var permissionManager = Components.classes[PERMISSION_CONTRACTID]
+ .getService(nsIPermissionManager);
+ let box = document.getElementById("permPluginsRow");
+ for (let permissionEntry of box.childNodes) {
+ if (permissionEntry.hasAttribute("permString")) {
+ let permString = permissionEntry.getAttribute("permString");
+ let permission = permissionManager.testPermission(gPermURI, permString);
+ setRadioState(permString, permission);
+ }
+ }
+}
diff --git a/browser/base/content/pageinfo/security.js b/browser/base/content/pageinfo/security.js
new file mode 100644
index 000000000..a9c4dd496
--- /dev/null
+++ b/browser/base/content/pageinfo/security.js
@@ -0,0 +1,345 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var security = {
+ // Display the server certificate (static)
+ viewCert : function () {
+ var cert = security._cert;
+ viewCertHelper(window, cert);
+ },
+
+ _getSecurityInfo : function() {
+ const nsIX509Cert = Components.interfaces.nsIX509Cert;
+ const nsIX509CertDB = Components.interfaces.nsIX509CertDB;
+ const nsX509CertDB = "@mozilla.org/security/x509certdb;1";
+ const nsISSLStatusProvider = Components.interfaces.nsISSLStatusProvider;
+ const nsISSLStatus = Components.interfaces.nsISSLStatus;
+
+ // We don't have separate info for a frame, return null until further notice
+ // (see bug 138479)
+ if (gWindow != gWindow.top)
+ return null;
+
+ var hName = null;
+ try {
+ hName = gWindow.location.host;
+ }
+ catch (exception) { }
+
+ var ui = security._getSecurityUI();
+ if (!ui)
+ return null;
+
+ var isBroken =
+ (ui.state & Components.interfaces.nsIWebProgressListener.STATE_IS_BROKEN);
+ var isInsecure =
+ (ui.state & Components.interfaces.nsIWebProgressListener.STATE_IS_INSECURE);
+ var isEV =
+ (ui.state & Components.interfaces.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL);
+ ui.QueryInterface(nsISSLStatusProvider);
+ var status = ui.SSLStatus;
+
+ if (!isInsecure && status) {
+ status.QueryInterface(nsISSLStatus);
+ var cert = status.serverCert;
+ var issuerName =
+ this.mapIssuerOrganization(cert.issuerOrganization) || cert.issuerName;
+
+ var retval = {
+ hostName : hName,
+ cAName : issuerName,
+ encryptionAlgorithm : undefined,
+ encryptionStrength : undefined,
+ isBroken : isBroken,
+ isEV : isEV,
+ cert : cert,
+ fullLocation : gWindow.location
+ };
+
+ try {
+ retval.encryptionAlgorithm = status.cipherName;
+ retval.encryptionStrength = status.secretKeyLength;
+ }
+ catch (e) {
+ }
+
+ return retval;
+ } else {
+ return {
+ hostName : hName,
+ cAName : "",
+ encryptionAlgorithm : "",
+ encryptionStrength : 0,
+ isBroken : isBroken,
+ isEV : isEV,
+ cert : null,
+ fullLocation : gWindow.location
+ };
+ }
+ },
+
+ // Find the secureBrowserUI object (if present)
+ _getSecurityUI : function() {
+ if (window.opener.gBrowser)
+ return window.opener.gBrowser.securityUI;
+ return null;
+ },
+
+ // Interface for mapping a certificate issuer organization to
+ // the value to be displayed.
+ // Bug 82017 - this implementation should be moved to pipnss C++ code
+ mapIssuerOrganization: function(name) {
+ if (!name) return null;
+
+ if (name == "RSA Data Security, Inc.") return "Verisign, Inc.";
+
+ // No mapping required
+ return name;
+ },
+
+ /**
+ * Open the cookie manager window
+ */
+ viewCookies : function()
+ {
+ var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Components.interfaces.nsIWindowMediator);
+ var win = wm.getMostRecentWindow("Browser:Cookies");
+ var eTLDService = Components.classes["@mozilla.org/network/effective-tld-service;1"].
+ getService(Components.interfaces.nsIEffectiveTLDService);
+
+ var eTLD;
+ var uri = gDocument.documentURIObject;
+ try {
+ eTLD = eTLDService.getBaseDomain(uri);
+ }
+ catch (e) {
+ // getBaseDomain will fail if the host is an IP address or is empty
+ eTLD = uri.asciiHost;
+ }
+
+ if (win) {
+ win.gCookiesWindow.setFilter(eTLD);
+ win.focus();
+ }
+ else
+ window.openDialog("chrome://browser/content/preferences/cookies.xul",
+ "Browser:Cookies", "", {filterString : eTLD});
+ },
+
+ /**
+ * Open the login manager window
+ */
+ viewPasswords : function()
+ {
+ var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Components.interfaces.nsIWindowMediator);
+ var win = wm.getMostRecentWindow("Toolkit:PasswordManager");
+ if (win) {
+ win.setFilter(this._getSecurityInfo().hostName);
+ win.focus();
+ }
+ else
+ window.openDialog("chrome://passwordmgr/content/passwordManager.xul",
+ "Toolkit:PasswordManager", "",
+ {filterString : this._getSecurityInfo().hostName});
+ },
+
+ _cert : null
+};
+
+function securityOnLoad() {
+ var info = security._getSecurityInfo();
+ if (!info) {
+ document.getElementById("securityTab").hidden = true;
+ document.getElementById("securityBox").collapsed = true;
+ return;
+ }
+ else {
+ document.getElementById("securityTab").hidden = false;
+ document.getElementById("securityBox").collapsed = false;
+ }
+
+ const pageInfoBundle = document.getElementById("pageinfobundle");
+
+ /* Set Identity section text */
+ setText("security-identity-domain-value", info.hostName);
+
+ var owner, verifier, generalPageIdentityString;
+ if (info.cert && !info.isBroken) {
+ // Try to pull out meaningful values. Technically these fields are optional
+ // so we'll employ fallbacks where appropriate. The EV spec states that Org
+ // fields must be specified for subject and issuer so that case is simpler.
+ if (info.isEV) {
+ owner = info.cert.organization;
+ verifier = security.mapIssuerOrganization(info.cAName);
+ generalPageIdentityString = pageInfoBundle.getFormattedString("generalSiteIdentity",
+ [owner, verifier]);
+ }
+ else {
+ // Technically, a non-EV cert might specify an owner in the O field or not,
+ // depending on the CA's issuing policies. However we don't have any programmatic
+ // way to tell those apart, and no policy way to establish which organization
+ // vetting standards are good enough (that's what EV is for) so we default to
+ // treating these certs as domain-validated only.
+ owner = pageInfoBundle.getString("securityNoOwner");
+ verifier = security.mapIssuerOrganization(info.cAName ||
+ info.cert.issuerCommonName ||
+ info.cert.issuerName);
+ generalPageIdentityString = owner;
+ }
+ }
+ else {
+ // We don't have valid identity credentials.
+ owner = pageInfoBundle.getString("securityNoOwner");
+ verifier = pageInfoBundle.getString("notset");
+ generalPageIdentityString = owner;
+ }
+
+ setText("security-identity-owner-value", owner);
+ setText("security-identity-verifier-value", verifier);
+ setText("general-security-identity", generalPageIdentityString);
+
+ /* Manage the View Cert button*/
+ var viewCert = document.getElementById("security-view-cert");
+ if (info.cert) {
+ security._cert = info.cert;
+ viewCert.collapsed = false;
+ }
+ else
+ viewCert.collapsed = true;
+
+ /* Set Privacy & History section text */
+ var yesStr = pageInfoBundle.getString("yes");
+ var noStr = pageInfoBundle.getString("no");
+
+ var uri = gDocument.documentURIObject;
+ setText("security-privacy-cookies-value",
+ hostHasCookies(uri) ? yesStr : noStr);
+ setText("security-privacy-passwords-value",
+ realmHasPasswords(uri) ? yesStr : noStr);
+
+ var visitCount = previousVisitCount(info.hostName);
+ if(visitCount > 1) {
+ setText("security-privacy-history-value",
+ pageInfoBundle.getFormattedString("securityNVisits", [visitCount.toLocaleString()]));
+ }
+ else if (visitCount == 1) {
+ setText("security-privacy-history-value",
+ pageInfoBundle.getString("securityOneVisit"));
+ }
+ else {
+ setText("security-privacy-history-value", noStr);
+ }
+
+ /* Set the Technical Detail section messages */
+ const pkiBundle = document.getElementById("pkiBundle");
+ var hdr;
+ var msg1;
+ var msg2;
+
+ if (info.isBroken) {
+ hdr = pkiBundle.getString("pageInfo_MixedContent");
+ msg1 = pkiBundle.getString("pageInfo_Privacy_Mixed1");
+ msg2 = pkiBundle.getString("pageInfo_Privacy_None2");
+ }
+ else if (info.encryptionStrength >= 90) {
+ hdr = pkiBundle.getFormattedString("pageInfo_StrongEncryptionWithBits",
+ [info.encryptionAlgorithm, info.encryptionStrength + ""]);
+ msg1 = pkiBundle.getString("pageInfo_Privacy_Strong1");
+ msg2 = pkiBundle.getString("pageInfo_Privacy_Strong2");
+ security._cert = info.cert;
+ }
+ else if (info.encryptionStrength > 0) {
+ hdr = pkiBundle.getFormattedString("pageInfo_WeakEncryptionWithBits",
+ [info.encryptionAlgorithm, info.encryptionStrength + ""]);
+ msg1 = pkiBundle.getFormattedString("pageInfo_Privacy_Weak1", [info.hostName]);
+ msg2 = pkiBundle.getString("pageInfo_Privacy_Weak2");
+ }
+ else {
+ hdr = pkiBundle.getString("pageInfo_NoEncryption");
+ if (info.hostName != null)
+ msg1 = pkiBundle.getFormattedString("pageInfo_Privacy_None1", [info.hostName]);
+ else
+ msg1 = pkiBundle.getString("pageInfo_Privacy_None3");
+ msg2 = pkiBundle.getString("pageInfo_Privacy_None2");
+ }
+ setText("security-technical-shortform", hdr);
+ setText("security-technical-longform1", msg1);
+ setText("security-technical-longform2", msg2);
+ setText("general-security-privacy", hdr);
+}
+
+function setText(id, value)
+{
+ var element = document.getElementById(id);
+ if (!element)
+ return;
+ if (element.localName == "textbox" || element.localName == "label")
+ element.value = value;
+ else {
+ if (element.hasChildNodes())
+ element.removeChild(element.firstChild);
+ var textNode = document.createTextNode(value);
+ element.appendChild(textNode);
+ }
+}
+
+function viewCertHelper(parent, cert)
+{
+ if (!cert)
+ return;
+
+ var cd = Components.classes[CERTIFICATEDIALOGS_CONTRACTID].getService(nsICertificateDialogs);
+ cd.viewCert(parent, cert);
+}
+
+/**
+ * Return true iff we have cookies for uri
+ */
+function hostHasCookies(uri) {
+ var cookieManager = Components.classes["@mozilla.org/cookiemanager;1"]
+ .getService(Components.interfaces.nsICookieManager2);
+
+ return cookieManager.countCookiesFromHost(uri.asciiHost) > 0;
+}
+
+/**
+ * Return true iff realm (proto://host:port) (extracted from uri) has
+ * saved passwords
+ */
+function realmHasPasswords(uri) {
+ var passwordManager = Components.classes["@mozilla.org/login-manager;1"]
+ .getService(Components.interfaces.nsILoginManager);
+ return passwordManager.countLogins(uri.prePath, "", "") > 0;
+}
+
+/**
+ * Return the number of previous visits recorded for host before today.
+ *
+ * @param host - the domain name to look for in history
+ */
+function previousVisitCount(host, endTimeReference) {
+ if (!host)
+ return false;
+
+ var historyService = Components.classes["@mozilla.org/browser/nav-history-service;1"]
+ .getService(Components.interfaces.nsINavHistoryService);
+
+ var options = historyService.getNewQueryOptions();
+ options.resultType = options.RESULTS_AS_VISIT;
+
+ // Search for visits to this host before today
+ var query = historyService.getNewQuery();
+ query.endTimeReference = query.TIME_RELATIVE_TODAY;
+ query.endTime = 0;
+ query.domain = host;
+
+ var result = historyService.executeQuery(query, options);
+ result.root.containerOpen = true;
+ var cc = result.root.childCount;
+ result.root.containerOpen = false;
+ return cc;
+}
diff --git a/browser/base/content/popup-notifications.inc b/browser/base/content/popup-notifications.inc
new file mode 100644
index 000000000..172b5407a
--- /dev/null
+++ b/browser/base/content/popup-notifications.inc
@@ -0,0 +1,108 @@
+# to be included inside a popupset element
+
+ <panel id="notification-popup"
+ type="arrow"
+ footertype="promobox"
+ position="after_start"
+ hidden="true"
+ orient="vertical"
+ role="alert"/>
+
+ <!-- Popup for site identity information -->
+ <panel id="identity-popup"
+ type="arrow"
+ hidden="true"
+ noautofocus="true"
+ consumeoutsideclicks="true"
+ onpopupshown="gIdentityHandler.onPopupShown(event);"
+ level="top">
+ <hbox id="identity-popup-container" align="top">
+ <image id="identity-popup-icon"/>
+ <vbox id="identity-popup-content-box">
+ <label id="identity-popup-connectedToLabel"
+ class="identity-popup-label"
+ value="&identity.connectedTo;"/>
+ <label id="identity-popup-connectedToLabel2"
+ class="identity-popup-label"
+ value="&identity.unverifiedsite2;"/>
+ <description id="identity-popup-content-host"
+ class="identity-popup-description"/>
+ <label id="identity-popup-runByLabel"
+ class="identity-popup-label"
+ value="&identity.runBy;"/>
+ <description id="identity-popup-content-owner"
+ class="identity-popup-description"/>
+ <description id="identity-popup-content-supplemental"
+ class="identity-popup-description"/>
+ <description id="identity-popup-content-verifier"
+ class="identity-popup-description"/>
+ <hbox id="identity-popup-encryption" flex="1">
+ <vbox>
+ <image id="identity-popup-encryption-icon"/>
+ </vbox>
+ <description id="identity-popup-encryption-label" flex="1"
+ class="identity-popup-description"/>
+ </hbox>
+ <!-- Footer button to open security page info -->
+ <hbox id="identity-popup-button-container" pack="end">
+ <button id="identity-popup-more-info-button"
+ label="&identity.moreInfoLinkText;"
+ onblur="gIdentityHandler.hideIdentityPopup();"
+ oncommand="gIdentityHandler.handleMoreInfoClick(event);"/>
+ </hbox>
+ </vbox>
+ </hbox>
+ </panel>
+
+
+ <popupnotification id="webRTC-shareDevices-notification" hidden="true">
+ <popupnotificationcontent id="webRTC-selectCamera" orient="vertical">
+ <separator class="thin"/>
+ <label value="&getUserMedia.selectCamera.label;"
+ accesskey="&getUserMedia.selectCamera.accesskey;"
+ control="webRTC-selectCamera-menulist"/>
+ <menulist id="webRTC-selectCamera-menulist">
+ <menupopup id="webRTC-selectCamera-menupopup"/>
+ </menulist>
+ </popupnotificationcontent>
+ <popupnotificationcontent id="webRTC-selectMicrophone" orient="vertical">
+ <separator class="thin"/>
+ <label value="&getUserMedia.selectMicrophone.label;"
+ accesskey="&getUserMedia.selectMicrophone.accesskey;"
+ control="webRTC-selectMicrophone-menulist"/>
+ <menulist id="webRTC-selectMicrophone-menulist">
+ <menupopup id="webRTC-selectMicrophone-menupopup"/>
+ </menulist>
+ </popupnotificationcontent>
+ </popupnotification>
+
+ <popupnotification id="geolocation-notification" hidden="true">
+ <popupnotificationcontent orient="vertical" align="start">
+ <separator class="thin"/>
+ <label id="geolocation-learnmore-link" class="text-link"/>
+ </popupnotificationcontent>
+ </popupnotification>
+
+ <popupnotification id="servicesInstall-notification" hidden="true">
+ <popupnotificationcontent orient="vertical" align="start">
+ <separator class="thin"/>
+ <label id="servicesInstall-learnmore-link" class="text-link"/>
+ </popupnotificationcontent>
+ </popupnotification>
+
+ <popupnotification id="pointerLock-notification" hidden="true">
+ <popupnotificationcontent orient="vertical" align="start">
+ <separator class="thin"/>
+ <label id="pointerLock-cancel" value="&pointerLock.notification.message;"/>
+ </popupnotificationcontent>
+ </popupnotification>
+
+ <popupnotification id="mixed-content-blocked-notification" hidden="true">
+ <popupnotificationcontent orient="vertical" align="start">
+ <separator/>
+ <description id="mixed-content-blocked-moreinfo">&mixedContentBlocked.moreinfo;</description>
+ <separator/>
+ <label id="mixed-content-blocked-helplink" class="text-link"
+ value="&mixedContentBlocked.helplink;"/>
+ </popupnotificationcontent>
+ </popupnotification>
diff --git a/browser/base/content/report-phishing-overlay.xul b/browser/base/content/report-phishing-overlay.xul
new file mode 100644
index 000000000..76baf01da
--- /dev/null
+++ b/browser/base/content/report-phishing-overlay.xul
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE overlay [
+<!ENTITY % reportphishDTD SYSTEM "chrome://browser/locale/safebrowsing/report-phishing.dtd">
+%reportphishDTD;
+<!ENTITY % safebrowsingDTD SYSTEM "chrome://browser/locale/safebrowsing/phishing-afterload-warning-message.dtd">
+%safebrowsingDTD;
+]>
+
+<overlay id="reportPhishingMenuOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <broadcasterset id="mainBroadcasterSet">
+ <broadcaster id="reportPhishingBroadcaster" disabled="true"/>
+ <broadcaster id="reportPhishingErrorBroadcaster" disabled="true"/>
+ </broadcasterset>
+ <menupopup id="menu_HelpPopup">
+ <menuitem id="menu_HelpPopup_reportPhishingtoolmenu"
+ label="&reportPhishSiteMenu.title2;"
+ accesskey="&reportPhishSiteMenu.accesskey;"
+ insertbefore="aboutSeparator"
+ observes="reportPhishingBroadcaster"
+ oncommand="openUILink(gSafeBrowsing.getReportURL('Phish'), event);"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="menu_HelpPopup_reportPhishingErrortoolmenu"
+ label="&safeb.palm.notforgery.label2;"
+ accesskey="&reportPhishSiteMenu.accesskey;"
+ insertbefore="aboutSeparator"
+ observes="reportPhishingErrorBroadcaster"
+ oncommand="openUILinkIn(gSafeBrowsing.getReportURL('Error'), 'tab');"
+ onclick="checkForMiddleClick(this, event);"/>
+ </menupopup>
+</overlay>
diff --git a/browser/base/content/safeMode.css b/browser/base/content/safeMode.css
new file mode 100644
index 000000000..4f093a452
--- /dev/null
+++ b/browser/base/content/safeMode.css
@@ -0,0 +1,8 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#resetProfileFooter {
+ font-weight: bold;
+}
+
diff --git a/browser/base/content/safeMode.js b/browser/base/content/safeMode.js
new file mode 100644
index 000000000..b01925b75
--- /dev/null
+++ b/browser/base/content/safeMode.js
@@ -0,0 +1,152 @@
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is
+# Mike Connor.
+# Portions created by the Initial Developer are Copyright (C) 2005
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Mike Connor <mconnor@steelgryphon.com>
+# Asaf Romano <mano@mozilla.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+Components.utils.import("resource://gre/modules/AddonManager.jsm");
+
+function restartApp() {
+ var appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"]
+ .getService(Components.interfaces.nsIAppStartup);
+ appStartup.quit(appStartup.eForceQuit | appStartup.eRestart);
+}
+
+function clearAllPrefs() {
+ var prefService = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefService);
+ prefService.resetUserPrefs();
+
+ // Remove the pref-overrides dir, if it exists
+ try {
+ var fileLocator = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIProperties);
+ const NS_APP_PREFS_OVERRIDE_DIR = "PrefDOverride";
+ var prefOverridesDir = fileLocator.get(NS_APP_PREFS_OVERRIDE_DIR,
+ Components.interfaces.nsIFile);
+ prefOverridesDir.remove(true);
+ } catch (ex) {
+ Components.utils.reportError(ex);
+ }
+}
+
+function restoreDefaultBookmarks() {
+ var prefBranch = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ prefBranch.setBoolPref("browser.bookmarks.restore_default_bookmarks", true);
+}
+
+function deleteLocalstore() {
+ const nsIDirectoryServiceContractID = "@mozilla.org/file/directory_service;1";
+ const nsIProperties = Components.interfaces.nsIProperties;
+ var directoryService = Components.classes[nsIDirectoryServiceContractID]
+ .getService(nsIProperties);
+ var localstoreFile = directoryService.get("LStoreS", Components.interfaces.nsIFile);
+ try {
+ localstoreFile.remove(false);
+ } catch(e) {
+ Components.utils.reportError(e);
+ }
+}
+
+function disableAddons() {
+ AddonManager.getAllAddons(function(aAddons) {
+ aAddons.forEach(function(aAddon) {
+ if (aAddon.type == "theme") {
+ // Setting userDisabled to false on the default theme activates it,
+ // disables all other themes and deactivates the applied persona, if
+ // any.
+ const DEFAULT_THEME_ID = "{972ce4c6-7e08-4474-a285-3208198ce6fd}";
+ if (aAddon.id == DEFAULT_THEME_ID)
+ aAddon.userDisabled = false;
+ }
+ else {
+ aAddon.userDisabled = true;
+ }
+ });
+
+ restartApp();
+ });
+}
+
+function restoreDefaultSearchEngines() {
+ var searchService = Components.classes["@mozilla.org/browser/search-service;1"]
+ .getService(Components.interfaces.nsIBrowserSearchService);
+
+ searchService.restoreDefaultEngines();
+}
+
+function onOK() {
+ try {
+ if (document.getElementById("resetUserPrefs").checked)
+ clearAllPrefs();
+ if (document.getElementById("deleteBookmarks").checked)
+ restoreDefaultBookmarks();
+ if (document.getElementById("resetToolbars").checked)
+ deleteLocalstore();
+ if (document.getElementById("restoreSearch").checked)
+ restoreDefaultSearchEngines();
+ if (document.getElementById("disableAddons").checked) {
+ disableAddons();
+ // disableAddons will asynchronously restart the application
+ return false;
+ }
+ } catch(e) {
+ }
+
+ restartApp();
+ return false;
+}
+
+function onCancel() {
+ var appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"]
+ .getService(Components.interfaces.nsIAppStartup);
+ appStartup.quit(appStartup.eForceQuit);
+}
+
+function onLoad() {
+ document.getElementById("tasks")
+ .addEventListener("CheckboxStateChange", UpdateOKButtonState, false);
+}
+
+function UpdateOKButtonState() {
+ document.documentElement.getButton("accept").disabled =
+ !document.getElementById("resetUserPrefs").checked &&
+ !document.getElementById("deleteBookmarks").checked &&
+ !document.getElementById("resetToolbars").checked &&
+ !document.getElementById("disableAddons").checked &&
+ !document.getElementById("restoreSearch").checked;
+}
diff --git a/browser/base/content/safeMode.xul b/browser/base/content/safeMode.xul
new file mode 100644
index 000000000..846906a25
--- /dev/null
+++ b/browser/base/content/safeMode.xul
@@ -0,0 +1,87 @@
+<?xml version="1.0"?>
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is
+# Mike Connor.
+# Portions created by the Initial Developer are Copyright (C) 2005
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Mike Connor <mconnor@steelgryphon.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+<!DOCTYPE prefwindow [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % safeModeDTD SYSTEM "chrome://browser/locale/safeMode.dtd" >
+%safeModeDTD;
+<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd" >
+%browserDTD;
+]>
+
+<?xml-stylesheet href="chrome://global/skin/"?>
+
+<dialog id="safeModeDialog"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&safeModeDialog.title;"
+ buttons="accept,cancel,extra1"
+ buttonlabelaccept="&changeAndRestartButton.label;"
+#ifdef XP_WIN
+ buttonlabelcancel="&quitApplicationCmdWin.label;"
+#else
+ buttonlabelcancel="&quitApplicationCmd.label;"
+#endif
+ buttonlabelextra1="&continueButton.label;"
+ width="&window.width;"
+ ondialogaccept="return onOK()"
+ ondialogcancel="onCancel()"
+ ondialogextra1="window.close()"
+ onload="onLoad();"
+ buttondisabledaccept="true">
+
+ <script type="application/javascript" src="chrome://browser/content/safeMode.js"/>
+
+ <stringbundle id="preferencesBundle" src="chrome://browser/locale/preferences/preferences.properties"/>
+
+ <description>&safeModeDescription.label;</description>
+
+ <separator class="thin"/>
+
+ <label value="&safeModeDescription2.label;"/>
+ <vbox id="tasks">
+ <checkbox id="disableAddons" label="&disableAddons.label;" accesskey="&disableAddons.accesskey;"/>
+ <checkbox id="resetToolbars" label="&resetToolbars.label;" accesskey="&resetToolbars.accesskey;"/>
+ <checkbox id="deleteBookmarks" label="&deleteBookmarks.label;" accesskey="&deleteBookmarks.accesskey;"/>
+ <checkbox id="resetUserPrefs" label="&resetUserPrefs.label;" accesskey="&resetUserPrefs.accesskey;"/>
+ <checkbox id="restoreSearch" label="&restoreSearch.label;" accesskey="&restoreSearch.accesskey;"/>
+ </vbox>
+
+ <separator class="thin"/>
+</dialog>
diff --git a/browser/base/content/sanitize.js b/browser/base/content/sanitize.js
new file mode 100644
index 000000000..42d7514cc
--- /dev/null
+++ b/browser/base/content/sanitize.js
@@ -0,0 +1,518 @@
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
+ "resource://gre/modules/FormHistory.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/commonjs/sdk/core/promise.js");
+
+function Sanitizer() {}
+Sanitizer.prototype = {
+ // warning to the caller: this one may raise an exception (e.g. bug #265028)
+ clearItem: function (aItemName)
+ {
+ if (this.items[aItemName].canClear)
+ this.items[aItemName].clear();
+ },
+
+ canClearItem: function (aItemName, aCallback, aArg)
+ {
+ let canClear = this.items[aItemName].canClear;
+ if (typeof canClear == "function") {
+ canClear(aCallback, aArg);
+ return false;
+ }
+
+ aCallback(aItemName, canClear, aArg);
+ return canClear;
+ },
+
+ prefDomain: "",
+
+ getNameFromPreference: function (aPreferenceName)
+ {
+ return aPreferenceName.substr(this.prefDomain.length);
+ },
+
+ /**
+ * Deletes privacy sensitive data in a batch, according to user preferences.
+ * Returns a promise which is resolved if no errors occurred. If an error
+ * occurs, a message is reported to the console and all other items are still
+ * cleared before the promise is finally rejected.
+ */
+ sanitize: function ()
+ {
+ var deferred = Promise.defer();
+ var psvc = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefService);
+ var branch = psvc.getBranch(this.prefDomain);
+ var seenError = false;
+
+ // Cache the range of times to clear
+ if (this.ignoreTimespan)
+ var range = null; // If we ignore timespan, clear everything
+ else
+ range = this.range || Sanitizer.getClearRange();
+
+ let itemCount = Object.keys(this.items).length;
+ let onItemComplete = function() {
+ if (!--itemCount) {
+ seenError ? deferred.reject() : deferred.resolve();
+ }
+ };
+ for (var itemName in this.items) {
+ let item = this.items[itemName];
+ item.range = range;
+ if ("clear" in item && branch.getBoolPref(itemName)) {
+ let clearCallback = (itemName, aCanClear) => {
+ // Some of these clear() may raise exceptions (see bug #265028)
+ // to sanitize as much as possible, we catch and store them,
+ // rather than fail fast.
+ // Callers should check returned errors and give user feedback
+ // about items that could not be sanitized
+ let item = this.items[itemName];
+ try {
+ if (aCanClear)
+ item.clear();
+ } catch(er) {
+ seenError = true;
+ Cu.reportError("Error sanitizing " + itemName + ": " + er + "\n");
+ }
+ onItemComplete();
+ };
+ this.canClearItem(itemName, clearCallback);
+ } else {
+ onItemComplete();
+ }
+ }
+
+ return deferred.promise;
+ },
+
+ // Time span only makes sense in certain cases. Consumers who want
+ // to only clear some private data can opt in by setting this to false,
+ // and can optionally specify a specific range. If timespan is not ignored,
+ // and range is not set, sanitize() will use the value of the timespan
+ // pref to determine a range
+ ignoreTimespan : true,
+ range : null,
+
+ items: {
+ cache: {
+ clear: function ()
+ {
+ var cacheService = Cc["@mozilla.org/network/cache-service;1"].
+ getService(Ci.nsICacheService);
+ try {
+ // Cache doesn't consult timespan, nor does it have the
+ // facility for timespan-based eviction. Wipe it.
+ cacheService.evictEntries(Ci.nsICache.STORE_ANYWHERE);
+ } catch(er) {}
+
+ var imageCache = Cc["@mozilla.org/image/tools;1"].
+ getService(Ci.imgITools).getImgCacheForDocument(null);
+ try {
+ imageCache.clearCache(false); // true=chrome, false=content
+ } catch(er) {}
+ },
+
+ get canClear()
+ {
+ return true;
+ }
+ },
+
+ cookies: {
+ clear: function ()
+ {
+ var cookieMgr = Components.classes["@mozilla.org/cookiemanager;1"]
+ .getService(Ci.nsICookieManager);
+ if (this.range) {
+ // Iterate through the cookies and delete any created after our cutoff.
+ var cookiesEnum = cookieMgr.enumerator;
+ while (cookiesEnum.hasMoreElements()) {
+ var cookie = cookiesEnum.getNext().QueryInterface(Ci.nsICookie2);
+
+ if (cookie.creationTime > this.range[0])
+ // This cookie was created after our cutoff, clear it
+ cookieMgr.remove(cookie.host, cookie.name, cookie.path, false);
+ }
+ }
+ else {
+ // Remove everything
+ cookieMgr.removeAll();
+ }
+
+ // Clear plugin data.
+ const phInterface = Ci.nsIPluginHost;
+ const FLAG_CLEAR_ALL = phInterface.FLAG_CLEAR_ALL;
+ let ph = Cc["@mozilla.org/plugin/host;1"].getService(phInterface);
+
+ // Determine age range in seconds. (-1 means clear all.) We don't know
+ // that this.range[1] is actually now, so we compute age range based
+ // on the lower bound. If this.range results in a negative age, do
+ // nothing.
+ let age = this.range ? (Date.now() / 1000 - this.range[0] / 1000000)
+ : -1;
+ if (!this.range || age >= 0) {
+ let tags = ph.getPluginTags();
+ for (let i = 0; i < tags.length; i++) {
+ try {
+ ph.clearSiteData(tags[i], null, FLAG_CLEAR_ALL, age);
+ } catch (e) {
+ // If the plugin doesn't support clearing by age, clear everything.
+ if (e.result == Components.results.
+ NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED) {
+ try {
+ ph.clearSiteData(tags[i], null, FLAG_CLEAR_ALL, -1);
+ } catch (e) {
+ // Ignore errors from the plugin
+ }
+ }
+ }
+ }
+ }
+ },
+
+ get canClear()
+ {
+ return true;
+ }
+ },
+
+ offlineApps: {
+ clear: function ()
+ {
+ Components.utils.import("resource:///modules/offlineAppCache.jsm");
+ OfflineAppCacheHelper.clear();
+ },
+
+ get canClear()
+ {
+ return true;
+ }
+ },
+
+ history: {
+ clear: function ()
+ {
+ if (this.range)
+ PlacesUtils.history.removeVisitsByTimeframe(this.range[0], this.range[1]);
+ else
+ PlacesUtils.history.removeAllPages();
+
+ try {
+ var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ os.notifyObservers(null, "browser:purge-session-history", "");
+ }
+ catch (e) { }
+
+ // Clear last URL of the Open Web Location dialog
+ var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ try {
+ prefs.clearUserPref("general.open_location.last_url");
+ }
+ catch (e) { }
+ },
+
+ get canClear()
+ {
+ // bug 347231: Always allow clearing history due to dependencies on
+ // the browser:purge-session-history notification. (like error console)
+ return true;
+ }
+ },
+
+ formdata: {
+ clear: function ()
+ {
+ // Clear undo history of all searchBars
+ var windowManager = Components.classes['@mozilla.org/appshell/window-mediator;1']
+ .getService(Components.interfaces.nsIWindowMediator);
+ var windows = windowManager.getEnumerator("navigator:browser");
+ while (windows.hasMoreElements()) {
+ let currentDocument = windows.getNext().document;
+ let searchBar = currentDocument.getElementById("searchbar");
+ if (searchBar)
+ searchBar.textbox.reset();
+ let findBar = currentDocument.getElementById("FindToolbar");
+ if (findBar)
+ findBar.clear();
+ }
+
+ let change = { op: "remove" };
+ if (this.range) {
+ [ change.firstUsedStart, change.firstUsedEnd ] = this.range;
+ }
+ FormHistory.update(change);
+ },
+
+ canClear : function(aCallback, aArg)
+ {
+ var windowManager = Components.classes['@mozilla.org/appshell/window-mediator;1']
+ .getService(Components.interfaces.nsIWindowMediator);
+ var windows = windowManager.getEnumerator("navigator:browser");
+ while (windows.hasMoreElements()) {
+ let currentDocument = windows.getNext().document;
+ let searchBar = currentDocument.getElementById("searchbar");
+ if (searchBar) {
+ let transactionMgr = searchBar.textbox.editor.transactionManager;
+ if (searchBar.value ||
+ transactionMgr.numberOfUndoItems ||
+ transactionMgr.numberOfRedoItems) {
+ aCallback("formdata", true, aArg);
+ return false;
+ }
+ }
+ let findBar = currentDocument.getElementById("FindToolbar");
+ if (findBar && findBar.canClear) {
+ aCallback("formdata", true, aArg);
+ return false;
+ }
+ }
+
+ let count = 0;
+ let countDone = {
+ handleResult : function(aResult) count = aResult,
+ handleError : function(aError) Components.utils.reportError(aError),
+ handleCompletion :
+ function(aReason) { aCallback("formdata", aReason == 0 && count > 0, aArg); }
+ };
+ FormHistory.count({}, countDone);
+ return false;
+ }
+ },
+
+ downloads: {
+ clear: function ()
+ {
+ var dlMgr = Components.classes["@mozilla.org/download-manager;1"]
+ .getService(Components.interfaces.nsIDownloadManager);
+
+ var dlsToRemove = [];
+ if (this.range) {
+ // First, remove the completed/cancelled downloads
+ dlMgr.removeDownloadsByTimeframe(this.range[0], this.range[1]);
+
+ // Queue up any active downloads that started in the time span as well
+ for (let dlsEnum of [dlMgr.activeDownloads, dlMgr.activePrivateDownloads]) {
+ while (dlsEnum.hasMoreElements()) {
+ var dl = dlsEnum.next();
+ if (dl.startTime >= this.range[0])
+ dlsToRemove.push(dl);
+ }
+ }
+ }
+ else {
+ // Clear all completed/cancelled downloads
+ dlMgr.cleanUp();
+ dlMgr.cleanUpPrivate();
+
+ // Queue up all active ones as well
+ for (let dlsEnum of [dlMgr.activeDownloads, dlMgr.activePrivateDownloads]) {
+ while (dlsEnum.hasMoreElements()) {
+ dlsToRemove.push(dlsEnum.next());
+ }
+ }
+ }
+
+ // Remove any queued up active downloads
+ dlsToRemove.forEach(function (dl) {
+ dl.remove();
+ });
+ },
+
+ get canClear()
+ {
+ var dlMgr = Components.classes["@mozilla.org/download-manager;1"]
+ .getService(Components.interfaces.nsIDownloadManager);
+ return dlMgr.canCleanUp || dlMgr.canCleanUpPrivate;
+ }
+ },
+
+ passwords: {
+ clear: function ()
+ {
+ var pwmgr = Components.classes["@mozilla.org/login-manager;1"]
+ .getService(Components.interfaces.nsILoginManager);
+ // Passwords are timeless, and don't respect the timeSpan setting
+ pwmgr.removeAllLogins();
+ },
+
+ get canClear()
+ {
+ var pwmgr = Components.classes["@mozilla.org/login-manager;1"]
+ .getService(Components.interfaces.nsILoginManager);
+ var count = pwmgr.countLogins("", "", ""); // count all logins
+ return (count > 0);
+ }
+ },
+
+ sessions: {
+ clear: function ()
+ {
+ // clear all auth tokens
+ var sdr = Components.classes["@mozilla.org/security/sdr;1"]
+ .getService(Components.interfaces.nsISecretDecoderRing);
+ sdr.logoutAndTeardown();
+
+ // clear FTP and plain HTTP auth sessions
+ var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ os.notifyObservers(null, "net:clear-active-logins", null);
+ },
+
+ get canClear()
+ {
+ return true;
+ }
+ },
+
+ siteSettings: {
+ clear: function ()
+ {
+ // Clear site-specific permissions like "Allow this site to open popups"
+ var pm = Components.classes["@mozilla.org/permissionmanager;1"]
+ .getService(Components.interfaces.nsIPermissionManager);
+ pm.removeAll();
+
+ // Clear site-specific settings like page-zoom level
+ var cps = Components.classes["@mozilla.org/content-pref/service;1"]
+ .getService(Components.interfaces.nsIContentPrefService2);
+ cps.removeAllDomains(null);
+
+ // Clear "Never remember passwords for this site", which is not handled by
+ // the permission manager
+ var pwmgr = Components.classes["@mozilla.org/login-manager;1"]
+ .getService(Components.interfaces.nsILoginManager);
+ var hosts = pwmgr.getAllDisabledHosts();
+ for each (var host in hosts) {
+ pwmgr.setLoginSavingEnabled(host, true);
+ }
+ },
+
+ get canClear()
+ {
+ return true;
+ }
+ }
+ }
+};
+
+
+
+// "Static" members
+Sanitizer.prefDomain = "privacy.sanitize.";
+Sanitizer.prefShutdown = "sanitizeOnShutdown";
+Sanitizer.prefDidShutdown = "didShutdownSanitize";
+
+// Time span constants corresponding to values of the privacy.sanitize.timeSpan
+// pref. Used to determine how much history to clear, for various items
+Sanitizer.TIMESPAN_EVERYTHING = 0;
+Sanitizer.TIMESPAN_HOUR = 1;
+Sanitizer.TIMESPAN_2HOURS = 2;
+Sanitizer.TIMESPAN_4HOURS = 3;
+Sanitizer.TIMESPAN_TODAY = 4;
+
+// Return a 2 element array representing the start and end times,
+// in the uSec-since-epoch format that PRTime likes. If we should
+// clear everything, return null. Use ts if it is defined; otherwise
+// use the timeSpan pref.
+Sanitizer.getClearRange = function (ts) {
+ if (ts === undefined)
+ ts = Sanitizer.prefs.getIntPref("timeSpan");
+ if (ts === Sanitizer.TIMESPAN_EVERYTHING)
+ return null;
+
+ // PRTime is microseconds while JS time is milliseconds
+ var endDate = Date.now() * 1000;
+ switch (ts) {
+ case Sanitizer.TIMESPAN_HOUR :
+ var startDate = endDate - 3600000000; // 1*60*60*1000000
+ break;
+ case Sanitizer.TIMESPAN_2HOURS :
+ startDate = endDate - 7200000000; // 2*60*60*1000000
+ break;
+ case Sanitizer.TIMESPAN_4HOURS :
+ startDate = endDate - 14400000000; // 4*60*60*1000000
+ break;
+ case Sanitizer.TIMESPAN_TODAY :
+ var d = new Date(); // Start with today
+ d.setHours(0); // zero us back to midnight...
+ d.setMinutes(0);
+ d.setSeconds(0);
+ startDate = d.valueOf() * 1000; // convert to epoch usec
+ break;
+ default:
+ throw "Invalid time span for clear private data: " + ts;
+ }
+ return [startDate, endDate];
+};
+
+Sanitizer._prefs = null;
+Sanitizer.__defineGetter__("prefs", function()
+{
+ return Sanitizer._prefs ? Sanitizer._prefs
+ : Sanitizer._prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefService)
+ .getBranch(Sanitizer.prefDomain);
+});
+
+// Shows sanitization UI
+Sanitizer.showUI = function(aParentWindow)
+{
+ var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
+ .getService(Components.interfaces.nsIWindowWatcher);
+#ifdef XP_MACOSX
+ ww.openWindow(null, // make this an app-modal window on Mac
+#else
+ ww.openWindow(aParentWindow,
+#endif
+ "chrome://browser/content/sanitize.xul",
+ "Sanitize",
+ "chrome,titlebar,dialog,centerscreen,modal",
+ null);
+};
+
+/**
+ * Deletes privacy sensitive data in a batch, optionally showing the
+ * sanitize UI, according to user preferences
+ */
+Sanitizer.sanitize = function(aParentWindow)
+{
+ Sanitizer.showUI(aParentWindow);
+};
+
+Sanitizer.onStartup = function()
+{
+ // we check for unclean exit with pending sanitization
+ Sanitizer._checkAndSanitize();
+};
+
+Sanitizer.onShutdown = function()
+{
+ // we check if sanitization is needed and perform it
+ Sanitizer._checkAndSanitize();
+};
+
+// this is called on startup and shutdown, to perform pending sanitizations
+Sanitizer._checkAndSanitize = function()
+{
+ const prefs = Sanitizer.prefs;
+ if (prefs.getBoolPref(Sanitizer.prefShutdown) &&
+ !prefs.prefHasUserValue(Sanitizer.prefDidShutdown)) {
+ // this is a shutdown or a startup after an unclean exit
+ var s = new Sanitizer();
+ s.prefDomain = "privacy.clearOnShutdown.";
+ s.sanitize().then(function() {
+ prefs.setBoolPref(Sanitizer.prefDidShutdown, true);
+ });
+ }
+};
diff --git a/browser/base/content/sanitize.xul b/browser/base/content/sanitize.xul
new file mode 100644
index 000000000..10b3813a8
--- /dev/null
+++ b/browser/base/content/sanitize.xul
@@ -0,0 +1,183 @@
+<?xml version="1.0"?>
+
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://global/skin/"?>
+<?xml-stylesheet href="chrome://browser/skin/sanitizeDialog.css"?>
+
+#ifdef CRH_DIALOG_TREE_VIEW
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+#endif
+
+<?xml-stylesheet href="chrome://browser/content/sanitizeDialog.css"?>
+
+<!DOCTYPE prefwindow [
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+ <!ENTITY % sanitizeDTD SYSTEM "chrome://browser/locale/sanitize.dtd">
+ %brandDTD;
+ %sanitizeDTD;
+]>
+
+<prefwindow id="SanitizeDialog" type="child"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ dlgbuttons="accept,cancel"
+ title="&sanitizeDialog2.title;"
+ noneverythingtitle="&sanitizeDialog2.title;"
+ style="width: &dialog.width2;;"
+ ondialogaccept="return gSanitizePromptDialog.sanitize();">
+
+ <prefpane id="SanitizeDialogPane" onpaneload="gSanitizePromptDialog.init();">
+ <stringbundle id="bundleBrowser"
+ src="chrome://browser/locale/browser.properties"/>
+
+ <script type="application/javascript"
+ src="chrome://browser/content/sanitize.js"/>
+
+#ifdef CRH_DIALOG_TREE_VIEW
+ <script type="application/javascript"
+ src="chrome://global/content/globalOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/places/treeView.js"/>
+ <script type="application/javascript"><![CDATA[
+ Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
+ Components.utils.import("resource:///modules/PlacesUIUtils.jsm");
+ ]]></script>
+#endif
+
+ <script type="application/javascript"
+ src="chrome://browser/content/sanitizeDialog.js"/>
+
+ <preferences id="sanitizePreferences">
+ <preference id="privacy.cpd.history" name="privacy.cpd.history" type="bool"/>
+ <preference id="privacy.cpd.formdata" name="privacy.cpd.formdata" type="bool"/>
+ <preference id="privacy.cpd.downloads" name="privacy.cpd.downloads" type="bool" disabled="true"/>
+ <preference id="privacy.cpd.cookies" name="privacy.cpd.cookies" type="bool"/>
+ <preference id="privacy.cpd.cache" name="privacy.cpd.cache" type="bool"/>
+ <preference id="privacy.cpd.sessions" name="privacy.cpd.sessions" type="bool"/>
+ <preference id="privacy.cpd.offlineApps" name="privacy.cpd.offlineApps" type="bool"/>
+ <preference id="privacy.cpd.siteSettings" name="privacy.cpd.siteSettings" type="bool"/>
+ </preferences>
+
+ <preferences id="nonItemPreferences">
+ <preference id="privacy.sanitize.timeSpan"
+ name="privacy.sanitize.timeSpan"
+ type="int"/>
+ </preferences>
+
+ <hbox id="SanitizeDurationBox" align="center">
+ <label value="&clearTimeDuration.label;"
+ accesskey="&clearTimeDuration.accesskey;"
+ control="sanitizeDurationChoice"
+ id="sanitizeDurationLabel"/>
+ <menulist id="sanitizeDurationChoice"
+ preference="privacy.sanitize.timeSpan"
+ onselect="gSanitizePromptDialog.selectByTimespan();"
+ flex="1">
+ <menupopup id="sanitizeDurationPopup">
+#ifdef CRH_DIALOG_TREE_VIEW
+ <menuitem label="" value="-1" id="sanitizeDurationCustom"/>
+#endif
+ <menuitem label="&clearTimeDuration.lastHour;" value="1"/>
+ <menuitem label="&clearTimeDuration.last2Hours;" value="2"/>
+ <menuitem label="&clearTimeDuration.last4Hours;" value="3"/>
+ <menuitem label="&clearTimeDuration.today;" value="4"/>
+ <menuseparator/>
+ <menuitem label="&clearTimeDuration.everything;" value="0"/>
+ </menupopup>
+ </menulist>
+ <label id="sanitizeDurationSuffixLabel"
+ value="&clearTimeDuration.suffix;"/>
+ </hbox>
+
+ <separator class="thin"/>
+
+#ifdef CRH_DIALOG_TREE_VIEW
+ <deck id="durationDeck">
+ <tree id="placesTree" flex="1" hidecolumnpicker="true" rows="10"
+ disabled="true" disableKeyNavigation="true">
+ <treecols>
+ <treecol id="date" label="&clearTimeDuration.dateColumn;" flex="1"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="title" label="&clearTimeDuration.nameColumn;" flex="5"/>
+ </treecols>
+ <treechildren id="placesTreechildren"
+ ondragstart="gSanitizePromptDialog.grippyMoved('ondragstart', event);"
+ ondragover="gSanitizePromptDialog.grippyMoved('ondragover', event);"
+ onkeypress="gSanitizePromptDialog.grippyMoved('onkeypress', event);"
+ onmousedown="gSanitizePromptDialog.grippyMoved('onmousedown', event);"/>
+ </tree>
+#endif
+
+ <vbox id="sanitizeEverythingWarningBox">
+ <spacer flex="1"/>
+ <hbox align="center">
+ <image id="sanitizeEverythingWarningIcon"/>
+ <vbox id="sanitizeEverythingWarningDescBox" flex="1">
+ <description id="sanitizeEverythingWarning"/>
+ <description id="sanitizeEverythingUndoWarning">&sanitizeEverythingUndoWarning;</description>
+ </vbox>
+ </hbox>
+ <spacer flex="1"/>
+ </vbox>
+
+#ifdef CRH_DIALOG_TREE_VIEW
+ </deck>
+#endif
+
+ <separator class="thin"/>
+
+ <hbox id="detailsExpanderWrapper" align="center">
+ <button type="image"
+ id="detailsExpander"
+ class="expander-down"
+ persist="class"
+ oncommand="gSanitizePromptDialog.toggleItemList();"/>
+ <label id="detailsExpanderLabel"
+ value="&detailsProgressiveDisclosure.label;"
+ accesskey="&detailsProgressiveDisclosure.accesskey;"
+ control="detailsExpander"/>
+ </hbox>
+ <listbox id="itemList" rows="7" collapsed="true" persist="collapsed">
+ <listitem label="&itemHistoryAndDownloads.label;"
+ type="checkbox"
+ accesskey="&itemHistoryAndDownloads.accesskey;"
+ preference="privacy.cpd.history"
+ onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+ <listitem label="&itemFormSearchHistory.label;"
+ type="checkbox"
+ accesskey="&itemFormSearchHistory.accesskey;"
+ preference="privacy.cpd.formdata"
+ onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+ <listitem label="&itemCookies.label;"
+ type="checkbox"
+ accesskey="&itemCookies.accesskey;"
+ preference="privacy.cpd.cookies"
+ onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+ <listitem label="&itemCache.label;"
+ type="checkbox"
+ accesskey="&itemCache.accesskey;"
+ preference="privacy.cpd.cache"
+ onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+ <listitem label="&itemActiveLogins.label;"
+ type="checkbox"
+ accesskey="&itemActiveLogins.accesskey;"
+ preference="privacy.cpd.sessions"
+ onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+ <listitem label="&itemOfflineApps.label;"
+ type="checkbox"
+ accesskey="&itemOfflineApps.accesskey;"
+ preference="privacy.cpd.offlineApps"
+ onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+ <listitem label="&itemSitePreferences.label;"
+ type="checkbox"
+ accesskey="&itemSitePreferences.accesskey;"
+ preference="privacy.cpd.siteSettings"
+ noduration="true"
+ onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+ </listbox>
+
+ </prefpane>
+</prefwindow>
diff --git a/browser/base/content/sanitizeDialog.css b/browser/base/content/sanitizeDialog.css
new file mode 100644
index 000000000..27c3c0866
--- /dev/null
+++ b/browser/base/content/sanitizeDialog.css
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Places tree */
+
+#placesTreechildren {
+ -moz-user-focus: normal;
+}
+
+#placesTreechildren::-moz-tree-cell(grippyRow),
+#placesTreechildren::-moz-tree-cell-text(grippyRow),
+#placesTreechildren::-moz-tree-image(grippyRow) {
+ cursor: -moz-grab;
+}
+
+
+/* Sanitize everything warnings */
+
+#sanitizeEverythingWarning,
+#sanitizeEverythingUndoWarning {
+ white-space: pre-wrap;
+}
diff --git a/browser/base/content/sanitizeDialog.js b/browser/base/content/sanitizeDialog.js
new file mode 100644
index 000000000..18df5e4a4
--- /dev/null
+++ b/browser/base/content/sanitizeDialog.js
@@ -0,0 +1,910 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+var gSanitizePromptDialog = {
+
+ get bundleBrowser()
+ {
+ if (!this._bundleBrowser)
+ this._bundleBrowser = document.getElementById("bundleBrowser");
+ return this._bundleBrowser;
+ },
+
+ get selectedTimespan()
+ {
+ var durList = document.getElementById("sanitizeDurationChoice");
+ return parseInt(durList.value);
+ },
+
+ get sanitizePreferences()
+ {
+ if (!this._sanitizePreferences) {
+ this._sanitizePreferences =
+ document.getElementById("sanitizePreferences");
+ }
+ return this._sanitizePreferences;
+ },
+
+ get warningBox()
+ {
+ return document.getElementById("sanitizeEverythingWarningBox");
+ },
+
+ init: function ()
+ {
+ // This is used by selectByTimespan() to determine if the window has loaded.
+ this._inited = true;
+
+ var s = new Sanitizer();
+ s.prefDomain = "privacy.cpd.";
+
+ let sanitizeItemList = document.querySelectorAll("#itemList > [preference]");
+ for (let i = 0; i < sanitizeItemList.length; i++) {
+ let prefItem = sanitizeItemList[i];
+ let name = s.getNameFromPreference(prefItem.getAttribute("preference"));
+ s.canClearItem(name, function canClearCallback(aItem, aCanClear, aPrefItem) {
+ if (!aCanClear) {
+ aPrefItem.preference = null;
+ aPrefItem.checked = false;
+ aPrefItem.disabled = true;
+ }
+ }, prefItem);
+ }
+
+ document.documentElement.getButton("accept").label =
+ this.bundleBrowser.getString("sanitizeButtonOK");
+
+ if (this.selectedTimespan === Sanitizer.TIMESPAN_EVERYTHING) {
+ this.prepareWarning();
+ this.warningBox.hidden = false;
+ document.title =
+ this.bundleBrowser.getString("sanitizeDialog2.everything.title");
+ }
+ else
+ this.warningBox.hidden = true;
+ },
+
+ selectByTimespan: function ()
+ {
+ // This method is the onselect handler for the duration dropdown. As a
+ // result it's called a couple of times before onload calls init().
+ if (!this._inited)
+ return;
+
+ var warningBox = this.warningBox;
+
+ // If clearing everything
+ if (this.selectedTimespan === Sanitizer.TIMESPAN_EVERYTHING) {
+ this.prepareWarning();
+ if (warningBox.hidden) {
+ warningBox.hidden = false;
+ window.resizeBy(0, warningBox.boxObject.height);
+ }
+ window.document.title =
+ this.bundleBrowser.getString("sanitizeDialog2.everything.title");
+ return;
+ }
+
+ // If clearing a specific time range
+ if (!warningBox.hidden) {
+ window.resizeBy(0, -warningBox.boxObject.height);
+ warningBox.hidden = true;
+ }
+ window.document.title =
+ window.document.documentElement.getAttribute("noneverythingtitle");
+ },
+
+ sanitize: function ()
+ {
+ // Update pref values before handing off to the sanitizer (bug 453440)
+ this.updatePrefs();
+ var s = new Sanitizer();
+ s.prefDomain = "privacy.cpd.";
+
+ s.range = Sanitizer.getClearRange(this.selectedTimespan);
+ s.ignoreTimespan = !s.range;
+
+ // As the sanitize is async, we disable the buttons, update the label on
+ // the 'accept' button to indicate things are happening and return false -
+ // once the async operation completes (either with or without errors)
+ // we close the window.
+ let docElt = document.documentElement;
+ let acceptButton = docElt.getButton("accept");
+ acceptButton.disabled = true;
+ acceptButton.setAttribute("label",
+ this.bundleBrowser.getString("sanitizeButtonClearing"));
+ docElt.getButton("cancel").disabled = true;
+ try {
+ s.sanitize().then(window.close, window.close);
+ } catch (er) {
+ Components.utils.reportError("Exception during sanitize: " + er);
+ return true; // We *do* want to close immediately on error.
+ }
+ return false;
+ },
+
+ /**
+ * If the panel that displays a warning when the duration is "Everything" is
+ * not set up, sets it up. Otherwise does nothing.
+ *
+ * @param aDontShowItemList Whether only the warning message should be updated.
+ * True means the item list visibility status should not
+ * be changed.
+ */
+ prepareWarning: function (aDontShowItemList) {
+ // If the date and time-aware locale warning string is ever used again,
+ // initialize it here. Currently we use the no-visits warning string,
+ // which does not include date and time. See bug 480169 comment 48.
+
+ var warningStringID;
+ if (this.hasNonSelectedItems()) {
+ warningStringID = "sanitizeSelectedWarning";
+ if (!aDontShowItemList)
+ this.showItemList();
+ }
+ else {
+ warningStringID = "sanitizeEverythingWarning2";
+ }
+
+ var warningDesc = document.getElementById("sanitizeEverythingWarning");
+ warningDesc.textContent =
+ this.bundleBrowser.getString(warningStringID);
+ },
+
+ /**
+ * Called when the value of a preference element is synced from the actual
+ * pref. Enables or disables the OK button appropriately.
+ */
+ onReadGeneric: function ()
+ {
+ var found = false;
+
+ // Find any other pref that's checked and enabled.
+ var i = 0;
+ while (!found && i < this.sanitizePreferences.childNodes.length) {
+ var preference = this.sanitizePreferences.childNodes[i];
+
+ found = !!preference.value &&
+ !preference.disabled;
+ i++;
+ }
+
+ try {
+ document.documentElement.getButton("accept").disabled = !found;
+ }
+ catch (e) { }
+
+ // Update the warning prompt if needed
+ this.prepareWarning(true);
+
+ return undefined;
+ },
+
+ /**
+ * Sanitizer.prototype.sanitize() requires the prefs to be up-to-date.
+ * Because the type of this prefwindow is "child" -- and that's needed because
+ * without it the dialog has no OK and Cancel buttons -- the prefs are not
+ * updated on dialogaccept on platforms that don't support instant-apply
+ * (i.e., Windows). We must therefore manually set the prefs from their
+ * corresponding preference elements.
+ */
+ updatePrefs : function ()
+ {
+ var tsPref = document.getElementById("privacy.sanitize.timeSpan");
+ Sanitizer.prefs.setIntPref("timeSpan", this.selectedTimespan);
+
+ // Keep the pref for the download history in sync with the history pref.
+ document.getElementById("privacy.cpd.downloads").value =
+ document.getElementById("privacy.cpd.history").value;
+
+ // Now manually set the prefs from their corresponding preference
+ // elements.
+ var prefs = this.sanitizePreferences.rootBranch;
+ for (let i = 0; i < this.sanitizePreferences.childNodes.length; ++i) {
+ var p = this.sanitizePreferences.childNodes[i];
+ prefs.setBoolPref(p.name, p.value);
+ }
+ },
+
+ /**
+ * Check if all of the history items have been selected like the default status.
+ */
+ hasNonSelectedItems: function () {
+ let checkboxes = document.querySelectorAll("#itemList > [preference]");
+ for (let i = 0; i < checkboxes.length; ++i) {
+ let pref = document.getElementById(checkboxes[i].getAttribute("preference"));
+ if (!pref.value)
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Show the history items list.
+ */
+ showItemList: function () {
+ var itemList = document.getElementById("itemList");
+ var expanderButton = document.getElementById("detailsExpander");
+
+ if (itemList.collapsed) {
+ expanderButton.className = "expander-up";
+ itemList.setAttribute("collapsed", "false");
+ if (document.documentElement.boxObject.height)
+ window.resizeBy(0, itemList.boxObject.height);
+ }
+ },
+
+ /**
+ * Hide the history items list.
+ */
+ hideItemList: function () {
+ var itemList = document.getElementById("itemList");
+ var expanderButton = document.getElementById("detailsExpander");
+
+ if (!itemList.collapsed) {
+ expanderButton.className = "expander-down";
+ window.resizeBy(0, -itemList.boxObject.height);
+ itemList.setAttribute("collapsed", "true");
+ }
+ },
+
+ /**
+ * Called by the item list expander button to toggle the list's visibility.
+ */
+ toggleItemList: function ()
+ {
+ var itemList = document.getElementById("itemList");
+
+ if (itemList.collapsed)
+ this.showItemList();
+ else
+ this.hideItemList();
+ }
+
+#ifdef CRH_DIALOG_TREE_VIEW
+ // A duration value; used in the same context as Sanitizer.TIMESPAN_HOUR,
+ // Sanitizer.TIMESPAN_2HOURS, et al. This should match the value attribute
+ // of the sanitizeDurationCustom menuitem.
+ get TIMESPAN_CUSTOM()
+ {
+ return -1;
+ },
+
+ get placesTree()
+ {
+ if (!this._placesTree)
+ this._placesTree = document.getElementById("placesTree");
+ return this._placesTree;
+ },
+
+ init: function ()
+ {
+ // This is used by selectByTimespan() to determine if the window has loaded.
+ this._inited = true;
+
+ var s = new Sanitizer();
+ s.prefDomain = "privacy.cpd.";
+
+ let sanitizeItemList = document.querySelectorAll("#itemList > [preference]");
+ for (let i = 0; i < sanitizeItemList.length; i++) {
+ let prefItem = sanitizeItemList[i];
+ let name = s.getNameFromPreference(prefItem.getAttribute("preference"));
+ s.canClearItem(name, function canClearCallback(aCanClear) {
+ if (!aCanClear) {
+ prefItem.preference = null;
+ prefItem.checked = false;
+ prefItem.disabled = true;
+ }
+ });
+ }
+
+ document.documentElement.getButton("accept").label =
+ this.bundleBrowser.getString("sanitizeButtonOK");
+
+ this.selectByTimespan();
+ },
+
+ /**
+ * Sets up the hashes this.durationValsToRows, which maps duration values
+ * to rows in the tree, this.durationRowsToVals, which maps rows in
+ * the tree to duration values, and this.durationStartTimes, which maps
+ * duration values to their corresponding start times.
+ */
+ initDurationDropdown: function ()
+ {
+ // First, calculate the start times for each duration.
+ this.durationStartTimes = {};
+ var durVals = [];
+ var durPopup = document.getElementById("sanitizeDurationPopup");
+ var durMenuitems = durPopup.childNodes;
+ for (let i = 0; i < durMenuitems.length; i++) {
+ let durMenuitem = durMenuitems[i];
+ let durVal = parseInt(durMenuitem.value);
+ if (durMenuitem.localName === "menuitem" &&
+ durVal !== Sanitizer.TIMESPAN_EVERYTHING &&
+ durVal !== this.TIMESPAN_CUSTOM) {
+ durVals.push(durVal);
+ let durTimes = Sanitizer.getClearRange(durVal);
+ this.durationStartTimes[durVal] = durTimes[0];
+ }
+ }
+
+ // Sort the duration values ascending. Because one tree index can map to
+ // more than one duration, this ensures that this.durationRowsToVals maps
+ // a row index to the largest duration possible in the code below.
+ durVals.sort();
+
+ // Now calculate the rows in the tree of the durations' start times. For
+ // each duration, we are looking for the node in the tree whose time is the
+ // smallest time greater than or equal to the duration's start time.
+ this.durationRowsToVals = {};
+ this.durationValsToRows = {};
+ var view = this.placesTree.view;
+ // For all rows in the tree except the grippy row...
+ for (let i = 0; i < view.rowCount - 1; i++) {
+ let unfoundDurVals = [];
+ let nodeTime = view.QueryInterface(Ci.nsINavHistoryResultTreeViewer).
+ nodeForTreeIndex(i).time;
+ // For all durations whose rows have not yet been found in the tree, see
+ // if index i is their index. An index may map to more than one duration,
+ // in which case the final duration (the largest) wins.
+ for (let j = 0; j < durVals.length; j++) {
+ let durVal = durVals[j];
+ let durStartTime = this.durationStartTimes[durVal];
+ if (nodeTime < durStartTime) {
+ this.durationValsToRows[durVal] = i - 1;
+ this.durationRowsToVals[i - 1] = durVal;
+ }
+ else
+ unfoundDurVals.push(durVal);
+ }
+ durVals = unfoundDurVals;
+ }
+
+ // If any durations were not found above, then every node in the tree has a
+ // time greater than or equal to the duration. In other words, those
+ // durations include the entire tree (except the grippy row).
+ for (let i = 0; i < durVals.length; i++) {
+ let durVal = durVals[i];
+ this.durationValsToRows[durVal] = view.rowCount - 2;
+ this.durationRowsToVals[view.rowCount - 2] = durVal;
+ }
+ },
+
+ /**
+ * If the Places tree is not set up, sets it up. Otherwise does nothing.
+ */
+ ensurePlacesTreeIsInited: function ()
+ {
+ if (this._placesTreeIsInited)
+ return;
+
+ this._placesTreeIsInited = true;
+
+ // Either "Last Four Hours" or "Today" will have the most history. If
+ // it's been more than 4 hours since today began, "Today" will. Otherwise
+ // "Last Four Hours" will.
+ var times = Sanitizer.getClearRange(Sanitizer.TIMESPAN_TODAY);
+
+ // If it's been less than 4 hours since today began, use the past 4 hours.
+ if (times[1] - times[0] < 14400000000) { // 4*60*60*1000000
+ times = Sanitizer.getClearRange(Sanitizer.TIMESPAN_4HOURS);
+ }
+
+ var histServ = Cc["@mozilla.org/browser/nav-history-service;1"].
+ getService(Ci.nsINavHistoryService);
+ var query = histServ.getNewQuery();
+ query.beginTimeReference = query.TIME_RELATIVE_EPOCH;
+ query.beginTime = times[0];
+ query.endTimeReference = query.TIME_RELATIVE_EPOCH;
+ query.endTime = times[1];
+ var opts = histServ.getNewQueryOptions();
+ opts.sortingMode = opts.SORT_BY_DATE_DESCENDING;
+ opts.queryType = opts.QUERY_TYPE_HISTORY;
+ var result = histServ.executeQuery(query, opts);
+
+ var view = gContiguousSelectionTreeHelper.setTree(this.placesTree,
+ new PlacesTreeView());
+ result.addObserver(view, false);
+ this.initDurationDropdown();
+ },
+
+ /**
+ * Called on select of the duration dropdown and when grippyMoved() sets a
+ * duration based on the location of the grippy row. Selects all the nodes in
+ * the tree that are contained in the selected duration. If clearing
+ * everything, the warning panel is shown instead.
+ */
+ selectByTimespan: function ()
+ {
+ // This method is the onselect handler for the duration dropdown. As a
+ // result it's called a couple of times before onload calls init().
+ if (!this._inited)
+ return;
+
+ var durDeck = document.getElementById("durationDeck");
+ var durList = document.getElementById("sanitizeDurationChoice");
+ var durVal = parseInt(durList.value);
+ var durCustom = document.getElementById("sanitizeDurationCustom");
+
+ // If grippy row is not at a duration boundary, show the custom menuitem;
+ // otherwise, hide it. Since the user cannot specify a custom duration by
+ // using the dropdown, this conditional is true only when this method is
+ // called onselect from grippyMoved(), so no selection need be made.
+ if (durVal === this.TIMESPAN_CUSTOM) {
+ durCustom.hidden = false;
+ return;
+ }
+ durCustom.hidden = true;
+
+ // If clearing everything, show the warning and change the dialog's title.
+ if (durVal === Sanitizer.TIMESPAN_EVERYTHING) {
+ this.prepareWarning();
+ durDeck.selectedIndex = 1;
+ window.document.title =
+ this.bundleBrowser.getString("sanitizeDialog2.everything.title");
+ document.documentElement.getButton("accept").disabled = false;
+ return;
+ }
+
+ // Otherwise -- if clearing a specific time range -- select that time range
+ // in the tree.
+ this.ensurePlacesTreeIsInited();
+ durDeck.selectedIndex = 0;
+ window.document.title =
+ window.document.documentElement.getAttribute("noneverythingtitle");
+ var durRow = this.durationValsToRows[durVal];
+ gContiguousSelectionTreeHelper.rangedSelect(durRow);
+ gContiguousSelectionTreeHelper.scrollToGrippy();
+
+ // If duration is empty (there are no selected rows), disable the dialog's
+ // OK button.
+ document.documentElement.getButton("accept").disabled = durRow < 0;
+ },
+
+ sanitize: function ()
+ {
+ // Update pref values before handing off to the sanitizer (bug 453440)
+ this.updatePrefs();
+ var s = new Sanitizer();
+ s.prefDomain = "privacy.cpd.";
+
+ var durList = document.getElementById("sanitizeDurationChoice");
+ var durValue = parseInt(durList.value);
+ s.ignoreTimespan = durValue === Sanitizer.TIMESPAN_EVERYTHING;
+
+ // Set the sanitizer's time range if we're not clearing everything.
+ if (!s.ignoreTimespan) {
+ // If user selected a custom timespan, use that.
+ if (durValue === this.TIMESPAN_CUSTOM) {
+ var view = this.placesTree.view;
+ var now = Date.now() * 1000;
+ // We disable the dialog's OK button if there's no selection, but we'll
+ // handle that case just in... case.
+ if (view.selection.getRangeCount() === 0)
+ s.range = [now, now];
+ else {
+ var startIndexRef = {};
+ // Tree sorted by visit date DEscending, so start time time comes last.
+ view.selection.getRangeAt(0, {}, startIndexRef);
+ view.QueryInterface(Ci.nsINavHistoryResultTreeViewer);
+ var startNode = view.nodeForTreeIndex(startIndexRef.value);
+ s.range = [startNode.time, now];
+ }
+ }
+ // Otherwise use the predetermined range.
+ else
+ s.range = [this.durationStartTimes[durValue], Date.now() * 1000];
+ }
+
+ try {
+ s.sanitize();
+ } catch (er) {
+ Components.utils.reportError("Exception during sanitize: " + er);
+ }
+ return true;
+ },
+
+ /**
+ * In order to mark the custom Places tree view and its nsINavHistoryResult
+ * for garbage collection, we need to break the reference cycle between the
+ * two.
+ */
+ unload: function ()
+ {
+ let result = this.placesTree.getResult();
+ result.removeObserver(this.placesTree.view);
+ this.placesTree.view = null;
+ },
+
+ /**
+ * Called when the user moves the grippy by dragging it, clicking in the tree,
+ * or on keypress. Updates the duration dropdown so that it displays the
+ * appropriate specific or custom duration.
+ *
+ * @param aEventName
+ * The name of the event whose handler called this method, e.g.,
+ * "ondragstart", "onkeypress", etc.
+ * @param aEvent
+ * The event captured in the event handler.
+ */
+ grippyMoved: function (aEventName, aEvent)
+ {
+ gContiguousSelectionTreeHelper[aEventName](aEvent);
+ var lastSelRow = gContiguousSelectionTreeHelper.getGrippyRow() - 1;
+ var durList = document.getElementById("sanitizeDurationChoice");
+ var durValue = parseInt(durList.value);
+
+ // Multiple durations can map to the same row. Don't update the dropdown
+ // if the current duration is valid for lastSelRow.
+ if ((durValue !== this.TIMESPAN_CUSTOM ||
+ lastSelRow in this.durationRowsToVals) &&
+ (durValue === this.TIMESPAN_CUSTOM ||
+ this.durationValsToRows[durValue] !== lastSelRow)) {
+ // Setting durList.value causes its onselect handler to fire, which calls
+ // selectByTimespan().
+ if (lastSelRow in this.durationRowsToVals)
+ durList.value = this.durationRowsToVals[lastSelRow];
+ else
+ durList.value = this.TIMESPAN_CUSTOM;
+ }
+
+ // If there are no selected rows, disable the dialog's OK button.
+ document.documentElement.getButton("accept").disabled = lastSelRow < 0;
+ }
+#endif
+
+};
+
+
+#ifdef CRH_DIALOG_TREE_VIEW
+/**
+ * A helper for handling contiguous selection in the tree.
+ */
+var gContiguousSelectionTreeHelper = {
+
+ /**
+ * Gets the tree associated with this helper.
+ */
+ get tree()
+ {
+ return this._tree;
+ },
+
+ /**
+ * Sets the tree that this module handles. The tree is assigned a new view
+ * that is equipped to handle contiguous selection. You can pass in an
+ * object that will be used as the prototype of the new view. Otherwise
+ * the tree's current view is used as the prototype.
+ *
+ * @param aTreeElement
+ * The tree element
+ * @param aProtoTreeView
+ * If defined, this will be used as the prototype of the tree's new
+ * view
+ * @return The new view
+ */
+ setTree: function CSTH_setTree(aTreeElement, aProtoTreeView)
+ {
+ this._tree = aTreeElement;
+ var newView = this._makeTreeView(aProtoTreeView || aTreeElement.view);
+ aTreeElement.view = newView;
+ return newView;
+ },
+
+ /**
+ * The index of the row that the grippy occupies. Note that the index of the
+ * last selected row is getGrippyRow() - 1. If getGrippyRow() is 0, then
+ * no selection exists.
+ *
+ * @return The row index of the grippy
+ */
+ getGrippyRow: function CSTH_getGrippyRow()
+ {
+ var sel = this.tree.view.selection;
+ var rangeCount = sel.getRangeCount();
+ if (rangeCount === 0)
+ return 0;
+ if (rangeCount !== 1) {
+ throw "contiguous selection tree helper: getGrippyRow called with " +
+ "multiple selection ranges";
+ }
+ var max = {};
+ sel.getRangeAt(0, {}, max);
+ return max.value + 1;
+ },
+
+ /**
+ * Helper function for the dragover event. Your dragover listener should
+ * call this. It updates the selection in the tree under the mouse.
+ *
+ * @param aEvent
+ * The observed dragover event
+ */
+ ondragover: function CSTH_ondragover(aEvent)
+ {
+ // Without this when dragging on Windows the mouse cursor is a "no" sign.
+ // This makes it a drop symbol.
+ var ds = Cc["@mozilla.org/widget/dragservice;1"].
+ getService(Ci.nsIDragService).
+ getCurrentSession();
+ ds.canDrop = true;
+ ds.dragAction = 0;
+
+ var tbo = this.tree.treeBoxObject;
+ aEvent.QueryInterface(Ci.nsIDOMMouseEvent);
+ var hoverRow = tbo.getRowAt(aEvent.clientX, aEvent.clientY);
+
+ if (hoverRow < 0)
+ return;
+
+ this.rangedSelect(hoverRow - 1);
+ },
+
+ /**
+ * Helper function for the dragstart event. Your dragstart listener should
+ * call this. It starts a drag session.
+ *
+ * @param aEvent
+ * The observed dragstart event
+ */
+ ondragstart: function CSTH_ondragstart(aEvent)
+ {
+ var tbo = this.tree.treeBoxObject;
+ var clickedRow = tbo.getRowAt(aEvent.clientX, aEvent.clientY);
+
+ if (clickedRow !== this.getGrippyRow())
+ return;
+
+ // This part is a hack. What we really want is a grab and slide, not
+ // drag and drop. Start a move drag session with dummy data and a
+ // dummy region. Set the region's coordinates to (Infinity, Infinity)
+ // so it's drawn offscreen and its size to (1, 1).
+ var arr = Cc["@mozilla.org/supports-array;1"].
+ createInstance(Ci.nsISupportsArray);
+ var trans = Cc["@mozilla.org/widget/transferable;1"].
+ createInstance(Ci.nsITransferable);
+ trans.init(null);
+ trans.setTransferData('dummy-flavor', null, 0);
+ arr.AppendElement(trans);
+ var reg = Cc["@mozilla.org/gfx/region;1"].
+ createInstance(Ci.nsIScriptableRegion);
+ reg.setToRect(Infinity, Infinity, 1, 1);
+ var ds = Cc["@mozilla.org/widget/dragservice;1"].
+ getService(Ci.nsIDragService);
+ ds.invokeDragSession(aEvent.target, arr, reg, ds.DRAGDROP_ACTION_MOVE);
+ },
+
+ /**
+ * Helper function for the keypress event. Your keypress listener should
+ * call this. Users can use Up, Down, Page Up/Down, Home, and End to move
+ * the bottom of the selection window.
+ *
+ * @param aEvent
+ * The observed keypress event
+ */
+ onkeypress: function CSTH_onkeypress(aEvent)
+ {
+ var grippyRow = this.getGrippyRow();
+ var tbo = this.tree.treeBoxObject;
+ var rangeEnd;
+ switch (aEvent.keyCode) {
+ case aEvent.DOM_VK_HOME:
+ rangeEnd = 0;
+ break;
+ case aEvent.DOM_VK_PAGE_UP:
+ rangeEnd = grippyRow - tbo.getPageLength();
+ break;
+ case aEvent.DOM_VK_UP:
+ rangeEnd = grippyRow - 2;
+ break;
+ case aEvent.DOM_VK_DOWN:
+ rangeEnd = grippyRow;
+ break;
+ case aEvent.DOM_VK_PAGE_DOWN:
+ rangeEnd = grippyRow + tbo.getPageLength();
+ break;
+ case aEvent.DOM_VK_END:
+ rangeEnd = this.tree.view.rowCount - 2;
+ break;
+ default:
+ return;
+ break;
+ }
+
+ aEvent.stopPropagation();
+
+ // First, clip rangeEnd. this.rangedSelect() doesn't clip the range if we
+ // select past the ends of the tree.
+ if (rangeEnd < 0)
+ rangeEnd = -1;
+ else if (this.tree.view.rowCount - 2 < rangeEnd)
+ rangeEnd = this.tree.view.rowCount - 2;
+
+ // Next, (de)select.
+ this.rangedSelect(rangeEnd);
+
+ // Finally, scroll the tree. We always want one row above and below the
+ // grippy row to be visible if possible.
+ if (rangeEnd < grippyRow) // moved up
+ tbo.ensureRowIsVisible(rangeEnd < 0 ? 0 : rangeEnd);
+ else { // moved down
+ if (rangeEnd + 2 < this.tree.view.rowCount)
+ tbo.ensureRowIsVisible(rangeEnd + 2);
+ else if (rangeEnd + 1 < this.tree.view.rowCount)
+ tbo.ensureRowIsVisible(rangeEnd + 1);
+ }
+ },
+
+ /**
+ * Helper function for the mousedown event. Your mousedown listener should
+ * call this. Users can click on individual rows to make the selection
+ * jump to them immediately.
+ *
+ * @param aEvent
+ * The observed mousedown event
+ */
+ onmousedown: function CSTH_onmousedown(aEvent)
+ {
+ var tbo = this.tree.treeBoxObject;
+ var clickedRow = tbo.getRowAt(aEvent.clientX, aEvent.clientY);
+
+ if (clickedRow < 0 || clickedRow >= this.tree.view.rowCount)
+ return;
+
+ if (clickedRow < this.getGrippyRow())
+ this.rangedSelect(clickedRow);
+ else if (clickedRow > this.getGrippyRow())
+ this.rangedSelect(clickedRow - 1);
+ },
+
+ /**
+ * Selects range [0, aEndRow] in the tree. The grippy row will then be at
+ * index aEndRow + 1. aEndRow may be -1, in which case the selection is
+ * cleared and the grippy row will be at index 0.
+ *
+ * @param aEndRow
+ * The range [0, aEndRow] will be selected.
+ */
+ rangedSelect: function CSTH_rangedSelect(aEndRow)
+ {
+ var tbo = this.tree.treeBoxObject;
+ if (aEndRow < 0)
+ this.tree.view.selection.clearSelection();
+ else
+ this.tree.view.selection.rangedSelect(0, aEndRow, false);
+ tbo.invalidateRange(tbo.getFirstVisibleRow(), tbo.getLastVisibleRow());
+ },
+
+ /**
+ * Scrolls the tree so that the grippy row is in the center of the view.
+ */
+ scrollToGrippy: function CSTH_scrollToGrippy()
+ {
+ var rowCount = this.tree.view.rowCount;
+ var tbo = this.tree.treeBoxObject;
+ var pageLen = tbo.getPageLength() ||
+ parseInt(this.tree.getAttribute("rows")) ||
+ 10;
+
+ // All rows fit on a single page.
+ if (rowCount <= pageLen)
+ return;
+
+ var scrollToRow = this.getGrippyRow() - Math.ceil(pageLen / 2.0);
+
+ // Grippy row is in first half of first page.
+ if (scrollToRow < 0)
+ scrollToRow = 0;
+
+ // Grippy row is in last half of last page.
+ else if (rowCount < scrollToRow + pageLen)
+ scrollToRow = rowCount - pageLen;
+
+ tbo.scrollToRow(scrollToRow);
+ },
+
+ /**
+ * Creates a new tree view suitable for contiguous selection. If
+ * aProtoTreeView is specified, it's used as the new view's prototype.
+ * Otherwise the tree's current view is used as the prototype.
+ *
+ * @param aProtoTreeView
+ * Used as the new view's prototype if specified
+ */
+ _makeTreeView: function CSTH__makeTreeView(aProtoTreeView)
+ {
+ var view = aProtoTreeView;
+ var that = this;
+
+ //XXXadw: When Alex gets the grippy icon done, this may or may not change,
+ // depending on how we style it.
+ view.isSeparator = function CSTH_View_isSeparator(aRow)
+ {
+ return aRow === that.getGrippyRow();
+ };
+
+ // rowCount includes the grippy row.
+ view.__defineGetter__("_rowCount", view.__lookupGetter__("rowCount"));
+ view.__defineGetter__("rowCount",
+ function CSTH_View_rowCount()
+ {
+ return this._rowCount + 1;
+ });
+
+ // This has to do with visual feedback in the view itself, e.g., drawing
+ // a small line underneath the dropzone. Not what we want.
+ view.canDrop = function CSTH_View_canDrop() { return false; };
+
+ // No clicking headers to sort the tree or sort feedback on columns.
+ view.cycleHeader = function CSTH_View_cycleHeader() {};
+ view.sortingChanged = function CSTH_View_sortingChanged() {};
+
+ // Override a bunch of methods to account for the grippy row.
+
+ view._getCellProperties = view.getCellProperties;
+ view.getCellProperties =
+ function CSTH_View_getCellProperties(aRow, aCol)
+ {
+ var grippyRow = that.getGrippyRow();
+ if (aRow === grippyRow)
+ return "grippyRow";
+ if (aRow < grippyRow)
+ return this._getCellProperties(aRow, aCol);
+
+ return this._getCellProperties(aRow - 1, aCol);
+ };
+
+ view._getRowProperties = view.getRowProperties;
+ view.getRowProperties =
+ function CSTH_View_getRowProperties(aRow)
+ {
+ var grippyRow = that.getGrippyRow();
+ if (aRow === grippyRow)
+ return "grippyRow";
+
+ if (aRow < grippyRow)
+ return this._getRowProperties(aRow);
+
+ return this._getRowProperties(aRow - 1);
+ };
+
+ view._getCellText = view.getCellText;
+ view.getCellText =
+ function CSTH_View_getCellText(aRow, aCol)
+ {
+ var grippyRow = that.getGrippyRow();
+ if (aRow === grippyRow)
+ return "";
+ aRow = aRow < grippyRow ? aRow : aRow - 1;
+ return this._getCellText(aRow, aCol);
+ };
+
+ view._getImageSrc = view.getImageSrc;
+ view.getImageSrc =
+ function CSTH_View_getImageSrc(aRow, aCol)
+ {
+ var grippyRow = that.getGrippyRow();
+ if (aRow === grippyRow)
+ return "";
+ aRow = aRow < grippyRow ? aRow : aRow - 1;
+ return this._getImageSrc(aRow, aCol);
+ };
+
+ view.isContainer = function CSTH_View_isContainer(aRow) { return false; };
+ view.getParentIndex = function CSTH_View_getParentIndex(aRow) { return -1; };
+ view.getLevel = function CSTH_View_getLevel(aRow) { return 0; };
+ view.hasNextSibling = function CSTH_View_hasNextSibling(aRow, aAfterIndex)
+ {
+ return aRow < this.rowCount - 1;
+ };
+
+ return view;
+ }
+};
+#endif
diff --git a/browser/base/content/socialchat.xml b/browser/base/content/socialchat.xml
new file mode 100644
index 000000000..a4843eefa
--- /dev/null
+++ b/browser/base/content/socialchat.xml
@@ -0,0 +1,747 @@
+<?xml version="1.0"?>
+
+<bindings id="socialChatBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xbl="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <binding id="chatbox">
+ <content orient="vertical" mousethrough="never">
+ <xul:hbox class="chat-titlebar" xbl:inherits="minimized,selected,activity" align="baseline">
+ <xul:hbox flex="1" onclick="document.getBindingParent(this).onTitlebarClick(event);">
+ <xul:image class="chat-status-icon" xbl:inherits="src=image"/>
+ <xul:label class="chat-title" flex="1" xbl:inherits="value=label" crop="center"/>
+ </xul:hbox>
+ <xul:toolbarbutton id="notification-icon" class="notification-anchor-icon chat-toolbarbutton"
+ oncommand="document.getBindingParent(this).showNotifications(); event.stopPropagation();"/>
+ <xul:toolbarbutton anonid="minimize" class="chat-minimize-button chat-toolbarbutton"
+ oncommand="document.getBindingParent(this).toggle();"/>
+ <xul:toolbarbutton anonid="swap" class="chat-swap-button chat-toolbarbutton"
+ oncommand="document.getBindingParent(this).swapWindows();"/>
+ <xul:toolbarbutton anonid="close" class="chat-close-button chat-toolbarbutton"
+ oncommand="document.getBindingParent(this).close();"/>
+ </xul:hbox>
+ <xul:browser anonid="content" class="chat-frame" flex="1"
+ context="contentAreaContextMenu"
+ disableglobalhistory="true"
+ tooltip="aHTMLTooltip"
+ xbl:inherits="src,origin" type="content"/>
+ </content>
+
+ <implementation implements="nsIDOMEventListener">
+ <constructor><![CDATA[
+ let Social = Components.utils.import("resource:///modules/Social.jsm", {}).Social;
+ this.content.__defineGetter__("popupnotificationanchor",
+ () => document.getAnonymousElementByAttribute(this, "id", "notification-icon"));
+ Social.setErrorListener(this.content, function(aBrowser) {
+ aBrowser.webNavigation.loadURI("about:socialerror?mode=compactInfo", null, null, null, null);
+ });
+ if (!this.chatbar) {
+ document.getAnonymousElementByAttribute(this, "anonid", "minimize").hidden = true;
+ document.getAnonymousElementByAttribute(this, "anonid", "close").hidden = true;
+ }
+ let contentWindow = this.contentWindow;
+ this.addEventListener("DOMContentLoaded", function DOMContentLoaded(event) {
+ if (event.target != this.contentDocument)
+ return;
+ this.removeEventListener("DOMContentLoaded", DOMContentLoaded, true);
+ this.isActive = !this.minimized;
+ // process this._callbacks, then set to null so the chatbox creator
+ // knows to make new callbacks immediately.
+ if (this._callbacks) {
+ for (let callback of this._callbacks) {
+ if (callback)
+ callback(contentWindow);
+ }
+ this._callbacks = null;
+ }
+
+ // content can send a socialChatActivity event to have the UI update.
+ let chatActivity = function() {
+ this.setAttribute("activity", true);
+ if (this.chatbar)
+ this.chatbar.updateTitlebar(this);
+ }.bind(this);
+ contentWindow.addEventListener("socialChatActivity", chatActivity);
+ contentWindow.addEventListener("unload", function unload() {
+ contentWindow.removeEventListener("unload", unload);
+ contentWindow.removeEventListener("socialChatActivity", chatActivity);
+ });
+ }, true);
+ if (this.src)
+ this.setAttribute("src", this.src);
+ ]]></constructor>
+
+ <field name="content" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "content");
+ </field>
+
+ <property name="contentWindow">
+ <getter>
+ return this.content.contentWindow;
+ </getter>
+ </property>
+
+ <property name="contentDocument">
+ <getter>
+ return this.content.contentDocument;
+ </getter>
+ </property>
+
+ <property name="minimized">
+ <getter>
+ return this.getAttribute("minimized") == "true";
+ </getter>
+ <setter><![CDATA[
+ // Note that this.isActive is set via our transitionend handler so
+ // the content doesn't see intermediate values.
+ let parent = this.chatbar;
+ if (val) {
+ this.setAttribute("minimized", "true");
+ // If this chat is the selected one a new one needs to be selected.
+ if (parent && parent.selectedChat == this)
+ parent._selectAnotherChat();
+ } else {
+ this.removeAttribute("minimized");
+ // this chat gets selected.
+ if (parent)
+ parent.selectedChat = this;
+ }
+ ]]></setter>
+ </property>
+
+ <property name="chatbar">
+ <getter>
+ if (this.parentNode.nodeName == "chatbar")
+ return this.parentNode;
+ return null;
+ </getter>
+ </property>
+
+ <property name="isActive">
+ <getter>
+ return this.content.docShell.isActive;
+ </getter>
+ <setter>
+ this.content.docShell.isActive = !!val;
+
+ // let the chat frame know if it is being shown or hidden
+ let evt = this.contentDocument.createEvent("CustomEvent");
+ evt.initCustomEvent(val ? "socialFrameShow" : "socialFrameHide", true, true, {});
+ this.contentDocument.documentElement.dispatchEvent(evt);
+ </setter>
+ </property>
+
+ <method name="showNotifications">
+ <body><![CDATA[
+ PopupNotifications._reshowNotifications(this.content.popupnotificationanchor,
+ this.content);
+ ]]></body>
+ </method>
+
+ <method name="swapDocShells">
+ <parameter name="aTarget"/>
+ <body><![CDATA[
+ aTarget.setAttribute('label', this.contentDocument.title);
+ aTarget.content.setAttribute("origin", this.content.getAttribute("origin"));
+ aTarget.content.popupnotificationanchor.className = this.content.popupnotificationanchor.className;
+ this.content.socialErrorListener.remove();
+ aTarget.content.socialErrorListener.remove();
+ this.content.swapDocShells(aTarget.content);
+ Social.setErrorListener(this.content, function(aBrowser) {}); // 'this' will be destroyed soon.
+ Social.setErrorListener(aTarget.content, function(aBrowser) {
+ aBrowser.webNavigation.loadURI("about:socialerror?mode=compactInfo", null, null, null, null);
+ });
+ ]]></body>
+ </method>
+
+ <method name="onTitlebarClick">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ if (!this.chatbar)
+ return;
+ if (aEvent.button == 0) { // left-click: toggle minimized.
+ this.toggle();
+ // if we restored it, we want to focus it.
+ if (!this.minimized)
+ this.chatbar.focus();
+ } else if (aEvent.button == 1) // middle-click: close chat
+ this.close();
+ ]]></body>
+ </method>
+
+ <method name="close">
+ <body><![CDATA[
+ if (this.chatbar)
+ this.chatbar.remove(this);
+ else
+ window.close();
+ ]]></body>
+ </method>
+
+ <method name="swapWindows">
+ <body><![CDATA[
+ let provider = Social._getProviderFromOrigin(this.content.getAttribute("origin"));
+ if (this.chatbar) {
+ this.chatbar.detachChatbox(this, { "centerscreen": "yes" }, win => {
+ win.document.title = provider.name;
+ });
+ } else {
+ // attach this chatbox to the topmost browser window
+ let findChromeWindowForChats = Cu.import("resource://gre/modules/MozSocialAPI.jsm").findChromeWindowForChats;
+ let win = findChromeWindowForChats();
+ let chatbar = win.SocialChatBar.chatbar;
+ chatbar.openChat(provider, "about:blank", win => {
+ this.swapDocShells(chatbar.selectedChat);
+ chatbar.focus();
+ this.close();
+ });
+ }
+ ]]></body>
+ </method>
+
+ <method name="toggle">
+ <body><![CDATA[
+ this.minimized = !this.minimized;
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="focus" phase="capturing">
+ if (this.chatbar)
+ this.chatbar.selectedChat = this;
+ </handler>
+ <handler event="DOMTitleChanged"><![CDATA[
+ this.setAttribute('label', this.contentDocument.title);
+ if (this.chatbar)
+ this.chatbar.updateTitlebar(this);
+ ]]></handler>
+ <handler event="DOMLinkAdded"><![CDATA[
+ // much of this logic is from DOMLinkHandler in browser.js
+ // this sets the presence icon for a chat user, we simply use favicon style updating
+ let link = event.originalTarget;
+ let rel = link.rel && link.rel.toLowerCase();
+ if (!link || !link.ownerDocument || !rel || !link.href)
+ return;
+ if (link.rel.indexOf("icon") < 0)
+ return;
+
+ let uri = DOMLinkHandler.getLinkIconURI(link);
+ if (!uri)
+ return;
+
+ // we made it this far, use it
+ this.setAttribute('image', uri.spec);
+ if (this.chatbar)
+ this.chatbar.updateTitlebar(this);
+ ]]></handler>
+ <handler event="transitionend">
+ if (this.isActive == this.minimized)
+ this.isActive = !this.minimized;
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="chatbar">
+ <content>
+ <xul:hbox align="end" pack="end" anonid="innerbox" class="chatbar-innerbox" mousethrough="always" flex="1">
+ <xul:spacer flex="1" anonid="spacer" class="chatbar-overflow-spacer"/>
+ <xul:toolbarbutton anonid="nub" class="chatbar-button" type="menu" collapsed="true" mousethrough="never">
+ <xul:menupopup anonid="nubMenu" oncommand="document.getBindingParent(this).showChat(event.target.chat)"/>
+ </xul:toolbarbutton>
+ <children/>
+ </xul:hbox>
+ </content>
+
+ <implementation implements="nsIDOMEventListener">
+ <constructor>
+ // to avoid reflows we cache the width of the nub.
+ this.cachedWidthNub = 0;
+ this._selectedChat = null;
+ </constructor>
+
+ <field name="innerbox" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "innerbox");
+ </field>
+
+ <field name="menupopup" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "nubMenu");
+ </field>
+
+ <field name="nub" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "nub");
+ </field>
+
+ <method name="focus">
+ <body><![CDATA[
+ if (!this.selectedChat)
+ return;
+ Services.focus.focusedWindow = this.selectedChat.contentWindow;
+ ]]></body>
+ </method>
+
+ <method name="_isChatFocused">
+ <parameter name="aChatbox"/>
+ <body><![CDATA[
+ // If there are no XBL bindings for the chat it can't be focused.
+ if (!aChatbox.content)
+ return false;
+ let fw = Services.focus.focusedWindow;
+ if (!fw)
+ return false;
+ // We want to see if the focused window is in the subtree below our browser...
+ let containingBrowser = fw.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+ return containingBrowser == aChatbox.content;
+ ]]></body>
+ </method>
+
+ <property name="selectedChat">
+ <getter><![CDATA[
+ return this._selectedChat;
+ ]]></getter>
+ <setter><![CDATA[
+ // this is pretty horrible, but we:
+ // * want to avoid doing touching 'selected' attribute when the
+ // specified chat is already selected.
+ // * remove 'activity' attribute on newly selected tab *even if*
+ // newly selected is already selected.
+ // * need to handle either current or new being null.
+ if (this._selectedChat != val) {
+ if (this._selectedChat) {
+ this._selectedChat.removeAttribute("selected");
+ }
+ this._selectedChat = val;
+ if (val) {
+ this._selectedChat.setAttribute("selected", "true");
+ }
+ }
+ if (val) {
+ this._selectedChat.removeAttribute("activity");
+ }
+ ]]></setter>
+ </property>
+
+ <field name="menuitemMap">new WeakMap()</field>
+ <field name="chatboxForURL">new Map();</field>
+
+ <property name="hasCollapsedChildren">
+ <getter><![CDATA[
+ return !!this.querySelector("[collapsed]");
+ ]]></getter>
+ </property>
+
+ <property name="collapsedChildren">
+ <getter><![CDATA[
+ // A generator yielding all collapsed chatboxes, in the order in
+ // which they should be restored.
+ let child = this.lastElementChild;
+ while (child) {
+ if (child.collapsed)
+ yield child;
+ child = child.previousElementSibling;
+ }
+ ]]></getter>
+ </property>
+
+ <property name="visibleChildren">
+ <getter><![CDATA[
+ // A generator yielding all non-collapsed chatboxes.
+ let child = this.firstElementChild;
+ while (child) {
+ if (!child.collapsed)
+ yield child;
+ child = child.nextElementSibling;
+ }
+ ]]></getter>
+ </property>
+
+ <property name="collapsibleChildren">
+ <getter><![CDATA[
+ // A generator yielding all children which are able to be collapsed
+ // in the order in which they should be collapsed.
+ // (currently this is all visible ones other than the selected one.)
+ for (let child of this.visibleChildren)
+ if (child != this.selectedChat)
+ yield child;
+ ]]></getter>
+ </property>
+
+ <method name="_selectAnotherChat">
+ <body><![CDATA[
+ // Select a different chat (as the currently selected one is no
+ // longer suitable as the selection - maybe it is being minimized or
+ // closed.) We only select non-minimized and non-collapsed chats,
+ // and if none are found, set the selectedChat to null.
+ // It's possible in the future we will track most-recently-selected
+ // chats or similar to find the "best" candidate - for now though
+ // the choice is somewhat arbitrary.
+ let moveFocus = this.selectedChat && this._isChatFocused(this.selectedChat);
+ for (let other of this.children) {
+ if (other != this.selectedChat && !other.minimized && !other.collapsed) {
+ this.selectedChat = other;
+ if (moveFocus)
+ this.focus();
+ return;
+ }
+ }
+ // can't find another - so set no chat as selected.
+ this.selectedChat = null;
+ ]]></body>
+ </method>
+
+ <method name="updateTitlebar">
+ <parameter name="aChatbox"/>
+ <body><![CDATA[
+ if (aChatbox.collapsed) {
+ let menuitem = this.menuitemMap.get(aChatbox);
+ if (aChatbox.getAttribute("activity")) {
+ menuitem.setAttribute("activity", true);
+ this.nub.setAttribute("activity", true);
+ }
+ menuitem.setAttribute("label", aChatbox.getAttribute("label"));
+ menuitem.setAttribute("image", aChatbox.getAttribute("image"));
+ }
+ ]]></body>
+ </method>
+
+ <method name="calcTotalWidthOf">
+ <parameter name="aElement"/>
+ <body><![CDATA[
+ let cs = document.defaultView.getComputedStyle(aElement);
+ let margins = parseInt(cs.marginLeft) + parseInt(cs.marginRight);
+ return aElement.getBoundingClientRect().width + margins;
+ ]]></body>
+ </method>
+
+ <method name="getTotalChildWidth">
+ <parameter name="aChatbox"/>
+ <body><![CDATA[
+ // These are from the CSS for the chatbox and must be kept in sync.
+ // We can't use calcTotalWidthOf due to the transitions...
+ const CHAT_WIDTH_OPEN = 260;
+ const CHAT_WIDTH_MINIMIZED = 160;
+ return aChatbox.minimized ? CHAT_WIDTH_MINIMIZED : CHAT_WIDTH_OPEN;
+ ]]></body>
+ </method>
+
+ <method name="collapseChat">
+ <parameter name="aChatbox"/>
+ <body><![CDATA[
+ // we ensure that the cached width for a child of this type is
+ // up-to-date so we can use it when resizing.
+ this.getTotalChildWidth(aChatbox);
+ aChatbox.collapsed = true;
+ aChatbox.isActive = false;
+ let menu = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "menuitem");
+ menu.setAttribute("class", "menuitem-iconic");
+ menu.setAttribute("label", aChatbox.contentDocument.title);
+ menu.setAttribute("image", aChatbox.getAttribute("image"));
+ menu.chat = aChatbox;
+ this.menuitemMap.set(aChatbox, menu);
+ this.menupopup.appendChild(menu);
+ this.nub.collapsed = false;
+ ]]></body>
+ </method>
+
+ <method name="showChat">
+ <parameter name="aChatbox"/>
+ <parameter name="aMode"/>
+ <body><![CDATA[
+ if ((aMode != "minimized") && aChatbox.minimized)
+ aChatbox.minimized = false;
+ if (this.selectedChat != aChatbox)
+ this.selectedChat = aChatbox;
+ if (!aChatbox.collapsed)
+ return; // already showing - no more to do.
+ this._showChat(aChatbox);
+ // showing a collapsed chat might mean another needs to be collapsed
+ // to make room...
+ this.resize();
+ ]]></body>
+ </method>
+
+ <method name="_showChat">
+ <parameter name="aChatbox"/>
+ <body><![CDATA[
+ // the actual implementation - doesn't check for overflow, assumes
+ // collapsed, etc.
+ let menuitem = this.menuitemMap.get(aChatbox);
+ this.menuitemMap.delete(aChatbox);
+ this.menupopup.removeChild(menuitem);
+ aChatbox.collapsed = false;
+ aChatbox.isActive = !aChatbox.minimized;
+ ]]></body>
+ </method>
+
+ <method name="remove">
+ <parameter name="aChatbox"/>
+ <body><![CDATA[
+ this._remove(aChatbox);
+ // The removal of a chat may mean a collapsed one can spring up,
+ // or that the popup should be hidden. We also defer the selection
+ // of another chat until after a resize, as a new candidate may
+ // become uncollapsed after the resize.
+ this.resize();
+ if (this.selectedChat == aChatbox) {
+ this._selectAnotherChat();
+ }
+ ]]></body>
+ </method>
+
+ <method name="_remove">
+ <parameter name="aChatbox"/>
+ <body><![CDATA[
+ aChatbox.content.socialErrorListener.remove();
+ this.removeChild(aChatbox);
+ // child might have been collapsed.
+ let menuitem = this.menuitemMap.get(aChatbox);
+ if (menuitem) {
+ this.menuitemMap.delete(aChatbox);
+ this.menupopup.removeChild(menuitem);
+ }
+ this.chatboxForURL.delete(aChatbox.getAttribute('src'));
+ ]]></body>
+ </method>
+
+ <method name="removeAll">
+ <body><![CDATA[
+ this.selectedChat = null;
+ while (this.firstElementChild) {
+ this._remove(this.firstElementChild);
+ }
+ // and the nub/popup must also die.
+ this.nub.collapsed = true;
+ ]]></body>
+ </method>
+
+ <method name="openChat">
+ <parameter name="aProvider"/>
+ <parameter name="aURL"/>
+ <parameter name="aCallback"/>
+ <parameter name="aMode"/>
+ <body><![CDATA[
+ let cb = this.chatboxForURL.get(aURL);
+ if (cb) {
+ cb = cb.get();
+ if (cb.parentNode) {
+ this.showChat(cb, aMode);
+ if (aCallback) {
+ if (cb._callbacks == null) {
+ // DOMContentLoaded has already fired, so callback now.
+ aCallback(cb.contentWindow);
+ } else {
+ // DOMContentLoaded for this chat is yet to fire...
+ cb._callbacks.push(aCallback);
+ }
+ }
+ return;
+ }
+ this.chatboxForURL.delete(aURL);
+ }
+ cb = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "chatbox");
+ // _callbacks is a javascript property instead of a <field> as it
+ // must exist before the (possibly delayed) bindings are created.
+ cb._callbacks = [aCallback];
+ // src also a javascript property; the src attribute is set in the ctor.
+ cb.src = aURL;
+ if (aMode == "minimized")
+ cb.setAttribute("minimized", "true");
+ cb.setAttribute("origin", aProvider.origin);
+ this.insertBefore(cb, this.firstChild);
+ this.selectedChat = cb;
+ this.chatboxForURL.set(aURL, Cu.getWeakReference(cb));
+ this.resize();
+ ]]></body>
+ </method>
+
+ <method name="resize">
+ <body><![CDATA[
+ // Checks the current size against the collapsed state of children
+ // and collapses or expands as necessary such that as many as possible
+ // are shown.
+ // So 2 basic strategies:
+ // * Collapse/Expand one at a time until we can't collapse/expand any
+ // more - but this is one reflow per change.
+ // * Calculate the dimensions ourself and choose how many to collapse
+ // or expand based on this, then do them all in one go. This is one
+ // reflow regardless of how many we change.
+ // So we go the more complicated but more efficient second option...
+ let availWidth = this.getBoundingClientRect().width;
+ let currentWidth = 0;
+ if (!this.nub.collapsed) { // the nub is visible.
+ if (!this.cachedWidthNub)
+ this.cachedWidthNub = this.calcTotalWidthOf(this.nub);
+ currentWidth += this.cachedWidthNub;
+ }
+ for (let child of this.visibleChildren) {
+ currentWidth += this.getTotalChildWidth(child);
+ }
+
+ if (currentWidth > availWidth) {
+ // we need to collapse some.
+ let toCollapse = [];
+ for (let child of this.collapsibleChildren) {
+ if (currentWidth <= availWidth)
+ break;
+ toCollapse.push(child);
+ currentWidth -= this.getTotalChildWidth(child);
+ }
+ if (toCollapse.length) {
+ for (let child of toCollapse)
+ this.collapseChat(child);
+ }
+ } else if (currentWidth < availWidth) {
+ // we *might* be able to expand some - see how many.
+ // XXX - if this was clever, it could know when removing the nub
+ // leaves enough space to show all collapsed
+ let toShow = [];
+ for (let child of this.collapsedChildren) {
+ currentWidth += this.getTotalChildWidth(child);
+ if (currentWidth > availWidth)
+ break;
+ toShow.push(child);
+ }
+ for (let child of toShow)
+ this._showChat(child);
+
+ // If none remain collapsed remove the nub.
+ if (!this.hasCollapsedChildren) {
+ this.nub.collapsed = true;
+ }
+ }
+ // else: achievement unlocked - we are pixel-perfect!
+ ]]></body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ if (aEvent.type == "resize" && aEvent.eventPhase == aEvent.BUBBLING_PHASE) {
+ this.resize();
+ }
+ ]]></body>
+ </method>
+
+ <method name="_getDragTarget">
+ <parameter name="event"/>
+ <body><![CDATA[
+ return event.target.localName == "chatbox" ? event.target : null;
+ ]]></body>
+ </method>
+
+ <!-- Moves a chatbox to a new window. -->
+ <method name="detachChatbox">
+ <parameter name="aChatbox"/>
+ <parameter name="aOptions"/>
+ <parameter name="aCallback"/>
+ <body><![CDATA[
+ let options = "";
+ for (let name in aOptions)
+ options += "," + name + "=" + aOptions[name];
+
+ let otherWin = window.openDialog("chrome://browser/content/chatWindow.xul", null, "chrome,all" + options);
+
+ otherWin.addEventListener("load", function _chatLoad(event) {
+ if (event.target != otherWin.document)
+ return;
+
+ otherWin.removeEventListener("load", _chatLoad, true);
+ let otherChatbox = otherWin.document.getElementById("chatter");
+ aChatbox.swapDocShells(otherChatbox);
+ aChatbox.close();
+ if (aCallback)
+ aCallback(otherWin);
+ }, true);
+ ]]></body>
+ </method>
+
+ </implementation>
+
+ <handlers>
+ <handler event="popupshown"><![CDATA[
+ this.nub.removeAttribute("activity");
+ ]]></handler>
+ <handler event="load"><![CDATA[
+ window.addEventListener("resize", this);
+ ]]></handler>
+ <handler event="unload"><![CDATA[
+ window.removeEventListener("resize", this);
+ ]]></handler>
+
+ <handler event="dragstart"><![CDATA[
+ // chat window dragging is essentially duplicated from tabbrowser.xml
+ // to acheive the same visual experience
+ let chatbox = this._getDragTarget(event);
+ if (!chatbox) {
+ return;
+ }
+
+ let dt = event.dataTransfer;
+ // we do not set a url in the drag data to prevent moving to tabbrowser
+ // or otherwise having unexpected drop handlers do something with our
+ // chatbox
+ dt.mozSetDataAt("application/x-moz-chatbox", chatbox, 0);
+
+ // Set the cursor to an arrow during tab drags.
+ dt.mozCursor = "default";
+
+ // Create a canvas to which we capture the current tab.
+ // Until canvas is HiDPI-aware (bug 780362), we need to scale the desired
+ // canvas size (in CSS pixels) to the window's backing resolution in order
+ // to get a full-resolution drag image for use on HiDPI displays.
+ let windowUtils = window.getInterface(Ci.nsIDOMWindowUtils);
+ let scale = windowUtils.screenPixelsPerCSSPixel / windowUtils.fullZoom;
+ let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+ canvas.mozOpaque = true;
+ canvas.width = 160 * scale;
+ canvas.height = 90 * scale;
+ PageThumbs.captureToCanvas(chatbox.contentWindow, canvas);
+ dt.setDragImage(canvas, -16 * scale, -16 * scale);
+
+ event.stopPropagation();
+ ]]></handler>
+
+ <handler event="dragend"><![CDATA[
+ let dt = event.dataTransfer;
+ let draggedChat = dt.mozGetDataAt("application/x-moz-chatbox", 0);
+ if (dt.mozUserCancelled || dt.dropEffect != "none") {
+ return;
+ }
+
+ let eX = event.screenX;
+ let eY = event.screenY;
+ // screen.availLeft et. al. only check the screen that this window is on,
+ // but we want to look at the screen the tab is being dropped onto.
+ let sX = {}, sY = {}, sWidth = {}, sHeight = {};
+ Cc["@mozilla.org/gfx/screenmanager;1"]
+ .getService(Ci.nsIScreenManager)
+ .screenForRect(eX, eY, 1, 1)
+ .GetAvailRect(sX, sY, sWidth, sHeight);
+ // default size for the chat window as used in chatWindow.xul, use them
+ // here to attempt to keep the window fully within the screen when
+ // opening at the drop point. If the user has resized the window to
+ // something larger (which gets persisted), at least a good portion of
+ // the window should still be within the screen.
+ let winWidth = 400;
+ let winHeight = 420;
+ // ensure new window entirely within screen
+ let left = Math.min(Math.max(eX, sX.value),
+ sX.value + sWidth.value - winWidth);
+ let top = Math.min(Math.max(eY, sY.value),
+ sY.value + sHeight.value - winHeight);
+
+ let provider = Social._getProviderFromOrigin(draggedChat.content.getAttribute("origin"));
+ this.detachChatbox(draggedChat, { screenX: left, screenY: top }, win => {
+ win.document.title = provider.name;
+ });
+
+ event.stopPropagation();
+ ]]></handler>
+ </handlers>
+ </binding>
+
+</bindings>
diff --git a/browser/base/content/softwareUpdateOverlay.xul b/browser/base/content/softwareUpdateOverlay.xul
new file mode 100644
index 000000000..01170e46c
--- /dev/null
+++ b/browser/base/content/softwareUpdateOverlay.xul
@@ -0,0 +1,18 @@
+<?xml version="1.0"?>
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?>
+
+<overlay id="softwareUpdateOverlay"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<window id="updates">
+
+#include browserMountPoints.inc
+
+</window>
+
+</overlay>
diff --git a/browser/base/content/sync/aboutSyncTabs-bindings.xml b/browser/base/content/sync/aboutSyncTabs-bindings.xml
new file mode 100644
index 000000000..e6108209a
--- /dev/null
+++ b/browser/base/content/sync/aboutSyncTabs-bindings.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<bindings id="tabBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="tab-listing" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+ <content>
+ <xul:hbox flex="1">
+ <xul:vbox pack="start">
+ <xul:image class="tabIcon"
+ xbl:inherits="src=icon"/>
+ </xul:vbox>
+ <xul:vbox pack="start" flex="1">
+ <xul:label xbl:inherits="value=title,selected"
+ crop="end" flex="1" class="title"/>
+ <xul:label xbl:inherits="value=url,selected"
+ crop="end" flex="1" class="url"/>
+ </xul:vbox>
+ </xul:hbox>
+ </content>
+ <handlers>
+ <handler event="dblclick" button="0">
+ <![CDATA[
+ RemoteTabViewer.openSelected();
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="client-listing" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+ <content>
+ <xul:hbox pack="start" align="center" onfocus="event.target.blur()" onselect="return false;">
+ <xul:image/>
+ <xul:label xbl:inherits="value=clientName"
+ class="clientName"
+ crop="center" flex="1"/>
+ </xul:hbox>
+ </content>
+ </binding>
+</bindings>
diff --git a/browser/base/content/sync/aboutSyncTabs.css b/browser/base/content/sync/aboutSyncTabs.css
new file mode 100644
index 000000000..5a353175b
--- /dev/null
+++ b/browser/base/content/sync/aboutSyncTabs.css
@@ -0,0 +1,11 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+richlistitem[type="tab"] {
+ -moz-binding: url(chrome://browser/content/sync/aboutSyncTabs-bindings.xml#tab-listing);
+}
+
+richlistitem[type="client"] {
+ -moz-binding: url(chrome://browser/content/sync/aboutSyncTabs-bindings.xml#client-listing);
+}
diff --git a/browser/base/content/sync/aboutSyncTabs.js b/browser/base/content/sync/aboutSyncTabs.js
new file mode 100644
index 000000000..815f6120c
--- /dev/null
+++ b/browser/base/content/sync/aboutSyncTabs.js
@@ -0,0 +1,251 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+const Cu = Components.utils;
+
+Cu.import("resource://services-sync/main.js");
+Cu.import("resource:///modules/PlacesUIUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+let RemoteTabViewer = {
+ _tabsList: null,
+
+ init: function () {
+ Services.obs.addObserver(this, "weave:service:login:finish", false);
+ Services.obs.addObserver(this, "weave:engine:sync:finish", false);
+
+ this._tabsList = document.getElementById("tabsList");
+
+ this.buildList(true);
+ },
+
+ uninit: function () {
+ Services.obs.removeObserver(this, "weave:service:login:finish");
+ Services.obs.removeObserver(this, "weave:engine:sync:finish");
+ },
+
+ buildList: function(force) {
+ if (!Weave.Service.isLoggedIn || !this._refetchTabs(force))
+ return;
+ //XXXzpao We should say something about not being logged in & not having data
+ // or tell the appropriate condition. (bug 583344)
+
+ this._generateTabList();
+ },
+
+ createItem: function(attrs) {
+ let item = document.createElement("richlistitem");
+
+ // Copy the attributes from the argument into the item
+ for (let attr in attrs)
+ item.setAttribute(attr, attrs[attr]);
+
+ if (attrs["type"] == "tab")
+ item.label = attrs.title != "" ? attrs.title : attrs.url;
+
+ return item;
+ },
+
+ filterTabs: function(event) {
+ let val = event.target.value.toLowerCase();
+ let numTabs = this._tabsList.getRowCount();
+ let clientTabs = 0;
+ let currentClient = null;
+ for (let i = 0;i < numTabs;i++) {
+ let item = this._tabsList.getItemAtIndex(i);
+ let hide = false;
+ if (item.getAttribute("type") == "tab") {
+ if (!item.getAttribute("url").toLowerCase().contains(val) &&
+ !item.getAttribute("title").toLowerCase().contains(val))
+ hide = true;
+ else
+ clientTabs++;
+ }
+ else if (item.getAttribute("type") == "client") {
+ if (currentClient) {
+ if (clientTabs == 0)
+ currentClient.hidden = true;
+ }
+ currentClient = item;
+ clientTabs = 0;
+ }
+ item.hidden = hide;
+ }
+ if (clientTabs == 0)
+ currentClient.hidden = true;
+ },
+
+ openSelected: function() {
+ let items = this._tabsList.selectedItems;
+ let urls = [];
+ for (let i = 0;i < items.length;i++) {
+ if (items[i].getAttribute("type") == "tab") {
+ urls.push(items[i].getAttribute("url"));
+ let index = this._tabsList.getIndexOfItem(items[i]);
+ this._tabsList.removeItemAt(index);
+ }
+ }
+ if (urls.length) {
+ getTopWin().gBrowser.loadTabs(urls);
+ this._tabsList.clearSelection();
+ }
+ },
+
+ bookmarkSingleTab: function() {
+ let item = this._tabsList.selectedItems[0];
+ let uri = Weave.Utils.makeURI(item.getAttribute("url"));
+ let title = item.getAttribute("title");
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "bookmark"
+ , uri: uri
+ , title: title
+ , hiddenRows: [ "description"
+ , "location"
+ , "loadInSidebar"
+ , "keyword" ]
+ }, window.top);
+ },
+
+ bookmarkSelectedTabs: function() {
+ let items = this._tabsList.selectedItems;
+ let URIs = [];
+ for (let i = 0;i < items.length;i++) {
+ if (items[i].getAttribute("type") == "tab") {
+ let uri = Weave.Utils.makeURI(items[i].getAttribute("url"));
+ if (!uri)
+ continue;
+
+ URIs.push(uri);
+ }
+ }
+ if (URIs.length) {
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "folder"
+ , URIList: URIs
+ , hiddenRows: [ "description" ]
+ }, window.top);
+ }
+ },
+
+ _generateTabList: function() {
+ let engine = Weave.Service.engineManager.get("tabs");
+ let list = this._tabsList;
+
+ // clear out existing richlistitems
+ let count = list.getRowCount();
+ if (count > 0) {
+ for (let i = count - 1; i >= 0; i--)
+ list.removeItemAt(i);
+ }
+
+ for (let [guid, client] in Iterator(engine.getAllClients())) {
+ // Create the client node, but don't add it in-case we don't show any tabs
+ let appendClient = true;
+ let seenURLs = {};
+ client.tabs.forEach(function({title, urlHistory, icon}) {
+ let url = urlHistory[0];
+ if (engine.locallyOpenTabMatchesURL(url) || url in seenURLs)
+ return;
+
+ seenURLs[url] = null;
+
+ if (appendClient) {
+ let attrs = {
+ type: "client",
+ clientName: client.clientName,
+ class: Weave.Service.clientsEngine.isMobile(client.id) ? "mobile" : "desktop"
+ };
+ let clientEnt = this.createItem(attrs);
+ list.appendChild(clientEnt);
+ appendClient = false;
+ clientEnt.disabled = true;
+ }
+ let attrs = {
+ type: "tab",
+ title: title || url,
+ url: url,
+ icon: Weave.Utils.getIcon(icon)
+ }
+ let tab = this.createItem(attrs);
+ list.appendChild(tab);
+ }, this);
+ }
+ },
+
+ adjustContextMenu: function(event) {
+ let mode = "all";
+ switch (this._tabsList.selectedItems.length) {
+ case 0:
+ break;
+ case 1:
+ mode = "single"
+ break;
+ default:
+ mode = "multiple";
+ break;
+ }
+ let menu = document.getElementById("tabListContext");
+ let el = menu.firstChild;
+ while (el) {
+ let showFor = el.getAttribute("showFor");
+ if (showFor)
+ el.hidden = showFor != mode && showFor != "all";
+
+ el = el.nextSibling;
+ }
+ },
+
+ _refetchTabs: function(force) {
+ if (!force) {
+ // Don't bother refetching tabs if we already did so recently
+ let lastFetch = 0;
+ try {
+ lastFetch = Services.prefs.getIntPref("services.sync.lastTabFetch");
+ }
+ catch (e) { /* Just use the default value of 0 */ }
+ let now = Math.floor(Date.now() / 1000);
+ if (now - lastFetch < 30)
+ return false;
+ }
+
+ // if Clients hasn't synced yet this session, need to sync it as well
+ if (Weave.Service.clientsEngine.lastSync == 0)
+ Weave.Service.clientsEngine.sync();
+
+ // Force a sync only for the tabs engine
+ let engine = Weave.Service.engineManager.get("tabs");
+ engine.lastModified = null;
+ engine.sync();
+ Services.prefs.setIntPref("services.sync.lastTabFetch",
+ Math.floor(Date.now() / 1000));
+
+ return true;
+ },
+
+ observe: function(subject, topic, data) {
+ switch (topic) {
+ case "weave:service:login:finish":
+ this.buildList(true);
+ break;
+ case "weave:engine:sync:finish":
+ if (subject == "tabs")
+ this._generateTabList();
+ break;
+ }
+ },
+
+ handleClick: function(event) {
+ if (event.target.getAttribute("type") != "tab")
+ return;
+
+ if (event.button == 1) {
+ let url = event.target.getAttribute("url");
+ openUILink(url, event);
+ let index = this._tabsList.getIndexOfItem(event.target);
+ this._tabsList.removeItemAt(index);
+ }
+ }
+}
+
diff --git a/browser/base/content/sync/aboutSyncTabs.xul b/browser/base/content/sync/aboutSyncTabs.xul
new file mode 100644
index 000000000..a4aa0032f
--- /dev/null
+++ b/browser/base/content/sync/aboutSyncTabs.xul
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/aboutSyncTabs.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/sync/aboutSyncTabs.css" type="text/css"?>
+
+<!DOCTYPE window [
+ <!ENTITY % aboutSyncTabsDTD SYSTEM "chrome://browser/locale/aboutSyncTabs.dtd">
+ %aboutSyncTabsDTD;
+]>
+
+<window id="tabs-display"
+ onload="RemoteTabViewer.init()"
+ onunload="RemoteTabViewer.uninit()"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="&tabs.otherDevices.label;">
+ <script type="application/javascript;version=1.8" src="chrome://browser/content/sync/aboutSyncTabs.js"/>
+ <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
+ <html:head>
+ <html:link rel="icon" href="chrome://browser/skin/sync-16.png"/>
+ </html:head>
+
+ <popupset id="contextmenus">
+ <menupopup id="tabListContext">
+ <menuitem label="&tabs.context.openTab.label;"
+ accesskey="&tabs.context.openTab.accesskey;"
+ oncommand="RemoteTabViewer.openSelected()"
+ showFor="single"/>
+ <menuitem label="&tabs.context.bookmarkSingleTab.label;"
+ accesskey="&tabs.context.bookmarkSingleTab.accesskey;"
+ oncommand="RemoteTabViewer.bookmarkSingleTab(event)"
+ showFor="single"/>
+ <menuitem label="&tabs.context.openMultipleTabs.label;"
+ accesskey="&tabs.context.openMultipleTabs.accesskey;"
+ oncommand="RemoteTabViewer.openSelected()"
+ showFor="multiple"/>
+ <menuitem label="&tabs.context.bookmarkMultipleTabs.label;"
+ accesskey="&tabs.context.bookmarkMultipleTabs.accesskey;"
+ oncommand="RemoteTabViewer.bookmarkSelectedTabs()"
+ showFor="multiple"/>
+ <menuseparator/>
+ <menuitem label="&tabs.context.refreshList.label;"
+ accesskey="&tabs.context.refreshList.accesskey;"
+ oncommand="RemoteTabViewer.buildList()"
+ showFor="all"/>
+ </menupopup>
+ </popupset>
+ <richlistbox context="tabListContext" id="tabsList" seltype="multiple"
+ align="center" flex="1"
+ onclick="RemoteTabViewer.handleClick(event)"
+ oncontextmenu="RemoteTabViewer.adjustContextMenu(event)">
+ <hbox id="headers" align="center">
+ <label id="tabsListHeading"
+ value="&tabs.otherDevices.label;"/>
+ <spacer flex="1"/>
+ <textbox type="search"
+ emptytext="&tabs.searchText.label;"
+ oncommand="RemoteTabViewer.filterTabs(event)"/>
+ </hbox>
+
+ </richlistbox>
+</window>
+
diff --git a/browser/base/content/sync/addDevice.js b/browser/base/content/sync/addDevice.js
new file mode 100644
index 000000000..556e75768
--- /dev/null
+++ b/browser/base/content/sync/addDevice.js
@@ -0,0 +1,157 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cu = Components.utils;
+
+Cu.import("resource://services-sync/main.js");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const PIN_PART_LENGTH = 4;
+
+const ADD_DEVICE_PAGE = 0;
+const SYNC_KEY_PAGE = 1;
+const DEVICE_CONNECTED_PAGE = 2;
+
+let gSyncAddDevice = {
+
+ init: function init() {
+ this.pin1.setAttribute("maxlength", PIN_PART_LENGTH);
+ this.pin2.setAttribute("maxlength", PIN_PART_LENGTH);
+ this.pin3.setAttribute("maxlength", PIN_PART_LENGTH);
+
+ this.nextFocusEl = {pin1: this.pin2,
+ pin2: this.pin3,
+ pin3: this.wizard.getButton("next")};
+
+ this.throbber = document.getElementById("pairDeviceThrobber");
+ this.errorRow = document.getElementById("errorRow");
+
+ // Kick off a sync. That way the server will have the most recent data from
+ // this computer and it will show up immediately on the new device.
+ Weave.Service.scheduler.scheduleNextSync(0);
+ },
+
+ onPageShow: function onPageShow() {
+ this.wizard.getButton("back").hidden = true;
+
+ switch (this.wizard.pageIndex) {
+ case ADD_DEVICE_PAGE:
+ this.onTextBoxInput();
+ this.wizard.canRewind = false;
+ this.wizard.getButton("next").hidden = false;
+ this.pin1.focus();
+ break;
+ case SYNC_KEY_PAGE:
+ this.wizard.canAdvance = false;
+ this.wizard.canRewind = true;
+ this.wizard.getButton("back").hidden = false;
+ this.wizard.getButton("next").hidden = true;
+ document.getElementById("weavePassphrase").value =
+ Weave.Utils.hyphenatePassphrase(Weave.Service.identity.syncKey);
+ break;
+ case DEVICE_CONNECTED_PAGE:
+ this.wizard.canAdvance = true;
+ this.wizard.canRewind = false;
+ this.wizard.getButton("cancel").hidden = true;
+ break;
+ }
+ },
+
+ onWizardAdvance: function onWizardAdvance() {
+ switch (this.wizard.pageIndex) {
+ case ADD_DEVICE_PAGE:
+ this.startTransfer();
+ return false;
+ case DEVICE_CONNECTED_PAGE:
+ window.close();
+ return false;
+ }
+ return true;
+ },
+
+ startTransfer: function startTransfer() {
+ this.errorRow.hidden = true;
+ // When onAbort is called, Weave may already be gone.
+ const JPAKE_ERROR_USERABORT = Weave.JPAKE_ERROR_USERABORT;
+
+ let self = this;
+ let jpakeclient = this._jpakeclient = new Weave.JPAKEClient({
+ onPaired: function onPaired() {
+ let credentials = {account: Weave.Service.identity.account,
+ password: Weave.Service.identity.basicPassword,
+ synckey: Weave.Service.identity.syncKey,
+ serverURL: Weave.Service.serverURL};
+ jpakeclient.sendAndComplete(credentials);
+ },
+ onComplete: function onComplete() {
+ delete self._jpakeclient;
+ self.wizard.pageIndex = DEVICE_CONNECTED_PAGE;
+
+ // Schedule a Sync for soonish to fetch the data uploaded by the
+ // device with which we just paired.
+ Weave.Service.scheduler.scheduleNextSync(Weave.Service.scheduler.activeInterval);
+ },
+ onAbort: function onAbort(error) {
+ delete self._jpakeclient;
+
+ // Aborted by user, ignore.
+ if (error == JPAKE_ERROR_USERABORT) {
+ return;
+ }
+
+ self.errorRow.hidden = false;
+ self.throbber.hidden = true;
+ self.pin1.value = self.pin2.value = self.pin3.value = "";
+ self.pin1.disabled = self.pin2.disabled = self.pin3.disabled = false;
+ self.pin1.focus();
+ }
+ });
+ this.throbber.hidden = false;
+ this.pin1.disabled = this.pin2.disabled = this.pin3.disabled = true;
+ this.wizard.canAdvance = false;
+
+ let pin = this.pin1.value + this.pin2.value + this.pin3.value;
+ let expectDelay = false;
+ jpakeclient.pairWithPIN(pin, expectDelay);
+ },
+
+ onWizardBack: function onWizardBack() {
+ if (this.wizard.pageIndex != SYNC_KEY_PAGE)
+ return true;
+
+ this.wizard.pageIndex = ADD_DEVICE_PAGE;
+ return false;
+ },
+
+ onWizardCancel: function onWizardCancel() {
+ if (this._jpakeclient) {
+ this._jpakeclient.abort();
+ delete this._jpakeclient;
+ }
+ return true;
+ },
+
+ onTextBoxInput: function onTextBoxInput(textbox) {
+ if (textbox && textbox.value.length == PIN_PART_LENGTH)
+ this.nextFocusEl[textbox.id].focus();
+
+ this.wizard.canAdvance = (this.pin1.value.length == PIN_PART_LENGTH
+ && this.pin2.value.length == PIN_PART_LENGTH
+ && this.pin3.value.length == PIN_PART_LENGTH);
+ },
+
+ goToSyncKeyPage: function goToSyncKeyPage() {
+ this.wizard.pageIndex = SYNC_KEY_PAGE;
+ }
+
+};
+// onWizardAdvance() and onPageShow() are run before init() so we'll set
+// these up as lazy getters.
+["wizard", "pin1", "pin2", "pin3"].forEach(function (id) {
+ XPCOMUtils.defineLazyGetter(gSyncAddDevice, id, function() {
+ return document.getElementById(id);
+ });
+});
diff --git a/browser/base/content/sync/addDevice.xul b/browser/base/content/sync/addDevice.xul
new file mode 100644
index 000000000..83c3b7b3c
--- /dev/null
+++ b/browser/base/content/sync/addDevice.xul
@@ -0,0 +1,129 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/syncSetup.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/syncCommon.css" type="text/css"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
+<!ENTITY % syncSetupDTD SYSTEM "chrome://browser/locale/syncSetup.dtd">
+%brandDTD;
+%syncBrandDTD;
+%syncSetupDTD;
+]>
+<wizard xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ id="wizard"
+ title="&pairDevice.title.label;"
+ windowtype="Sync:AddDevice"
+ persist="screenX screenY"
+ onwizardnext="return gSyncAddDevice.onWizardAdvance();"
+ onwizardback="return gSyncAddDevice.onWizardBack();"
+ onwizardcancel="gSyncAddDevice.onWizardCancel();"
+ onload="gSyncAddDevice.init();">
+
+ <script type="application/javascript"
+ src="chrome://browser/content/sync/addDevice.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/sync/utils.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/utilityOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://global/content/printUtils.js"/>
+
+ <wizardpage id="addDevicePage"
+ label="&pairDevice.title.label;"
+ onpageshow="gSyncAddDevice.onPageShow();">
+ <description>
+ &pairDevice.dialog.description.label;
+ <label class="text-link"
+ value="&addDevice.showMeHow.label;"
+ href="https://services.mozilla.com/sync/help/add-device"/>
+ </description>
+ <separator class="groove-thin"/>
+ <description>
+ &addDevice.dialog.enterCode.label;
+ </description>
+ <separator class="groove-thin"/>
+ <vbox align="center">
+ <textbox id="pin1"
+ class="pin"
+ oninput="gSyncAddDevice.onTextBoxInput(this);"
+ onfocus="this.select();"
+ />
+ <textbox id="pin2"
+ class="pin"
+ oninput="gSyncAddDevice.onTextBoxInput(this);"
+ onfocus="this.select();"
+ />
+ <textbox id="pin3"
+ class="pin"
+ oninput="gSyncAddDevice.onTextBoxInput(this);"
+ onfocus="this.select();"
+ />
+ </vbox>
+ <separator class="groove-thin"/>
+ <vbox id="pairDeviceThrobber" align="center" hidden="true">
+ <image/>
+ </vbox>
+ <hbox id="errorRow" pack="center" hidden="true">
+ <image class="statusIcon" status="error"/>
+ <label class="status"
+ value="&addDevice.dialog.tryAgain.label;"/>
+ </hbox>
+ <spacer flex="3"/>
+ <label class="text-link"
+ value="&addDevice.dontHaveDevice.label;"
+ onclick="gSyncAddDevice.goToSyncKeyPage();"/>
+ </wizardpage>
+
+ <!-- Need a non-empty label here, otherwise we get a default label on Mac -->
+ <wizardpage id="syncKeyPage"
+ label=" "
+ onpageshow="gSyncAddDevice.onPageShow();">
+ <description>
+ &addDevice.dialog.recoveryKey.label;
+ </description>
+ <spacer/>
+
+ <groupbox>
+ <label value="&recoveryKeyEntry.label;"
+ accesskey="&recoveryKeyEntry.accesskey;"
+ control="weavePassphrase"/>
+ <textbox id="weavePassphrase"
+ readonly="true"/>
+ </groupbox>
+
+ <groupbox align="center">
+ <description>&recoveryKeyBackup.description;</description>
+ <hbox>
+ <button id="printSyncKeyButton"
+ label="&button.syncKeyBackup.print.label;"
+ accesskey="&button.syncKeyBackup.print.accesskey;"
+ oncommand="gSyncUtils.passphrasePrint('weavePassphrase');"/>
+ <button id="saveSyncKeyButton"
+ label="&button.syncKeyBackup.save.label;"
+ accesskey="&button.syncKeyBackup.save.accesskey;"
+ oncommand="gSyncUtils.passphraseSave('weavePassphrase');"/>
+ </hbox>
+ </groupbox>
+ </wizardpage>
+
+ <wizardpage id="deviceConnectedPage"
+ label="&addDevice.dialog.connected.label;"
+ onpageshow="gSyncAddDevice.onPageShow();">
+ <vbox align="center">
+ <image id="successPageIcon"/>
+ </vbox>
+ <separator/>
+ <description class="normal">
+ &addDevice.dialog.successful.label;
+ </description>
+ </wizardpage>
+
+</wizard>
diff --git a/browser/base/content/sync/genericChange.js b/browser/base/content/sync/genericChange.js
new file mode 100644
index 000000000..6d1ce9485
--- /dev/null
+++ b/browser/base/content/sync/genericChange.js
@@ -0,0 +1,234 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+
+Components.utils.import("resource://services-sync/main.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+let Change = {
+ _dialog: null,
+ _dialogType: null,
+ _status: null,
+ _statusIcon: null,
+ _firstBox: null,
+ _secondBox: null,
+
+ get _passphraseBox() {
+ delete this._passphraseBox;
+ return this._passphraseBox = document.getElementById("passphraseBox");
+ },
+
+ get _currentPasswordInvalid() {
+ return Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED;
+ },
+
+ get _updatingPassphrase() {
+ return this._dialogType == "UpdatePassphrase";
+ },
+
+ onLoad: function Change_onLoad() {
+ /* Load labels */
+ let introText = document.getElementById("introText");
+ let introText2 = document.getElementById("introText2");
+ let warningText = document.getElementById("warningText");
+
+ // load some other elements & info from the window
+ this._dialog = document.getElementById("change-dialog");
+ this._dialogType = window.arguments[0];
+ this._duringSetup = window.arguments[1];
+ this._status = document.getElementById("status");
+ this._statusIcon = document.getElementById("statusIcon");
+ this._statusRow = document.getElementById("statusRow");
+ this._firstBox = document.getElementById("textBox1");
+ this._secondBox = document.getElementById("textBox2");
+
+ this._dialog.getButton("finish").disabled = true;
+ this._dialog.getButton("back").hidden = true;
+
+ this._stringBundle =
+ Services.strings.createBundle("chrome://browser/locale/syncGenericChange.properties");
+
+ switch (this._dialogType) {
+ case "UpdatePassphrase":
+ case "ResetPassphrase":
+ document.getElementById("textBox1Row").hidden = true;
+ document.getElementById("textBox2Row").hidden = true;
+ document.getElementById("passphraseLabel").value
+ = this._str("new.recoverykey.label");
+ document.getElementById("passphraseSpacer").hidden = false;
+
+ if (this._updatingPassphrase) {
+ document.getElementById("passphraseHelpBox").hidden = false;
+ document.title = this._str("new.recoverykey.title");
+ introText.textContent = this._str("new.recoverykey.introText");
+ this._dialog.getButton("finish").label
+ = this._str("new.recoverykey.acceptButton");
+ }
+ else {
+ document.getElementById("generatePassphraseButton").hidden = false;
+ document.getElementById("passphraseBackupButtons").hidden = false;
+ let pp = Weave.Service.identity.syncKey;
+ if (Weave.Utils.isPassphrase(pp))
+ pp = Weave.Utils.hyphenatePassphrase(pp);
+ this._passphraseBox.value = pp;
+ this._passphraseBox.focus();
+ document.title = this._str("change.recoverykey.title");
+ introText.textContent = this._str("change.synckey.introText2");
+ warningText.textContent = this._str("change.recoverykey.warningText");
+ this._dialog.getButton("finish").label
+ = this._str("change.recoverykey.acceptButton");
+ if (this._duringSetup) {
+ this._dialog.getButton("finish").disabled = false;
+ }
+ }
+ break;
+ case "ChangePassword":
+ document.getElementById("passphraseRow").hidden = true;
+ let box1label = document.getElementById("textBox1Label");
+ let box2label = document.getElementById("textBox2Label");
+ box1label.value = this._str("new.password.label");
+
+ if (this._currentPasswordInvalid) {
+ document.title = this._str("new.password.title");
+ introText.textContent = this._str("new.password.introText");
+ this._dialog.getButton("finish").label
+ = this._str("new.password.acceptButton");
+ document.getElementById("textBox2Row").hidden = true;
+ }
+ else {
+ document.title = this._str("change.password.title");
+ box2label.value = this._str("new.password.confirm");
+ introText.textContent = this._str("change.password3.introText");
+ warningText.textContent = this._str("change.password.warningText");
+ this._dialog.getButton("finish").label
+ = this._str("change.password.acceptButton");
+ }
+ break;
+ }
+ document.getElementById("change-page")
+ .setAttribute("label", document.title);
+ },
+
+ _clearStatus: function _clearStatus() {
+ this._status.value = "";
+ this._statusIcon.removeAttribute("status");
+ },
+
+ _updateStatus: function Change__updateStatus(str, state) {
+ this._updateStatusWithString(this._str(str), state);
+ },
+
+ _updateStatusWithString: function Change__updateStatusWithString(string, state) {
+ this._statusRow.hidden = false;
+ this._status.value = string;
+ this._statusIcon.setAttribute("status", state);
+
+ let error = state == "error";
+ this._dialog.getButton("cancel").disabled = !error;
+ this._dialog.getButton("finish").disabled = !error;
+ document.getElementById("printSyncKeyButton").disabled = !error;
+ document.getElementById("saveSyncKeyButton").disabled = !error;
+
+ if (state == "success")
+ window.setTimeout(window.close, 1500);
+ },
+
+ onDialogAccept: function() {
+ switch (this._dialogType) {
+ case "UpdatePassphrase":
+ case "ResetPassphrase":
+ return this.doChangePassphrase();
+ break;
+ case "ChangePassword":
+ return this.doChangePassword();
+ break;
+ }
+ },
+
+ doGeneratePassphrase: function () {
+ let passphrase = Weave.Utils.generatePassphrase();
+ this._passphraseBox.value = Weave.Utils.hyphenatePassphrase(passphrase);
+ this._dialog.getButton("finish").disabled = false;
+ },
+
+ doChangePassphrase: function Change_doChangePassphrase() {
+ let pp = Weave.Utils.normalizePassphrase(this._passphraseBox.value);
+ if (this._updatingPassphrase) {
+ Weave.Service.identity.syncKey = pp;
+ if (Weave.Service.login()) {
+ this._updateStatus("change.recoverykey.success", "success");
+ Weave.Service.persistLogin();
+ Weave.Service.scheduler.delayedAutoConnect(0);
+ }
+ else {
+ this._updateStatus("new.passphrase.status.incorrect", "error");
+ }
+ }
+ else {
+ this._updateStatus("change.recoverykey.label", "active");
+
+ if (Weave.Service.changePassphrase(pp))
+ this._updateStatus("change.recoverykey.success", "success");
+ else
+ this._updateStatus("change.recoverykey.error", "error");
+ }
+
+ return false;
+ },
+
+ doChangePassword: function Change_doChangePassword() {
+ if (this._currentPasswordInvalid) {
+ Weave.Service.identity.basicPassword = this._firstBox.value;
+ if (Weave.Service.login()) {
+ this._updateStatus("change.password.status.success", "success");
+ Weave.Service.persistLogin();
+ }
+ else {
+ this._updateStatus("new.password.status.incorrect", "error");
+ }
+ }
+ else {
+ this._updateStatus("change.password.status.active", "active");
+
+ if (Weave.Service.changePassword(this._firstBox.value))
+ this._updateStatus("change.password.status.success", "success");
+ else
+ this._updateStatus("change.password.status.error", "error");
+ }
+
+ return false;
+ },
+
+ validate: function (event) {
+ let valid = false;
+ let errorString = "";
+
+ if (this._dialogType == "ChangePassword") {
+ if (this._currentPasswordInvalid)
+ [valid, errorString] = gSyncUtils.validatePassword(this._firstBox);
+ else
+ [valid, errorString] = gSyncUtils.validatePassword(this._firstBox, this._secondBox);
+ }
+ else {
+ //Pale Moon: Enforce minimum length of 8 for allowed custom passphrase
+ //and don't restrict it to "out of sync" situations only. People who
+ //go to this page generally know what they are doing ;)
+ valid = this._passphraseBox.value.length >= 8;
+ }
+
+ if (errorString == "")
+ this._clearStatus();
+ else
+ this._updateStatusWithString(errorString, "error");
+
+ this._statusRow.hidden = valid;
+ this._dialog.getButton("finish").disabled = !valid;
+ },
+
+ _str: function Change__string(str) {
+ return this._stringBundle.GetStringFromName(str);
+ }
+};
diff --git a/browser/base/content/sync/genericChange.xul b/browser/base/content/sync/genericChange.xul
new file mode 100644
index 000000000..a35b1a20e
--- /dev/null
+++ b/browser/base/content/sync/genericChange.xul
@@ -0,0 +1,123 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/syncSetup.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/syncCommon.css" type="text/css"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
+<!ENTITY % syncSetupDTD SYSTEM "chrome://browser/locale/syncSetup.dtd">
+%brandDTD;
+%syncBrandDTD;
+%syncSetupDTD;
+]>
+<wizard xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ id="change-dialog"
+ windowtype="Weave:ChangeSomething"
+ persist="screenX screenY"
+ onwizardnext="Change.onLoad()"
+ onwizardfinish="return Change.onDialogAccept();">
+
+ <script type="application/javascript"
+ src="chrome://browser/content/sync/genericChange.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/sync/utils.js"/>
+ <script type="application/javascript"
+ src="chrome://global/content/printUtils.js"/>
+
+ <wizardpage id="change-page"
+ label="">
+
+ <description id="introText">
+ </description>
+
+ <separator class="thin"/>
+
+ <groupbox>
+ <grid>
+ <columns>
+ <column align="right"/>
+ <column flex="3"/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row id="textBox1Row" align="center">
+ <label id="textBox1Label" control="textBox1"/>
+ <textbox id="textBox1" type="password" oninput="Change.validate()"/>
+ <spacer/>
+ </row>
+ <row id="textBox2Row" align="center">
+ <label id="textBox2Label" control="textBox2"/>
+ <textbox id="textBox2" type="password" oninput="Change.validate()"/>
+ <spacer/>
+ </row>
+ </rows>
+ </grid>
+
+ <vbox id="passphraseRow">
+ <hbox flex="1">
+ <label id="passphraseLabel" control="passphraseBox"/>
+ <spacer flex="1"/>
+ <label id="generatePassphraseButton"
+ hidden="true"
+ value="&syncGenerateNewKey.label;"
+ class="text-link inline-link"
+ onclick="event.stopPropagation();
+ Change.doGeneratePassphrase();"/>
+ </hbox>
+ <textbox id="passphraseBox"
+ flex="1"
+ onfocus="this.select()"
+ oninput="Change.validate()"/>
+ </vbox>
+
+ <vbox id="feedback" pack="center">
+ <hbox id="statusRow" align="center">
+ <image id="statusIcon" class="statusIcon"/>
+ <label id="status" class="status" value=" "/>
+ </hbox>
+ </vbox>
+ </groupbox>
+
+ <separator class="thin"/>
+
+ <hbox id="passphraseBackupButtons"
+ hidden="true"
+ pack="center">
+ <button id="printSyncKeyButton"
+ label="&button.syncKeyBackup.print.label;"
+ accesskey="&button.syncKeyBackup.print.accesskey;"
+ oncommand="gSyncUtils.passphrasePrint('passphraseBox');"/>
+ <button id="saveSyncKeyButton"
+ label="&button.syncKeyBackup.save.label;"
+ accesskey="&button.syncKeyBackup.save.accesskey;"
+ oncommand="gSyncUtils.passphraseSave('passphraseBox');"/>
+ </hbox>
+
+ <vbox id="passphraseHelpBox"
+ hidden="true">
+ <description>
+ &existingRecoveryKey.description;
+ <label class="text-link"
+ href="https://services.mozilla.com/sync/help/manual-setup">
+ &addDevice.showMeHow.label;
+ </label>
+ </description>
+ </vbox>
+
+ <spacer id="passphraseSpacer"
+ flex="1"
+ hidden="true"/>
+
+ <description id="warningText" class="data">
+ </description>
+
+ <spacer flex="1"/>
+ </wizardpage>
+</wizard>
diff --git a/browser/base/content/sync/key.xhtml b/browser/base/content/sync/key.xhtml
new file mode 100644
index 000000000..1363132e7
--- /dev/null
+++ b/browser/base/content/sync/key.xhtml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+ <!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
+ %syncBrandDTD;
+ <!ENTITY % syncKeyDTD SYSTEM "chrome://browser/locale/syncKey.dtd">
+ %syncKeyDTD;
+ <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd" >
+ %globalDTD;
+]>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>&syncKey.page.title;</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+ <meta name="robots" content="noindex"/>
+ <style type="text/css">
+ #synckey { font-size: 150% }
+ footer { font-size: 70% }
+ /* Bug 575675: Need to have an a:visited rule in a chrome document. */
+ a:visited { color: purple; }
+ </style>
+</head>
+
+<body dir="&locale.dir;">
+<h1>&syncKey.page.title;</h1>
+
+<p id="synckey" dir="ltr">SYNCKEY</p>
+
+<p>&syncKey.page.description2;</p>
+
+<div id="column1">
+ <h2>&syncKey.keepItSecret.heading;</h2>
+ <p>&syncKey.keepItSecret.description;</p>
+</div>
+
+<div id="column2">
+ <h2>&syncKey.keepItSafe.heading;</h2>
+ <p><em>&syncKey.keepItSafe1.description;</em>&syncKey.keepItSafe2.description;<em>&syncKey.keepItSafe3.description;</em>&syncKey.keepItSafe4a.description;</p>
+</div>
+
+<p>&syncKey.findOutMore1.label;<a href="https://services.mozilla.com">https://services.mozilla.com</a>&syncKey.findOutMore2.label;</p>
+
+<footer>
+ &syncKey.footer1.label;<a id="tosLink" href="termsURL">termsURL</a>&syncKey.footer2.label;<a id="ppLink" href="privacyURL">privacyURL</a>&syncKey.footer3.label;
+</footer>
+
+</body>
+</html>
diff --git a/browser/base/content/sync/notification.xml b/browser/base/content/sync/notification.xml
new file mode 100644
index 000000000..94e83f141
--- /dev/null
+++ b/browser/base/content/sync/notification.xml
@@ -0,0 +1,129 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE bindings [
+<!ENTITY % notificationDTD SYSTEM "chrome://global/locale/notification.dtd">
+%notificationDTD;
+]>
+
+<bindings id="notificationBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xbl="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <binding id="notificationbox" extends="chrome://global/content/bindings/notification.xml#notificationbox">
+ <content>
+ <xul:vbox xbl:inherits="hidden=notificationshidden">
+ <xul:spacer/>
+ <children includes="notification"/>
+ </xul:vbox>
+ <children/>
+ </content>
+
+ <implementation>
+ <constructor><![CDATA[
+ let temp = {};
+ Cu.import("resource://services-common/observers.js", temp);
+ temp.Observers.add("weave:notification:added", this.onNotificationAdded, this);
+ temp.Observers.add("weave:notification:removed", this.onNotificationRemoved, this);
+
+ for each (var notification in Weave.Notifications.notifications)
+ this._appendNotification(notification);
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ let temp = {};
+ Cu.import("resource://services-common/observers.js", temp);
+ temp.Observers.remove("weave:notification:added", this.onNotificationAdded, this);
+ temp.Observers.remove("weave:notification:removed", this.onNotificationRemoved, this);
+ ]]></destructor>
+
+ <method name="onNotificationAdded">
+ <parameter name="subject"/>
+ <parameter name="data"/>
+ <body><![CDATA[
+ this._appendNotification(subject);
+ ]]></body>
+ </method>
+
+ <method name="onNotificationRemoved">
+ <parameter name="subject"/>
+ <parameter name="data"/>
+ <body><![CDATA[
+ // If the view of the notification hasn't been removed yet, remove it.
+ var notifications = this.allNotifications;
+ for each (var notification in notifications) {
+ if (notification.notification == subject) {
+ notification.close();
+ break;
+ }
+ }
+ ]]></body>
+ </method>
+
+ <method name="_appendNotification">
+ <parameter name="notification"/>
+ <body><![CDATA[
+ var node = this.appendNotification(notification.description,
+ notification.title,
+ notification.iconURL,
+ notification.priority,
+ notification.buttons);
+ node.notification = notification;
+ ]]></body>
+ </method>
+
+ </implementation>
+ </binding>
+
+ <binding id="notification" extends="chrome://global/content/bindings/notification.xml#notification">
+ <content>
+ <xul:hbox class="notification-inner outset" flex="1" xbl:inherits="type">
+ <xul:toolbarbutton ondblclick="event.stopPropagation();"
+ class="messageCloseButton tabbable"
+ xbl:inherits="hidden=hideclose"
+ tooltiptext="&closeNotification.tooltip;"
+ oncommand="document.getBindingParent(this).close()"/>
+ <xul:hbox anonid="details" align="center" flex="1">
+ <xul:image anonid="messageImage" class="messageImage" xbl:inherits="src=image"/>
+ <xul:description anonid="messageText" class="messageText" xbl:inherits="xbl:text=label"/>
+
+ <!-- The children are the buttons defined by the notification. -->
+ <xul:hbox oncommand="document.getBindingParent(this)._doButtonCommand(event);">
+ <children/>
+ </xul:hbox>
+ </xul:hbox>
+ </xul:hbox>
+ </content>
+ <implementation>
+ <!-- Note: this used to be a field, but for some reason it kept getting
+ - reset to its default value for TabNotification elements.
+ - As a property, that doesn't happen, even though the property stores
+ - its value in a JS property |_notification| that is not defined
+ - in XBL as a field or property. Maybe this is wrong, but it works.
+ -->
+ <property name="notification"
+ onget="return this._notification"
+ onset="this._notification = val; return val;"/>
+ <method name="close">
+ <body><![CDATA[
+ Weave.Notifications.remove(this.notification);
+
+ // We should be able to call the base class's close method here
+ // to remove the notification element from the notification box,
+ // but we can't because of bug 373652, so instead we copied its code
+ // and execute it below.
+ var control = this.control;
+ if (control)
+ control.removeNotification(this);
+ else
+ this.hidden = true;
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+</bindings>
diff --git a/browser/base/content/sync/progress.js b/browser/base/content/sync/progress.js
new file mode 100644
index 000000000..2063f612a
--- /dev/null
+++ b/browser/base/content/sync/progress.js
@@ -0,0 +1,71 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://services-sync/main.js");
+
+let gProgressBar;
+let gCounter = 0;
+
+function onLoad(event) {
+ Services.obs.addObserver(onEngineSync, "weave:engine:sync:finish", false);
+ Services.obs.addObserver(onEngineSync, "weave:engine:sync:error", false);
+ Services.obs.addObserver(onServiceSync, "weave:service:sync:finish", false);
+ Services.obs.addObserver(onServiceSync, "weave:service:sync:error", false);
+
+ gProgressBar = document.getElementById('uploadProgressBar');
+
+ if (Services.prefs.getPrefType("services.sync.firstSync") != Ci.nsIPrefBranch.PREF_INVALID) {
+ gProgressBar.hidden = false;
+ }
+ else {
+ gProgressBar.hidden = true;
+ }
+}
+
+function onUnload(event) {
+ cleanUpObservers();
+}
+
+function cleanUpObservers() {
+ try {
+ Services.obs.removeObserver(onEngineSync, "weave:engine:sync:finish");
+ Services.obs.removeObserver(onEngineSync, "weave:engine:sync:error");
+ Services.obs.removeObserver(onServiceSync, "weave:service:sync:finish");
+ Services.obs.removeObserver(onServiceSync, "weave:service:sync:error");
+ }
+ catch (e) {
+ // may be double called by unload & exit. Ignore.
+ }
+}
+
+function onEngineSync(subject, topic, data) {
+ // The Clients engine syncs first. At this point we don't necessarily know
+ // yet how many engines will be enabled, so we'll ignore the Clients engine
+ // and evaluate how many engines are enabled when the first "real" engine
+ // syncs.
+ if (data == "clients") {
+ return;
+ }
+
+ if (!gCounter &&
+ Services.prefs.getPrefType("services.sync.firstSync") != Ci.nsIPrefBranch.PREF_INVALID) {
+ gProgressBar.max = Weave.Service.engineManager.getEnabled().length;
+ }
+
+ gCounter += 1;
+ gProgressBar.setAttribute("value", gCounter);
+}
+
+function onServiceSync(subject, topic, data) {
+ // To address the case where 0 engines are synced, we will fill the
+ // progress bar so the user knows that the sync has finished.
+ gProgressBar.setAttribute("value", gProgressBar.max);
+ cleanUpObservers();
+}
+
+function closeTab() {
+ window.close();
+}
diff --git a/browser/base/content/sync/progress.xhtml b/browser/base/content/sync/progress.xhtml
new file mode 100644
index 000000000..d403cb20d
--- /dev/null
+++ b/browser/base/content/sync/progress.xhtml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE html [
+ <!ENTITY % htmlDTD
+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "DTD/xhtml1-strict.dtd">
+ %htmlDTD;
+ <!ENTITY % syncProgressDTD
+ SYSTEM "chrome://browser/locale/syncProgress.dtd">
+ %syncProgressDTD;
+ <!ENTITY % syncSetupDTD
+ SYSTEM "chrome://browser/locale/syncSetup.dtd">
+ %syncSetupDTD;
+ <!ENTITY % globalDTD
+ SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>&syncProgress.pageTitle;</title>
+
+ <link rel="stylesheet" type="text/css" media="all"
+ href="chrome://browser/skin/syncProgress.css"/>
+
+ <link rel="icon" type="image/png" id="favicon"
+ href="chrome://browser/skin/sync-16.png"/>
+
+ <script type="text/javascript;version=1.8"
+ src="chrome://browser/content/sync/progress.js"/>
+ </head>
+ <body onload="onLoad(event)" onunload="onUnload(event)" dir="&locale.dir;">
+ <title>&setup.successPage.title;</title>
+ <div id="floatingBox" class="main-content">
+ <div id="title">
+ <h1>&setup.successPage.title;</h1>
+ </div>
+ <div id="successLogo">
+ <img id="brandSyncLogo" src="chrome://browser/skin/sync-128.png" alt="&syncProgress.logoAltText;" />
+ </div>
+ <div id="loadingText">
+ <p id="blurb">&syncProgress.textBlurb; </p>
+ </div>
+ <div id="progressBar">
+ <progress id="uploadProgressBar" value="0"/>
+ </div>
+ <div id="bottomRow">
+ <button id="closeButton" onclick="closeTab()">&syncProgress.closeButton; </button>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/browser/base/content/sync/quota.js b/browser/base/content/sync/quota.js
new file mode 100644
index 000000000..04858fa3b
--- /dev/null
+++ b/browser/base/content/sync/quota.js
@@ -0,0 +1,268 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+Cu.import("resource://services-sync/main.js");
+Cu.import("resource://gre/modules/DownloadUtils.jsm");
+
+let gSyncQuota = {
+
+ init: function init() {
+ this.bundle = document.getElementById("quotaStrings");
+ let caption = document.getElementById("treeCaption");
+ caption.firstChild.nodeValue = this.bundle.getString("quota.treeCaption.label");
+
+ gUsageTreeView.init();
+ this.tree = document.getElementById("usageTree");
+ this.tree.view = gUsageTreeView;
+
+ this.loadData();
+ },
+
+ loadData: function loadData() {
+ this._usage_req = Weave.Service.getStorageInfo(Weave.INFO_COLLECTION_USAGE,
+ function (error, usage) {
+ delete gSyncQuota._usage_req;
+ // displayUsageData handles null values, so no need to check 'error'.
+ gUsageTreeView.displayUsageData(usage);
+ });
+
+ let usageLabel = document.getElementById("usageLabel");
+ let bundle = this.bundle;
+
+ this._quota_req = Weave.Service.getStorageInfo(Weave.INFO_QUOTA,
+ function (error, quota) {
+ delete gSyncQuota._quota_req;
+
+ if (error) {
+ usageLabel.value = bundle.getString("quota.usageError.label");
+ return;
+ }
+ let used = gSyncQuota.convertKB(quota[0]);
+ if (!quota[1]) {
+ // No quota on the server.
+ usageLabel.value = bundle.getFormattedString(
+ "quota.usageNoQuota.label", used);
+ return;
+ }
+ let percent = Math.round(100 * quota[0] / quota[1]);
+ let total = gSyncQuota.convertKB(quota[1]);
+ usageLabel.value = bundle.getFormattedString(
+ "quota.usagePercentage.label", [percent].concat(used).concat(total));
+ });
+ },
+
+ onCancel: function onCancel() {
+ if (this._usage_req) {
+ this._usage_req.abort();
+ }
+ if (this._quota_req) {
+ this._quota_req.abort();
+ }
+ return true;
+ },
+
+ onAccept: function onAccept() {
+ let engines = gUsageTreeView.getEnginesToDisable();
+ for each (let engine in engines) {
+ Weave.Service.engineManager.get(engine).enabled = false;
+ }
+ if (engines.length) {
+ // The 'Weave' object will disappear once the window closes.
+ let Service = Weave.Service;
+ Weave.Utils.nextTick(function() { Service.sync(); });
+ }
+ return this.onCancel();
+ },
+
+ convertKB: function convertKB(value) {
+ return DownloadUtils.convertByteUnits(value * 1024);
+ }
+
+};
+
+let gUsageTreeView = {
+
+ _ignored: {keys: true,
+ meta: true,
+ clients: true},
+
+ /*
+ * Internal data structures underlaying the tree.
+ */
+ _collections: [],
+ _byname: {},
+
+ init: function init() {
+ let retrievingLabel = gSyncQuota.bundle.getString("quota.retrieving.label");
+ for each (let engine in Weave.Service.engineManager.getEnabled()) {
+ if (this._ignored[engine.name])
+ continue;
+
+ // Some engines use the same pref, which means they can only be turned on
+ // and off together. We need to combine them here as well.
+ let existing = this._byname[engine.prefName];
+ if (existing) {
+ existing.engines.push(engine.name);
+ continue;
+ }
+
+ let obj = {name: engine.prefName,
+ title: this._collectionTitle(engine),
+ engines: [engine.name],
+ enabled: true,
+ sizeLabel: retrievingLabel};
+ this._collections.push(obj);
+ this._byname[engine.prefName] = obj;
+ }
+ },
+
+ _collectionTitle: function _collectionTitle(engine) {
+ try {
+ return gSyncQuota.bundle.getString(
+ "collection." + engine.prefName + ".label");
+ } catch (ex) {
+ return engine.Name;
+ }
+ },
+
+ /*
+ * Process the quota information as returned by info/collection_usage.
+ */
+ displayUsageData: function displayUsageData(data) {
+ for each (let coll in this._collections) {
+ coll.size = 0;
+ // If we couldn't retrieve any data, just blank out the label.
+ if (!data) {
+ coll.sizeLabel = "";
+ continue;
+ }
+
+ for each (let engineName in coll.engines)
+ coll.size += data[engineName] || 0;
+ let sizeLabel = "";
+ sizeLabel = gSyncQuota.bundle.getFormattedString(
+ "quota.sizeValueUnit.label", gSyncQuota.convertKB(coll.size));
+ coll.sizeLabel = sizeLabel;
+ }
+ let sizeColumn = this.treeBox.columns.getNamedColumn("size");
+ this.treeBox.invalidateColumn(sizeColumn);
+ },
+
+ /*
+ * Handle click events on the tree.
+ */
+ onTreeClick: function onTreeClick(event) {
+ if (event.button == 2)
+ return;
+
+ let row = {}, col = {};
+ this.treeBox.getCellAt(event.clientX, event.clientY, row, col, {});
+ if (col.value && col.value.id == "enabled")
+ this.toggle(row.value);
+ },
+
+ /*
+ * Toggle enabled state of an engine.
+ */
+ toggle: function toggle(row) {
+ // Update the tree
+ let collection = this._collections[row];
+ collection.enabled = !collection.enabled;
+ this.treeBox.invalidateRow(row);
+
+ // Display which ones will be removed
+ let freeup = 0;
+ let toremove = [];
+ for each (collection in this._collections) {
+ if (collection.enabled)
+ continue;
+ toremove.push(collection.name);
+ freeup += collection.size;
+ }
+
+ let caption = document.getElementById("treeCaption");
+ if (!toremove.length) {
+ caption.className = "";
+ caption.firstChild.nodeValue = gSyncQuota.bundle.getString(
+ "quota.treeCaption.label");
+ return;
+ }
+
+ toremove = [this._byname[coll].title for each (coll in toremove)];
+ toremove = toremove.join(gSyncQuota.bundle.getString("quota.list.separator"));
+ caption.firstChild.nodeValue = gSyncQuota.bundle.getFormattedString(
+ "quota.removal.label", [toremove]);
+ if (freeup)
+ caption.firstChild.nodeValue += gSyncQuota.bundle.getFormattedString(
+ "quota.freeup.label", gSyncQuota.convertKB(freeup));
+ caption.className = "captionWarning";
+ },
+
+ /*
+ * Return a list of engines (or rather their pref names) that should be
+ * disabled.
+ */
+ getEnginesToDisable: function getEnginesToDisable() {
+ return [coll.name for each (coll in this._collections) if (!coll.enabled)];
+ },
+
+ // nsITreeView
+
+ get rowCount() {
+ return this._collections.length;
+ },
+
+ getRowProperties: function(index) { return ""; },
+ getCellProperties: function(row, col) { return ""; },
+ getColumnProperties: function(col) { return ""; },
+ isContainer: function(index) { return false; },
+ isContainerOpen: function(index) { return false; },
+ isContainerEmpty: function(index) { return false; },
+ isSeparator: function(index) { return false; },
+ isSorted: function() { return false; },
+ canDrop: function(index, orientation, dataTransfer) { return false; },
+ drop: function(row, orientation, dataTransfer) {},
+ getParentIndex: function(rowIndex) {},
+ hasNextSibling: function(rowIndex, afterIndex) { return false; },
+ getLevel: function(index) { return 0; },
+ getImageSrc: function(row, col) {},
+
+ getCellValue: function(row, col) {
+ return this._collections[row].enabled;
+ },
+
+ getCellText: function getCellText(row, col) {
+ let collection = this._collections[row];
+ switch (col.id) {
+ case "collection":
+ return collection.title;
+ case "size":
+ return collection.sizeLabel;
+ default:
+ return "";
+ }
+ },
+
+ setTree: function setTree(tree) {
+ this.treeBox = tree;
+ },
+
+ toggleOpenState: function(index) {},
+ cycleHeader: function(col) {},
+ selectionChanged: function() {},
+ cycleCell: function(row, col) {},
+ isEditable: function(row, col) { return false; },
+ isSelectable: function (row, col) { return false; },
+ setCellValue: function(row, col, value) {},
+ setCellText: function(row, col, value) {},
+ performAction: function(action) {},
+ performActionOnRow: function(action, row) {},
+ performActionOnCell: function(action, row, col) {}
+
+};
diff --git a/browser/base/content/sync/quota.xul b/browser/base/content/sync/quota.xul
new file mode 100644
index 000000000..99e6ed78b
--- /dev/null
+++ b/browser/base/content/sync/quota.xul
@@ -0,0 +1,65 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/syncQuota.css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
+<!ENTITY % syncQuotaDTD SYSTEM "chrome://browser/locale/syncQuota.dtd">
+%brandDTD;
+%syncBrandDTD;
+%syncQuotaDTD;
+]>
+<dialog id="quotaDialog"
+ windowtype="Sync:ViewQuota"
+ persist="screenX screenY width height"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="gSyncQuota.init()"
+ buttons="accept,cancel"
+ title="&quota.dialogTitle.label;"
+ ondialogcancel="return gSyncQuota.onCancel();"
+ ondialogaccept="return gSyncQuota.onAccept();">
+
+ <script type="application/javascript"
+ src="chrome://browser/content/sync/quota.js"/>
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="quotaStrings"
+ src="chrome://browser/locale/syncQuota.properties"/>
+ </stringbundleset>
+
+ <vbox flex="1">
+ <label id="usageLabel"
+ value="&quota.retrievingInfo.label;"/>
+ <separator/>
+ <tree id="usageTree"
+ seltype="single"
+ hidecolumnpicker="true"
+ onclick="gUsageTreeView.onTreeClick(event);"
+ flex="1">
+ <treecols>
+ <treecol id="enabled"
+ type="checkbox"
+ fixed="true"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="collection"
+ label="&quota.typeColumn.label;"
+ flex="1"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="size"
+ label="&quota.sizeColumn.label;"
+ flex="1"/>
+ </treecols>
+ <treechildren flex="1"/>
+ </tree>
+ <separator/>
+ <description id="treeCaption"> </description>
+ </vbox>
+
+</dialog>
diff --git a/browser/base/content/sync/setup.js b/browser/base/content/sync/setup.js
new file mode 100644
index 000000000..3b07c9df0
--- /dev/null
+++ b/browser/base/content/sync/setup.js
@@ -0,0 +1,1088 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+// page consts
+
+const PAIR_PAGE = 0;
+const INTRO_PAGE = 1;
+const NEW_ACCOUNT_START_PAGE = 2;
+const EXISTING_ACCOUNT_CONNECT_PAGE = 3;
+const EXISTING_ACCOUNT_LOGIN_PAGE = 4;
+const OPTIONS_PAGE = 5;
+const OPTIONS_CONFIRM_PAGE = 6;
+
+// Broader than we'd like, but after this changed from api-secure.recaptcha.net
+// we had no choice. At least we only do this for the duration of setup.
+// See discussion in Bugs 508112 and 653307.
+const RECAPTCHA_DOMAIN = "https://www.google.com";
+
+const PIN_PART_LENGTH = 4;
+
+Cu.import("resource://services-sync/main.js");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
+Cu.import("resource://gre/modules/PluralForm.jsm");
+
+
+function setVisibility(element, visible) {
+ element.style.visibility = visible ? "visible" : "hidden";
+}
+
+var gSyncSetup = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
+ Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference]),
+
+ captchaBrowser: null,
+ wizard: null,
+ _disabledSites: [],
+
+ status: {
+ password: false,
+ email: false,
+ server: false
+ },
+
+ get _remoteSites() [Weave.Service.serverURL, RECAPTCHA_DOMAIN],
+
+ get _usingMainServers() {
+ if (this._settingUpNew)
+ return document.getElementById("server").selectedIndex == 0;
+ return document.getElementById("existingServer").selectedIndex == 0;
+ },
+
+ init: function () {
+ let obs = [
+ ["weave:service:change-passphrase", "onResetPassphrase"],
+ ["weave:service:login:start", "onLoginStart"],
+ ["weave:service:login:error", "onLoginEnd"],
+ ["weave:service:login:finish", "onLoginEnd"]];
+
+ // Add the observers now and remove them on unload
+ let self = this;
+ let addRem = function(add) {
+ obs.forEach(function([topic, func]) {
+ //XXXzpao This should use Services.obs.* but Weave's Obs does nice handling
+ // of `this`. Fix in a followup. (bug 583347)
+ if (add)
+ Weave.Svc.Obs.add(topic, self[func], self);
+ else
+ Weave.Svc.Obs.remove(topic, self[func], self);
+ });
+ };
+ addRem(true);
+ window.addEventListener("unload", function() addRem(false), false);
+
+ window.setTimeout(function () {
+ // Force Service to be loaded so that engines are registered.
+ // See Bug 670082.
+ Weave.Service;
+ }, 0);
+
+ this.captchaBrowser = document.getElementById("captcha");
+
+ this.wizardType = null;
+ if (window.arguments && window.arguments[0]) {
+ this.wizardType = window.arguments[0];
+ }
+ switch (this.wizardType) {
+ case null:
+ this.wizard.pageIndex = INTRO_PAGE;
+ // Fall through!
+ case "pair":
+ this.captchaBrowser.addProgressListener(this);
+ Weave.Svc.Prefs.set("firstSync", "notReady");
+ break;
+ case "reset":
+ this._resettingSync = true;
+ this.wizard.pageIndex = OPTIONS_PAGE;
+ break;
+ }
+
+ this.wizard.getButton("extra1").label =
+ this._stringBundle.GetStringFromName("button.syncOptions.label");
+
+ // Remember these values because the options pages change them temporarily.
+ this._nextButtonLabel = this.wizard.getButton("next").label;
+ this._nextButtonAccesskey = this.wizard.getButton("next")
+ .getAttribute("accesskey");
+ this._backButtonLabel = this.wizard.getButton("back").label;
+ this._backButtonAccesskey = this.wizard.getButton("back")
+ .getAttribute("accesskey");
+ },
+
+ startNewAccountSetup: function () {
+ if (!Weave.Utils.ensureMPUnlocked())
+ return false;
+ this._settingUpNew = true;
+ this.wizard.pageIndex = NEW_ACCOUNT_START_PAGE;
+ },
+
+ useExistingAccount: function () {
+ if (!Weave.Utils.ensureMPUnlocked())
+ return false;
+ this._settingUpNew = false;
+ if (this.wizardType == "pair") {
+ // We're already pairing, so there's no point in pairing again.
+ // Go straight to the manual login page.
+ this.wizard.pageIndex = EXISTING_ACCOUNT_LOGIN_PAGE;
+ } else {
+ this.wizard.pageIndex = EXISTING_ACCOUNT_CONNECT_PAGE;
+ }
+ },
+
+ resetPassphrase: function resetPassphrase() {
+ // Apply the existing form fields so that
+ // Weave.Service.changePassphrase() has the necessary credentials.
+ Weave.Service.identity.account = document.getElementById("existingAccountName").value;
+ Weave.Service.identity.basicPassword = document.getElementById("existingPassword").value;
+
+ // Generate a new passphrase so that Weave.Service.login() will
+ // actually do something.
+ let passphrase = Weave.Utils.generatePassphrase();
+ Weave.Service.identity.syncKey = passphrase;
+
+ // Only open the dialog if username + password are actually correct.
+ Weave.Service.login();
+ if ([Weave.LOGIN_FAILED_INVALID_PASSPHRASE,
+ Weave.LOGIN_FAILED_NO_PASSPHRASE,
+ Weave.LOGIN_SUCCEEDED].indexOf(Weave.Status.login) == -1) {
+ return;
+ }
+
+ // Hide any errors about the passphrase, we know it's not right.
+ let feedback = document.getElementById("existingPassphraseFeedbackRow");
+ feedback.hidden = true;
+ let el = document.getElementById("existingPassphrase");
+ el.value = Weave.Utils.hyphenatePassphrase(passphrase);
+
+ // changePassphrase() will sync, make sure we set the "firstSync" pref
+ // according to the user's pref.
+ Weave.Svc.Prefs.reset("firstSync");
+ this.setupInitialSync();
+ gSyncUtils.resetPassphrase(true);
+ },
+
+ onResetPassphrase: function () {
+ document.getElementById("existingPassphrase").value =
+ Weave.Utils.hyphenatePassphrase(Weave.Service.identity.syncKey);
+ this.checkFields();
+ this.wizard.advance();
+ },
+
+ onLoginStart: function () {
+ this.toggleLoginFeedback(false);
+ },
+
+ onLoginEnd: function () {
+ this.toggleLoginFeedback(true);
+ },
+
+ sendCredentialsAfterSync: function () {
+ let send = function() {
+ Services.obs.removeObserver("weave:service:sync:finish", send);
+ Services.obs.removeObserver("weave:service:sync:error", send);
+ let credentials = {account: Weave.Service.identity.account,
+ password: Weave.Service.identity.basicPassword,
+ synckey: Weave.Service.identity.syncKey,
+ serverURL: Weave.Service.serverURL};
+ this._jpakeclient.sendAndComplete(credentials);
+ }.bind(this);
+ Services.obs.addObserver("weave:service:sync:finish", send, false);
+ Services.obs.addObserver("weave:service:sync:error", send, false);
+ },
+
+ toggleLoginFeedback: function (stop) {
+ document.getElementById("login-throbber").hidden = stop;
+ let password = document.getElementById("existingPasswordFeedbackRow");
+ let server = document.getElementById("existingServerFeedbackRow");
+ let passphrase = document.getElementById("existingPassphraseFeedbackRow");
+
+ if (!stop || (Weave.Status.login == Weave.LOGIN_SUCCEEDED)) {
+ password.hidden = server.hidden = passphrase.hidden = true;
+ return;
+ }
+
+ let feedback;
+ switch (Weave.Status.login) {
+ case Weave.LOGIN_FAILED_NETWORK_ERROR:
+ case Weave.LOGIN_FAILED_SERVER_ERROR:
+ feedback = server;
+ break;
+ case Weave.LOGIN_FAILED_LOGIN_REJECTED:
+ case Weave.LOGIN_FAILED_NO_USERNAME:
+ case Weave.LOGIN_FAILED_NO_PASSWORD:
+ feedback = password;
+ break;
+ case Weave.LOGIN_FAILED_INVALID_PASSPHRASE:
+ feedback = passphrase;
+ break;
+ }
+ this._setFeedbackMessage(feedback, false, Weave.Status.login);
+ },
+
+ setupInitialSync: function () {
+ let action = document.getElementById("mergeChoiceRadio").selectedItem.id;
+ switch (action) {
+ case "resetClient":
+ // if we're not resetting sync, we don't need to explicitly
+ // call resetClient
+ if (!this._resettingSync)
+ return;
+ // otherwise, fall through
+ case "wipeClient":
+ case "wipeRemote":
+ Weave.Svc.Prefs.set("firstSync", action);
+ break;
+ }
+ },
+
+ // fun with validation!
+ checkFields: function () {
+ this.wizard.canAdvance = this.readyToAdvance();
+ },
+
+ readyToAdvance: function () {
+ switch (this.wizard.pageIndex) {
+ case INTRO_PAGE:
+ return false;
+ case NEW_ACCOUNT_START_PAGE:
+ for (let i in this.status) {
+ if (!this.status[i])
+ return false;
+ }
+ if (this._usingMainServers)
+ return document.getElementById("tos").checked;
+
+ return true;
+ case EXISTING_ACCOUNT_LOGIN_PAGE:
+ let hasUser = document.getElementById("existingAccountName").value != "";
+ let hasPass = document.getElementById("existingPassword").value != "";
+ let hasKey = document.getElementById("existingPassphrase").value != "";
+
+ if (hasUser && hasPass && hasKey) {
+ if (this._usingMainServers)
+ return true;
+
+ if (this._validateServer(document.getElementById("existingServer"))) {
+ return true;
+ }
+ }
+ return false;
+ }
+ // Default, e.g. wizard's special page -1 etc.
+ return true;
+ },
+
+ onPINInput: function onPINInput(textbox) {
+ if (textbox && textbox.value.length == PIN_PART_LENGTH) {
+ this.nextFocusEl[textbox.id].focus();
+ }
+ this.wizard.canAdvance = (this.pin1.value.length == PIN_PART_LENGTH &&
+ this.pin2.value.length == PIN_PART_LENGTH &&
+ this.pin3.value.length == PIN_PART_LENGTH);
+ },
+
+ onEmailInput: function () {
+ // Check account validity when the user stops typing for 1 second.
+ if (this._checkAccountTimer)
+ window.clearTimeout(this._checkAccountTimer);
+ this._checkAccountTimer = window.setTimeout(function () {
+ gSyncSetup.checkAccount();
+ }, 1000);
+ },
+
+ checkAccount: function() {
+ delete this._checkAccountTimer;
+ let value = Weave.Utils.normalizeAccount(
+ document.getElementById("weaveEmail").value);
+ if (!value) {
+ this.status.email = false;
+ this.checkFields();
+ return;
+ }
+
+ let re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
+ let feedback = document.getElementById("emailFeedbackRow");
+ let valid = re.test(value);
+
+ let str = "";
+ if (!valid) {
+ str = "invalidEmail.label";
+ } else {
+ let availCheck = Weave.Service.checkAccount(value);
+ valid = availCheck == "available";
+ if (!valid) {
+ if (availCheck == "notAvailable")
+ str = "usernameNotAvailable.label";
+ else
+ str = availCheck;
+ }
+ }
+
+ this._setFeedbackMessage(feedback, valid, str);
+ this.status.email = valid;
+ if (valid)
+ Weave.Service.identity.account = value;
+ this.checkFields();
+ },
+
+ onPasswordChange: function () {
+ let password = document.getElementById("weavePassword");
+ let pwconfirm = document.getElementById("weavePasswordConfirm");
+ let [valid, errorString] = gSyncUtils.validatePassword(password, pwconfirm);
+
+ let feedback = document.getElementById("passwordFeedbackRow");
+ this._setFeedback(feedback, valid, errorString);
+
+ this.status.password = valid;
+ this.checkFields();
+ },
+
+ onPageShow: function() {
+ switch (this.wizard.pageIndex) {
+ case PAIR_PAGE:
+ this.wizard.getButton("back").hidden = true;
+ this.wizard.getButton("extra1").hidden = true;
+ this.onPINInput();
+ this.pin1.focus();
+ break;
+ case INTRO_PAGE:
+ // We may not need the captcha in the Existing Account branch of the
+ // wizard. However, we want to preload it to avoid any flickering while
+ // the Create Account page is shown.
+ this.loadCaptcha();
+ this.wizard.getButton("next").hidden = true;
+ this.wizard.getButton("back").hidden = true;
+ this.wizard.getButton("extra1").hidden = true;
+ this.checkFields();
+ break;
+ case NEW_ACCOUNT_START_PAGE:
+ this.wizard.getButton("extra1").hidden = false;
+ this.wizard.getButton("next").hidden = false;
+ this.wizard.getButton("back").hidden = false;
+ this.onServerCommand();
+ this.wizard.canRewind = true;
+ this.checkFields();
+ break;
+ case EXISTING_ACCOUNT_CONNECT_PAGE:
+ Weave.Svc.Prefs.set("firstSync", "existingAccount");
+ this.wizard.getButton("next").hidden = false;
+ this.wizard.getButton("back").hidden = false;
+ this.wizard.getButton("extra1").hidden = false;
+ this.wizard.canAdvance = false;
+ this.wizard.canRewind = true;
+ this.startEasySetup();
+ break;
+ case EXISTING_ACCOUNT_LOGIN_PAGE:
+ this.wizard.getButton("next").hidden = false;
+ this.wizard.getButton("back").hidden = false;
+ this.wizard.getButton("extra1").hidden = false;
+ this.wizard.canRewind = true;
+ this.checkFields();
+ break;
+ case OPTIONS_PAGE:
+ this.wizard.canRewind = false;
+ this.wizard.canAdvance = true;
+ if (!this._resettingSync) {
+ this.wizard.getButton("next").label =
+ this._stringBundle.GetStringFromName("button.syncOptionsDone.label");
+ this.wizard.getButton("next").removeAttribute("accesskey");
+ }
+ this.wizard.getButton("next").hidden = false;
+ this.wizard.getButton("back").hidden = true;
+ this.wizard.getButton("cancel").hidden = !this._resettingSync;
+ this.wizard.getButton("extra1").hidden = true;
+ document.getElementById("syncComputerName").value = Weave.Service.clientsEngine.localName;
+ document.getElementById("syncOptions").collapsed = this._resettingSync;
+ document.getElementById("mergeOptions").collapsed = this._settingUpNew;
+ break;
+ case OPTIONS_CONFIRM_PAGE:
+ this.wizard.canRewind = true;
+ this.wizard.canAdvance = true;
+ this.wizard.getButton("back").label =
+ this._stringBundle.GetStringFromName("button.syncOptionsCancel.label");
+ this.wizard.getButton("back").removeAttribute("accesskey");
+ this.wizard.getButton("back").hidden = this._resettingSync;
+ this.wizard.getButton("next").hidden = false;
+ this.wizard.getButton("finish").hidden = true;
+ break;
+ }
+ },
+
+#ifdef XP_WIN
+#ifdef MOZ_METRO
+ _securelyStoreForMetroSync: function(weaveEmail, weavePassword, weaveKey) {
+ try {
+ let metroUtils = Cc["@mozilla.org/windows-metroutils;1"].
+ createInstance(Ci.nsIWinMetroUtils);
+ if (!metroUtils)
+ return;
+ metroUtils.storeSyncInfo(weaveEmail, weavePassword, weaveKey);
+ } catch (ex) {
+ }
+ },
+#endif
+#endif
+
+ onWizardAdvance: function () {
+ // Check pageIndex so we don't prompt before the Sync setup wizard appears.
+ // This is a fallback in case the Master Password gets locked mid-wizard.
+ if ((this.wizard.pageIndex >= 0) &&
+ !Weave.Utils.ensureMPUnlocked()) {
+ return false;
+ }
+
+ switch (this.wizard.pageIndex) {
+ case PAIR_PAGE:
+ this.startPairing();
+ return false;
+ case NEW_ACCOUNT_START_PAGE:
+ // If the user selects Next (e.g. by hitting enter) when we haven't
+ // executed the delayed checks yet, execute them immediately.
+ if (this._checkAccountTimer) {
+ this.checkAccount();
+ }
+ if (this._checkServerTimer) {
+ this.checkServer();
+ }
+ if (!this.wizard.canAdvance) {
+ return false;
+ }
+
+ let doc = this.captchaBrowser.contentDocument;
+ let getField = function getField(field) {
+ let node = doc.getElementById("recaptcha_" + field + "_field");
+ return node && node.value;
+ };
+
+ // Display throbber
+ let feedback = document.getElementById("captchaFeedback");
+ let image = feedback.firstChild;
+ let label = image.nextSibling;
+ image.setAttribute("status", "active");
+ label.value = this._stringBundle.GetStringFromName("verifying.label");
+ setVisibility(feedback, true);
+
+ let password = document.getElementById("weavePassword").value;
+ let email = Weave.Utils.normalizeAccount(
+ document.getElementById("weaveEmail").value);
+ let challenge = getField("challenge");
+ let response = getField("response");
+
+ let error = Weave.Service.createAccount(email, password,
+ challenge, response);
+
+ if (error == null) {
+ Weave.Service.identity.account = email;
+ Weave.Service.identity.basicPassword = password;
+ Weave.Service.identity.syncKey = Weave.Utils.generatePassphrase();
+ this._handleNoScript(false);
+ Weave.Svc.Prefs.set("firstSync", "newAccount");
+#ifdef XP_WIN
+#ifdef MOZ_METRO
+ if (document.getElementById("metroSetupCheckbox").checked) {
+ this._securelyStoreForMetroSync(email, password, Weave.Service.identity.syncKey);
+ }
+#endif
+#endif
+ this.wizardFinish();
+ return false;
+ }
+
+ image.setAttribute("status", "error");
+ label.value = Weave.Utils.getErrorString(error);
+ return false;
+ case EXISTING_ACCOUNT_LOGIN_PAGE:
+ Weave.Service.identity.account = Weave.Utils.normalizeAccount(
+ document.getElementById("existingAccountName").value);
+ Weave.Service.identity.basicPassword =
+ document.getElementById("existingPassword").value;
+ let pp = document.getElementById("existingPassphrase").value;
+ Weave.Service.identity.syncKey = Weave.Utils.normalizePassphrase(pp);
+ if (Weave.Service.login()) {
+ this.wizardFinish();
+ }
+ return false;
+ case OPTIONS_PAGE:
+ let desc = document.getElementById("mergeChoiceRadio").selectedIndex;
+ // No confirmation needed on new account setup or merge option
+ // with existing account.
+ if (this._settingUpNew || (!this._resettingSync && desc == 0))
+ return this.returnFromOptions();
+ return this._handleChoice();
+ case OPTIONS_CONFIRM_PAGE:
+ if (this._resettingSync) {
+ this.wizardFinish();
+ return false;
+ }
+ return this.returnFromOptions();
+ }
+ return true;
+ },
+
+ onWizardBack: function () {
+ switch (this.wizard.pageIndex) {
+ case NEW_ACCOUNT_START_PAGE:
+ case EXISTING_ACCOUNT_LOGIN_PAGE:
+ this.wizard.pageIndex = INTRO_PAGE;
+ return false;
+ case EXISTING_ACCOUNT_CONNECT_PAGE:
+ this.abortEasySetup();
+ this.wizard.pageIndex = INTRO_PAGE;
+ return false;
+ case EXISTING_ACCOUNT_LOGIN_PAGE:
+ // If we were already pairing on entry, we went straight to the manual
+ // login page. If subsequently we go back, return to the page that lets
+ // us choose whether we already have an account.
+ if (this.wizardType == "pair") {
+ this.wizard.pageIndex = INTRO_PAGE;
+ return false;
+ }
+ return true;
+ case OPTIONS_CONFIRM_PAGE:
+ // Backing up from the confirmation page = resetting first sync to merge.
+ document.getElementById("mergeChoiceRadio").selectedIndex = 0;
+ return this.returnFromOptions();
+ }
+ return true;
+ },
+
+ wizardFinish: function () {
+ this.setupInitialSync();
+
+ if (this.wizardType == "pair") {
+ this.completePairing();
+ }
+
+ if (!this._resettingSync) {
+ function isChecked(element) {
+ return document.getElementById(element).hasAttribute("checked");
+ }
+
+ let prefs = ["engine.bookmarks", "engine.passwords", "engine.history",
+ "engine.tabs", "engine.prefs", "engine.addons"];
+ for (let i = 0;i < prefs.length;i++) {
+ Weave.Svc.Prefs.set(prefs[i], isChecked(prefs[i]));
+ }
+ this._handleNoScript(false);
+ if (Weave.Svc.Prefs.get("firstSync", "") == "notReady")
+ Weave.Svc.Prefs.reset("firstSync");
+
+ Weave.Service.persistLogin();
+ Weave.Svc.Obs.notify("weave:service:setup-complete");
+
+ gSyncUtils.openFirstSyncProgressPage();
+ }
+ Weave.Utils.nextTick(Weave.Service.sync, Weave.Service);
+ window.close();
+ },
+
+ onWizardCancel: function () {
+ if (this._resettingSync)
+ return;
+
+ this.abortEasySetup();
+ this._handleNoScript(false);
+ Weave.Service.startOver();
+ },
+
+ onSyncOptions: function () {
+ this._beforeOptionsPage = this.wizard.pageIndex;
+ this.wizard.pageIndex = OPTIONS_PAGE;
+ },
+
+ returnFromOptions: function() {
+ this.wizard.getButton("next").label = this._nextButtonLabel;
+ this.wizard.getButton("next").setAttribute("accesskey",
+ this._nextButtonAccesskey);
+ this.wizard.getButton("back").label = this._backButtonLabel;
+ this.wizard.getButton("back").setAttribute("accesskey",
+ this._backButtonAccesskey);
+ this.wizard.getButton("cancel").hidden = false;
+ this.wizard.getButton("extra1").hidden = false;
+ this.wizard.pageIndex = this._beforeOptionsPage;
+ return false;
+ },
+
+ startPairing: function startPairing() {
+ this.pairDeviceErrorRow.hidden = true;
+ // When onAbort is called, Weave may already be gone.
+ const JPAKE_ERROR_USERABORT = Weave.JPAKE_ERROR_USERABORT;
+
+ let self = this;
+ let jpakeclient = this._jpakeclient = new Weave.JPAKEClient({
+ onPaired: function onPaired() {
+ self.wizard.pageIndex = INTRO_PAGE;
+ },
+ onComplete: function onComplete() {
+ // This method will never be called since SendCredentialsController
+ // will take over after the wizard completes.
+ },
+ onAbort: function onAbort(error) {
+ delete self._jpakeclient;
+
+ // Aborted by user, ignore. The window is almost certainly going to close
+ // or is already closed.
+ if (error == JPAKE_ERROR_USERABORT) {
+ return;
+ }
+
+ self.pairDeviceErrorRow.hidden = false;
+ self.pairDeviceThrobber.hidden = true;
+ self.pin1.value = self.pin2.value = self.pin3.value = "";
+ self.pin1.disabled = self.pin2.disabled = self.pin3.disabled = false;
+ if (self.wizard.pageIndex == PAIR_PAGE) {
+ self.pin1.focus();
+ }
+ }
+ });
+ this.pairDeviceThrobber.hidden = false;
+ this.pin1.disabled = this.pin2.disabled = this.pin3.disabled = true;
+ this.wizard.canAdvance = false;
+
+ let pin = this.pin1.value + this.pin2.value + this.pin3.value;
+ let expectDelay = true;
+ jpakeclient.pairWithPIN(pin, expectDelay);
+ },
+
+ completePairing: function completePairing() {
+ if (!this._jpakeclient) {
+ // The channel was aborted while we were setting up the account
+ // locally. XXX TODO should we do anything here, e.g. tell
+ // the user on the last wizard page that it's ok, they just
+ // have to pair again?
+ return;
+ }
+ let controller = new Weave.SendCredentialsController(this._jpakeclient,
+ Weave.Service);
+ this._jpakeclient.controller = controller;
+ },
+
+ startEasySetup: function () {
+ // Don't do anything if we have a client already (e.g. we went to
+ // Sync Options and just came back).
+ if (this._jpakeclient)
+ return;
+
+ // When onAbort is called, Weave may already be gone
+ const JPAKE_ERROR_USERABORT = Weave.JPAKE_ERROR_USERABORT;
+
+ let self = this;
+ this._jpakeclient = new Weave.JPAKEClient({
+ displayPIN: function displayPIN(pin) {
+ document.getElementById("easySetupPIN1").value = pin.slice(0, 4);
+ document.getElementById("easySetupPIN2").value = pin.slice(4, 8);
+ document.getElementById("easySetupPIN3").value = pin.slice(8);
+ },
+
+ onPairingStart: function onPairingStart() {},
+
+ onComplete: function onComplete(credentials) {
+ Weave.Service.identity.account = credentials.account;
+ Weave.Service.identity.basicPassword = credentials.password;
+ Weave.Service.identity.syncKey = credentials.synckey;
+ Weave.Service.serverURL = credentials.serverURL;
+ gSyncSetup.wizardFinish();
+ },
+
+ onAbort: function onAbort(error) {
+ delete self._jpakeclient;
+
+ // Ignore if wizard is aborted.
+ if (error == JPAKE_ERROR_USERABORT)
+ return;
+
+ // Automatically go to manual setup if we couldn't acquire a channel.
+ if (error == Weave.JPAKE_ERROR_CHANNEL) {
+ self.wizard.pageIndex = EXISTING_ACCOUNT_LOGIN_PAGE;
+ return;
+ }
+
+ // Restart on all other errors.
+ self.startEasySetup();
+ }
+ });
+ this._jpakeclient.receiveNoPIN();
+ },
+
+ abortEasySetup: function () {
+ document.getElementById("easySetupPIN1").value = "";
+ document.getElementById("easySetupPIN2").value = "";
+ document.getElementById("easySetupPIN3").value = "";
+ if (!this._jpakeclient)
+ return;
+
+ this._jpakeclient.abort();
+ delete this._jpakeclient;
+ },
+
+ manualSetup: function () {
+ this.abortEasySetup();
+ this.wizard.pageIndex = EXISTING_ACCOUNT_LOGIN_PAGE;
+ },
+
+ // _handleNoScript is needed because it blocks the captcha. So we temporarily
+ // allow the necessary sites so that we can verify the user is in fact a human.
+ // This was done with the help of Giorgio (NoScript author). See bug 508112.
+ _handleNoScript: function (addExceptions) {
+ // if NoScript isn't installed, or is disabled, bail out.
+ let ns = Cc["@maone.net/noscript-service;1"];
+ if (ns == null)
+ return;
+
+ ns = ns.getService().wrappedJSObject;
+ if (addExceptions) {
+ this._remoteSites.forEach(function(site) {
+ site = ns.getSite(site);
+ if (!ns.isJSEnabled(site)) {
+ this._disabledSites.push(site); // save status
+ ns.setJSEnabled(site, true); // allow site
+ }
+ }, this);
+ }
+ else {
+ this._disabledSites.forEach(function(site) {
+ ns.setJSEnabled(site, false);
+ });
+ this._disabledSites = [];
+ }
+ },
+
+ onExistingServerCommand: function () {
+ let control = document.getElementById("existingServer");
+ if (control.selectedIndex == 0) {
+ control.removeAttribute("editable");
+ Weave.Svc.Prefs.reset("serverURL");
+ } else {
+ control.setAttribute("editable", "true");
+ // Force a style flush to ensure that the binding is attached.
+ control.clientTop;
+ control.value = "";
+ control.inputField.focus();
+ }
+ document.getElementById("existingServerFeedbackRow").hidden = true;
+ this.checkFields();
+ },
+
+ onExistingServerInput: function () {
+ // Check custom server validity when the user stops typing for 1 second.
+ if (this._existingServerTimer)
+ window.clearTimeout(this._existingServerTimer);
+ this._existingServerTimer = window.setTimeout(function () {
+ gSyncSetup.checkFields();
+ }, 1000);
+ },
+
+ onServerCommand: function () {
+ setVisibility(document.getElementById("TOSRow"), this._usingMainServers);
+ let control = document.getElementById("server");
+ if (!this._usingMainServers) {
+ control.setAttribute("editable", "true");
+ // Force a style flush to ensure that the binding is attached.
+ control.clientTop;
+ control.value = "";
+ control.inputField.focus();
+ // checkServer() will call checkAccount() and checkFields().
+ this.checkServer();
+ return;
+ }
+ control.removeAttribute("editable");
+ Weave.Svc.Prefs.reset("serverURL");
+ if (this._settingUpNew) {
+ this.loadCaptcha();
+ }
+ this.checkAccount();
+ this.status.server = true;
+ document.getElementById("serverFeedbackRow").hidden = true;
+ this.checkFields();
+ },
+
+ onServerInput: function () {
+ // Check custom server validity when the user stops typing for 1 second.
+ if (this._checkServerTimer)
+ window.clearTimeout(this._checkServerTimer);
+ this._checkServerTimer = window.setTimeout(function () {
+ gSyncSetup.checkServer();
+ }, 1000);
+ },
+
+ checkServer: function () {
+ delete this._checkServerTimer;
+ let el = document.getElementById("server");
+ let valid = false;
+ let feedback = document.getElementById("serverFeedbackRow");
+ let str = "";
+ if (el.value) {
+ valid = this._validateServer(el);
+ let str = valid ? "" : "serverInvalid.label";
+ this._setFeedbackMessage(feedback, valid, str);
+ }
+ else
+ this._setFeedbackMessage(feedback, true);
+
+ // Recheck account against the new server.
+ if (valid)
+ this.checkAccount();
+
+ this.status.server = valid;
+ this.checkFields();
+ },
+
+ _validateServer: function (element) {
+ let valid = false;
+ let val = element.value;
+ if (!val)
+ return false;
+
+ let uri = Weave.Utils.makeURI(val);
+
+ if (!uri)
+ uri = Weave.Utils.makeURI("https://" + val);
+
+ if (uri && this._settingUpNew) {
+ function isValid(uri) {
+ Weave.Service.serverURL = uri.spec;
+ let check = Weave.Service.checkAccount("a");
+ return (check == "available" || check == "notAvailable");
+ }
+
+ if (uri.schemeIs("http")) {
+ uri.scheme = "https";
+ if (isValid(uri))
+ valid = true;
+ else
+ // setting the scheme back to http
+ uri.scheme = "http";
+ }
+ if (!valid)
+ valid = isValid(uri);
+
+ if (valid) {
+ this.loadCaptcha();
+ }
+ }
+ else if (uri) {
+ valid = true;
+ Weave.Service.serverURL = uri.spec;
+ }
+
+ if (valid)
+ element.value = Weave.Service.serverURL;
+ else
+ Weave.Svc.Prefs.reset("serverURL");
+
+ return valid;
+ },
+
+ _handleChoice: function () {
+ let desc = document.getElementById("mergeChoiceRadio").selectedIndex;
+ document.getElementById("chosenActionDeck").selectedIndex = desc;
+ switch (desc) {
+ case 1:
+ if (this._case1Setup)
+ break;
+
+ let places_db = PlacesUtils.history
+ .QueryInterface(Ci.nsPIPlacesDatabase)
+ .DBConnection;
+ if (Weave.Service.engineManager.get("history").enabled) {
+ let daysOfHistory = 0;
+ let stm = places_db.createStatement(
+ "SELECT ROUND(( " +
+ "strftime('%s','now','localtime','utc') - " +
+ "( " +
+ "SELECT visit_date FROM moz_historyvisits " +
+ "ORDER BY visit_date ASC LIMIT 1 " +
+ ")/1000000 " +
+ ")/86400) AS daysOfHistory ");
+
+ if (stm.step())
+ daysOfHistory = stm.getInt32(0);
+ // Support %S for historical reasons (see bug 600141)
+ document.getElementById("historyCount").value =
+ PluralForm.get(daysOfHistory,
+ this._stringBundle.GetStringFromName("historyDaysCount.label"))
+ .replace("%S", daysOfHistory)
+ .replace("#1", daysOfHistory);
+ } else {
+ document.getElementById("historyCount").hidden = true;
+ }
+
+ if (Weave.Service.engineManager.get("bookmarks").enabled) {
+ let bookmarks = 0;
+ let stm = places_db.createStatement(
+ "SELECT count(*) AS bookmarks " +
+ "FROM moz_bookmarks b " +
+ "LEFT JOIN moz_bookmarks t ON " +
+ "b.parent = t.id WHERE b.type = 1 AND t.parent <> :tag");
+ stm.params.tag = PlacesUtils.tagsFolderId;
+ if (stm.executeStep())
+ bookmarks = stm.row.bookmarks;
+ // Support %S for historical reasons (see bug 600141)
+ document.getElementById("bookmarkCount").value =
+ PluralForm.get(bookmarks,
+ this._stringBundle.GetStringFromName("bookmarksCount.label"))
+ .replace("%S", bookmarks)
+ .replace("#1", bookmarks);
+ } else {
+ document.getElementById("bookmarkCount").hidden = true;
+ }
+
+ if (Weave.Service.engineManager.get("passwords").enabled) {
+ let logins = Services.logins.getAllLogins({});
+ // Support %S for historical reasons (see bug 600141)
+ document.getElementById("passwordCount").value =
+ PluralForm.get(logins.length,
+ this._stringBundle.GetStringFromName("passwordsCount.label"))
+ .replace("%S", logins.length)
+ .replace("#1", logins.length);
+ } else {
+ document.getElementById("passwordCount").hidden = true;
+ }
+
+ if (!Weave.Service.engineManager.get("prefs").enabled) {
+ document.getElementById("prefsWipe").hidden = true;
+ }
+
+ let addonsEngine = Weave.Service.engineManager.get("addons");
+ if (addonsEngine.enabled) {
+ let ids = addonsEngine._store.getAllIDs();
+ let blessedcount = 0;
+ for each (let i in ids) {
+ if (i) {
+ blessedcount++;
+ }
+ }
+ // bug 600141 does not apply, as this does not have to support existing strings
+ document.getElementById("addonCount").value =
+ PluralForm.get(blessedcount,
+ this._stringBundle.GetStringFromName("addonsCount.label"))
+ .replace("#1", blessedcount);
+ } else {
+ document.getElementById("addonCount").hidden = true;
+ }
+
+ this._case1Setup = true;
+ break;
+ case 2:
+ if (this._case2Setup)
+ break;
+ let count = 0;
+ function appendNode(label) {
+ let box = document.getElementById("clientList");
+ let node = document.createElement("label");
+ node.setAttribute("value", label);
+ node.setAttribute("class", "data indent");
+ box.appendChild(node);
+ }
+
+ for each (let name in Weave.Service.clientsEngine.stats.names) {
+ // Don't list the current client
+ if (name == Weave.Service.clientsEngine.localName)
+ continue;
+
+ // Only show the first several client names
+ if (++count <= 5)
+ appendNode(name);
+ }
+ if (count > 5) {
+ // Support %S for historical reasons (see bug 600141)
+ let label =
+ PluralForm.get(count - 5,
+ this._stringBundle.GetStringFromName("additionalClientCount.label"))
+ .replace("%S", count - 5)
+ .replace("#1", count - 5);
+ appendNode(label);
+ }
+ this._case2Setup = true;
+ break;
+ }
+
+ return true;
+ },
+
+ // sets class and string on a feedback element
+ // if no property string is passed in, we clear label/style
+ _setFeedback: function (element, success, string) {
+ element.hidden = success || !string;
+ let classname = success ? "success" : "error";
+ let image = element.getElementsByAttribute("class", "statusIcon")[0];
+ image.setAttribute("status", classname);
+ let label = element.getElementsByAttribute("class", "status")[0];
+ label.value = string;
+ },
+
+ // shim
+ _setFeedbackMessage: function (element, success, string) {
+ let str = "";
+ if (string) {
+ try {
+ str = this._stringBundle.GetStringFromName(string);
+ } catch(e) {}
+
+ if (!str)
+ str = Weave.Utils.getErrorString(string);
+ }
+ this._setFeedback(element, success, str);
+ },
+
+ loadCaptcha: function loadCaptcha() {
+ let captchaURI = Weave.Service.miscAPI + "captcha_html";
+ // First check for NoScript and whitelist the right sites.
+ this._handleNoScript(true);
+ if (this.captchaBrowser.currentURI.spec != captchaURI) {
+ this.captchaBrowser.loadURI(captchaURI);
+ }
+ },
+
+ onStateChange: function(webProgress, request, stateFlags, status) {
+ // We're only looking for the end of the frame load
+ if ((stateFlags & Ci.nsIWebProgressListener.STATE_STOP) == 0)
+ return;
+ if ((stateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) == 0)
+ return;
+ if ((stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) == 0)
+ return;
+
+ // If we didn't find a captcha, assume it's not needed and don't show it.
+ let responseStatus = request.QueryInterface(Ci.nsIHttpChannel).responseStatus;
+ setVisibility(this.captchaBrowser, responseStatus != 404);
+ //XXX TODO we should really log any responseStatus other than 200
+ },
+ onProgressChange: function() {},
+ onStatusChange: function() {},
+ onSecurityChange: function() {},
+ onLocationChange: function () {}
+};
+
+// Define lazy getters for various XUL elements.
+//
+// onWizardAdvance() and onPageShow() are run before init(), so we'll even
+// define things that will almost certainly be used (like 'wizard') as a lazy
+// getter here.
+["wizard",
+ "pin1",
+ "pin2",
+ "pin3",
+ "pairDeviceErrorRow",
+ "pairDeviceThrobber"].forEach(function (id) {
+ XPCOMUtils.defineLazyGetter(gSyncSetup, id, function() {
+ return document.getElementById(id);
+ });
+});
+XPCOMUtils.defineLazyGetter(gSyncSetup, "nextFocusEl", function () {
+ return {pin1: this.pin2,
+ pin2: this.pin3,
+ pin3: this.wizard.getButton("next")};
+});
+XPCOMUtils.defineLazyGetter(gSyncSetup, "_stringBundle", function() {
+ return Services.strings.createBundle("chrome://browser/locale/syncSetup.properties");
+});
diff --git a/browser/base/content/sync/setup.xul b/browser/base/content/sync/setup.xul
new file mode 100644
index 000000000..f01dad9f9
--- /dev/null
+++ b/browser/base/content/sync/setup.xul
@@ -0,0 +1,504 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/syncSetup.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/syncCommon.css" type="text/css"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
+<!ENTITY % syncSetupDTD SYSTEM "chrome://browser/locale/syncSetup.dtd">
+%brandDTD;
+%syncBrandDTD;
+%syncSetupDTD;
+]>
+<wizard id="wizard"
+ title="&accountSetupTitle.label;"
+ windowtype="Weave:AccountSetup"
+ persist="screenX screenY"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onwizardnext="return gSyncSetup.onWizardAdvance()"
+ onwizardback="return gSyncSetup.onWizardBack()"
+ onwizardcancel="gSyncSetup.onWizardCancel()"
+ onload="gSyncSetup.init()">
+
+ <script type="application/javascript"
+ src="chrome://browser/content/sync/setup.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/sync/utils.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/utilityOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://global/content/printUtils.js"/>
+
+ <wizardpage id="addDevicePage"
+ label="&pairDevice.title.label;"
+ onpageshow="gSyncSetup.onPageShow()">
+ <description>
+ &pairDevice.dialog.description.label;
+ <label class="text-link"
+ value="&addDevice.showMeHow.label;"
+ href="https://services.mozilla.com/sync/help/add-device"/>
+ </description>
+ <separator class="groove-thin"/>
+ <description>
+ &addDevice.dialog.enterCode.label;
+ </description>
+ <separator class="groove-thin"/>
+ <vbox align="center">
+ <textbox id="pin1"
+ class="pin"
+ oninput="gSyncSetup.onPINInput(this);"
+ onfocus="this.select();"
+ />
+ <textbox id="pin2"
+ class="pin"
+ oninput="gSyncSetup.onPINInput(this);"
+ onfocus="this.select();"
+ />
+ <textbox id="pin3"
+ class="pin"
+ oninput="gSyncSetup.onPINInput(this);"
+ onfocus="this.select();"
+ />
+ </vbox>
+ <separator class="groove-thin"/>
+ <vbox id="pairDeviceThrobber" align="center" hidden="true">
+ <image/>
+ </vbox>
+ <hbox id="pairDeviceErrorRow" pack="center" hidden="true">
+ <image class="statusIcon" status="error"/>
+ <label class="status"
+ value="&addDevice.dialog.tryAgain.label;"/>
+ </hbox>
+ </wizardpage>
+
+ <wizardpage id="pickSetupType"
+ label="&syncBrand.fullName.label;"
+ onpageshow="gSyncSetup.onPageShow()">
+ <vbox align="center" flex="1">
+ <description style="padding: 0 7em;">
+ &setup.pickSetupType.description2;
+ </description>
+ <spacer flex="3"/>
+ <button id="newAccount"
+ class="accountChoiceButton"
+ label="&button.createNewAccount.label;"
+ oncommand="gSyncSetup.startNewAccountSetup()"
+ align="center"/>
+ <spacer flex="1"/>
+ </vbox>
+ <separator class="groove"/>
+ <vbox align="center" flex="1">
+ <spacer flex="1"/>
+ <button id="existingAccount"
+ class="accountChoiceButton"
+ label="&button.haveAccount.label;"
+ oncommand="gSyncSetup.useExistingAccount()"/>
+ <spacer flex="3"/>
+ </vbox>
+ </wizardpage>
+
+ <wizardpage label="&setup.newAccountDetailsPage.title.label;"
+ id="newAccountStart"
+ onextra1="gSyncSetup.onSyncOptions()"
+ onpageshow="gSyncSetup.onPageShow();">
+ <grid>
+ <columns>
+ <column/>
+ <column class="inputColumn" flex="1"/>
+ </columns>
+ <rows>
+ <row id="emailRow" align="center">
+ <label value="&setup.emailAddress.label;"
+ accesskey="&setup.emailAddress.accesskey;"
+ control="weaveEmail"/>
+ <textbox id="weaveEmail"
+ oninput="gSyncSetup.onEmailInput()"/>
+ </row>
+ <row id="emailFeedbackRow" align="center" hidden="true">
+ <spacer/>
+ <hbox>
+ <image class="statusIcon"/>
+ <label class="status" value=" "/>
+ </hbox>
+ </row>
+ <row id="passwordRow" align="center">
+ <label value="&setup.choosePassword.label;"
+ accesskey="&setup.choosePassword.accesskey;"
+ control="weavePassword"/>
+ <textbox id="weavePassword"
+ type="password"
+ onchange="gSyncSetup.onPasswordChange()"/>
+ </row>
+ <row id="confirmRow" align="center">
+ <label value="&setup.confirmPassword.label;"
+ accesskey="&setup.confirmPassword.accesskey;"
+ control="weavePasswordConfirm"/>
+ <textbox id="weavePasswordConfirm"
+ type="password"
+ onchange="gSyncSetup.onPasswordChange()"/>
+ </row>
+ <row id="passwordFeedbackRow" align="center" hidden="true">
+ <spacer/>
+ <hbox>
+ <image class="statusIcon"/>
+ <label class="status" value=" "/>
+ </hbox>
+ </row>
+ <row align="center">
+ <label control="server"
+ value="&server.label;"/>
+ <menulist id="server"
+ oncommand="gSyncSetup.onServerCommand()"
+ oninput="gSyncSetup.onServerInput()">
+ <menupopup>
+ <menuitem label="&serverType.default.label;"
+ value="main"/>
+ <menuitem label="&serverType.custom2.label;"
+ value="custom"/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row id="serverFeedbackRow" align="center" hidden="true">
+ <spacer/>
+ <hbox>
+ <image class="statusIcon"/>
+ <label class="status" value=" "/>
+ </hbox>
+ </row>
+#ifdef XP_WIN
+#ifdef MOZ_METRO
+ <row id="metroRow" align="center">
+ <spacer/>
+ <hbox align="center">
+ <checkbox label="&setup.setupMetro.label;"
+ accesskey="&setup.setupMetro.accesskey;"
+ control="weavePasswordConfirm"
+ id="metroSetupCheckbox"
+ checked="true"/>
+ </hbox>
+ </row>
+#endif
+#endif
+ <row id="TOSRow" align="center">
+ <spacer/>
+ <hbox align="center">
+ <checkbox id="tos"
+ accesskey="&setup.tosAgree1.accesskey;"
+ oncommand="this.focus(); gSyncSetup.checkFields();"/>
+ <description id="tosDesc"
+ flex="1"
+ onclick="document.getElementById('tos').focus();
+ document.getElementById('tos').click()">
+ &setup.tosAgree1.label;
+ <label class="text-link inline-link"
+ onclick="event.stopPropagation();gSyncUtils.openToS();">
+ &setup.tosLink.label;
+ </label>
+ &setup.tosAgree2.label;
+ <label class="text-link inline-link"
+ onclick="event.stopPropagation();gSyncUtils.openPrivacyPolicy();">
+ &setup.ppLink.label;
+ </label>
+ &setup.tosAgree3.label;
+ </description>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ <spacer flex="1"/>
+ <vbox flex="1" align="center">
+ <browser height="150"
+ width="500"
+ id="captcha"
+ type="content"
+ disablehistory="true"/>
+ <spacer flex="1"/>
+ <hbox id="captchaFeedback">
+ <image class="statusIcon"/>
+ <label class="status" value=" "/>
+ </hbox>
+ </vbox>
+ </wizardpage>
+
+ <wizardpage id="addDevice"
+ label="&pairDevice.title.label;"
+ onextra1="gSyncSetup.onSyncOptions()"
+ onpageshow="gSyncSetup.onPageShow()">
+ <description>
+ &pairDevice.setup.description.label;
+ <label class="text-link"
+ value="&addDevice.showMeHow.label;"
+ href="https://services.mozilla.com/sync/help/easy-setup"/>
+ </description>
+ <label value="&addDevice.setup.enterCode.label;"
+ control="easySetupPIN1"/>
+ <spacer flex="1"/>
+ <vbox align="center" flex="1">
+ <textbox id="easySetupPIN1"
+ class="pin"
+ value=""
+ readonly="true"
+ />
+ <textbox id="easySetupPIN2"
+ class="pin"
+ value=""
+ readonly="true"
+ />
+ <textbox id="easySetupPIN3"
+ class="pin"
+ value=""
+ readonly="true"
+ />
+ </vbox>
+ <spacer flex="3"/>
+ <label class="text-link"
+ value="&addDevice.dontHaveDevice.label;"
+ onclick="gSyncSetup.manualSetup();"/>
+ </wizardpage>
+
+ <wizardpage id="existingAccount"
+ label="&setup.signInPage.title.label;"
+ onextra1="gSyncSetup.onSyncOptions()"
+ onpageshow="gSyncSetup.onPageShow()">
+ <grid>
+ <columns>
+ <column/>
+ <column class="inputColumn" flex="1"/>
+ </columns>
+ <rows>
+ <row id="existingAccountRow" align="center">
+ <label id="existingAccountLabel"
+ value="&signIn.account2.label;"
+ accesskey="&signIn.account2.accesskey;"
+ control="existingAccount"/>
+ <textbox id="existingAccountName"
+ oninput="gSyncSetup.checkFields(event)"
+ onchange="gSyncSetup.checkFields(event)"/>
+ </row>
+ <row id="existingPasswordRow" align="center">
+ <label id="existingPasswordLabel"
+ value="&signIn.password.label;"
+ accesskey="&signIn.password.accesskey;"
+ control="existingPassword"/>
+ <textbox id="existingPassword"
+ type="password"
+ onkeyup="gSyncSetup.checkFields(event)"
+ onchange="gSyncSetup.checkFields(event)"/>
+ </row>
+ <row id="existingPasswordFeedbackRow" align="center" hidden="true">
+ <spacer/>
+ <hbox>
+ <image class="statusIcon"/>
+ <label class="status" value=" "/>
+ </hbox>
+ </row>
+ <row align="center">
+ <spacer/>
+ <label class="text-link"
+ value="&resetPassword.label;"
+ onclick="gSyncUtils.resetPassword(); return false;"/>
+ </row>
+ <row align="center">
+ <label control="existingServer"
+ value="&server.label;"/>
+ <menulist id="existingServer"
+ oncommand="gSyncSetup.onExistingServerCommand()"
+ oninput="gSyncSetup.onExistingServerInput()">
+ <menupopup>
+ <menuitem label="&serverType.default.label;"
+ value="main"/>
+ <menuitem label="&serverType.custom2.label;"
+ value="custom"/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row id="existingServerFeedbackRow" align="center" hidden="true">
+ <spacer/>
+ <hbox>
+ <image class="statusIcon"/>
+ <vbox>
+ <label class="status" value=" "/>
+ </vbox>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+
+ <groupbox>
+ <label id="existingPassphraseLabel"
+ value="&signIn.recoveryKey.label;"
+ accesskey="&signIn.recoveryKey.accesskey;"
+ control="existingPassphrase"/>
+ <textbox id="existingPassphrase"
+ oninput="gSyncSetup.checkFields()"/>
+ <hbox id="login-throbber" hidden="true">
+ <image/>
+ <label value="&verifying.label;"/>
+ </hbox>
+ <vbox align="left" id="existingPassphraseFeedbackRow" hidden="true">
+ <hbox>
+ <image class="statusIcon"/>
+ <label class="status" value=" "/>
+ </hbox>
+ </vbox>
+ </groupbox>
+
+ <vbox id="passphraseHelpBox">
+ <description>
+ &existingRecoveryKey.description;
+ <label class="text-link"
+ href="https://services.mozilla.com/sync/help/manual-setup">
+ &addDevice.showMeHow.label;
+ </label>
+ <spacer id="passphraseHelpSpacer"/>
+ <label class="text-link"
+ onclick="gSyncSetup.resetPassphrase(); return false;">
+ &resetSyncKey.label;
+ </label>
+ </description>
+ </vbox>
+ </wizardpage>
+
+ <wizardpage id="syncOptionsPage"
+ label="&setup.optionsPage.title;"
+ onpageshow="gSyncSetup.onPageShow()">
+ <groupbox id="syncOptions">
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1" style="-moz-margin-end: 2px"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label value="&syncDeviceName.label;"
+ accesskey="&syncDeviceName.accesskey;"
+ control="syncComputerName"/>
+ <textbox id="syncComputerName" flex="1"
+ onchange="gSyncUtils.changeName(this)"/>
+ </row>
+ <row>
+ <label value="&syncMy.label;" />
+ <vbox>
+ <checkbox label="&engine.addons.label;"
+ accesskey="&engine.addons.accesskey;"
+ id="engine.addons"
+ checked="true"/>
+ <checkbox label="&engine.bookmarks.label;"
+ accesskey="&engine.bookmarks.accesskey;"
+ id="engine.bookmarks"
+ checked="true"/>
+ <checkbox label="&engine.passwords.label;"
+ accesskey="&engine.passwords.accesskey;"
+ id="engine.passwords"
+ checked="true"/>
+ <checkbox label="&engine.prefs.label;"
+ accesskey="&engine.prefs.accesskey;"
+ id="engine.prefs"
+ checked="true"/>
+ <checkbox label="&engine.history.label;"
+ accesskey="&engine.history.accesskey;"
+ id="engine.history"
+ checked="true"/>
+ <checkbox label="&engine.tabs.label;"
+ accesskey="&engine.tabs.accesskey;"
+ id="engine.tabs"
+ checked="true"/>
+ </vbox>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <groupbox id="mergeOptions">
+ <radiogroup id="mergeChoiceRadio" pack="start">
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows flex="1">
+ <row align="center">
+ <radio id="resetClient"
+ class="mergeChoiceButton"
+ aria-labelledby="resetClientLabel"/>
+ <label id="resetClientLabel" control="resetClient">
+ <html:strong>&choice2.merge.recommended.label;</html:strong>
+ &choice2a.merge.main.label;
+ </label>
+ </row>
+ <row align="center">
+ <radio id="wipeClient"
+ class="mergeChoiceButton"
+ aria-labelledby="wipeClientLabel"/>
+ <label id="wipeClientLabel"
+ control="wipeClient">
+ &choice2a.client.main.label;
+ </label>
+ </row>
+ <row align="center">
+ <radio id="wipeRemote"
+ class="mergeChoiceButton"
+ aria-labelledby="wipeRemoteLabel"/>
+ <label id="wipeRemoteLabel"
+ control="wipeRemote">
+ &choice2a.server.main.label;
+ </label>
+ </row>
+ </rows>
+ </grid>
+ </radiogroup>
+ </groupbox>
+ </wizardpage>
+
+ <wizardpage id="syncOptionsConfirm"
+ label="&setup.optionsConfirmPage.title;"
+ onpageshow="gSyncSetup.onPageShow()">
+ <deck id="chosenActionDeck">
+ <vbox id="chosenActionMerge" class="confirm">
+ <description class="normal">
+ &confirm.merge2.label;
+ </description>
+ </vbox>
+ <vbox id="chosenActionWipeClient" class="confirm">
+ <description class="normal">
+ &confirm.client3.label;
+ </description>
+ <separator class="thin"/>
+ <vbox id="dataList">
+ <label class="data indent" id="bookmarkCount"/>
+ <label class="data indent" id="historyCount"/>
+ <label class="data indent" id="passwordCount"/>
+ <label class="data indent" id="addonCount"/>
+ <label class="data indent" id="prefsWipe"
+ value="&engine.prefs.label;"/>
+ </vbox>
+ <separator class="thin"/>
+ <description class="normal">
+ &confirm.client2.moreinfo.label;
+ </description>
+ </vbox>
+ <vbox id="chosenActionWipeServer" class="confirm">
+ <description class="normal">
+ &confirm.server2.label;
+ </description>
+ <separator class="thin"/>
+ <vbox id="clientList">
+ </vbox>
+ </vbox>
+ </deck>
+ </wizardpage>
+ <!-- In terms of the wizard flow shown to the user, the 'syncOptionsConfirm'
+ page above is not the last wizard page. To prevent the wizard binding from
+ assuming that it is, we're inserting this dummy page here. This also means
+ that the wizard needs to always be closed manually via wizardFinish(). -->
+ <wizardpage>
+ </wizardpage>
+</wizard>
+
diff --git a/browser/base/content/sync/utils.js b/browser/base/content/sync/utils.js
new file mode 100644
index 000000000..af4c8a811
--- /dev/null
+++ b/browser/base/content/sync/utils.js
@@ -0,0 +1,218 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Equivalent to 0600 permissions; used for saved Sync Recovery Key.
+// This constant can be replaced when the equivalent values are available to
+// chrome JS; see Bug 433295 and Bug 757351.
+const PERMISSIONS_RWUSR = 0x180;
+
+// Weave should always exist before before this file gets included.
+let gSyncUtils = {
+ get bundle() {
+ delete this.bundle;
+ return this.bundle = Services.strings.createBundle("chrome://browser/locale/syncSetup.properties");
+ },
+
+ // opens in a new window if we're in a modal prefwindow world, in a new tab otherwise
+ _openLink: function (url) {
+ let thisDocEl = document.documentElement,
+ openerDocEl = window.opener && window.opener.document.documentElement;
+ if (thisDocEl.id == "accountSetup" && window.opener &&
+ openerDocEl.id == "BrowserPreferences" && !openerDocEl.instantApply)
+ openUILinkIn(url, "window");
+ else if (thisDocEl.id == "BrowserPreferences" && !thisDocEl.instantApply)
+ openUILinkIn(url, "window");
+ else if (document.documentElement.id == "change-dialog")
+ Services.wm.getMostRecentWindow("navigator:browser")
+ .openUILinkIn(url, "tab");
+ else
+ openUILinkIn(url, "tab");
+ },
+
+ changeName: function changeName(input) {
+ // Make sure to update to a modified name, e.g., empty-string -> default
+ Weave.Service.clientsEngine.localName = input.value;
+ input.value = Weave.Service.clientsEngine.localName;
+ },
+
+ openChange: function openChange(type, duringSetup) {
+ // Just re-show the dialog if it's already open
+ let openedDialog = Services.wm.getMostRecentWindow("Sync:" + type);
+ if (openedDialog != null) {
+ openedDialog.focus();
+ return;
+ }
+
+ // Open up the change dialog
+ let changeXUL = "chrome://browser/content/sync/genericChange.xul";
+ let changeOpt = "centerscreen,chrome,resizable=no";
+ Services.ww.activeWindow.openDialog(changeXUL, "", changeOpt,
+ type, duringSetup);
+ },
+
+ changePassword: function () {
+ if (Weave.Utils.ensureMPUnlocked())
+ this.openChange("ChangePassword");
+ },
+
+ resetPassphrase: function (duringSetup) {
+ if (Weave.Utils.ensureMPUnlocked())
+ this.openChange("ResetPassphrase", duringSetup);
+ },
+
+ updatePassphrase: function () {
+ if (Weave.Utils.ensureMPUnlocked())
+ this.openChange("UpdatePassphrase");
+ },
+
+ resetPassword: function () {
+ this._openLink(Weave.Service.pwResetURL);
+ },
+
+ openToS: function () {
+ this._openLink(Weave.Svc.Prefs.get("termsURL"));
+ },
+
+ openPrivacyPolicy: function () {
+ this._openLink(Weave.Svc.Prefs.get("privacyURL"));
+ },
+
+ openFirstSyncProgressPage: function () {
+ this._openLink("about:sync-progress");
+ },
+
+ /**
+ * Prepare an invisible iframe with the passphrase backup document.
+ * Used by both the print and saving methods.
+ *
+ * @param elid : ID of the form element containing the passphrase.
+ * @param callback : Function called once the iframe has loaded.
+ */
+ _preparePPiframe: function(elid, callback) {
+ let pp = document.getElementById(elid).value;
+
+ // Create an invisible iframe whose contents we can print.
+ let iframe = document.createElement("iframe");
+ iframe.setAttribute("src", "chrome://browser/content/sync/key.xhtml");
+ iframe.collapsed = true;
+ document.documentElement.appendChild(iframe);
+ iframe.contentWindow.addEventListener("load", function() {
+ iframe.contentWindow.removeEventListener("load", arguments.callee, false);
+
+ // Insert the Sync Key into the page.
+ let el = iframe.contentDocument.getElementById("synckey");
+ el.firstChild.nodeValue = pp;
+
+ // Insert the TOS and Privacy Policy URLs into the page.
+ let termsURL = Weave.Svc.Prefs.get("termsURL");
+ el = iframe.contentDocument.getElementById("tosLink");
+ el.setAttribute("href", termsURL);
+ el.firstChild.nodeValue = termsURL;
+
+ let privacyURL = Weave.Svc.Prefs.get("privacyURL");
+ el = iframe.contentDocument.getElementById("ppLink");
+ el.setAttribute("href", privacyURL);
+ el.firstChild.nodeValue = privacyURL;
+
+ callback(iframe);
+ }, false);
+ },
+
+ /**
+ * Print passphrase backup document.
+ *
+ * @param elid : ID of the form element containing the passphrase.
+ */
+ passphrasePrint: function(elid) {
+ this._preparePPiframe(elid, function(iframe) {
+ let webBrowserPrint = iframe.contentWindow
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebBrowserPrint);
+ let printSettings = PrintUtils.getPrintSettings();
+
+ // Display no header/footer decoration except for the date.
+ printSettings.headerStrLeft
+ = printSettings.headerStrCenter
+ = printSettings.headerStrRight
+ = printSettings.footerStrLeft
+ = printSettings.footerStrCenter = "";
+ printSettings.footerStrRight = "&D";
+
+ try {
+ webBrowserPrint.print(printSettings, null);
+ } catch (ex) {
+ // print()'s return codes are expressed as exceptions. Ignore.
+ }
+ });
+ },
+
+ /**
+ * Save passphrase backup document to disk as HTML file.
+ *
+ * @param elid : ID of the form element containing the passphrase.
+ */
+ passphraseSave: function(elid) {
+ let dialogTitle = this.bundle.GetStringFromName("save.recoverykey.title");
+ let defaultSaveName = this.bundle.GetStringFromName("save.recoverykey.defaultfilename");
+ this._preparePPiframe(elid, function(iframe) {
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ let fpCallback = function fpCallback_done(aResult) {
+ if (aResult == Ci.nsIFilePicker.returnOK ||
+ aResult == Ci.nsIFilePicker.returnReplace) {
+ let stream = Cc["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+ stream.init(fp.file, -1, PERMISSIONS_RWUSR, 0);
+
+ let serializer = new XMLSerializer();
+ let output = serializer.serializeToString(iframe.contentDocument);
+ output = output.replace(/<!DOCTYPE (.|\n)*?]>/,
+ '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ' +
+ '"DTD/xhtml1-strict.dtd">');
+ output = Weave.Utils.encodeUTF8(output);
+ stream.write(output, output.length);
+ }
+ };
+
+ fp.init(window, dialogTitle, Ci.nsIFilePicker.modeSave);
+ fp.appendFilters(Ci.nsIFilePicker.filterHTML);
+ fp.defaultString = defaultSaveName;
+ fp.open(fpCallback);
+ return false;
+ });
+ },
+
+ /**
+ * validatePassword
+ *
+ * @param el1 : the first textbox element in the form
+ * @param el2 : the second textbox element, if omitted it's an update form
+ *
+ * returns [valid, errorString]
+ */
+ validatePassword: function (el1, el2) {
+ let valid = false;
+ let val1 = el1.value;
+ let val2 = el2 ? el2.value : "";
+ let error = "";
+
+ if (!el2)
+ valid = val1.length >= Weave.MIN_PASS_LENGTH;
+ else if (val1 && val1 == Weave.Service.identity.username)
+ error = "change.password.pwSameAsUsername";
+ else if (val1 && val1 == Weave.Service.identity.account)
+ error = "change.password.pwSameAsEmail";
+ else if (val1 && val1 == Weave.Service.identity.basicPassword)
+ error = "change.password.pwSameAsPassword";
+ else if (val1 && val2) {
+ if (val1 == val2 && val1.length >= Weave.MIN_PASS_LENGTH)
+ valid = true;
+ else if (val1.length < Weave.MIN_PASS_LENGTH)
+ error = "change.password.tooShort";
+ else if (val1 != val2)
+ error = "change.password.mismatch";
+ }
+ let errorString = error ? Weave.Utils.getErrorString(error) : "";
+ return [valid, errorString];
+ }
+};
diff --git a/browser/base/content/tabbrowser.css b/browser/base/content/tabbrowser.css
new file mode 100644
index 000000000..2dd6a0529
--- /dev/null
+++ b/browser/base/content/tabbrowser.css
@@ -0,0 +1,65 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+.tabbrowser-tabbox {
+ -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-tabbox");
+}
+
+.tabbrowser-arrowscrollbox {
+ -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-arrowscrollbox");
+}
+
+.tab-close-button {
+ -moz-binding: url("chrome://browser/content/tabbrowser.xml#tabbrowser-close-tab-button");
+ display: none;
+}
+
+.tabbrowser-tabs[closebuttons="activetab"] > * > * > * > .tab-close-button:not([pinned])[selected="true"],
+.tabbrowser-tabs[closebuttons="alltabs"] > * > * > * > .tab-close-button:not([pinned]) {
+ display: -moz-box;
+}
+
+.tab-label[pinned] {
+ width: 0;
+ margin-left: 0 !important;
+ margin-right: 0 !important;
+ padding-left: 0 !important;
+ padding-right: 0 !important;
+}
+
+.tab-stack {
+ vertical-align: top; /* for pinned tabs */
+}
+
+tabpanels {
+ background-color: transparent;
+}
+
+.tab-drop-indicator {
+ position: relative;
+ z-index: 2;
+}
+
+.tab-throbber:not([busy]),
+.tab-throbber[busy] + .tab-icon-image {
+ display: none;
+}
+
+.closing-tabs-spacer {
+ pointer-events: none;
+}
+
+.tabbrowser-tabs:not(:hover) > .tabbrowser-arrowscrollbox > .closing-tabs-spacer {
+ transition: width .15s ease-out;
+}
+
+/**
+ * Optimization for tabs that are restored lazily. We can save a good amount of
+ * memory that to-be-restored tabs would otherwise consume simply by setting
+ * their browsers to 'display: none' as that will prevent them from having to
+ * create a presentation and the like.
+ */
+browser[pending] {
+ display: none;
+}
diff --git a/browser/base/content/tabbrowser.xml b/browser/base/content/tabbrowser.xml
new file mode 100644
index 000000000..6c76120a8
--- /dev/null
+++ b/browser/base/content/tabbrowser.xml
@@ -0,0 +1,4801 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE bindings [
+<!ENTITY % tabBrowserDTD SYSTEM "chrome://browser/locale/tabbrowser.dtd" >
+%tabBrowserDTD;
+]>
+
+<bindings id="tabBrowserBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="tabbrowser">
+ <resources>
+ <stylesheet src="chrome://browser/content/tabbrowser.css"/>
+ </resources>
+
+ <content>
+ <xul:stringbundle anonid="tbstringbundle" src="chrome://browser/locale/tabbrowser.properties"/>
+ <xul:tabbox anonid="tabbox" class="tabbrowser-tabbox"
+ flex="1" eventnode="document" xbl:inherits="handleCtrlPageUpDown"
+ onselect="if (event.target.localName == 'tabpanels') this.parentNode.updateCurrentBrowser();">
+ <xul:tabpanels flex="1" class="plain" selectedIndex="0" anonid="panelcontainer">
+ <xul:notificationbox flex="1">
+ <xul:hbox flex="1" class="browserSidebarContainer">
+ <xul:vbox flex="1" class="browserContainer">
+ <xul:stack flex="1" class="browserStack" anonid="browserStack">
+ <xul:browser anonid="initialBrowser" type="content-primary" message="true" disablehistory="true"
+ xbl:inherits="tooltip=contenttooltip,contextmenu=contentcontextmenu,autocompletepopup"/>
+ </xul:stack>
+ </xul:vbox>
+ </xul:hbox>
+ </xul:notificationbox>
+ </xul:tabpanels>
+ </xul:tabbox>
+ <children/>
+ </content>
+ <implementation implements="nsIDOMEventListener, nsIMessageListener">
+
+ <property name="tabContextMenu" readonly="true"
+ onget="return this.tabContainer.contextMenu;"/>
+
+ <field name="tabContainer" readonly="true">
+ document.getElementById(this.getAttribute("tabcontainer"));
+ </field>
+ <field name="tabs" readonly="true">
+ this.tabContainer.childNodes;
+ </field>
+
+ <property name="visibleTabs" readonly="true">
+ <getter><![CDATA[
+ if (!this._visibleTabs)
+ this._visibleTabs = Array.filter(this.tabs,
+ function (tab) !tab.hidden && !tab.closing);
+ return this._visibleTabs;
+ ]]></getter>
+ </property>
+
+ <field name="closingTabsEnum" readonly="true">({ ALL: 0, OTHER: 1, TO_END: 2 });</field>
+
+ <field name="_visibleTabs">null</field>
+
+ <field name="mURIFixup" readonly="true">
+ Components.classes["@mozilla.org/docshell/urifixup;1"]
+ .getService(Components.interfaces.nsIURIFixup);
+ </field>
+ <field name="mFaviconService" readonly="true">
+ Components.classes["@mozilla.org/browser/favicon-service;1"]
+ .getService(Components.interfaces.nsIFaviconService);
+ </field>
+ <field name="_placesAutocomplete" readonly="true">
+ Components.classes["@mozilla.org/autocomplete/search;1?name=history"]
+ .getService(Components.interfaces.mozIPlacesAutoComplete);
+ </field>
+ <field name="mTabBox" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "tabbox");
+ </field>
+ <field name="mPanelContainer" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "panelcontainer");
+ </field>
+ <field name="mStringBundle">
+ document.getAnonymousElementByAttribute(this, "anonid", "tbstringbundle");
+ </field>
+ <field name="mCurrentTab">
+ null
+ </field>
+ <field name="_lastRelatedTab">
+ null
+ </field>
+ <field name="mCurrentBrowser">
+ null
+ </field>
+ <field name="mProgressListeners">
+ []
+ </field>
+ <field name="mTabsProgressListeners">
+ []
+ </field>
+ <field name="mTabListeners">
+ []
+ </field>
+ <field name="mTabFilters">
+ []
+ </field>
+ <field name="mIsBusy">
+ false
+ </field>
+ <field name="arrowKeysShouldWrap" readonly="true">
+#ifdef XP_MACOSX
+ true
+#else
+ false
+#endif
+ </field>
+
+ <field name="_autoScrollPopup">
+ null
+ </field>
+
+ <field name="_previewMode">
+ false
+ </field>
+
+ <property name="_numPinnedTabs" readonly="true">
+ <getter><![CDATA[
+ for (var i = 0; i < this.tabs.length; i++) {
+ if (!this.tabs[i].pinned)
+ break;
+ }
+ return i;
+ ]]></getter>
+ </property>
+
+ <method name="updateWindowResizers">
+ <body><![CDATA[
+ if (!window.gShowPageResizers)
+ return;
+
+ var show = document.getElementById("addon-bar").collapsed &&
+ window.windowState == window.STATE_NORMAL;
+ for (let i = 0; i < this.browsers.length; i++) {
+ this.browsers[i].showWindowResizer = show;
+ }
+ ]]></body>
+ </method>
+
+ <method name="_setCloseKeyState">
+ <parameter name="aEnabled"/>
+ <body><![CDATA[
+ let keyClose = document.getElementById("key_close");
+ let closeKeyEnabled = keyClose.getAttribute("disabled") != "true";
+ if (closeKeyEnabled == aEnabled)
+ return;
+
+ if (aEnabled)
+ keyClose.removeAttribute("disabled");
+ else
+ keyClose.setAttribute("disabled", "true");
+
+ // We also want to remove the keyboard shortcut from the file menu
+ // when the shortcut is disabled, and bring it back when it's
+ // renabled.
+ //
+ // Fixing bug 630826 could make that happen automatically.
+ // Fixing bug 630830 could avoid the ugly hack below.
+
+ let closeMenuItem = document.getElementById("menu_close");
+ let parentPopup = closeMenuItem.parentNode;
+ let nextItem = closeMenuItem.nextSibling;
+ let clonedItem = closeMenuItem.cloneNode(true);
+
+ parentPopup.removeChild(closeMenuItem);
+
+ if (aEnabled)
+ clonedItem.setAttribute("key", "key_close");
+ else
+ clonedItem.removeAttribute("key");
+
+ parentPopup.insertBefore(clonedItem, nextItem);
+ ]]></body>
+ </method>
+
+ <method name="pinTab">
+ <parameter name="aTab"/>
+ <body><![CDATA[
+ if (aTab.pinned)
+ return;
+
+ if (aTab.hidden)
+ this.showTab(aTab);
+
+ this.moveTabTo(aTab, this._numPinnedTabs);
+ aTab.setAttribute("pinned", "true");
+ this.tabContainer._unlockTabSizing();
+ this.tabContainer._positionPinnedTabs();
+ this.tabContainer.adjustTabstrip();
+
+ this.getBrowserForTab(aTab).docShell.isAppTab = true;
+
+ if (aTab.selected)
+ this._setCloseKeyState(false);
+
+ let event = document.createEvent("Events");
+ event.initEvent("TabPinned", true, false);
+ aTab.dispatchEvent(event);
+ ]]></body>
+ </method>
+
+ <method name="unpinTab">
+ <parameter name="aTab"/>
+ <body><![CDATA[
+ if (!aTab.pinned)
+ return;
+
+ this.moveTabTo(aTab, this._numPinnedTabs - 1);
+ aTab.setAttribute("fadein", "true");
+ aTab.removeAttribute("pinned");
+ aTab.style.MozMarginStart = "";
+ this.tabContainer._unlockTabSizing();
+ this.tabContainer._positionPinnedTabs();
+ this.tabContainer.adjustTabstrip();
+
+ this.getBrowserForTab(aTab).docShell.isAppTab = false;
+
+ if (aTab.selected)
+ this._setCloseKeyState(true);
+
+ let event = document.createEvent("Events");
+ event.initEvent("TabUnpinned", true, false);
+ aTab.dispatchEvent(event);
+ ]]></body>
+ </method>
+
+ <method name="previewTab">
+ <parameter name="aTab"/>
+ <parameter name="aCallback"/>
+ <body>
+ <![CDATA[
+ let currentTab = this.selectedTab;
+ try {
+ // Suppress focus, ownership and selected tab changes
+ this._previewMode = true;
+ this.selectedTab = aTab;
+ aCallback();
+ } finally {
+ this.selectedTab = currentTab;
+ this._previewMode = false;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserAtIndex">
+ <parameter name="aIndex"/>
+ <body>
+ <![CDATA[
+ return this.browsers[aIndex];
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserIndexForDocument">
+ <parameter name="aDocument"/>
+ <body>
+ <![CDATA[
+ var tab = this._getTabForContentWindow(aDocument.defaultView);
+ return tab ? tab._tPos : -1;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserForDocument">
+ <parameter name="aDocument"/>
+ <body>
+ <![CDATA[
+ var tab = this._getTabForContentWindow(aDocument.defaultView);
+ return tab ? tab.linkedBrowser : null;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_getTabForContentWindow">
+ <parameter name="aWindow"/>
+ <body>
+ <![CDATA[
+ for (let i = 0; i < this.browsers.length; i++) {
+ if (this.browsers[i].contentWindow == aWindow)
+ return this.tabs[i];
+ }
+ return null;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_getTabForBrowser">
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ for (let i = 0; i < this.tabs.length; i++) {
+ if (this.tabs[i].linkedBrowser == aBrowser)
+ return this.tabs[i];
+ }
+ return null;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getNotificationBox">
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ return this.getSidebarContainer(aBrowser).parentNode;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getSidebarContainer">
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ return this.getBrowserContainer(aBrowser).parentNode;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserContainer">
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ return (aBrowser || this.mCurrentBrowser).parentNode.parentNode;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getTabModalPromptBox">
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ let browser = (aBrowser || this.mCurrentBrowser);
+ let stack = browser.parentNode;
+ let self = this;
+
+ let promptBox = {
+ appendPrompt : function(args, onCloseCallback) {
+ let newPrompt = document.createElementNS(XUL_NS, "tabmodalprompt");
+ stack.appendChild(newPrompt);
+ browser.setAttribute("tabmodalPromptShowing", true);
+
+ newPrompt.clientTop; // style flush to assure binding is attached
+
+ let tab = self._getTabForContentWindow(browser.contentWindow);
+ newPrompt.init(args, tab, onCloseCallback);
+ return newPrompt;
+ },
+
+ removePrompt : function(aPrompt) {
+ stack.removeChild(aPrompt);
+
+ let prompts = this.listPrompts();
+ if (prompts.length) {
+ let prompt = prompts[prompts.length - 1];
+ prompt.Dialog.setDefaultFocus();
+ } else {
+ browser.removeAttribute("tabmodalPromptShowing");
+ browser.focus();
+ }
+ },
+
+ listPrompts : function(aPrompt) {
+ let els = stack.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
+ // NodeList --> real JS array
+ let prompts = Array.slice(els);
+ return prompts;
+ },
+ };
+
+ return promptBox;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_callProgressListeners">
+ <parameter name="aBrowser"/>
+ <parameter name="aMethod"/>
+ <parameter name="aArguments"/>
+ <parameter name="aCallGlobalListeners"/>
+ <parameter name="aCallTabsListeners"/>
+ <body><![CDATA[
+ var rv = true;
+
+ if (!aBrowser)
+ aBrowser = this.mCurrentBrowser;
+
+ if (aCallGlobalListeners != false &&
+ aBrowser == this.mCurrentBrowser) {
+ this.mProgressListeners.forEach(function (p) {
+ if (aMethod in p) {
+ try {
+ if (!p[aMethod].apply(p, aArguments))
+ rv = false;
+ } catch (e) {
+ // don't inhibit other listeners
+ Components.utils.reportError(e);
+ }
+ }
+ });
+ }
+
+ if (aCallTabsListeners != false) {
+ aArguments.unshift(aBrowser);
+
+ this.mTabsProgressListeners.forEach(function (p) {
+ if (aMethod in p) {
+ try {
+ if (!p[aMethod].apply(p, aArguments))
+ rv = false;
+ } catch (e) {
+ // don't inhibit other listeners
+ Components.utils.reportError(e);
+ }
+ }
+ });
+ }
+
+ return rv;
+ ]]></body>
+ </method>
+
+ <!-- A web progress listener object definition for a given tab. -->
+ <method name="mTabProgressListener">
+ <parameter name="aTab"/>
+ <parameter name="aBrowser"/>
+ <parameter name="aStartsBlank"/>
+ <body>
+ <![CDATA[
+ return ({
+ mTabBrowser: this,
+ mTab: aTab,
+ mBrowser: aBrowser,
+ mBlank: aStartsBlank,
+
+ // cache flags for correct status UI update after tab switching
+ mStateFlags: 0,
+ mStatus: 0,
+ mMessage: "",
+ mTotalProgress: 0,
+
+ // count of open requests (should always be 0 or 1)
+ mRequestCount: 0,
+
+ destroy: function () {
+ delete this.mTab;
+ delete this.mBrowser;
+ delete this.mTabBrowser;
+ },
+
+ _callProgressListeners: function () {
+ Array.unshift(arguments, this.mBrowser);
+ return this.mTabBrowser._callProgressListeners.apply(this.mTabBrowser, arguments);
+ },
+
+ _shouldShowProgress: function (aRequest) {
+ if (this.mBlank)
+ return false;
+
+ if (gMultiProcessBrowser)
+ return true;
+
+ // Don't show progress indicators in tabs for about: URIs
+ // pointing to local resources.
+ try {
+ let channel = aRequest.QueryInterface(Ci.nsIChannel);
+ if (channel.originalURI.schemeIs("about") &&
+ (channel.URI.schemeIs("jar") || channel.URI.schemeIs("file")))
+ return false;
+ } catch (e) {}
+
+ return true;
+ },
+
+ onProgressChange: function (aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress) {
+ this.mTotalProgress = aMaxTotalProgress ? aCurTotalProgress / aMaxTotalProgress : 0;
+
+ if (!this._shouldShowProgress(aRequest))
+ return;
+
+ if (this.mTotalProgress)
+ this.mTab.setAttribute("progress", "true");
+
+ this._callProgressListeners("onProgressChange",
+ [aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress]);
+ },
+
+ onProgressChange64: function (aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress) {
+ return this.onProgressChange(aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress,
+ aMaxTotalProgress);
+ },
+
+ onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) {
+ if (!aRequest)
+ return;
+
+ var oldBlank = this.mBlank;
+
+ const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
+ const nsIChannel = Components.interfaces.nsIChannel;
+
+ if (aStateFlags & nsIWebProgressListener.STATE_START) {
+ this.mRequestCount++;
+ }
+ else if (aStateFlags & nsIWebProgressListener.STATE_STOP) {
+ const NS_ERROR_UNKNOWN_HOST = 2152398878;
+ if (--this.mRequestCount > 0 && aStatus == NS_ERROR_UNKNOWN_HOST) {
+ // to prevent bug 235825: wait for the request handled
+ // by the automatic keyword resolver
+ return;
+ }
+ // since we (try to) only handle STATE_STOP of the last request,
+ // the count of open requests should now be 0
+ this.mRequestCount = 0;
+ }
+
+ if (aStateFlags & nsIWebProgressListener.STATE_START &&
+ aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
+ // It's okay to clear what the user typed when we start
+ // loading a document. If the user types, this counter gets
+ // set to zero, if the document load ends without an
+ // onLocationChange, this counter gets decremented
+ // (so we keep it while switching tabs after failed loads)
+ // We need to add 2 because loadURIWithFlags may have
+ // cancelled a pending load which would have cleared
+ // its anchor scroll detection temporary increment.
+ if (aWebProgress.isTopLevel)
+ this.mBrowser.userTypedClear += 2;
+
+ if (this._shouldShowProgress(aRequest)) {
+ if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
+ this.mTab.setAttribute("busy", "true");
+ if (!gMultiProcessBrowser) {
+ if (!(this.mBrowser.docShell.loadType & Ci.nsIDocShell.LOAD_CMD_RELOAD))
+ this.mTabBrowser.setTabTitleLoading(this.mTab);
+ }
+ }
+
+ if (this.mTab.selected)
+ this.mTabBrowser.mIsBusy = true;
+ }
+ }
+ else if (aStateFlags & nsIWebProgressListener.STATE_STOP &&
+ aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK) {
+
+ if (this.mTab.hasAttribute("busy")) {
+ this.mTab.removeAttribute("busy");
+ this.mTabBrowser._tabAttrModified(this.mTab);
+ if (!this.mTab.selected)
+ this.mTab.setAttribute("unread", "true");
+ }
+ this.mTab.removeAttribute("progress");
+
+ if (aWebProgress.isTopLevel) {
+ if (!Components.isSuccessCode(aStatus) &&
+ !isTabEmpty(this.mTab)) {
+ // Restore the current document's location in case the
+ // request was stopped (possibly from a content script)
+ // before the location changed.
+
+ this.mBrowser.userTypedValue = null;
+
+ if (this.mTab.selected && gURLBar)
+ URLBarSetURI();
+ } else {
+ // The document is done loading, we no longer want the
+ // value cleared.
+
+ if (this.mBrowser.userTypedClear > 1)
+ this.mBrowser.userTypedClear -= 2;
+ else if (this.mBrowser.userTypedClear > 0)
+ this.mBrowser.userTypedClear--;
+ }
+
+ if (!this.mBrowser.mIconURL)
+ this.mTabBrowser.useDefaultIcon(this.mTab);
+ }
+
+ if (this.mBlank)
+ this.mBlank = false;
+
+ var location = aRequest.QueryInterface(nsIChannel).URI;
+
+ // For keyword URIs clear the user typed value since they will be changed into real URIs
+ if (location.scheme == "keyword")
+ this.mBrowser.userTypedValue = null;
+
+ if (this.mTab.label == this.mTabBrowser.mStringBundle.getString("tabs.connecting"))
+ this.mTabBrowser.setTabTitle(this.mTab);
+
+ if (this.mTab.selected)
+ this.mTabBrowser.mIsBusy = false;
+ }
+
+ if (oldBlank) {
+ this._callProgressListeners("onUpdateCurrentBrowser",
+ [aStateFlags, aStatus, "", 0],
+ true, false);
+ } else {
+ this._callProgressListeners("onStateChange",
+ [aWebProgress, aRequest, aStateFlags, aStatus],
+ true, false);
+ }
+
+ this._callProgressListeners("onStateChange",
+ [aWebProgress, aRequest, aStateFlags, aStatus],
+ false);
+
+ if (aStateFlags & (nsIWebProgressListener.STATE_START |
+ nsIWebProgressListener.STATE_STOP)) {
+ // reset cached temporary values at beginning and end
+ this.mMessage = "";
+ this.mTotalProgress = 0;
+ }
+ this.mStateFlags = aStateFlags;
+ this.mStatus = aStatus;
+ },
+
+ onLocationChange: function (aWebProgress, aRequest, aLocation,
+ aFlags) {
+ // OnLocationChange is called for both the top-level content
+ // and the subframes.
+ let topLevel = aWebProgress.isTopLevel;
+
+ if (topLevel) {
+ // The document loaded correctly, clear the value if we should
+ if (this.mBrowser.userTypedClear > 0 ||
+ (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE))
+ this.mBrowser.userTypedValue = null;
+
+ // Clear out the missing plugins list since it's related to the
+ // previous location.
+ this.mBrowser.missingPlugins = null;
+
+ // Don't clear the favicon if this onLocationChange was
+ // triggered by a pushState or a replaceState. See bug 550565.
+ if (!gMultiProcessBrowser) {
+ if (aWebProgress.isLoadingDocument &&
+ !(this.mBrowser.docShell.loadType & Ci.nsIDocShell.LOAD_CMD_PUSHSTATE))
+ this.mBrowser.mIconURL = null;
+ }
+
+ let autocomplete = this.mTabBrowser._placesAutocomplete;
+ if (this.mBrowser.registeredOpenURI) {
+ autocomplete.unregisterOpenPage(this.mBrowser.registeredOpenURI);
+ delete this.mBrowser.registeredOpenURI;
+ }
+ // Tabs in private windows aren't registered as "Open" so
+ // that they don't appear as switch-to-tab candidates.
+ if (!isBlankPageURL(aLocation.spec) &&
+ (!PrivateBrowsingUtils.isWindowPrivate(window) ||
+ PrivateBrowsingUtils.permanentPrivateBrowsing)) {
+ autocomplete.registerOpenPage(aLocation);
+ this.mBrowser.registeredOpenURI = aLocation;
+ }
+ }
+
+ if (!this.mBlank) {
+ this._callProgressListeners("onLocationChange",
+ [aWebProgress, aRequest, aLocation,
+ aFlags]);
+ }
+
+ if (topLevel)
+ this.mBrowser.lastURI = aLocation;
+ },
+
+ onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) {
+ if (this.mBlank)
+ return;
+
+ this._callProgressListeners("onStatusChange",
+ [aWebProgress, aRequest, aStatus, aMessage]);
+
+ this.mMessage = aMessage;
+ },
+
+ onSecurityChange: function (aWebProgress, aRequest, aState) {
+ this._callProgressListeners("onSecurityChange",
+ [aWebProgress, aRequest, aState]);
+ },
+
+ onRefreshAttempted: function (aWebProgress, aURI, aDelay, aSameURI) {
+ return this._callProgressListeners("onRefreshAttempted",
+ [aWebProgress, aURI, aDelay, aSameURI]);
+ },
+
+ QueryInterface: function (aIID) {
+ if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
+ aIID.equals(Components.interfaces.nsIWebProgressListener2) ||
+ aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
+ aIID.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ }
+ });
+ ]]>
+ </body>
+ </method>
+
+ <method name="setIcon">
+ <parameter name="aTab"/>
+ <parameter name="aURI"/>
+ <body>
+ <![CDATA[
+ var browser = this.getBrowserForTab(aTab);
+ browser.mIconURL = aURI instanceof Ci.nsIURI ? aURI.spec : aURI;
+
+ if (aURI && this.mFaviconService) {
+ if (!(aURI instanceof Ci.nsIURI))
+ aURI = makeURI(aURI);
+ this.mFaviconService.setAndFetchFaviconForPage(browser.currentURI,
+ aURI, false,
+ PrivateBrowsingUtils.isWindowPrivate(window) ?
+ this.mFaviconService.FAVICON_LOAD_PRIVATE :
+ this.mFaviconService.FAVICON_LOAD_NON_PRIVATE);
+ }
+
+ let sizedIconUrl = browser.mIconURL || "";
+ if (sizedIconUrl) {
+ let size = Math.round(16 * window.devicePixelRatio);
+ sizedIconUrl += (sizedIconUrl.contains("#") ? "&" : "#") +
+ "-moz-resolution=" + size + "," + size;
+ }
+ if (sizedIconUrl != aTab.getAttribute("image")) {
+ if (browser.mIconURL) //PMed
+ aTab.setAttribute("image", sizedIconUrl);
+ else
+ aTab.removeAttribute("image");
+ this._tabAttrModified(aTab);
+ }
+
+ this._callProgressListeners(browser, "onLinkIconAvailable", [browser.mIconURL]);
+ ]]>
+ </body>
+ </method>
+
+ <method name="getIcon">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ let browser = aTab ? this.getBrowserForTab(aTab) : this.selectedBrowser;
+ return browser.mIconURL;
+ ]]>
+ </body>
+ </method>
+
+ <method name="shouldLoadFavIcon">
+ <parameter name="aURI"/>
+ <body>
+ <![CDATA[
+ return (aURI &&
+ Services.prefs.getBoolPref("browser.chrome.site_icons") &&
+ Services.prefs.getBoolPref("browser.chrome.favicons") &&
+ ("schemeIs" in aURI) && (aURI.schemeIs("http") || aURI.schemeIs("https")));
+ ]]>
+ </body>
+ </method>
+
+ <method name="useDefaultIcon">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ // Bug 691610 - e10s support for useDefaultIcon
+ if (gMultiProcessBrowser)
+ return;
+
+ var browser = this.getBrowserForTab(aTab);
+ var docURIObject = browser.contentDocument.documentURIObject;
+ var icon = null;
+ <!-- Pale Moon: new image icon method, see bug #305986 -->
+ let req = browser.contentDocument.imageRequest;
+ let sz = Services.prefs.getIntPref("browser.chrome.image_icons.max_size");
+ if (browser.contentDocument instanceof ImageDocument &&
+ req && req.image) {
+ if (Services.prefs.getBoolPref("browser.chrome.site_icons") && sz) {
+ try {
+ <!-- Main method: draw on a hidden canvas -->
+ var canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+ var tabImg = document.getAnonymousElementByAttribute(aTab, "anonid", "tab-icon");
+ var w = tabImg.boxObject.width;
+ var h = tabImg.boxObject.height;
+ canvas.width = w;
+ canvas.height = h;
+ var ctx = canvas.getContext('2d');
+ ctx.drawImage(browser.contentDocument.body.firstChild, 0, 0, w, h);
+ icon = canvas.toDataURL();
+ }
+ catch (e) {
+ <!-- Fallback method in case canvas method fails, restricted by sz -->
+ try {
+
+ if (req &&
+ req.image &&
+ req.image.width <= sz &&
+ req.image.height <= sz)
+ icon = browser.currentURI;
+ }
+ catch (e) {
+ <!-- Both methods fail (very large or corrupt image): icon remains null -->
+ }
+ }
+ }
+ }
+ // Use documentURIObject in the check for shouldLoadFavIcon so that we
+ // do the right thing with about:-style error pages. Bug 453442
+ else if (this.shouldLoadFavIcon(docURIObject)) {
+ let url = docURIObject.prePath + "/favicon.ico";
+ if (!this.isFailedIcon(url))
+ icon = url;
+ }
+ this.setIcon(aTab, icon);
+ ]]>
+ </body>
+ </method>
+
+ <method name="isFailedIcon">
+ <parameter name="aURI"/>
+ <body>
+ <![CDATA[
+ if (this.mFaviconService) {
+ if (!(aURI instanceof Ci.nsIURI))
+ aURI = makeURI(aURI);
+ return this.mFaviconService.isFailedFavicon(aURI);
+ }
+ return null;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getWindowTitleForBrowser">
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ var newTitle = "";
+ var docElement = this.ownerDocument.documentElement;
+ var sep = docElement.getAttribute("titlemenuseparator");
+
+ // Strip out any null bytes in the content title, since the
+ // underlying widget implementations of nsWindow::SetTitle pass
+ // null-terminated strings to system APIs.
+ var docTitle = aBrowser.contentTitle.replace("\0", "", "g");
+
+ if (!docTitle)
+ docTitle = docElement.getAttribute("titledefault");
+
+ var modifier = docElement.getAttribute("titlemodifier");
+ if (docTitle) {
+ newTitle += docElement.getAttribute("titlepreface");
+ newTitle += docTitle;
+ if (modifier)
+ newTitle += sep;
+ }
+ newTitle += modifier;
+
+ // If location bar is hidden and the URL type supports a host,
+ // add the scheme and host to the title to prevent spoofing.
+ // XXX https://bugzilla.mozilla.org/show_bug.cgi?id=22183#c239
+ try {
+ if (docElement.getAttribute("chromehidden").contains("location")) {
+ var uri = this.mURIFixup.createExposableURI(
+ aBrowser.currentURI);
+ if (uri.scheme == "about")
+ newTitle = uri.spec + sep + newTitle;
+ else
+ newTitle = uri.prePath + sep + newTitle;
+ }
+ } catch (e) {}
+
+ return newTitle;
+ ]]>
+ </body>
+ </method>
+
+ <method name="freezeTitlebar">
+ <parameter name="aTitle"/>
+ <body>
+ <![CDATA[
+ this._frozenTitle = aTitle || "";
+ this.updateTitlebar();
+ ]]>
+ </body>
+ </method>
+
+ <method name="unfreezeTitlebar">
+ <body>
+ <![CDATA[
+ this._frozenTitle = "";
+ this.updateTitlebar();
+ ]]>
+ </body>
+ </method>
+
+ <method name="updateTitlebar">
+ <body>
+ <![CDATA[
+ this.ownerDocument.title = this._frozenTitle ||
+ this.getWindowTitleForBrowser(this.mCurrentBrowser);
+ ]]>
+ </body>
+ </method>
+
+ <method name="updateCurrentBrowser">
+ <parameter name="aForceUpdate"/>
+ <body>
+ <![CDATA[
+ var newBrowser = this.getBrowserAtIndex(this.tabContainer.selectedIndex);
+ if (this.mCurrentBrowser == newBrowser && !aForceUpdate)
+ return;
+
+ if (!aForceUpdate) {
+ TelemetryStopwatch.start("FX_TAB_SWITCH_UPDATE_MS");
+ window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils)
+ .beginTabSwitch();
+ }
+
+ var oldTab = this.mCurrentTab;
+
+ // Preview mode should not reset the owner
+ if (!this._previewMode && !oldTab.selected)
+ oldTab.owner = null;
+
+ if (this._lastRelatedTab) {
+ if (!this._lastRelatedTab.selected)
+ this._lastRelatedTab.owner = null;
+ this._lastRelatedTab = null;
+ }
+
+ var oldBrowser = this.mCurrentBrowser;
+ if (oldBrowser) {
+ oldBrowser.setAttribute("type", "content-targetable");
+ oldBrowser.docShellIsActive = false;
+ }
+
+ var updatePageReport = false;
+ if (!oldBrowser ||
+ (oldBrowser.pageReport && !newBrowser.pageReport) ||
+ (!oldBrowser.pageReport && newBrowser.pageReport))
+ updatePageReport = true;
+
+ newBrowser.setAttribute("type", "content-primary");
+ newBrowser.docShellIsActive =
+ (window.windowState != window.STATE_MINIMIZED);
+ this.mCurrentBrowser = newBrowser;
+ this.mCurrentTab = this.tabContainer.selectedItem;
+ this.showTab(this.mCurrentTab);
+
+ var backForwardContainer = document.getElementById("unified-back-forward-button");
+ if (backForwardContainer) {
+ backForwardContainer.setAttribute("switchingtabs", "true");
+ window.addEventListener("MozAfterPaint", function removeSwitchingtabsAttr() {
+ window.removeEventListener("MozAfterPaint", removeSwitchingtabsAttr);
+ backForwardContainer.removeAttribute("switchingtabs");
+ });
+ }
+
+ if (updatePageReport)
+ this.mCurrentBrowser.updatePageReport();
+
+ // Update the URL bar.
+ var loc = this.mCurrentBrowser.currentURI;
+
+ // Bug 666809 - SecurityUI support for e10s
+ var webProgress = this.mCurrentBrowser.webProgress;
+ var securityUI = this.mCurrentBrowser.securityUI;
+
+ this._callProgressListeners(null, "onLocationChange",
+ [webProgress, null, loc, 0], true,
+ false);
+
+ if (securityUI) {
+ this._callProgressListeners(null, "onSecurityChange",
+ [webProgress, null, securityUI.state], true, false);
+ }
+
+ var listener = this.mTabListeners[this.tabContainer.selectedIndex] || null;
+ if (listener && listener.mStateFlags) {
+ this._callProgressListeners(null, "onUpdateCurrentBrowser",
+ [listener.mStateFlags, listener.mStatus,
+ listener.mMessage, listener.mTotalProgress],
+ true, false);
+ }
+
+ if (!this._previewMode) {
+ this.mCurrentTab.removeAttribute("unread");
+ this.selectedTab.lastAccessed = Date.now();
+
+ // Bug 666816 - TypeAheadFind support for e10s
+ if (!gMultiProcessBrowser)
+ this._fastFind.setDocShell(this.mCurrentBrowser.docShell);
+
+ this.updateTitlebar();
+
+ this.mCurrentTab.removeAttribute("titlechanged");
+ }
+
+ // If the new tab is busy, and our current state is not busy, then
+ // we need to fire a start to all progress listeners.
+ const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
+ if (this.mCurrentTab.hasAttribute("busy") && !this.mIsBusy) {
+ this.mIsBusy = true;
+ this._callProgressListeners(null, "onStateChange",
+ [webProgress, null,
+ nsIWebProgressListener.STATE_START |
+ nsIWebProgressListener.STATE_IS_NETWORK, 0],
+ true, false);
+ }
+
+ // If the new tab is not busy, and our current state is busy, then
+ // we need to fire a stop to all progress listeners.
+ if (!this.mCurrentTab.hasAttribute("busy") && this.mIsBusy) {
+ this.mIsBusy = false;
+ this._callProgressListeners(null, "onStateChange",
+ [webProgress, null,
+ nsIWebProgressListener.STATE_STOP |
+ nsIWebProgressListener.STATE_IS_NETWORK, 0],
+ true, false);
+ }
+
+ this._setCloseKeyState(!this.mCurrentTab.pinned);
+
+ // TabSelect events are suppressed during preview mode to avoid confusing extensions and other bits of code
+ // that might rely upon the other changes suppressed.
+ // Focus is suppressed in the event that the main browser window is minimized - focusing a tab would restore the window
+ if (!this._previewMode) {
+ // We've selected the new tab, so go ahead and notify listeners.
+ let event = document.createEvent("Events");
+ event.initEvent("TabSelect", true, false);
+ this.mCurrentTab.dispatchEvent(event);
+
+ this._tabAttrModified(oldTab);
+ this._tabAttrModified(this.mCurrentTab);
+
+ // Adjust focus
+ oldBrowser._urlbarFocused = (gURLBar && gURLBar.focused);
+ do {
+ // When focus is in the tab bar, retain it there.
+ if (document.activeElement == oldTab) {
+ // We need to explicitly focus the new tab, because
+ // tabbox.xml does this only in some cases.
+ this.mCurrentTab.focus();
+ break;
+ }
+
+ // If there's a tabmodal prompt showing, focus it.
+ if (newBrowser.hasAttribute("tabmodalPromptShowing")) {
+ let XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ let prompts = newBrowser.parentNode.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
+ let prompt = prompts[prompts.length - 1];
+ prompt.Dialog.setDefaultFocus();
+ break;
+ }
+
+ // Focus the location bar if it was previously focused for that tab.
+ // In full screen mode, only bother making the location bar visible
+ // if the tab is a blank one.
+ if (newBrowser._urlbarFocused && gURLBar) {
+
+ // Explicitly close the popup if the URL bar retains focus
+ gURLBar.closePopup();
+
+ if (!window.fullScreen) {
+ gURLBar.focus();
+ break;
+ } else if (isTabEmpty(this.mCurrentTab)) {
+ focusAndSelectUrlBar();
+ break;
+ }
+ }
+
+ // If the find bar is focused, keep it focused.
+ if (gFindBarInitialized &&
+ !gFindBar.hidden &&
+ gFindBar.getElement("findbar-textbox").getAttribute("focused") == "true")
+ break;
+
+ // Otherwise, focus the content area.
+ let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
+ let focusFlags = fm.FLAG_NOSCROLL;
+
+ if (!gMultiProcessBrowser) {
+ let newFocusedElement = fm.getFocusedElementForWindow(window.content, true, {});
+
+ // for anchors, use FLAG_SHOWRING so that it is clear what link was
+ // last clicked when switching back to that tab
+ if (newFocusedElement &&
+ (newFocusedElement instanceof HTMLAnchorElement ||
+ newFocusedElement.getAttributeNS("http://www.w3.org/1999/xlink", "type") == "simple"))
+ focusFlags |= fm.FLAG_SHOWRING;
+ }
+ fm.setFocus(newBrowser, focusFlags);
+ } while (false);
+ }
+
+ this.tabContainer._setPositionalAttributes();
+
+ if (!aForceUpdate)
+ TelemetryStopwatch.finish("FX_TAB_SWITCH_UPDATE_MS");
+ ]]>
+ </body>
+ </method>
+
+ <method name="_tabAttrModified">
+ <parameter name="aTab"/>
+ <body><![CDATA[
+ if (aTab.closing)
+ return;
+
+ // This event should be dispatched when any of these attributes change:
+ // label, crop, busy, image, selected
+ var event = document.createEvent("Events");
+ event.initEvent("TabAttrModified", true, false);
+ aTab.dispatchEvent(event);
+ ]]></body>
+ </method>
+
+ <method name="setTabTitleLoading">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ aTab.label = this.mStringBundle.getString("tabs.connecting");
+ aTab.crop = "end";
+ this._tabAttrModified(aTab);
+ ]]>
+ </body>
+ </method>
+
+ <method name="setTabTitle">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ var browser = this.getBrowserForTab(aTab);
+ var crop = "end";
+ var title = browser.contentTitle;
+
+ if (!title) {
+ if (browser.currentURI.spec) {
+ try {
+ title = this.mURIFixup.createExposableURI(browser.currentURI).spec;
+ } catch(ex) {
+ title = browser.currentURI.spec;
+ }
+ }
+
+ if (title && !isBlankPageURL(title)) {
+ // At this point, we now have a URI.
+ // Let's try to unescape it using a character set
+ // in case the URI is not ASCII.
+ try {
+ var characterSet = browser.characterSet;
+ const textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
+ .getService(Components.interfaces.nsITextToSubURI);
+ title = textToSubURI.unEscapeNonAsciiURI(characterSet, title);
+ } catch(ex) { /* Do nothing. */ }
+
+ crop = "center";
+
+ } else // Still no title? Fall back to our untitled string.
+ title = this.mStringBundle.getString("tabs.emptyTabTitle");
+ }
+
+ if (aTab.label == title &&
+ aTab.crop == crop)
+ return false;
+
+ aTab.label = title;
+ aTab.crop = crop;
+ this._tabAttrModified(aTab);
+
+ if (aTab.selected)
+ this.updateTitlebar();
+
+ return true;
+ ]]>
+ </body>
+ </method>
+
+ <method name="loadOneTab">
+ <parameter name="aURI"/>
+ <parameter name="aReferrerURI"/>
+ <parameter name="aCharset"/>
+ <parameter name="aPostData"/>
+ <parameter name="aLoadInBackground"/>
+ <parameter name="aAllowThirdPartyFixup"/>
+ <body>
+ <![CDATA[
+ var aFromExternal;
+ var aRelatedToCurrent;
+ var aIsUTF8;
+ if (arguments.length == 2 &&
+ typeof arguments[1] == "object" &&
+ !(arguments[1] instanceof Ci.nsIURI)) {
+ let params = arguments[1];
+ aReferrerURI = params.referrerURI;
+ aCharset = params.charset;
+ aPostData = params.postData;
+ aLoadInBackground = params.inBackground;
+ aAllowThirdPartyFixup = params.allowThirdPartyFixup;
+ aFromExternal = params.fromExternal;
+ aRelatedToCurrent = params.relatedToCurrent;
+ aIsUTF8 = params.isUTF8;
+ }
+
+ var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
+ Services.prefs.getBoolPref("browser.tabs.loadInBackground");
+ var owner = bgLoad ? null : this.selectedTab;
+ var tab = this.addTab(aURI, {
+ referrerURI: aReferrerURI,
+ charset: aCharset,
+ postData: aPostData,
+ ownerTab: owner,
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ fromExternal: aFromExternal,
+ relatedToCurrent: aRelatedToCurrent,
+ isUTF8: aIsUTF8});
+ if (!bgLoad)
+ this.selectedTab = tab;
+
+ return tab;
+ ]]>
+ </body>
+ </method>
+
+ <method name="loadTabs">
+ <parameter name="aURIs"/>
+ <parameter name="aLoadInBackground"/>
+ <parameter name="aReplace"/>
+ <body><![CDATA[
+ if (!aURIs.length)
+ return;
+
+ // The tab selected after this new tab is closed (i.e. the new tab's
+ // "owner") is the next adjacent tab (i.e. not the previously viewed tab)
+ // when several urls are opened here (i.e. closing the first should select
+ // the next of many URLs opened) or if the pref to have UI links opened in
+ // the background is set (i.e. the link is not being opened modally)
+ //
+ // i.e.
+ // Number of URLs Load UI Links in BG Focus Last Viewed?
+ // == 1 false YES
+ // == 1 true NO
+ // > 1 false/true NO
+ var multiple = aURIs.length > 1;
+ var owner = multiple || aLoadInBackground ? null : this.selectedTab;
+ var firstTabAdded = null;
+
+ if (aReplace) {
+ try {
+ this.loadURI(aURIs[0], null, null);
+ } catch (e) {
+ // Ignore failure in case a URI is wrong, so we can continue
+ // opening the next ones.
+ }
+ }
+ else
+ firstTabAdded = this.addTab(aURIs[0], {ownerTab: owner, skipAnimation: multiple});
+
+ var tabNum = this.tabContainer.selectedIndex;
+ for (let i = 1; i < aURIs.length; ++i) {
+ let tab = this.addTab(aURIs[i], {skipAnimation: true});
+ if (aReplace)
+ this.moveTabTo(tab, ++tabNum);
+ }
+
+ if (!aLoadInBackground) {
+ if (firstTabAdded) {
+ // .selectedTab setter focuses the content area
+ this.selectedTab = firstTabAdded;
+ }
+ else
+ this.selectedBrowser.focus();
+ }
+ ]]></body>
+ </method>
+
+ <method name="addTab">
+ <parameter name="aURI"/>
+ <parameter name="aReferrerURI"/>
+ <parameter name="aCharset"/>
+ <parameter name="aPostData"/>
+ <parameter name="aOwner"/>
+ <parameter name="aAllowThirdPartyFixup"/>
+ <body>
+ <![CDATA[
+ const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var aFromExternal;
+ var aRelatedToCurrent;
+ var aSkipAnimation;
+ var aIsUTF8;
+ if (arguments.length == 2 &&
+ typeof arguments[1] == "object" &&
+ !(arguments[1] instanceof Ci.nsIURI)) {
+ let params = arguments[1];
+ aReferrerURI = params.referrerURI;
+ aCharset = params.charset;
+ aPostData = params.postData;
+ aOwner = params.ownerTab;
+ aAllowThirdPartyFixup = params.allowThirdPartyFixup;
+ aFromExternal = params.fromExternal;
+ aRelatedToCurrent = params.relatedToCurrent;
+ aSkipAnimation = params.skipAnimation;
+ aIsUTF8 = params.isUTF8;
+ }
+
+ // if we're adding tabs, we're past interrupt mode, ditch the owner
+ if (this.mCurrentTab.owner)
+ this.mCurrentTab.owner = null;
+
+ var t = document.createElementNS(NS_XUL, "tab");
+
+ var uriIsAboutBlank = !aURI || aURI == "about:blank";
+
+ if (!aURI || isBlankPageURL(aURI))
+ t.setAttribute("label", this.mStringBundle.getString("tabs.emptyTabTitle"));
+ else
+ t.setAttribute("label", aURI);
+
+ t.setAttribute("crop", "end");
+ t.setAttribute("validate", "never"); //PMed
+ t.setAttribute("onerror", "this.removeAttribute('image');");
+ t.className = "tabbrowser-tab";
+
+ this.tabContainer._unlockTabSizing();
+
+ // When overflowing, new tabs are scrolled into view smoothly, which
+ // doesn't go well together with the width transition. So we skip the
+ // transition in that case.
+ let animate = !aSkipAnimation &&
+ this.tabContainer.getAttribute("overflow") != "true" &&
+ Services.prefs.getBoolPref("browser.tabs.animate");
+ if (!animate) {
+ t.setAttribute("fadein", "true");
+ setTimeout(function (tabContainer) {
+ tabContainer._handleNewTab(t);
+ }, 0, this.tabContainer);
+ }
+
+ // invalidate caches
+ this._browsers = null;
+ this._visibleTabs = null;
+
+ this.tabContainer.appendChild(t);
+
+ // If this new tab is owned by another, assert that relationship
+ if (aOwner)
+ t.owner = aOwner;
+
+ var b = document.createElementNS(
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "browser");
+ b.setAttribute("type", "content-targetable");
+ b.setAttribute("message", "true");
+ b.setAttribute("contextmenu", this.getAttribute("contentcontextmenu"));
+ b.setAttribute("tooltip", this.getAttribute("contenttooltip"));
+
+ if (Services.prefs.getPrefType("browser.tabs.remote") == Services.prefs.PREF_BOOL &&
+ Services.prefs.getBoolPref("browser.tabs.remote")) {
+ b.setAttribute("remote", "true");
+ }
+
+ if (window.gShowPageResizers && document.getElementById("addon-bar").collapsed &&
+ window.windowState == window.STATE_NORMAL) {
+ b.setAttribute("showresizer", "true");
+ }
+
+ if (this.hasAttribute("autocompletepopup"))
+ b.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
+ b.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
+
+ // Create the browserStack container
+ var stack = document.createElementNS(NS_XUL, "stack");
+ stack.className = "browserStack";
+ stack.appendChild(b);
+ stack.setAttribute("flex", "1");
+
+ // Create the browserContainer
+ var browserContainer = document.createElementNS(NS_XUL, "vbox");
+ browserContainer.className = "browserContainer";
+ browserContainer.appendChild(stack);
+ browserContainer.setAttribute("flex", "1");
+
+ // Create the sidebar container
+ var browserSidebarContainer = document.createElementNS(NS_XUL,
+ "hbox");
+ browserSidebarContainer.className = "browserSidebarContainer";
+ browserSidebarContainer.appendChild(browserContainer);
+ browserSidebarContainer.setAttribute("flex", "1");
+
+ // Add the Message and the Browser to the box
+ var notificationbox = document.createElementNS(NS_XUL,
+ "notificationbox");
+ notificationbox.setAttribute("flex", "1");
+ notificationbox.appendChild(browserSidebarContainer);
+
+ var position = this.tabs.length - 1;
+ var uniqueId = "panel" + Date.now() + position;
+ notificationbox.id = uniqueId;
+ t.linkedPanel = uniqueId;
+ t.linkedBrowser = b;
+ t._tPos = position;
+ this.tabContainer._setPositionalAttributes();
+
+ // Prevent the superfluous initial load of a blank document
+ // if we're going to load something other than about:blank.
+ if (!uriIsAboutBlank) {
+ b.setAttribute("nodefaultsrc", "true");
+ }
+
+ // NB: this appendChild call causes us to run constructors for the
+ // browser element, which fires off a bunch of notifications. Some
+ // of those notifications can cause code to run that inspects our
+ // state, so it is important that the tab element is fully
+ // initialized by this point.
+ this.mPanelContainer.appendChild(notificationbox);
+
+ this.tabContainer.updateVisibility();
+
+ // wire up a progress listener for the new browser object.
+ var tabListener = this.mTabProgressListener(t, b, uriIsAboutBlank);
+ const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
+ .createInstance(Components.interfaces.nsIWebProgress);
+ filter.addProgressListener(tabListener, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
+ b.webProgress.addProgressListener(filter, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
+ this.mTabListeners[position] = tabListener;
+ this.mTabFilters[position] = filter;
+
+ b._fastFind = this.fastFind;
+ b.droppedLinkHandler = handleDroppedLink;
+
+ // If we just created a new tab that loads the default
+ // newtab url, swap in a preloaded page if possible.
+ // Do nothing if we're a private window.
+ let docShellsSwapped = false;
+ if (aURI == BROWSER_NEW_TAB_URL &&
+ !PrivateBrowsingUtils.isWindowPrivate(window)) {
+ docShellsSwapped = gBrowserNewTabPreloader.newTab(t);
+ }
+
+ // Dispatch a new tab notification. We do this once we're
+ // entirely done, so that things are in a consistent state
+ // even if the event listener opens or closes tabs.
+ var evt = document.createEvent("Events");
+ evt.initEvent("TabOpen", true, false);
+ t.dispatchEvent(evt);
+
+ // If we didn't swap docShells with a preloaded browser
+ // then let's just continue loading the page normally.
+ if (!docShellsSwapped && !uriIsAboutBlank) {
+ // pretend the user typed this so it'll be available till
+ // the document successfully loads
+ if (aURI && gInitialPages.indexOf(aURI) == -1)
+ b.userTypedValue = aURI;
+
+ let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
+ if (aAllowThirdPartyFixup)
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ if (aFromExternal)
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL;
+ if (aIsUTF8)
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_URI_IS_UTF8;
+ try {
+ b.loadURIWithFlags(aURI, flags, aReferrerURI, aCharset, aPostData);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+
+ // We start our browsers out as inactive, and then maintain
+ // activeness in the tab switcher.
+ b.docShellIsActive = false;
+
+ // Check if we're opening a tab related to the current tab and
+ // move it to after the current tab.
+ // aReferrerURI is null or undefined if the tab is opened from
+ // an external application or bookmark, i.e. somewhere other
+ // than the current tab.
+ if ((aRelatedToCurrent == null ? aReferrerURI : aRelatedToCurrent) &&
+ Services.prefs.getBoolPref("browser.tabs.insertRelatedAfterCurrent")) {
+ let newTabPos = (this._lastRelatedTab ||
+ this.selectedTab)._tPos + 1;
+ if (this._lastRelatedTab)
+ this._lastRelatedTab.owner = null;
+ else
+ t.owner = this.selectedTab;
+ this.moveTabTo(t, newTabPos);
+ this._lastRelatedTab = t;
+ }
+
+ if (animate) {
+ mozRequestAnimationFrame(function () {
+ this.tabContainer._handleTabTelemetryStart(t, aURI);
+
+ // kick the animation off
+ t.setAttribute("fadein", "true");
+
+ // This call to adjustTabstrip is redundant but needed so that
+ // when opening a second tab, the first tab's close buttons
+ // appears immediately rather than when the transition ends.
+ if (this.tabs.length - this._removingTabs.length == 2)
+ this.tabContainer.adjustTabstrip();
+ }.bind(this));
+ }
+
+ return t;
+ ]]>
+ </body>
+ </method>
+
+ <method name="warnAboutClosingTabs">
+ <parameter name="aCloseTabs"/>
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ var tabsToClose;
+ switch (aCloseTabs) {
+ case this.closingTabsEnum.ALL:
+ tabsToClose = this.tabs.length - this._removingTabs.length -
+ gBrowser._numPinnedTabs;
+ break;
+ case this.closingTabsEnum.OTHER:
+ tabsToClose = this.visibleTabs.length - 1 - gBrowser._numPinnedTabs;
+ break;
+ case this.closingTabsEnum.TO_END:
+ if (!aTab)
+ throw new Error("Required argument missing: aTab");
+
+ tabsToClose = this.getTabsToTheEndFrom(aTab).length;
+ break;
+ default:
+ throw new Error("Invalid argument: " + aCloseTabs);
+ }
+
+ if (tabsToClose <= 1)
+ return true;
+
+ const pref = aCloseTabs == this.closingTabsEnum.ALL ?
+ "browser.tabs.warnOnClose" : "browser.tabs.warnOnCloseOtherTabs";
+ var shouldPrompt = Services.prefs.getBoolPref(pref);
+ if (!shouldPrompt)
+ return true;
+
+ var ps = Services.prompt;
+
+ // default to true: if it were false, we wouldn't get this far
+ var warnOnClose = { value: true };
+ var bundle = this.mStringBundle;
+
+ // 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 buttonPressed =
+ ps.confirmEx(window,
+ bundle.getString("tabs.closeWarningTitle"),
+ bundle.getFormattedString("tabs.closeWarningMultipleTabs",
+ [tabsToClose]),
+ (ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_0)
+ + (ps.BUTTON_TITLE_CANCEL * ps.BUTTON_POS_1),
+ bundle.getString("tabs.closeButtonMultiple"),
+ null, null,
+ aCloseTabs == this.closingTabsEnum.ALL ?
+ bundle.getString("tabs.closeWarningPromptMe") : null,
+ warnOnClose);
+ var reallyClose = (buttonPressed == 0);
+
+ // don't set the prefs unless they press OK and have unchecked the box
+ if (aCloseTabs == this.closingTabsEnum.ALL && reallyClose && !warnOnClose.value) {
+ Services.prefs.setBoolPref("browser.tabs.warnOnClose", false);
+ Services.prefs.setBoolPref("browser.tabs.warnOnCloseOtherTabs", false);
+ }
+ return reallyClose;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getTabsToTheEndFrom">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ var tabsToEnd = [];
+ let tabs = this.visibleTabs;
+ for (let i = tabs.length - 1; tabs[i] != aTab && i >= 0; --i) {
+ tabsToEnd.push(tabs[i]);
+ }
+ return tabsToEnd.reverse();
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeTabsToTheEndFrom">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ if (this.warnAboutClosingTabs(this.closingTabsEnum.TO_END, aTab)) {
+ let tabs = this.getTabsToTheEndFrom(aTab);
+ for (let i = tabs.length - 1; i >= 0; --i) {
+ this.removeTab(tabs[i], {animate: true});
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeAllTabsBut">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ if (aTab.pinned)
+ return;
+
+ if (this.warnAboutClosingTabs(this.closingTabsEnum.OTHER)) {
+ let tabs = this.visibleTabs;
+ this.selectedTab = aTab;
+
+ for (let i = tabs.length - 1; i >= 0; --i) {
+ if (tabs[i] != aTab && !tabs[i].pinned)
+ this.removeTab(tabs[i]);
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeCurrentTab">
+ <parameter name="aParams"/>
+ <body>
+ <![CDATA[
+ this.removeTab(this.mCurrentTab, aParams);
+ ]]>
+ </body>
+ </method>
+
+ <field name="_removingTabs">
+ []
+ </field>
+
+ <method name="removeTab">
+ <parameter name="aTab"/>
+ <parameter name="aParams"/>
+ <body>
+ <![CDATA[
+ if (aParams) {
+ var animate = aParams.animate;
+ var byMouse = aParams.byMouse;
+ }
+
+ // Handle requests for synchronously removing an already
+ // asynchronously closing tab.
+ if (!animate &&
+ aTab.closing) {
+ this._endRemoveTab(aTab);
+ return;
+ }
+
+ var isLastTab = (this.tabs.length - this._removingTabs.length == 1);
+
+ if (!this._beginRemoveTab(aTab, false, null, true))
+ return;
+
+ if (!aTab.pinned && !aTab.hidden && aTab._fullyOpen && byMouse)
+ this.tabContainer._lockTabSizing(aTab);
+ else
+ this.tabContainer._unlockTabSizing();
+
+ if (!animate /* the caller didn't opt in */ ||
+ isLastTab ||
+ aTab.pinned ||
+ aTab.hidden ||
+ this._removingTabs.length > 3 /* don't want lots of concurrent animations */ ||
+ aTab.getAttribute("fadein") != "true" /* fade-in transition hasn't been triggered yet */ ||
+ window.getComputedStyle(aTab).maxWidth == "0.1px" /* fade-in transition hasn't moved yet */ ||
+ !Services.prefs.getBoolPref("browser.tabs.animate")) {
+ this._endRemoveTab(aTab);
+ return;
+ }
+
+ this.tabContainer._handleTabTelemetryStart(aTab);
+
+ this._blurTab(aTab);
+ aTab.style.maxWidth = ""; // ensure that fade-out transition happens
+ aTab.removeAttribute("fadein");
+
+ if (this.tabs.length - this._removingTabs.length == 1) {
+ // The second tab just got closed and we will end up with a single
+ // one. Remove the first tab's close button immediately (if needed)
+ // rather than after the tabclose animation ends.
+ this.tabContainer.adjustTabstrip();
+ }
+
+ setTimeout(function (tab, tabbrowser) {
+ if (tab.parentNode &&
+ window.getComputedStyle(tab).maxWidth == "0.1px") {
+ NS_ASSERT(false, "Giving up waiting for the tab closing animation to finish (bug 608589)");
+ tabbrowser._endRemoveTab(tab);
+ }
+ }, 3000, aTab, this);
+ ]]>
+ </body>
+ </method>
+
+ <!-- Tab close requests are ignored if the window is closing anyway,
+ e.g. when holding Ctrl+W. -->
+ <field name="_windowIsClosing">
+ false
+ </field>
+
+ <method name="_beginRemoveTab">
+ <parameter name="aTab"/>
+ <parameter name="aTabWillBeMoved"/>
+ <parameter name="aCloseWindowWithLastTab"/>
+ <parameter name="aCloseWindowFastpath"/>
+ <body>
+ <![CDATA[
+ if (aTab.closing ||
+ aTab._pendingPermitUnload ||
+ this._windowIsClosing)
+ return false;
+
+ var browser = this.getBrowserForTab(aTab);
+
+ if (!aTabWillBeMoved) {
+ let ds = browser.docShell;
+ if (ds && ds.contentViewer) {
+ // We need to block while calling permitUnload() because it
+ // processes the event queue and may lead to another removeTab()
+ // call before permitUnload() even returned.
+ aTab._pendingPermitUnload = true;
+ let permitUnload = ds.contentViewer.permitUnload();
+ delete aTab._pendingPermitUnload;
+
+ if (!permitUnload)
+ return false;
+ }
+ }
+
+ var closeWindow = false;
+ var newTab = false;
+ if (this.tabs.length - this._removingTabs.length == 1) {
+ closeWindow = aCloseWindowWithLastTab != null ? aCloseWindowWithLastTab :
+ !window.toolbar.visible ||
+ this.tabContainer._closeWindowWithLastTab;
+
+ // Closing the tab and replacing it with a blank one is notably slower
+ // than closing the window right away. If the caller opts in, take
+ // the fast path.
+ if (closeWindow &&
+ aCloseWindowFastpath &&
+ this._removingTabs.length == 0 &&
+ (this._windowIsClosing = window.closeWindow(true, window.warnAboutClosingWindow)))
+ return null;
+
+ newTab = true;
+ }
+
+ aTab.closing = true;
+ this._removingTabs.push(aTab);
+ this._visibleTabs = null; // invalidate cache
+ if (newTab)
+ this.addTab(BROWSER_NEW_TAB_URL, {skipAnimation: true});
+ else
+ this.tabContainer.updateVisibility();
+
+ // We're committed to closing the tab now.
+ // Dispatch a notification.
+ // We dispatch it before any teardown so that event listeners can
+ // inspect the tab that's about to close.
+ var evt = document.createEvent("UIEvent");
+ evt.initUIEvent("TabClose", true, false, window, aTabWillBeMoved ? 1 : 0);
+ aTab.dispatchEvent(evt);
+
+ if (!gMultiProcessBrowser) {
+ // Prevent this tab from showing further dialogs, since we're closing it
+ var windowUtils = browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils);
+ windowUtils.preventFurtherDialogs();
+ }
+
+ // Remove the tab's filter and progress listener.
+ const filter = this.mTabFilters[aTab._tPos];
+
+ browser.webProgress.removeProgressListener(filter);
+
+ filter.removeProgressListener(this.mTabListeners[aTab._tPos]);
+ this.mTabListeners[aTab._tPos].destroy();
+
+ if (browser.registeredOpenURI && !aTabWillBeMoved) {
+ this._placesAutocomplete.unregisterOpenPage(browser.registeredOpenURI);
+ delete browser.registeredOpenURI;
+ }
+
+ // We are no longer the primary content area.
+ browser.setAttribute("type", "content-targetable");
+
+ // Remove this tab as the owner of any other tabs, since it's going away.
+ Array.forEach(this.tabs, function (tab) {
+ if ("owner" in tab && tab.owner == aTab)
+ // |tab| is a child of the tab we're removing, make it an orphan
+ tab.owner = null;
+ });
+
+ aTab._endRemoveArgs = [closeWindow, newTab];
+ return true;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_endRemoveTab">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ if (!aTab || !aTab._endRemoveArgs)
+ return;
+
+ var [aCloseWindow, aNewTab] = aTab._endRemoveArgs;
+ aTab._endRemoveArgs = null;
+
+ if (this._windowIsClosing) {
+ aCloseWindow = false;
+ aNewTab = false;
+ }
+
+ this._lastRelatedTab = null;
+
+ // update the UI early for responsiveness
+ aTab.collapsed = true;
+ this.tabContainer._fillTrailingGap();
+ this._blurTab(aTab);
+
+ this._removingTabs.splice(this._removingTabs.indexOf(aTab), 1);
+
+ if (aCloseWindow) {
+ this._windowIsClosing = true;
+ while (this._removingTabs.length)
+ this._endRemoveTab(this._removingTabs[0]);
+ } else if (!this._windowIsClosing) {
+ if (aNewTab)
+ focusAndSelectUrlBar();
+
+ // workaround for bug 345399
+ this.tabContainer.mTabstrip._updateScrollButtonsDisabledState();
+ }
+
+ // We're going to remove the tab and the browser now.
+ // Clean up mTabFilters and mTabListeners now rather than in
+ // _beginRemoveTab, so that their size is always in sync with the
+ // number of tabs and browsers (the xbl destructor depends on this).
+ this.mTabFilters.splice(aTab._tPos, 1);
+ this.mTabListeners.splice(aTab._tPos, 1);
+
+ var browser = this.getBrowserForTab(aTab);
+
+ // Because of the way XBL works (fields just set JS
+ // properties on the element) and the code we have in place
+ // to preserve the JS objects for any elements that have
+ // JS properties set on them, the browser element won't be
+ // destroyed until the document goes away. So we force a
+ // cleanup ourselves.
+ // This has to happen before we remove the child so that the
+ // XBL implementation of nsIObserver still works.
+ browser.destroy();
+
+ if (browser == this.mCurrentBrowser)
+ this.mCurrentBrowser = null;
+
+ var wasPinned = aTab.pinned;
+
+ // Invalidate browsers cache, as the tab is removed from the
+ // tab container.
+ this._browsers = null;
+
+ // Remove the tab ...
+ this.tabContainer.removeChild(aTab);
+
+ // ... and fix up the _tPos properties immediately.
+ for (let i = aTab._tPos; i < this.tabs.length; i++)
+ this.tabs[i]._tPos = i;
+
+ if (!this._windowIsClosing) {
+ if (wasPinned)
+ this.tabContainer._positionPinnedTabs();
+
+ // update tab close buttons state
+ this.tabContainer.adjustTabstrip();
+
+ setTimeout(function(tabs) {
+ tabs._lastTabClosedByMouse = false;
+ }, 0, this.tabContainer);
+ }
+
+ // Pale Moon: if resizing immediately, select the tab immediately to the left
+ // instead of the right (if not leftmost) to prevent focus swap and
+ // "selected tab not under cursor"
+ // FIXME: Tabs must be sliding in from the left for this, or it'd unfocus
+ // in the other direction! Disabled for now. Is there an easier way? :hover?
+ // Is this even needed when resizing immediately?...
+
+ //if (Services.prefs.getBoolPref("browser.tabs.resize_immediately")) {
+ // if (this.selectedTab._tPos > 1) {
+ // let newPos = this.selectedTab._tPos - 1;
+ // this.selectedTab = this.tabs[newPos];
+ // }
+ //}
+
+ // update tab positional properties and attributes
+ this.selectedTab._selected = true;
+ this.tabContainer._setPositionalAttributes();
+
+ // Removing the panel requires fixing up selectedPanel immediately
+ // (see below), which would be hindered by the potentially expensive
+ // browser removal. So we remove the browser and the panel in two
+ // steps.
+
+ var panel = this.getNotificationBox(browser);
+
+ // This will unload the document. An unload handler could remove
+ // dependant tabs, so it's important that the tabbrowser is now in
+ // a consistent state (tab removed, tab positions updated, etc.).
+ browser.parentNode.removeChild(browser);
+
+ // Release the browser in case something is erroneously holding a
+ // reference to the tab after its removal.
+ aTab.linkedBrowser = null;
+
+ // As the browser is removed, the removal of a dependent document can
+ // cause the whole window to close. So at this point, it's possible
+ // that the binding is destructed.
+ if (this.mTabBox) {
+ let selectedPanel = this.mTabBox.selectedPanel;
+
+ this.mPanelContainer.removeChild(panel);
+
+ // Under the hood, a selectedIndex attribute controls which panel
+ // is displayed. Removing a panel A which precedes the selected
+ // panel B makes selectedIndex point to the panel next to B. We
+ // need to explicitly preserve B as the selected panel.
+ this.mTabBox.selectedPanel = selectedPanel;
+ }
+
+ if (aCloseWindow)
+ this._windowIsClosing = closeWindow(true, window.warnAboutClosingWindow);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_blurTab">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ if (!aTab.selected)
+ return;
+
+ if (aTab.owner &&
+ !aTab.owner.hidden &&
+ !aTab.owner.closing &&
+ Services.prefs.getBoolPref("browser.tabs.selectOwnerOnClose")) {
+ this.selectedTab = aTab.owner;
+ return;
+ }
+
+ // Switch to a visible tab unless there aren't any others remaining
+ let remainingTabs = this.visibleTabs;
+ let numTabs = remainingTabs.length;
+ if (numTabs == 0 || numTabs == 1 && remainingTabs[0] == aTab) {
+ remainingTabs = Array.filter(this.tabs, function(tab) {
+ return !tab.closing;
+ }, this);
+ }
+
+ // Try to find a remaining tab that comes after the given tab
+ var tab = aTab;
+ do {
+ tab = tab.nextSibling;
+ } while (tab && remainingTabs.indexOf(tab) == -1);
+
+ if (!tab) {
+ tab = aTab;
+
+ do {
+ tab = tab.previousSibling;
+ } while (tab && remainingTabs.indexOf(tab) == -1);
+ }
+
+ this.selectedTab = tab;
+ ]]>
+ </body>
+ </method>
+
+ <method name="swapNewTabWithBrowser">
+ <parameter name="aNewTab"/>
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ // The browser must be standalone.
+ if (aBrowser.getTabBrowser())
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ // The tab is definitely not loading.
+ aNewTab.removeAttribute("busy");
+ if (aNewTab.selected) {
+ this.mIsBusy = false;
+ }
+
+ this._swapBrowserDocShells(aNewTab, aBrowser);
+
+ // Update the new tab's title.
+ this.setTabTitle(aNewTab);
+
+ if (aNewTab.selected) {
+ this.updateCurrentBrowser(true);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="swapBrowsersAndCloseOther">
+ <parameter name="aOurTab"/>
+ <parameter name="aOtherTab"/>
+ <body>
+ <![CDATA[
+ // Do not allow transfering a private tab to a non-private window
+ // and vice versa.
+ if (PrivateBrowsingUtils.isWindowPrivate(window) !=
+ PrivateBrowsingUtils.isWindowPrivate(aOtherTab.ownerDocument.defaultView))
+ return;
+
+ // That's gBrowser for the other window, not the tab's browser!
+ var remoteBrowser = aOtherTab.ownerDocument.defaultView.gBrowser;
+ var isPending = aOtherTab.hasAttribute("pending");
+
+ // First, start teardown of the other browser. Make sure to not
+ // fire the beforeunload event in the process. Close the other
+ // window if this was its last tab.
+ if (!remoteBrowser._beginRemoveTab(aOtherTab, true, true))
+ return;
+
+ let ourBrowser = this.getBrowserForTab(aOurTab);
+ let otherBrowser = aOtherTab.linkedBrowser;
+
+ // If the other tab is pending (i.e. has not been restored, yet)
+ // then do not switch docShells but retrieve the other tab's state
+ // and apply it to our tab.
+ if (isPending) {
+ let ss = Cc["@mozilla.org/browser/sessionstore;1"]
+ .getService(Ci.nsISessionStore)
+ ss.setTabState(aOurTab, ss.getTabState(aOtherTab));
+
+ // Make sure to unregister any open URIs.
+ this._swapRegisteredOpenURIs(ourBrowser, otherBrowser);
+ } else {
+ // Workarounds for bug 458697
+ // Icon might have been set on DOMLinkAdded, don't override that.
+ if (!ourBrowser.mIconURL && otherBrowser.mIconURL)
+ this.setIcon(aOurTab, otherBrowser.mIconURL);
+ var isBusy = aOtherTab.hasAttribute("busy");
+ if (isBusy) {
+ aOurTab.setAttribute("busy", "true");
+ this._tabAttrModified(aOurTab);
+ if (aOurTab.selected)
+ this.mIsBusy = true;
+ }
+
+ this._swapBrowserDocShells(aOurTab, otherBrowser);
+ }
+
+ // Finish tearing down the tab that's going away.
+ remoteBrowser._endRemoveTab(aOtherTab);
+
+ if (isBusy)
+ this.setTabTitleLoading(aOurTab);
+ else
+ this.setTabTitle(aOurTab);
+
+ // If the tab was already selected (this happpens in the scenario
+ // of replaceTabWithWindow), notify onLocationChange, etc.
+ if (aOurTab.selected)
+ this.updateCurrentBrowser(true);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_swapBrowserDocShells">
+ <parameter name="aOurTab"/>
+ <parameter name="aOtherBrowser"/>
+ <body>
+ <![CDATA[
+ // Unhook our progress listener
+ let index = aOurTab._tPos;
+ const filter = this.mTabFilters[index];
+ let tabListener = this.mTabListeners[index];
+ let ourBrowser = this.getBrowserForTab(aOurTab);
+ ourBrowser.webProgress.removeProgressListener(filter);
+ filter.removeProgressListener(tabListener);
+
+ // Make sure to unregister any open URIs.
+ this._swapRegisteredOpenURIs(ourBrowser, aOtherBrowser);
+
+ // Swap the docshells
+ ourBrowser.swapDocShells(aOtherBrowser);
+
+ // Restore the progress listener
+ this.mTabListeners[index] = tabListener =
+ this.mTabProgressListener(aOurTab, ourBrowser, false);
+
+ const notifyAll = Ci.nsIWebProgress.NOTIFY_ALL;
+ filter.addProgressListener(tabListener, notifyAll);
+ ourBrowser.webProgress.addProgressListener(filter, notifyAll);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_swapRegisteredOpenURIs">
+ <parameter name="aOurBrowser"/>
+ <parameter name="aOtherBrowser"/>
+ <body>
+ <![CDATA[
+ // If the current URI is registered as open remove it from the list.
+ if (aOurBrowser.registeredOpenURI) {
+ this._placesAutocomplete.unregisterOpenPage(aOurBrowser.registeredOpenURI);
+ delete aOurBrowser.registeredOpenURI;
+ }
+
+ // If the other/new URI is registered as open then copy it over.
+ if (aOtherBrowser.registeredOpenURI) {
+ aOurBrowser.registeredOpenURI = aOtherBrowser.registeredOpenURI;
+ delete aOtherBrowser.registeredOpenURI;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="reloadAllTabs">
+ <body>
+ <![CDATA[
+ let tabs = this.visibleTabs;
+ let l = tabs.length;
+ for (var i = 0; i < l; i++) {
+ try {
+ this.getBrowserForTab(tabs[i]).reload();
+ } catch (e) {
+ // ignore failure to reload so others will be reloaded
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="reloadTab">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ this.getBrowserForTab(aTab).reload();
+ ]]>
+ </body>
+ </method>
+
+ <method name="addProgressListener">
+ <parameter name="aListener"/>
+ <body>
+ <![CDATA[
+ if (arguments.length != 1) {
+ Components.utils.reportError("gBrowser.addProgressListener was " +
+ "called with a second argument, " +
+ "which is not supported. See bug " +
+ "608628.");
+ }
+
+ this.mProgressListeners.push(aListener);
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeProgressListener">
+ <parameter name="aListener"/>
+ <body>
+ <![CDATA[
+ this.mProgressListeners =
+ this.mProgressListeners.filter(function (l) l != aListener);
+ ]]>
+ </body>
+ </method>
+
+ <method name="addTabsProgressListener">
+ <parameter name="aListener"/>
+ <body>
+ this.mTabsProgressListeners.push(aListener);
+ </body>
+ </method>
+
+ <method name="removeTabsProgressListener">
+ <parameter name="aListener"/>
+ <body>
+ <![CDATA[
+ this.mTabsProgressListeners =
+ this.mTabsProgressListeners.filter(function (l) l != aListener);
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserForTab">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ return aTab.linkedBrowser;
+ ]]>
+ </body>
+ </method>
+
+ <method name="showOnlyTheseTabs">
+ <parameter name="aTabs"/>
+ <body>
+ <![CDATA[
+ Array.forEach(this.tabs, function(tab) {
+ if (aTabs.indexOf(tab) == -1)
+ this.hideTab(tab);
+ else
+ this.showTab(tab);
+ }, this);
+
+ this.tabContainer._handleTabSelect(false);
+ ]]>
+ </body>
+ </method>
+
+ <method name="showTab">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ if (aTab.hidden) {
+ aTab.removeAttribute("hidden");
+ this._visibleTabs = null; // invalidate cache
+
+ this.tabContainer.adjustTabstrip();
+
+ this.tabContainer._setPositionalAttributes();
+
+ let event = document.createEvent("Events");
+ event.initEvent("TabShow", true, false);
+ aTab.dispatchEvent(event);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="hideTab">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ if (!aTab.hidden && !aTab.pinned && !aTab.selected &&
+ !aTab.closing) {
+ aTab.setAttribute("hidden", "true");
+ this._visibleTabs = null; // invalidate cache
+
+ this.tabContainer.adjustTabstrip();
+
+ this.tabContainer._setPositionalAttributes();
+
+ let event = document.createEvent("Events");
+ event.initEvent("TabHide", true, false);
+ aTab.dispatchEvent(event);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="selectTabAtIndex">
+ <parameter name="aIndex"/>
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ let tabs = this.visibleTabs;
+
+ // count backwards for aIndex < 0
+ if (aIndex < 0)
+ aIndex += tabs.length;
+
+ if (aIndex >= 0 && aIndex < tabs.length)
+ this.selectedTab = tabs[aIndex];
+
+ if (aEvent) {
+ aEvent.preventDefault();
+ aEvent.stopPropagation();
+ }
+ ]]>
+ </body>
+ </method>
+
+ <property name="selectedTab">
+ <getter>
+ return this.mCurrentTab;
+ </getter>
+ <setter>
+ <![CDATA[
+ // Update the tab
+ this.mTabBox.selectedTab = val;
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="selectedBrowser"
+ onget="return this.mCurrentBrowser;"
+ readonly="true"/>
+
+ <property name="browsers" readonly="true">
+ <getter>
+ <![CDATA[
+ return this._browsers ||
+ (this._browsers = Array.map(this.tabs, function (tab) tab.linkedBrowser));
+ ]]>
+ </getter>
+ </property>
+ <field name="_browsers">null</field>
+
+ <!-- Moves a tab to a new browser window, unless it's already the only tab
+ in the current window, in which case this will do nothing. -->
+ <method name="replaceTabWithWindow">
+ <parameter name="aTab"/>
+ <parameter name="aOptions"/>
+ <body>
+ <![CDATA[
+ if (this.tabs.length == 1)
+ return null;
+
+ var options = "chrome,dialog=no,all";
+ for (var name in aOptions)
+ options += "," + name + "=" + aOptions[name];
+
+ // tell a new window to take the "dropped" tab
+ return window.openDialog(getBrowserURL(), "_blank", options, aTab);
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTabTo">
+ <parameter name="aTab"/>
+ <parameter name="aIndex"/>
+ <body>
+ <![CDATA[
+ var oldPosition = aTab._tPos;
+ if (oldPosition == aIndex)
+ return;
+
+ // Don't allow mixing pinned and unpinned tabs.
+ if (aTab.pinned)
+ aIndex = Math.min(aIndex, this._numPinnedTabs - 1);
+ else
+ aIndex = Math.max(aIndex, this._numPinnedTabs);
+ if (oldPosition == aIndex)
+ return;
+
+ this._lastRelatedTab = null;
+
+ this.mTabFilters.splice(aIndex, 0, this.mTabFilters.splice(aTab._tPos, 1)[0]);
+ this.mTabListeners.splice(aIndex, 0, this.mTabListeners.splice(aTab._tPos, 1)[0]);
+
+ let wasFocused = (document.activeElement == this.mCurrentTab);
+
+ aIndex = aIndex < aTab._tPos ? aIndex: aIndex+1;
+ this.mCurrentTab._selected = false;
+
+ // invalidate caches
+ this._browsers = null;
+ this._visibleTabs = null;
+
+ // use .item() instead of [] because dragging to the end of the strip goes out of
+ // bounds: .item() returns null (so it acts like appendChild), but [] throws
+ this.tabContainer.insertBefore(aTab, this.tabs.item(aIndex));
+
+ for (let i = 0; i < this.tabs.length; i++) {
+ this.tabs[i]._tPos = i;
+ this.tabs[i]._selected = false;
+ }
+ this.mCurrentTab._selected = true;
+
+ if (wasFocused)
+ this.mCurrentTab.focus();
+
+ this.tabContainer._handleTabSelect(false);
+
+ if (aTab.pinned)
+ this.tabContainer._positionPinnedTabs();
+
+ this.tabContainer._setPositionalAttributes();
+
+ var evt = document.createEvent("UIEvents");
+ evt.initUIEvent("TabMove", true, false, window, oldPosition);
+ aTab.dispatchEvent(evt);
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTabForward">
+ <body>
+ <![CDATA[
+ let nextTab = this.mCurrentTab.nextSibling;
+ while (nextTab && nextTab.hidden)
+ nextTab = nextTab.nextSibling;
+
+ if (nextTab)
+ this.moveTabTo(this.mCurrentTab, nextTab._tPos);
+ else if (this.arrowKeysShouldWrap)
+ this.moveTabToStart();
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTabBackward">
+ <body>
+ <![CDATA[
+ let previousTab = this.mCurrentTab.previousSibling;
+ while (previousTab && previousTab.hidden)
+ previousTab = previousTab.previousSibling;
+
+ if (previousTab)
+ this.moveTabTo(this.mCurrentTab, previousTab._tPos);
+ else if (this.arrowKeysShouldWrap)
+ this.moveTabToEnd();
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTabToStart">
+ <body>
+ <![CDATA[
+ var tabPos = this.mCurrentTab._tPos;
+ if (tabPos > 0)
+ this.moveTabTo(this.mCurrentTab, 0);
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTabToEnd">
+ <body>
+ <![CDATA[
+ var tabPos = this.mCurrentTab._tPos;
+ if (tabPos < this.browsers.length - 1)
+ this.moveTabTo(this.mCurrentTab, this.browsers.length - 1);
+ ]]>
+ </body>
+ </method>
+
+ <method name="moveTabOver">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ var direction = window.getComputedStyle(this.parentNode, null).direction;
+ if ((direction == "ltr" && aEvent.keyCode == KeyEvent.DOM_VK_RIGHT) ||
+ (direction == "rtl" && aEvent.keyCode == KeyEvent.DOM_VK_LEFT))
+ this.moveTabForward();
+ else
+ this.moveTabBackward();
+ ]]>
+ </body>
+ </method>
+
+ <method name="duplicateTab">
+ <parameter name="aTab"/><!-- can be from a different window as well -->
+ <body>
+ <![CDATA[
+ return Cc["@mozilla.org/browser/sessionstore;1"]
+ .getService(Ci.nsISessionStore)
+ .duplicateTab(window, aTab);
+ ]]>
+ </body>
+ </method>
+
+ <!-- BEGIN FORWARDED BROWSER PROPERTIES. IF YOU ADD A PROPERTY TO THE BROWSER ELEMENT
+ MAKE SURE TO ADD IT HERE AS WELL. -->
+ <property name="canGoBack"
+ onget="return this.mCurrentBrowser.canGoBack;"
+ readonly="true"/>
+
+ <property name="canGoForward"
+ onget="return this.mCurrentBrowser.canGoForward;"
+ readonly="true"/>
+
+ <method name="goBack">
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.goBack();
+ ]]>
+ </body>
+ </method>
+
+ <method name="goForward">
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.goForward();
+ ]]>
+ </body>
+ </method>
+
+ <method name="reload">
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.reload();
+ ]]>
+ </body>
+ </method>
+
+ <method name="reloadWithFlags">
+ <parameter name="aFlags"/>
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.reloadWithFlags(aFlags);
+ ]]>
+ </body>
+ </method>
+
+ <method name="stop">
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.stop();
+ ]]>
+ </body>
+ </method>
+
+ <!-- throws exception for unknown schemes -->
+ <method name="loadURI">
+ <parameter name="aURI"/>
+ <parameter name="aReferrerURI"/>
+ <parameter name="aCharset"/>
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.loadURI(aURI, aReferrerURI, aCharset);
+ ]]>
+ </body>
+ </method>
+
+ <!-- throws exception for unknown schemes -->
+ <method name="loadURIWithFlags">
+ <parameter name="aURI"/>
+ <parameter name="aFlags"/>
+ <parameter name="aReferrerURI"/>
+ <parameter name="aCharset"/>
+ <parameter name="aPostData"/>
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.loadURIWithFlags(aURI, aFlags, aReferrerURI, aCharset, aPostData);
+ ]]>
+ </body>
+ </method>
+
+ <method name="goHome">
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.goHome();
+ ]]>
+ </body>
+ </method>
+
+ <property name="homePage">
+ <getter>
+ <![CDATA[
+ return this.mCurrentBrowser.homePage;
+ ]]>
+ </getter>
+ <setter>
+ <![CDATA[
+ this.mCurrentBrowser.homePage = val;
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <method name="gotoIndex">
+ <parameter name="aIndex"/>
+ <body>
+ <![CDATA[
+ return this.mCurrentBrowser.gotoIndex(aIndex);
+ ]]>
+ </body>
+ </method>
+
+ <method name="attachFormFill">
+ <body><![CDATA[
+ for (var i = 0; i < this.mPanelContainer.childNodes.length; ++i) {
+ var cb = this.getBrowserAtIndex(i);
+ cb.attachFormFill();
+ }
+ ]]></body>
+ </method>
+
+ <method name="detachFormFill">
+ <body><![CDATA[
+ for (var i = 0; i < this.mPanelContainer.childNodes.length; ++i) {
+ var cb = this.getBrowserAtIndex(i);
+ cb.detachFormFill();
+ }
+ ]]></body>
+ </method>
+
+ <property name="pageReport"
+ onget="return this.mCurrentBrowser.pageReport;"
+ readonly="true"/>
+
+ <property name="currentURI"
+ onget="return this.mCurrentBrowser.currentURI;"
+ readonly="true"/>
+
+ <field name="_fastFind">null</field>
+ <property name="fastFind"
+ readonly="true">
+ <getter>
+ <![CDATA[
+ if (!this._fastFind) {
+ this._fastFind = Components.classes["@mozilla.org/typeaheadfind;1"]
+ .createInstance(Components.interfaces.nsITypeAheadFind);
+ this._fastFind.init(this.docShell);
+ }
+ return this._fastFind;
+ ]]>
+ </getter>
+ </property>
+
+ <property name="docShell"
+ onget="return this.mCurrentBrowser.docShell"
+ readonly="true"/>
+
+ <property name="webNavigation"
+ onget="return this.mCurrentBrowser.webNavigation"
+ readonly="true"/>
+
+ <property name="webBrowserFind"
+ readonly="true"
+ onget="return this.mCurrentBrowser.webBrowserFind"/>
+
+ <property name="webProgress"
+ readonly="true"
+ onget="return this.mCurrentBrowser.webProgress"/>
+
+ <property name="contentWindow"
+ readonly="true"
+ onget="return this.mCurrentBrowser.contentWindow"/>
+
+ <property name="sessionHistory"
+ onget="return this.mCurrentBrowser.sessionHistory;"
+ readonly="true"/>
+
+ <property name="markupDocumentViewer"
+ onget="return this.mCurrentBrowser.markupDocumentViewer;"
+ readonly="true"/>
+
+ <property name="contentViewerEdit"
+ onget="return this.mCurrentBrowser.contentViewerEdit;"
+ readonly="true"/>
+
+ <property name="contentViewerFile"
+ onget="return this.mCurrentBrowser.contentViewerFile;"
+ readonly="true"/>
+
+ <property name="contentDocument"
+ onget="return this.mCurrentBrowser.contentDocument;"
+ readonly="true"/>
+
+ <property name="contentTitle"
+ onget="return this.mCurrentBrowser.contentTitle;"
+ readonly="true"/>
+
+ <property name="contentPrincipal"
+ onget="return this.mCurrentBrowser.contentPrincipal;"
+ readonly="true"/>
+
+ <property name="securityUI"
+ onget="return this.mCurrentBrowser.securityUI;"
+ readonly="true"/>
+
+ <method name="_handleKeyEvent">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ if (!aEvent.isTrusted) {
+ // Don't let untrusted events mess with tabs.
+ return;
+ }
+
+ if (aEvent.altKey)
+ return;
+
+ if (aEvent.ctrlKey && aEvent.shiftKey && !aEvent.metaKey) {
+ switch (aEvent.keyCode) {
+ case aEvent.DOM_VK_PAGE_UP:
+ this.moveTabBackward();
+ aEvent.stopPropagation();
+ aEvent.preventDefault();
+ return;
+ case aEvent.DOM_VK_PAGE_DOWN:
+ this.moveTabForward();
+ aEvent.stopPropagation();
+ aEvent.preventDefault();
+ return;
+ }
+ }
+
+ // We need to take care of FAYT-watching as long as the findbar
+ // isn't initialized. The checks on aEvent are copied from
+ // _shouldFastFind (see findbar.xml).
+ if (!gFindBarInitialized &&
+ !(aEvent.ctrlKey || aEvent.metaKey) &&
+ !aEvent.defaultPrevented) {
+ let charCode = aEvent.charCode;
+ if (charCode) {
+ let char = String.fromCharCode(charCode);
+ if (char == "'" || char == "/" ||
+ Services.prefs.getBoolPref("accessibility.typeaheadfind")) {
+ gFindBar._onBrowserKeypress(aEvent);
+ return;
+ }
+ }
+ }
+
+#ifdef XP_MACOSX
+ if (!aEvent.metaKey)
+ return;
+
+ var offset = 1;
+ switch (aEvent.charCode) {
+ case '}'.charCodeAt(0):
+ offset = -1;
+ case '{'.charCodeAt(0):
+ if (window.getComputedStyle(this, null).direction == "ltr")
+ offset *= -1;
+ this.tabContainer.advanceSelectedTab(offset, true);
+ aEvent.stopPropagation();
+ aEvent.preventDefault();
+ }
+#else
+ if (aEvent.ctrlKey && !aEvent.shiftKey && !aEvent.metaKey &&
+ aEvent.keyCode == KeyEvent.DOM_VK_F4 &&
+ !this.mCurrentTab.pinned) {
+ this.removeCurrentTab({animate: true});
+ aEvent.stopPropagation();
+ aEvent.preventDefault();
+ }
+#endif
+ ]]></body>
+ </method>
+
+ <property name="userTypedClear"
+ onget="return this.mCurrentBrowser.userTypedClear;"
+ onset="return this.mCurrentBrowser.userTypedClear = val;"/>
+
+ <property name="userTypedValue"
+ onget="return this.mCurrentBrowser.userTypedValue;"
+ onset="return this.mCurrentBrowser.userTypedValue = val;"/>
+
+ <method name="createTooltip">
+ <parameter name="event"/>
+ <body><![CDATA[
+ event.stopPropagation();
+ var tab = document.tooltipNode;
+ if (tab.localName != "tab") {
+ event.preventDefault();
+ return;
+ }
+ event.target.setAttribute("label", tab.mOverCloseButton ?
+ tab.getAttribute("closetabtext") :
+ tab.getAttribute("label"));
+ ]]></body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ switch (aEvent.type) {
+ case "keypress":
+ this._handleKeyEvent(aEvent);
+ break;
+ case "sizemodechange":
+ if (aEvent.target == window) {
+ this.mCurrentBrowser.docShellIsActive =
+ (window.windowState != window.STATE_MINIMIZED);
+ }
+ break;
+ }
+ ]]></body>
+ </method>
+
+ <method name="receiveMessage">
+ <parameter name="aMessage"/>
+ <body><![CDATA[
+ let json = aMessage.json;
+ let browser = aMessage.target;
+
+ switch (aMessage.name) {
+ case "DOMTitleChanged":
+ let tab = this._getTabForBrowser(browser);
+ if (!tab)
+ return;
+ let titleChanged = this.setTabTitle(tab);
+ if (titleChanged && !tab.selected && !tab.hasAttribute("busy"))
+ tab.setAttribute("titlechanged", "true");
+ }
+ ]]></body>
+ </method>
+
+ <constructor>
+ <![CDATA[
+ let browserStack = document.getAnonymousElementByAttribute(this, "anonid", "browserStack");
+ this.mCurrentBrowser = document.getAnonymousElementByAttribute(this, "anonid", "initialBrowser");
+ if (Services.prefs.getBoolPref("browser.tabs.remote")) {
+ browserStack.removeChild(this.mCurrentBrowser);
+ this.mCurrentBrowser.setAttribute("remote", true);
+ browserStack.appendChild(this.mCurrentBrowser);
+ }
+
+ this.mCurrentTab = this.tabContainer.firstChild;
+ document.addEventListener("keypress", this, false);
+ window.addEventListener("sizemodechange", this, false);
+
+ var uniqueId = "panel" + Date.now();
+ this.mPanelContainer.childNodes[0].id = uniqueId;
+ this.mCurrentTab.linkedPanel = uniqueId;
+ this.mCurrentTab._tPos = 0;
+ this.mCurrentTab._fullyOpen = true;
+ this.mCurrentTab.linkedBrowser = this.mCurrentBrowser;
+
+ // set up the shared autoscroll popup
+ this._autoScrollPopup = this.mCurrentBrowser._createAutoScrollPopup();
+ this._autoScrollPopup.id = "autoscroller";
+ this.appendChild(this._autoScrollPopup);
+ this.mCurrentBrowser.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
+ this.mCurrentBrowser.droppedLinkHandler = handleDroppedLink;
+ this.updateWindowResizers();
+
+ // Hook up the event listeners to the first browser
+ var tabListener = this.mTabProgressListener(this.mCurrentTab, this.mCurrentBrowser, true);
+ const nsIWebProgress = Components.interfaces.nsIWebProgress;
+ const filter = Components.classes["@mozilla.org/appshell/component/browser-status-filter;1"]
+ .createInstance(nsIWebProgress);
+ filter.addProgressListener(tabListener, nsIWebProgress.NOTIFY_ALL);
+ this.mTabListeners[0] = tabListener;
+ this.mTabFilters[0] = filter;
+
+ try {
+ // We assume this can only fail because mCurrentBrowser's docShell
+ // hasn't been created, yet. This may be caused by code accessing
+ // gBrowser before the window has finished loading.
+ this._addProgressListenerForInitialTab();
+ } catch (e) {
+ // The binding was constructed too early, wait until the initial
+ // tab's document is ready, then add the progress listener.
+ this._waitForInitialContentDocument();
+ }
+
+ this.style.backgroundColor =
+ Services.prefs.getBoolPref("browser.display.use_system_colors") ?
+ "-moz-default-background-color" :
+ Services.prefs.getCharPref("browser.display.background_color");
+
+ if (Services.prefs.getBoolPref("browser.tabs.remote"))
+ messageManager.addMessageListener("DOMTitleChanged", this);
+ ]]>
+ </constructor>
+
+ <method name="_addProgressListenerForInitialTab">
+ <body><![CDATA[
+ this.webProgress.addProgressListener(this.mTabFilters[0], Ci.nsIWebProgress.NOTIFY_ALL);
+ ]]></body>
+ </method>
+
+ <method name="_waitForInitialContentDocument">
+ <body><![CDATA[
+ let obs = (subject, topic) => {
+ if (this.browsers[0].contentWindow == subject) {
+ Services.obs.removeObserver(obs, topic);
+ this._addProgressListenerForInitialTab();
+ }
+ };
+
+ // We use content-document-global-created as an approximation for
+ // "docShell is initialized". We can do this because in the
+ // mTabProgressListener we care most about the STATE_STOP notification
+ // that will reset mBlank. That means it's important to at least add
+ // the progress listener before the initial about:blank load stops
+ // if we can't do it before the load starts.
+ Services.obs.addObserver(obs, "content-document-global-created", false);
+ ]]></body>
+ </method>
+
+ <destructor>
+ <![CDATA[
+ for (var i = 0; i < this.mTabListeners.length; ++i) {
+ let browser = this.getBrowserAtIndex(i);
+ if (browser.registeredOpenURI) {
+ this._placesAutocomplete.unregisterOpenPage(browser.registeredOpenURI);
+ delete browser.registeredOpenURI;
+ }
+ browser.webProgress.removeProgressListener(this.mTabFilters[i]);
+ this.mTabFilters[i].removeProgressListener(this.mTabListeners[i]);
+ this.mTabFilters[i] = null;
+ this.mTabListeners[i].destroy();
+ this.mTabListeners[i] = null;
+ }
+ document.removeEventListener("keypress", this, false);
+ window.removeEventListener("sizemodechange", this, false);
+
+ if (Services.prefs.getBoolPref("browser.tabs.remote"))
+ messageManager.removeMessageListener("DOMTitleChanged", this);
+ ]]>
+ </destructor>
+
+ <!-- Deprecated stuff, implemented for backwards compatibility. -->
+ <method name="enterTabbedMode">
+ <body>
+ Application.console.log("enterTabbedMode is an obsolete method and " +
+ "will be removed in a future release.");
+ </body>
+ </method>
+ <field name="mTabbedMode" readonly="true">true</field>
+ <method name="setStripVisibilityTo">
+ <parameter name="aShow"/>
+ <body>
+ this.tabContainer.visible = aShow;
+ </body>
+ </method>
+ <method name="getStripVisibility">
+ <body>
+ return this.tabContainer.visible;
+ </body>
+ </method>
+ <property name="mContextTab" readonly="true"
+ onget="return TabContextMenu.contextTab;"/>
+ <property name="mPrefs" readonly="true"
+ onget="return Services.prefs;"/>
+ <property name="mTabContainer" readonly="true"
+ onget="return this.tabContainer;"/>
+ <property name="mTabs" readonly="true"
+ onget="return this.tabs;"/>
+ <!--
+ - Compatibility hack: several extensions depend on this property to
+ - access the tab context menu or tab container, so keep that working for
+ - now. Ideally we can remove this once extensions are using
+ - tabbrowser.tabContextMenu and tabbrowser.tabContainer directly.
+ -->
+ <property name="mStrip" readonly="true">
+ <getter>
+ <![CDATA[
+ return ({
+ self: this,
+ childNodes: [null, this.tabContextMenu, this.tabContainer],
+ firstChild: { nextSibling: this.tabContextMenu },
+ getElementsByAttribute: function (attr, attrValue) {
+ if (attr == "anonid" && attrValue == "tabContextMenu")
+ return [this.self.tabContextMenu];
+ return [];
+ },
+ // Also support adding event listeners (forward to the tab container)
+ addEventListener: function (a,b,c) { this.self.tabContainer.addEventListener(a,b,c); },
+ removeEventListener: function (a,b,c) { this.self.tabContainer.removeEventListener(a,b,c); }
+ });
+ ]]>
+ </getter>
+ </property>
+ </implementation>
+
+ <handlers>
+ <handler event="DOMWindowClose" phase="capturing">
+ <![CDATA[
+ if (!event.isTrusted)
+ return;
+
+ if (this.tabs.length == 1)
+ return;
+
+ var tab = this._getTabForContentWindow(event.target);
+ if (tab) {
+ this.removeTab(tab);
+ event.preventDefault();
+ }
+ ]]>
+ </handler>
+ <handler event="DOMWillOpenModalDialog" phase="capturing">
+ <![CDATA[
+ if (!event.isTrusted)
+ return;
+
+ // We're about to open a modal dialog, make sure the opening
+ // tab is brought to the front.
+ this.selectedTab = this._getTabForContentWindow(event.target.top);
+ ]]>
+ </handler>
+ <handler event="DOMTitleChanged">
+ <![CDATA[
+ if (!event.isTrusted)
+ return;
+
+ var contentWin = event.target.defaultView;
+ if (contentWin != contentWin.top)
+ return;
+
+ var tab = this._getTabForContentWindow(contentWin);
+ var titleChanged = this.setTabTitle(tab);
+ if (titleChanged && !tab.selected && !tab.hasAttribute("busy"))
+ tab.setAttribute("titlechanged", "true");
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="tabbrowser-tabbox"
+ extends="chrome://global/content/bindings/tabbox.xml#tabbox">
+ <implementation>
+ <property name="tabs" readonly="true"
+ onget="return document.getBindingParent(this).tabContainer;"/>
+ </implementation>
+ </binding>
+
+ <binding id="tabbrowser-arrowscrollbox" extends="chrome://global/content/bindings/scrollbox.xml#arrowscrollbox-clicktoscroll">
+ <implementation>
+ <!-- Override scrollbox.xml method, since our scrollbox's children are
+ inherited from the binding parent -->
+ <method name="_getScrollableElements">
+ <body><![CDATA[
+ return Array.filter(document.getBindingParent(this).childNodes,
+ this._canScrollToElement, this);
+ ]]></body>
+ </method>
+ <method name="_canScrollToElement">
+ <parameter name="tab"/>
+ <body><![CDATA[
+ return !tab.pinned && !tab.hidden;
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="underflow" phase="capturing"><![CDATA[
+ if (event.detail == 0)
+ return; // Ignore vertical events
+
+ var tabs = document.getBindingParent(this);
+ tabs.removeAttribute("overflow");
+
+ if (tabs._lastTabClosedByMouse)
+ tabs._expandSpacerBy(this._scrollButtonDown.clientWidth);
+
+ tabs.tabbrowser._removingTabs.forEach(tabs.tabbrowser.removeTab,
+ tabs.tabbrowser);
+
+ tabs._positionPinnedTabs();
+ ]]></handler>
+ <handler event="overflow"><![CDATA[
+ if (event.detail == 0)
+ return; // Ignore vertical events
+
+ var tabs = document.getBindingParent(this);
+ tabs.setAttribute("overflow", "true");
+ tabs._positionPinnedTabs();
+ tabs._handleTabSelect(false);
+ ]]></handler>
+ </handlers>
+ </binding>
+
+ <binding id="tabbrowser-tabs"
+ extends="chrome://global/content/bindings/tabbox.xml#tabs">
+ <resources>
+ <stylesheet src="chrome://browser/content/tabbrowser.css"/>
+ </resources>
+
+ <content>
+ <xul:hbox align="end">
+ <xul:image class="tab-drop-indicator" anonid="tab-drop-indicator" collapsed="true"/>
+ </xul:hbox>
+ <xul:arrowscrollbox anonid="arrowscrollbox" orient="horizontal" flex="1"
+ style="min-width: 1px;"
+#ifndef XP_MACOSX
+ clicktoscroll="true"
+#endif
+ class="tabbrowser-arrowscrollbox">
+# This is a hack to circumvent bug 472020, otherwise the tabs show up on the
+# right of the newtab button.
+ <children includes="tab"/>
+# This is to ensure anything extensions put here will go before the newtab
+# button, necessary due to the previous hack.
+ <children/>
+ <xul:toolbarbutton class="tabs-newtab-button"
+ command="cmd_newNavigatorTab"
+ onclick="checkForMiddleClick(this, event);"
+ onmouseover="document.getBindingParent(this)._enterNewTab();"
+ onmouseout="document.getBindingParent(this)._leaveNewTab();"
+ tooltiptext="&newTabButton.tooltip;"/>
+ <xul:spacer class="closing-tabs-spacer" anonid="closing-tabs-spacer"
+ style="width: 0;"/>
+ </xul:arrowscrollbox>
+ </content>
+
+ <implementation implements="nsIDOMEventListener">
+ <constructor>
+ <![CDATA[
+ this.mTabClipWidth = Services.prefs.getIntPref("browser.tabs.tabClipWidth");
+ this.mCloseButtons = Services.prefs.getIntPref("browser.tabs.closeButtons");
+ this._closeWindowWithLastTab = Services.prefs.getBoolPref("browser.tabs.closeWindowWithLastTab");
+
+ var tab = this.firstChild;
+ tab.setAttribute("label",
+ this.tabbrowser.mStringBundle.getString("tabs.emptyTabTitle"));
+ tab.setAttribute("crop", "end");
+ tab.setAttribute("onerror", "this.removeAttribute('image');");
+ this.adjustTabstrip();
+
+ Services.prefs.addObserver("browser.tabs.", this._prefObserver, false);
+ window.addEventListener("resize", this, false);
+ window.addEventListener("load", this, false);
+
+ try {
+ this._tabAnimationLoggingEnabled = Services.prefs.getBoolPref("browser.tabs.animationLogging.enabled");
+ } catch (ex) {
+ this._tabAnimationLoggingEnabled = false;
+ }
+ this._browserNewtabpageEnabled = Services.prefs.getBoolPref("browser.newtabpage.enabled");
+ ]]>
+ </constructor>
+
+ <destructor>
+ <![CDATA[
+ Services.prefs.removeObserver("browser.tabs.", this._prefObserver);
+ ]]>
+ </destructor>
+
+ <field name="tabbrowser" readonly="true">
+ document.getElementById(this.getAttribute("tabbrowser"));
+ </field>
+
+ <field name="tabbox" readonly="true">
+ this.tabbrowser.mTabBox;
+ </field>
+
+ <field name="contextMenu" readonly="true">
+ document.getElementById("tabContextMenu");
+ </field>
+
+ <field name="mTabstripWidth">0</field>
+
+ <field name="mTabstrip">
+ document.getAnonymousElementByAttribute(this, "anonid", "arrowscrollbox");
+ </field>
+
+ <field name="_firstTab">null</field>
+ <field name="_lastTab">null</field>
+ <field name="_afterSelectedTab">null</field>
+ <field name="_beforeHoveredTab">null</field>
+ <field name="_afterHoveredTab">null</field>
+
+ <method name="_setPositionalAttributes">
+ <body><![CDATA[
+ let visibleTabs = this.tabbrowser.visibleTabs;
+
+ if (!visibleTabs.length)
+ return;
+
+ let selectedIndex = visibleTabs.indexOf(this.selectedItem);
+
+ let lastVisible = visibleTabs.length - 1;
+
+ if (this._afterSelectedTab)
+ this._afterSelectedTab.removeAttribute("afterselected-visible");
+ if (this.selectedItem.closing || selectedIndex == lastVisible) {
+ this._afterSelectedTab = null;
+ } else {
+ this._afterSelectedTab = visibleTabs[selectedIndex + 1];
+ this._afterSelectedTab.setAttribute("afterselected-visible",
+ "true");
+ }
+
+ if (this._firstTab)
+ this._firstTab.removeAttribute("first-visible-tab");
+ this._firstTab = visibleTabs[0];
+ this._firstTab.setAttribute("first-visible-tab", "true");
+ if (this._lastTab)
+ this._lastTab.removeAttribute("last-visible-tab");
+ this._lastTab = visibleTabs[lastVisible];
+ this._lastTab.setAttribute("last-visible-tab", "true");
+ ]]></body>
+ </method>
+
+ <field name="_prefObserver"><![CDATA[({
+ tabContainer: this,
+
+ observe: function (subject, topic, data) {
+ switch (data) {
+ case "browser.tabs.closeButtons":
+ this.tabContainer.mCloseButtons = Services.prefs.getIntPref(data);
+ this.tabContainer.adjustTabstrip();
+ break;
+ case "browser.tabs.autoHide":
+ this.tabContainer.updateVisibility();
+ break;
+ case "browser.tabs.closeWindowWithLastTab":
+ this.tabContainer._closeWindowWithLastTab = Services.prefs.getBoolPref(data);
+ this.tabContainer.adjustTabstrip();
+ break;
+ }
+ }
+ });]]></field>
+ <field name="_blockDblClick">false</field>
+
+ <field name="_tabDropIndicator">
+ document.getAnonymousElementByAttribute(this, "anonid", "tab-drop-indicator");
+ </field>
+
+ <field name="_dragOverDelay">350</field>
+ <field name="_dragTime">0</field>
+
+ <field name="_container" readonly="true"><![CDATA[
+ this.parentNode && this.parentNode.localName == "toolbar" ? this.parentNode : this;
+ ]]></field>
+
+ <field name="_propagatedVisibilityOnce">false</field>
+
+ <property name="visible"
+ onget="return !this._container.collapsed;">
+ <setter><![CDATA[
+ if (val == this.visible &&
+ this._propagatedVisibilityOnce)
+ return val;
+
+ this._container.collapsed = !val;
+
+ this._propagateVisibility();
+ this._propagatedVisibilityOnce = true;
+
+ return val;
+ ]]></setter>
+ </property>
+
+ <method name="_enterNewTab">
+ <body><![CDATA[
+ let visibleTabs = this.tabbrowser.visibleTabs;
+ let candidate = visibleTabs[visibleTabs.length - 1];
+ if (!candidate.selected) {
+ this._beforeHoveredTab = candidate;
+ candidate.setAttribute("beforehovered", "true");
+ }
+ ]]></body>
+ </method>
+
+ <method name="_leaveNewTab">
+ <body><![CDATA[
+ if (this._beforeHoveredTab) {
+ this._beforeHoveredTab.removeAttribute("beforehovered");
+ this._beforeHoveredTab = null;
+ }
+ ]]></body>
+ </method>
+
+ <method name="_propagateVisibility">
+ <body><![CDATA[
+ let visible = this.visible;
+
+ document.getElementById("menu_closeWindow").hidden = !visible;
+ document.getElementById("menu_close").setAttribute("label",
+ this.tabbrowser.mStringBundle.getString(visible ? "tabs.closeTab" : "tabs.close"));
+
+ goSetCommandEnabled("cmd_ToggleTabsOnTop", visible);
+
+ TabsOnTop.syncUI();
+
+ TabsInTitlebar.allowedBy("tabs-visible", visible);
+ ]]></body>
+ </method>
+
+ <method name="updateVisibility">
+ <body><![CDATA[
+ if (this.childNodes.length - this.tabbrowser._removingTabs.length == 1)
+ this.visible = window.toolbar.visible &&
+ !Services.prefs.getBoolPref("browser.tabs.autoHide");
+ else
+ this.visible = true;
+ ]]></body>
+ </method>
+
+ <method name="adjustTabstrip">
+ <body><![CDATA[
+ let numTabs = this.childNodes.length -
+ this.tabbrowser._removingTabs.length;
+ // modes for tabstrip
+ // 0 - button on active tab only
+ // 1 - close buttons on all tabs
+ // 2 - no close buttons at all
+ // 3 - close button at the end of the tabstrip
+ switch (this.mCloseButtons) {
+ case 0:
+ if (numTabs == 1 && this._closeWindowWithLastTab)
+ this.setAttribute("closebuttons", "hidden");
+ else
+ this.setAttribute("closebuttons", "activetab");
+ break;
+ case 1:
+ if (numTabs == 1) {
+ if (this._closeWindowWithLastTab)
+ this.setAttribute("closebuttons", "hidden");
+ else
+ this.setAttribute("closebuttons", "alltabs");
+ } else if (numTabs == 2) {
+ // This is an optimization to avoid layout flushes by calling
+ // getBoundingClientRect() when we just opened a second tab. In
+ // this case it's highly unlikely that the tab width is smaller
+ // than mTabClipWidth and the tab close button obscures too much
+ // of the tab's label. In the edge case of the window being too
+ // narrow (or if tabClipWidth has been set to a way higher value),
+ // we'll correct the 'closebuttons' attribute after the tabopen
+ // animation has finished.
+ this.setAttribute("closebuttons", "alltabs");
+ } else {
+ let tab = this.tabbrowser.visibleTabs[this.tabbrowser._numPinnedTabs];
+ if (tab && tab.getBoundingClientRect().width > this.mTabClipWidth)
+ this.setAttribute("closebuttons", "alltabs");
+ else
+ this.setAttribute("closebuttons", "activetab");
+ }
+ break;
+ case 2:
+ case 3:
+ this.setAttribute("closebuttons", "never");
+ break;
+ }
+ var tabstripClosebutton = document.getElementById("tabs-closebutton");
+ if (tabstripClosebutton && tabstripClosebutton.parentNode == this._container)
+ tabstripClosebutton.collapsed = this.mCloseButtons != 3;
+ ]]></body>
+ </method>
+
+ <method name="_handleTabSelect">
+ <parameter name="aSmoothScroll"/>
+ <body><![CDATA[
+ if (this.getAttribute("overflow") == "true")
+ this.mTabstrip.ensureElementIsVisible(this.selectedItem, aSmoothScroll);
+ ]]></body>
+ </method>
+
+ <method name="_fillTrailingGap">
+ <body><![CDATA[
+ try {
+ // if we're at the right side (and not the logical end,
+ // which is why this works for both LTR and RTL)
+ // of the tabstrip, we need to ensure that we stay
+ // completely scrolled to the right side
+ var tabStrip = this.mTabstrip;
+ if (tabStrip.scrollPosition + tabStrip.scrollClientSize >
+ tabStrip.scrollSize)
+ tabStrip.scrollByPixels(-1);
+ } catch (e) {}
+ ]]></body>
+ </method>
+
+ <field name="_closingTabsSpacer">
+ document.getAnonymousElementByAttribute(this, "anonid", "closing-tabs-spacer");
+ </field>
+
+ <field name="_tabDefaultMaxWidth">NaN</field>
+ <field name="_lastTabClosedByMouse">false</field>
+ <field name="_hasTabTempMaxWidth">false</field>
+
+ <!-- Try to keep the active tab's close button under the mouse cursor -->
+ <method name="_lockTabSizing">
+ <parameter name="aTab"/>
+ <body><![CDATA[
+ var tabs = this.tabbrowser.visibleTabs;
+ if (!tabs.length)
+ return;
+
+ var isEndTab = (aTab._tPos > tabs[tabs.length-1]._tPos);
+ var tabWidth = aTab.getBoundingClientRect().width;
+
+ if (!this._tabDefaultMaxWidth)
+ this._tabDefaultMaxWidth =
+ parseFloat(window.getComputedStyle(aTab).maxWidth);
+ this._lastTabClosedByMouse = true;
+
+ if (this.getAttribute("overflow") == "true") {
+ // Don't need to do anything if we're in overflow mode and aren't scrolled
+ // all the way to the right, or if we're closing the last tab.
+ if (isEndTab || !this.mTabstrip._scrollButtonDown.disabled)
+ return;
+
+ // If the tab has an owner that will become the active tab, the owner will
+ // be to the left of it, so we actually want the left tab to slide over.
+ // This can't be done as easily in non-overflow mode, so we don't bother.
+ if (aTab.owner)
+ return;
+
+ //Pale Moon: Resize immediately if preffed
+ if (Services.prefs.getBoolPref("browser.tabs.resize_immediately"))
+ return;
+
+ this._expandSpacerBy(tabWidth);
+ } else { // non-overflow mode
+ // Locking is neither in effect nor needed, so let tabs expand normally.
+ if (isEndTab && !this._hasTabTempMaxWidth)
+ return;
+
+ //Pale Moon: Resize immediately if preffed
+ if (Services.prefs.getBoolPref("browser.tabs.resize_immediately"))
+ return;
+
+ let numPinned = this.tabbrowser._numPinnedTabs;
+ // Force tabs to stay the same width, unless we're closing the last tab,
+ // which case we need to let them expand just enough so that the overall
+ // tabbar width is the same.
+ if (isEndTab) {
+ let numNormalTabs = tabs.length - numPinned;
+ tabWidth = tabWidth * (numNormalTabs + 1) / numNormalTabs;
+ if (tabWidth > this._tabDefaultMaxWidth)
+ tabWidth = this._tabDefaultMaxWidth;
+ }
+ tabWidth += "px";
+ for (let i = numPinned; i < tabs.length; i++) {
+ let tab = tabs[i];
+ tab.style.setProperty("max-width", tabWidth, "important");
+ if (!isEndTab) { // keep tabs the same width
+ tab.style.transition = "none";
+ tab.clientTop; // flush styles to skip animation; see bug 649247
+ tab.style.transition = "";
+ }
+ }
+ this._hasTabTempMaxWidth = true;
+ this.tabbrowser.addEventListener("mousemove", this, false);
+ window.addEventListener("mouseout", this, false);
+ }
+ ]]></body>
+ </method>
+
+ <method name="_expandSpacerBy">
+ <parameter name="pixels"/>
+ <body><![CDATA[
+ let spacer = this._closingTabsSpacer;
+ spacer.style.width = parseFloat(spacer.style.width) + pixels + "px";
+ this.setAttribute("using-closing-tabs-spacer", "true");
+ this.tabbrowser.addEventListener("mousemove", this, false);
+ window.addEventListener("mouseout", this, false);
+ ]]></body>
+ </method>
+
+ <method name="_unlockTabSizing">
+ <body><![CDATA[
+ this.tabbrowser.removeEventListener("mousemove", this, false);
+ window.removeEventListener("mouseout", this, false);
+
+ if (this._hasTabTempMaxWidth) {
+ this._hasTabTempMaxWidth = false;
+ let tabs = this.tabbrowser.visibleTabs;
+ for (let i = 0; i < tabs.length; i++)
+ tabs[i].style.maxWidth = "";
+ }
+
+ if (this.hasAttribute("using-closing-tabs-spacer")) {
+ this.removeAttribute("using-closing-tabs-spacer");
+ this._closingTabsSpacer.style.width = 0;
+ }
+ ]]></body>
+ </method>
+
+ <field name="_lastNumPinned">0</field>
+ <method name="_positionPinnedTabs">
+ <body><![CDATA[
+ var numPinned = this.tabbrowser._numPinnedTabs;
+ var doPosition = this.getAttribute("overflow") == "true" &&
+ numPinned > 0;
+
+ if (doPosition) {
+ this.setAttribute("positionpinnedtabs", "true");
+
+ let scrollButtonWidth = this.mTabstrip._scrollButtonDown.getBoundingClientRect().width;
+ let paddingStart = this.mTabstrip.scrollboxPaddingStart;
+ let width = 0;
+
+ for (let i = numPinned - 1; i >= 0; i--) {
+ let tab = this.childNodes[i];
+ width += tab.getBoundingClientRect().width;
+ tab.style.MozMarginStart = - (width + scrollButtonWidth + paddingStart) + "px";
+ }
+
+ this.style.MozPaddingStart = width + paddingStart + "px";
+
+ } else {
+ this.removeAttribute("positionpinnedtabs");
+
+ for (let i = 0; i < numPinned; i++) {
+ let tab = this.childNodes[i];
+ tab.style.MozMarginStart = "";
+ }
+
+ this.style.MozPaddingStart = "";
+ }
+
+ if (this._lastNumPinned != numPinned) {
+ this._lastNumPinned = numPinned;
+ this._handleTabSelect(false);
+ }
+ ]]></body>
+ </method>
+
+ <method name="_animateTabMove">
+ <parameter name="event"/>
+ <body><![CDATA[
+ let draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
+
+ if (this.getAttribute("movingtab") != "true") {
+ this.setAttribute("movingtab", "true");
+ this.selectedItem = draggedTab;
+ }
+
+ if (!("animLastScreenX" in draggedTab._dragData))
+ draggedTab._dragData.animLastScreenX = draggedTab._dragData.screenX;
+
+ let screenX = event.screenX;
+ if (screenX == draggedTab._dragData.animLastScreenX)
+ return;
+
+ let draggingRight = screenX > draggedTab._dragData.animLastScreenX;
+ draggedTab._dragData.animLastScreenX = screenX;
+
+ let rtl = (window.getComputedStyle(this).direction == "rtl");
+ let pinned = draggedTab.pinned;
+ let numPinned = this.tabbrowser._numPinnedTabs;
+ let tabs = this.tabbrowser.visibleTabs
+ .slice(pinned ? 0 : numPinned,
+ pinned ? numPinned : undefined);
+ if (rtl)
+ tabs.reverse();
+ let tabWidth = draggedTab.getBoundingClientRect().width;
+
+ // Move the dragged tab based on the mouse position.
+
+ let leftTab = tabs[0];
+ let rightTab = tabs[tabs.length - 1];
+ let tabScreenX = draggedTab.boxObject.screenX;
+ let translateX = screenX - draggedTab._dragData.screenX;
+ if (!pinned)
+ translateX += this.mTabstrip.scrollPosition - draggedTab._dragData.scrollX;
+ let leftBound = leftTab.boxObject.screenX - tabScreenX;
+ let rightBound = (rightTab.boxObject.screenX + rightTab.boxObject.width) -
+ (tabScreenX + tabWidth);
+ translateX = Math.max(translateX, leftBound);
+ translateX = Math.min(translateX, rightBound);
+ draggedTab.style.transform = "translateX(" + translateX + "px)";
+
+ // Determine what tab we're dragging over.
+ // * Point of reference is the center of the dragged tab. If that
+ // point touches a background tab, the dragged tab would take that
+ // tab's position when dropped.
+ // * We're doing a binary search in order to reduce the amount of
+ // tabs we need to check.
+
+ let tabCenter = tabScreenX + translateX + tabWidth / 2;
+ let newIndex = -1;
+ let oldIndex = "animDropIndex" in draggedTab._dragData ?
+ draggedTab._dragData.animDropIndex : draggedTab._tPos;
+ let low = 0;
+ let high = tabs.length - 1;
+ while (low <= high) {
+ let mid = Math.floor((low + high) / 2);
+ if (tabs[mid] == draggedTab &&
+ ++mid > high)
+ break;
+ let boxObject = tabs[mid].boxObject;
+ let screenX = boxObject.screenX + getTabShift(tabs[mid], oldIndex);
+ if (screenX > tabCenter) {
+ high = mid - 1;
+ } else if (screenX + boxObject.width < tabCenter) {
+ low = mid + 1;
+ } else {
+ newIndex = tabs[mid]._tPos;
+ break;
+ }
+ }
+ if (newIndex >= oldIndex)
+ newIndex++;
+ if (newIndex < 0 || newIndex == oldIndex)
+ return;
+ draggedTab._dragData.animDropIndex = newIndex;
+
+ // Shift background tabs to leave a gap where the dragged tab
+ // would currently be dropped.
+
+ for (let tab of tabs) {
+ if (tab != draggedTab) {
+ let shift = getTabShift(tab, newIndex);
+ tab.style.transform = shift ? "translateX(" + shift + "px)" : "";
+ }
+ }
+
+ function getTabShift(tab, dropIndex) {
+ if (tab._tPos < draggedTab._tPos && tab._tPos >= dropIndex)
+ return rtl ? -tabWidth : tabWidth;
+ if (tab._tPos > draggedTab._tPos && tab._tPos < dropIndex)
+ return rtl ? tabWidth : -tabWidth;
+ return 0;
+ }
+ ]]></body>
+ </method>
+
+ <method name="_finishAnimateTabMove">
+ <body><![CDATA[
+ if (this.getAttribute("movingtab") != "true")
+ return;
+
+ for (let tab of this.tabbrowser.visibleTabs)
+ tab.style.transform = "";
+
+ this.removeAttribute("movingtab");
+
+ this._handleTabSelect();
+ ]]></body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ switch (aEvent.type) {
+ case "load":
+ this.updateVisibility();
+ break;
+ case "resize":
+ if (aEvent.target != window)
+ break;
+
+ let sizemode = document.documentElement.getAttribute("sizemode");
+ TabsInTitlebar.allowedBy("sizemode",
+ sizemode == "maximized" || sizemode == "fullscreen");
+
+ var width = this.mTabstrip.boxObject.width;
+ if (width != this.mTabstripWidth) {
+ this.adjustTabstrip();
+ this._fillTrailingGap();
+ this._handleTabSelect();
+ this.mTabstripWidth = width;
+ }
+
+ this.tabbrowser.updateWindowResizers();
+ break;
+ case "mouseout":
+ // If the "related target" (the node to which the pointer went) is not
+ // a child of the current document, the mouse just left the window.
+ let relatedTarget = aEvent.relatedTarget;
+ if (relatedTarget && relatedTarget.ownerDocument == document)
+ break;
+ case "mousemove":
+ if (document.getElementById("tabContextMenu").state != "open")
+ this._unlockTabSizing();
+ break;
+ }
+ ]]></body>
+ </method>
+
+ <field name="_animateElement">
+ this.mTabstrip._scrollButtonDown;
+ </field>
+
+ <method name="_notifyBackgroundTab">
+ <parameter name="aTab"/>
+ <body><![CDATA[
+ if (aTab.pinned)
+ return;
+
+ var scrollRect = this.mTabstrip.scrollClientRect;
+ var tab = aTab.getBoundingClientRect();
+
+ // Is the new tab already completely visible?
+ if (scrollRect.left <= tab.left && tab.right <= scrollRect.right)
+ return;
+
+ if (this.mTabstrip.smoothScroll) {
+ let selected = !this.selectedItem.pinned &&
+ this.selectedItem.getBoundingClientRect();
+
+ // Can we make both the new tab and the selected tab completely visible?
+ if (!selected ||
+ Math.max(tab.right - selected.left, selected.right - tab.left) <=
+ scrollRect.width) {
+ this.mTabstrip.ensureElementIsVisible(aTab);
+ return;
+ }
+
+ this.mTabstrip._smoothScrollByPixels(this.mTabstrip._isRTLScrollbox ?
+ selected.right - scrollRect.right :
+ selected.left - scrollRect.left);
+ }
+
+ if (!this._animateElement.hasAttribute("notifybgtab")) {
+ this._animateElement.setAttribute("notifybgtab", "true");
+ setTimeout(function (ele) {
+ ele.removeAttribute("notifybgtab");
+ }, 150, this._animateElement);
+ }
+ ]]></body>
+ </method>
+
+ <method name="_getDragTargetTab">
+ <parameter name="event"/>
+ <body><![CDATA[
+ let tab = event.target.localName == "tab" ? event.target : null;
+ if (tab &&
+ (event.type == "drop" || event.type == "dragover") &&
+ event.dataTransfer.dropEffect == "link") {
+ let boxObject = tab.boxObject;
+ if (event.screenX < boxObject.screenX + boxObject.width * .25 ||
+ event.screenX > boxObject.screenX + boxObject.width * .75)
+ return null;
+ }
+ return tab;
+ ]]></body>
+ </method>
+
+ <method name="_getDropIndex">
+ <parameter name="event"/>
+ <body><![CDATA[
+ var tabs = this.childNodes;
+ var tab = this._getDragTargetTab(event);
+ if (window.getComputedStyle(this, null).direction == "ltr") {
+ for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
+ if (event.screenX < tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2)
+ return i;
+ } else {
+ for (let i = tab ? tab._tPos : 0; i < tabs.length; i++)
+ if (event.screenX > tabs[i].boxObject.screenX + tabs[i].boxObject.width / 2)
+ return i;
+ }
+ return tabs.length;
+ ]]></body>
+ </method>
+
+ <method name="_setEffectAllowedForDataTransfer">
+ <parameter name="event"/>
+ <body><![CDATA[
+ var dt = event.dataTransfer;
+ // Disallow dropping multiple items
+ if (dt.mozItemCount > 1)
+ return dt.effectAllowed = "none";
+
+ var types = dt.mozTypesAt(0);
+ var sourceNode = null;
+ // tabs are always added as the first type
+ if (types[0] == TAB_DROP_TYPE) {
+ var sourceNode = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
+ if (sourceNode instanceof XULElement &&
+ sourceNode.localName == "tab" &&
+ sourceNode.ownerDocument.defaultView instanceof ChromeWindow &&
+ sourceNode.ownerDocument.documentElement.getAttribute("windowtype") == "navigator:browser" &&
+ sourceNode.ownerDocument.defaultView.gBrowser.tabContainer == sourceNode.parentNode) {
+ // Do not allow transfering a private tab to a non-private window
+ // and vice versa.
+ if (PrivateBrowsingUtils.isWindowPrivate(window) !=
+ PrivateBrowsingUtils.isWindowPrivate(sourceNode.ownerDocument.defaultView))
+ return dt.effectAllowed = "none";
+
+#ifdef XP_MACOSX
+ return dt.effectAllowed = event.altKey ? "copy" : "move";
+#else
+ return dt.effectAllowed = event.ctrlKey ? "copy" : "move";
+#endif
+ }
+ }
+
+ if (browserDragAndDrop.canDropLink(event)) {
+ // Here we need to do this manually
+ return dt.effectAllowed = dt.dropEffect = "link";
+ }
+ return dt.effectAllowed = "none";
+ ]]></body>
+ </method>
+
+ <method name="_handleNewTab">
+ <parameter name="tab"/>
+ <body><![CDATA[
+ if (tab.parentNode != this)
+ return;
+ tab._fullyOpen = true;
+
+ this.adjustTabstrip();
+
+ if (tab.getAttribute("selected") == "true") {
+ this._fillTrailingGap();
+ this._handleTabSelect();
+ } else {
+ this._notifyBackgroundTab(tab);
+ }
+
+ // XXXmano: this is a temporary workaround for bug 345399
+ // We need to manually update the scroll buttons disabled state
+ // if a tab was inserted to the overflow area or removed from it
+ // without any scrolling and when the tabbar has already
+ // overflowed.
+ this.mTabstrip._updateScrollButtonsDisabledState();
+ ]]></body>
+ </method>
+
+ <method name="_canAdvanceToTab">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ return !aTab.closing;
+ ]]>
+ </body>
+ </method>
+
+ <method name="_handleTabTelemetryStart">
+ <parameter name="aTab"/>
+ <parameter name="aURI"/>
+ <body>
+ <![CDATA[
+ // Animation-smoothness telemetry/logging
+ if (Services.telemetry.canRecord || this._tabAnimationLoggingEnabled) {
+ if (aURI == "about:newtab" && (aTab._tPos == 1 || aTab._tPos == 2)) {
+ // Indicate newtab page animation where other tabs are unaffected
+ // (for which case, the 2nd or 3rd tabs are good representatives, even if not absolute)
+ aTab._recordingTabOpenPlain = true;
+ }
+ aTab._recordingHandle = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .startFrameTimeRecording();
+ }
+
+ // Overall animation duration
+ aTab._animStartTime = Date.now();
+ ]]>
+ </body>
+ </method>
+
+ <method name="_handleTabTelemetryEnd">
+ <parameter name="aTab"/>
+ <body>
+ <![CDATA[
+ if (!aTab._animStartTime) {
+ return;
+ }
+
+ Services.telemetry.getHistogramById(aTab.closing ?
+ "FX_TAB_ANIM_CLOSE_MS" :
+ "FX_TAB_ANIM_OPEN_MS")
+ .add(Date.now() - aTab._animStartTime);
+ aTab._animStartTime = 0;
+
+ // Handle tab animation smoothness telemetry/logging of frame intervals and paint times
+ if (!("_recordingHandle" in aTab)) {
+ return;
+ }
+
+ let paints = {};
+ let intervals = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .stopFrameTimeRecording(aTab._recordingHandle, paints);
+ delete aTab._recordingHandle;
+ paints = paints.value; // The result array itself.
+ let frameCount = intervals.length;
+
+ if (this._tabAnimationLoggingEnabled) {
+ let msg = "Tab " + (aTab.closing ? "close" : "open") + " (Frame-interval / paint-processing):\n";
+ for (let i = 0; i < frameCount; i++) {
+ msg += Math.round(intervals[i]) + " / " + Math.round(paints[i]) + "\n";
+ }
+ Services.console.logStringMessage(msg);
+ }
+
+ // For telemetry, the first frame interval is not useful since it may represent an interval
+ // to a relatively old frame (prior to recording start). So we'll ignore it for the average.
+ // But if we recorded only 1 frame (very rare), then the first paint duration is a good
+ // representative of the first frame interval for our cause (indicates very bad animation).
+ // First paint duration is always useful for us.
+ if (frameCount > 0) {
+ let averageInterval = 0;
+ let averagePaint = paints[0];
+ for (let i = 1; i < frameCount; i++) {
+ averageInterval += intervals[i];
+ averagePaint += paints[i];
+ };
+ averagePaint /= frameCount;
+ averageInterval = (frameCount == 1)
+ ? averagePaint
+ : averageInterval / (frameCount - 1);
+
+ Services.telemetry.getHistogramById("FX_TAB_ANIM_ANY_FRAME_INTERVAL_MS").add(averageInterval);
+ Services.telemetry.getHistogramById("FX_TAB_ANIM_ANY_FRAME_PAINT_MS").add(averagePaint);
+
+ if (aTab._recordingTabOpenPlain) {
+ delete aTab._recordingTabOpenPlain;
+ // While we do have a telemetry probe NEWTAB_PAGE_ENABLED to monitor newtab preview, it'll be
+ // easier to overview the data without slicing by it. Hence the additional histograms with _PREVIEW.
+ let preview = this._browserNewtabpageEnabled ? "_PREVIEW" : "";
+ Services.telemetry.getHistogramById("FX_TAB_ANIM_OPEN" + preview + "_FRAME_INTERVAL_MS").add(averageInterval);
+ Services.telemetry.getHistogramById("FX_TAB_ANIM_OPEN" + preview + "_FRAME_PAINT_MS").add(averagePaint);
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <!-- Deprecated stuff, implemented for backwards compatibility. -->
+ <property name="mTabstripClosebutton" readonly="true"
+ onget="return document.getElementById('tabs-closebutton');"/>
+ <property name="mAllTabsPopup" readonly="true"
+ onget="return document.getElementById('alltabs-popup');"/>
+ </implementation>
+
+ <handlers>
+ <handler event="TabSelect" action="this._handleTabSelect();"/>
+
+ <handler event="transitionend"><![CDATA[
+ if (event.propertyName != "max-width")
+ return;
+
+ var tab = event.target;
+
+ this._handleTabTelemetryEnd(tab);
+
+ if (tab.getAttribute("fadein") == "true") {
+ if (tab._fullyOpen)
+ this.adjustTabstrip();
+ else
+ this._handleNewTab(tab);
+ } else if (tab.closing) {
+ this.tabbrowser._endRemoveTab(tab);
+ }
+ ]]></handler>
+
+ <handler event="dblclick"><![CDATA[
+#ifndef XP_MACOSX
+ // When the tabbar has an unified appearance with the titlebar
+ // and menubar, a double-click in it should have the same behavior
+ // as double-clicking the titlebar
+ if (TabsInTitlebar.enabled ||
+ (TabsOnTop.enabled && this.parentNode._dragBindingAlive))
+ return;
+#endif
+
+ if (event.button != 0 ||
+ event.originalTarget.localName != "box")
+ return;
+
+ // See hack note in the tabbrowser-close-tab-button binding
+ if (!this._blockDblClick)
+ BrowserOpenTab();
+
+ event.preventDefault();
+ ]]></handler>
+
+ <handler event="click"><![CDATA[
+ if (event.button != 1)
+ return;
+
+ if (event.target.localName == "tab") {
+ if (this.childNodes.length > 1 || !this._closeWindowWithLastTab)
+ this.tabbrowser.removeTab(event.target, {animate: true, byMouse: true});
+ } else if (event.originalTarget.localName == "box") {
+ BrowserOpenTab();
+ } else {
+ return;
+ }
+
+ event.stopPropagation();
+ ]]></handler>
+
+ <handler event="keypress"><![CDATA[
+ if (event.altKey || event.shiftKey ||
+#ifdef XP_MACOSX
+ !event.metaKey)
+#else
+ !event.ctrlKey || event.metaKey)
+#endif
+ return;
+
+ switch (event.keyCode) {
+ case KeyEvent.DOM_VK_UP:
+ this.tabbrowser.moveTabBackward();
+ break;
+ case KeyEvent.DOM_VK_DOWN:
+ this.tabbrowser.moveTabForward();
+ break;
+ case KeyEvent.DOM_VK_RIGHT:
+ case KeyEvent.DOM_VK_LEFT:
+ this.tabbrowser.moveTabOver(event);
+ break;
+ case KeyEvent.DOM_VK_HOME:
+ this.tabbrowser.moveTabToStart();
+ break;
+ case KeyEvent.DOM_VK_END:
+ this.tabbrowser.moveTabToEnd();
+ break;
+ default:
+ // Stop the keypress event for the above keyboard
+ // shortcuts only.
+ return;
+ }
+ event.stopPropagation();
+ event.preventDefault();
+ ]]></handler>
+
+ <handler event="dragstart"><![CDATA[
+ var tab = this._getDragTargetTab(event);
+ if (!tab)
+ return;
+
+ let dt = event.dataTransfer;
+ dt.mozSetDataAt(TAB_DROP_TYPE, tab, 0);
+ let browser = tab.linkedBrowser;
+
+ // We must not set text/x-moz-url or text/plain data here,
+ // otherwise trying to deatch the tab by dropping it on the desktop
+ // may result in an "internet shortcut"
+ dt.mozSetDataAt("text/x-moz-text-internal", browser.currentURI.spec, 0);
+
+ // Set the cursor to an arrow during tab drags.
+ dt.mozCursor = "default";
+
+ // Create a canvas to which we capture the current tab.
+ // Until canvas is HiDPI-aware (bug 780362), we need to scale the desired
+ // canvas size (in CSS pixels) to the window's backing resolution in order
+ // to get a full-resolution drag image for use on HiDPI displays.
+ let windowUtils = window.getInterface(Ci.nsIDOMWindowUtils);
+ let scale = windowUtils.screenPixelsPerCSSPixel / windowUtils.fullZoom;
+ let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+ canvas.mozOpaque = true;
+ canvas.width = 160 * scale;
+ canvas.height = 90 * scale;
+ PageThumbs.captureToCanvas(browser.contentWindow, canvas);
+ dt.setDragImage(canvas, -16 * scale, -16 * scale);
+
+ // _dragData.offsetX/Y give the coordinates that the mouse should be
+ // positioned relative to the corner of the new window created upon
+ // dragend such that the mouse appears to have the same position
+ // relative to the corner of the dragged tab.
+ function clientX(ele) ele.getBoundingClientRect().left;
+ let tabOffsetX = clientX(tab) - clientX(this);
+ tab._dragData = {
+ offsetX: event.screenX - window.screenX - tabOffsetX,
+ offsetY: event.screenY - window.screenY,
+ scrollX: this.mTabstrip.scrollPosition,
+ screenX: event.screenX
+ };
+
+ event.stopPropagation();
+ ]]></handler>
+
+ <handler event="dragover"><![CDATA[
+ var effects = this._setEffectAllowedForDataTransfer(event);
+
+ var ind = this._tabDropIndicator;
+ if (effects == "" || effects == "none") {
+ ind.collapsed = true;
+ return;
+ }
+ event.preventDefault();
+ event.stopPropagation();
+
+ var tabStrip = this.mTabstrip;
+ var ltr = (window.getComputedStyle(this, null).direction == "ltr");
+
+ // autoscroll the tab strip if we drag over the scroll
+ // buttons, even if we aren't dragging a tab, but then
+ // return to avoid drawing the drop indicator
+ var pixelsToScroll = 0;
+ if (this.getAttribute("overflow") == "true") {
+ var targetAnonid = event.originalTarget.getAttribute("anonid");
+ switch (targetAnonid) {
+ case "scrollbutton-up":
+ pixelsToScroll = tabStrip.scrollIncrement * -1;
+ break;
+ case "scrollbutton-down":
+ pixelsToScroll = tabStrip.scrollIncrement;
+ break;
+ }
+ if (pixelsToScroll)
+ tabStrip.scrollByPixels((ltr ? 1 : -1) * pixelsToScroll);
+ }
+
+ if (effects == "move" &&
+ this == event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0).parentNode) {
+ ind.collapsed = true;
+ this._animateTabMove(event);
+ return;
+ }
+
+ this._finishAnimateTabMove();
+
+ if (effects == "link") {
+ let tab = this._getDragTargetTab(event);
+ if (tab) {
+ if (!this._dragTime)
+ this._dragTime = Date.now();
+ if (Date.now() >= this._dragTime + this._dragOverDelay)
+ this.selectedItem = tab;
+ ind.collapsed = true;
+ return;
+ }
+ }
+
+ var rect = tabStrip.getBoundingClientRect();
+ var newMargin;
+ if (pixelsToScroll) {
+ // if we are scrolling, put the drop indicator at the edge
+ // so that it doesn't jump while scrolling
+ let scrollRect = tabStrip.scrollClientRect;
+ let minMargin = scrollRect.left - rect.left;
+ let maxMargin = Math.min(minMargin + scrollRect.width,
+ scrollRect.right);
+ if (!ltr)
+ [minMargin, maxMargin] = [this.clientWidth - maxMargin,
+ this.clientWidth - minMargin];
+ newMargin = (pixelsToScroll > 0) ? maxMargin : minMargin;
+ }
+ else {
+ let newIndex = this._getDropIndex(event);
+ if (newIndex == this.childNodes.length) {
+ let tabRect = this.childNodes[newIndex-1].getBoundingClientRect();
+ if (ltr)
+ newMargin = tabRect.right - rect.left;
+ else
+ newMargin = rect.right - tabRect.left;
+ }
+ else {
+ let tabRect = this.childNodes[newIndex].getBoundingClientRect();
+ if (ltr)
+ newMargin = tabRect.left - rect.left;
+ else
+ newMargin = rect.right - tabRect.right;
+ }
+ }
+
+ ind.collapsed = false;
+
+ newMargin += ind.clientWidth / 2;
+ if (!ltr)
+ newMargin *= -1;
+
+ ind.style.transform = "translate(" + Math.round(newMargin) + "px)";
+ ind.style.MozMarginStart = (-ind.clientWidth) + "px";
+ ]]></handler>
+
+ <handler event="drop"><![CDATA[
+ var dt = event.dataTransfer;
+ var dropEffect = dt.dropEffect;
+ var draggedTab;
+ if (dropEffect != "link") { // copy or move
+ draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
+ // not our drop then
+ if (!draggedTab)
+ return;
+ }
+
+ this._tabDropIndicator.collapsed = true;
+ event.stopPropagation();
+ if (draggedTab && dropEffect == "copy") {
+ // copy the dropped tab (wherever it's from)
+ let newIndex = this._getDropIndex(event);
+ let newTab = this.tabbrowser.duplicateTab(draggedTab);
+ this.tabbrowser.moveTabTo(newTab, newIndex);
+ if (draggedTab.parentNode != this || event.shiftKey)
+ this.selectedItem = newTab;
+ } else if (draggedTab && draggedTab.parentNode == this) {
+ this._finishAnimateTabMove();
+
+ // actually move the dragged tab
+ if ("animDropIndex" in draggedTab._dragData) {
+ let newIndex = draggedTab._dragData.animDropIndex;
+ if (newIndex > draggedTab._tPos)
+ newIndex--;
+ this.tabbrowser.moveTabTo(draggedTab, newIndex);
+ }
+ } else if (draggedTab) {
+ // swap the dropped tab with a new one we create and then close
+ // it in the other window (making it seem to have moved between
+ // windows)
+ let newIndex = this._getDropIndex(event);
+ let newTab = this.tabbrowser.addTab("about:blank");
+ let newBrowser = this.tabbrowser.getBrowserForTab(newTab);
+ // Stop the about:blank load
+ newBrowser.stop();
+ // make sure it has a docshell
+ newBrowser.docShell;
+
+ let numPinned = this.tabbrowser._numPinnedTabs;
+ if (newIndex < numPinned || draggedTab.pinned && newIndex == numPinned)
+ this.tabbrowser.pinTab(newTab);
+ this.tabbrowser.moveTabTo(newTab, newIndex);
+
+ // We need to select the tab before calling swapBrowsersAndCloseOther
+ // so that window.content in chrome windows points to the right tab
+ // when pagehide/show events are fired.
+ this.tabbrowser.selectedTab = newTab;
+
+ draggedTab.parentNode._finishAnimateTabMove();
+ this.tabbrowser.swapBrowsersAndCloseOther(newTab, draggedTab);
+
+ // Call updateCurrentBrowser to make sure the URL bar is up to date
+ // for our new tab after we've done swapBrowsersAndCloseOther.
+ this.tabbrowser.updateCurrentBrowser(true);
+ } else {
+ // Pass true to disallow dropping javascript: or data: urls
+ let url;
+ try {
+ url = browserDragAndDrop.drop(event, { }, true);
+ } catch (ex) {}
+
+// // valid urls don't contain spaces ' '; if we have a space it isn't a valid url.
+// if (!url || url.contains(" ")) //PMed
+ if (!url) //FF
+ return;
+
+ let bgLoad = Services.prefs.getBoolPref("browser.tabs.loadInBackground");
+
+ if (event.shiftKey)
+ bgLoad = !bgLoad;
+
+ let tab = this._getDragTargetTab(event);
+ if (!tab || dropEffect == "copy") {
+ // We're adding a new tab.
+ let newIndex = this._getDropIndex(event);
+ let newTab = this.tabbrowser.loadOneTab(url, {inBackground: bgLoad, allowThirdPartyFixup: true});
+ this.tabbrowser.moveTabTo(newTab, newIndex);
+ } else {
+ // Load in an existing tab.
+ try {
+ this.tabbrowser.getBrowserForTab(tab).loadURIWithFlags(url, Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP);
+ if (!bgLoad)
+ this.selectedItem = tab;
+ } catch(ex) {
+ // Just ignore invalid urls
+ }
+ }
+ }
+
+ if (draggedTab) {
+ delete draggedTab._dragData;
+ }
+ ]]></handler>
+
+ <handler event="dragend"><![CDATA[
+ // Note: while this case is correctly handled here, this event
+ // isn't dispatched when the tab is moved within the tabstrip,
+ // see bug 460801.
+
+ this._finishAnimateTabMove();
+
+ var dt = event.dataTransfer;
+ var draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
+ if (dt.mozUserCancelled || dt.dropEffect != "none") {
+ delete draggedTab._dragData;
+ return;
+ }
+
+ // Disable detach within the browser toolbox
+ var eX = event.screenX;
+ var eY = event.screenY;
+ var wX = window.screenX;
+ // check if the drop point is horizontally within the window
+ if (eX > wX && eX < (wX + window.outerWidth)) {
+ let bo = this.mTabstrip.boxObject;
+ // also avoid detaching if the the tab was dropped too close to
+ // the tabbar (half a tab)
+ let endScreenY = bo.screenY + 1.5 * bo.height;
+ if (eY < endScreenY && eY > window.screenY)
+ return;
+ }
+
+ // screen.availLeft et. al. only check the screen that this window is on,
+ // but we want to look at the screen the tab is being dropped onto.
+ var sX = {}, sY = {}, sWidth = {}, sHeight = {};
+ Cc["@mozilla.org/gfx/screenmanager;1"]
+ .getService(Ci.nsIScreenManager)
+ .screenForRect(eX, eY, 1, 1)
+ .GetAvailRect(sX, sY, sWidth, sHeight);
+ // ensure new window entirely within screen
+ var winWidth = Math.min(window.outerWidth, sWidth.value);
+ var winHeight = Math.min(window.outerHeight, sHeight.value);
+ var left = Math.min(Math.max(eX - draggedTab._dragData.offsetX, sX.value),
+ sX.value + sWidth.value - winWidth);
+ var top = Math.min(Math.max(eY - draggedTab._dragData.offsetY, sY.value),
+ sY.value + sHeight.value - winHeight);
+
+ delete draggedTab._dragData;
+
+ if (this.tabbrowser.tabs.length == 1) {
+ // resize _before_ move to ensure the window fits the new screen. if
+ // the window is too large for its screen, the window manager may do
+ // automatic repositioning.
+ window.resizeTo(winWidth, winHeight);
+ window.moveTo(left, top);
+ window.focus();
+ } else {
+ this.tabbrowser.replaceTabWithWindow(draggedTab, { screenX: left,
+ screenY: top,
+#ifndef XP_WIN
+ outerWidth: winWidth,
+ outerHeight: winHeight
+#endif
+ });
+ }
+ event.stopPropagation();
+ ]]></handler>
+
+ <handler event="dragexit"><![CDATA[
+ this._dragTime = 0;
+
+ // This does not work at all (see bug 458613)
+ var target = event.relatedTarget;
+ while (target && target != this)
+ target = target.parentNode;
+ if (target)
+ return;
+
+ this._tabDropIndicator.collapsed = true;
+ event.stopPropagation();
+ ]]></handler>
+ </handlers>
+ </binding>
+
+ <!-- close-tab-button binding
+ This binding relies on the structure of the tabbrowser binding.
+ Therefore it should only be used as a child of the tab or the tabs
+ element (in both cases, when they are anonymous nodes of <tabbrowser>).
+ -->
+ <binding id="tabbrowser-close-tab-button"
+ extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-image">
+ <handlers>
+ <handler event="click" button="0"><![CDATA[
+ var bindingParent = document.getBindingParent(this);
+ var tabContainer = bindingParent.parentNode;
+ /* The only sequence in which a second click event (i.e. dblclik)
+ * can be dispatched on an in-tab close button is when it is shown
+ * after the first click (i.e. the first click event was dispatched
+ * on the tab). This happens when we show the close button only on
+ * the active tab. (bug 352021)
+ * The only sequence in which a third click event can be dispatched
+ * on an in-tab close button is when the tab was opened with a
+ * double click on the tabbar. (bug 378344)
+ * In both cases, it is most likely that the close button area has
+ * been accidentally clicked, therefore we do not close the tab.
+ *
+ * We don't want to ignore processing of more than one click event,
+ * though, since the user might actually be repeatedly clicking to
+ * close many tabs at once.
+ */
+ if (event.detail > 1 && !this._ignoredClick) {
+ this._ignoredClick = true;
+ return;
+ }
+
+ // Reset the "ignored click" flag
+ this._ignoredClick = false;
+
+ tabContainer.tabbrowser.removeTab(bindingParent, {animate: true, byMouse: true});
+ tabContainer._blockDblClick = true;
+
+ /* XXXmano hack (see bug 343628):
+ * Since we're removing the event target, if the user
+ * double-clicks this button, the dblclick event will be dispatched
+ * with the tabbar as its event target (and explicit/originalTarget),
+ * which treats that as a mouse gesture for opening a new tab.
+ * In this context, we're manually blocking the dblclick event
+ * (see dblclick handler).
+ */
+ var clickedOnce = false;
+ function enableDblClick(event) {
+ var target = event.originalTarget;
+ if (target.className == 'tab-close-button')
+ target._ignoredClick = true;
+ if (!clickedOnce) {
+ clickedOnce = true;
+ return;
+ }
+ tabContainer._blockDblClick = false;
+ tabContainer.removeEventListener("click", enableDblClick, true);
+ }
+ tabContainer.addEventListener("click", enableDblClick, true);
+ ]]></handler>
+
+ <handler event="dblclick" button="0" phase="capturing">
+ // for the one-close-button case
+ event.stopPropagation();
+ </handler>
+
+ <handler event="dragstart">
+ event.stopPropagation();
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="tabbrowser-tab" display="xul:hbox"
+ extends="chrome://global/content/bindings/tabbox.xml#tab">
+ <resources>
+ <stylesheet src="chrome://browser/content/tabbrowser.css"/>
+ </resources>
+
+ <content context="tabContextMenu" closetabtext="&closeTab.label;">
+ <xul:stack class="tab-stack" flex="1">
+ <xul:hbox xbl:inherits="pinned,selected,titlechanged"
+ class="tab-background">
+ <xul:hbox xbl:inherits="pinned,selected,titlechanged"
+ class="tab-background-start"/>
+ <xul:hbox xbl:inherits="pinned,selected,titlechanged"
+ class="tab-background-middle"/>
+ <xul:hbox xbl:inherits="pinned,selected,titlechanged"
+ class="tab-background-end"/>
+ </xul:hbox>
+ <xul:hbox xbl:inherits="pinned,selected,titlechanged"
+ class="tab-content" align="center">
+ <xul:image xbl:inherits="fadein,pinned,busy,progress,selected"
+ class="tab-throbber"
+ role="presentation"
+ layer="true" />
+ <xul:image xbl:inherits="validate,src=image,fadein,pinned,selected"
+ class="tab-icon-image"
+ role="presentation"
+ anonid="tab-icon"/>
+ <xul:label flex="1"
+ xbl:inherits="value=label,crop,accesskey,fadein,pinned,selected"
+ class="tab-text tab-label"
+ role="presentation"/>
+ <xul:toolbarbutton anonid="close-button"
+ xbl:inherits="fadein,pinned,selected"
+ class="tab-close-button"/>
+ </xul:hbox>
+ </xul:stack>
+ </content>
+
+ <implementation>
+ <property name="pinned" readonly="true">
+ <getter>
+ return this.getAttribute("pinned") == "true";
+ </getter>
+ </property>
+ <property name="hidden" readonly="true">
+ <getter>
+ return this.getAttribute("hidden") == "true";
+ </getter>
+ </property>
+
+ <field name="mOverCloseButton">false</field>
+ <field name="mCorrespondingMenuitem">null</field>
+ <field name="closing">false</field>
+ <field name="lastAccessed">0</field>
+ </implementation>
+
+ <handlers>
+ <handler event="mouseover"><![CDATA[
+ let anonid = event.originalTarget.getAttribute("anonid");
+ if (anonid == "close-button")
+ this.mOverCloseButton = true;
+
+ let tab = event.target;
+ if (tab.closing)
+ return;
+
+ let tabContainer = this.parentNode;
+ let visibleTabs = tabContainer.tabbrowser.visibleTabs;
+ let tabIndex = visibleTabs.indexOf(tab);
+ if (tabIndex == 0) {
+ tabContainer._beforeHoveredTab = null;
+ } else {
+ let candidate = visibleTabs[tabIndex - 1];
+ if (!candidate.selected) {
+ tabContainer._beforeHoveredTab = candidate;
+ candidate.setAttribute("beforehovered", "true");
+ }
+ }
+
+ if (tabIndex == visibleTabs.length - 1) {
+ tabContainer._afterHoveredTab = null;
+ } else {
+ let candidate = visibleTabs[tabIndex + 1];
+ if (!candidate.selected) {
+ tabContainer._afterHoveredTab = candidate;
+ candidate.setAttribute("afterhovered", "true");
+ }
+ }
+ ]]></handler>
+ <handler event="mouseout"><![CDATA[
+ let anonid = event.originalTarget.getAttribute("anonid");
+ if (anonid == "close-button")
+ this.mOverCloseButton = false;
+
+ let tabContainer = this.parentNode;
+ if (tabContainer._beforeHoveredTab) {
+ tabContainer._beforeHoveredTab.removeAttribute("beforehovered");
+ tabContainer._beforeHoveredTab = null;
+ }
+ if (tabContainer._afterHoveredTab) {
+ tabContainer._afterHoveredTab.removeAttribute("afterhovered");
+ tabContainer._afterHoveredTab = null;
+ }
+ ]]></handler>
+ <handler event="dragstart" phase="capturing">
+ this.style.MozUserFocus = '';
+ </handler>
+ <handler event="mousedown" phase="capturing">
+ <![CDATA[
+ if (this.selected) {
+ this.style.MozUserFocus = 'ignore';
+ this.clientTop; // just using this to flush style updates
+ } else if (this.mOverCloseButton) {
+ // Prevent tabbox.xml from selecting the tab.
+ event.stopPropagation();
+ }
+ ]]>
+ </handler>
+ <handler event="mouseup">
+ this.style.MozUserFocus = '';
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="tabbrowser-alltabs-popup"
+ extends="chrome://global/content/bindings/popup.xml#popup">
+ <implementation implements="nsIDOMEventListener">
+ <method name="_tabOnAttrModified">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ var tab = aEvent.target;
+ if (tab.mCorrespondingMenuitem)
+ this._setMenuitemAttributes(tab.mCorrespondingMenuitem, tab);
+ ]]></body>
+ </method>
+
+ <method name="_tabOnTabClose">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ var tab = aEvent.target;
+ if (tab.mCorrespondingMenuitem)
+ this.removeChild(tab.mCorrespondingMenuitem);
+ ]]></body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ switch (aEvent.type) {
+ case "TabAttrModified":
+ this._tabOnAttrModified(aEvent);
+ break;
+ case "TabClose":
+ this._tabOnTabClose(aEvent);
+ break;
+ case "scroll":
+ this._updateTabsVisibilityStatus();
+ break;
+ }
+ ]]></body>
+ </method>
+
+ <method name="_updateTabsVisibilityStatus">
+ <body><![CDATA[
+ var tabContainer = gBrowser.tabContainer;
+ // We don't want menu item decoration unless there is overflow.
+ if (tabContainer.getAttribute("overflow") != "true")
+ return;
+
+ var tabstripBO = tabContainer.mTabstrip.scrollBoxObject;
+ for (var i = 0; i < this.childNodes.length; i++) {
+ let curTab = this.childNodes[i].tab;
+ let curTabBO = curTab.boxObject;
+ if (curTabBO.screenX >= tabstripBO.screenX &&
+ curTabBO.screenX + curTabBO.width <= tabstripBO.screenX + tabstripBO.width)
+ this.childNodes[i].setAttribute("tabIsVisible", "true");
+ else
+ this.childNodes[i].removeAttribute("tabIsVisible");
+ }
+ ]]></body>
+ </method>
+
+ <method name="_createTabMenuItem">
+ <parameter name="aTab"/>
+ <body><![CDATA[
+ var menuItem = document.createElementNS(
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+ "menuitem");
+
+ menuItem.setAttribute("class", "menuitem-iconic alltabs-item menuitem-with-favicon");
+
+ this._setMenuitemAttributes(menuItem, aTab);
+
+ if (!aTab.mCorrespondingMenuitem) {
+ aTab.mCorrespondingMenuitem = menuItem;
+ menuItem.tab = aTab;
+
+ this.appendChild(menuItem);
+ }
+ ]]></body>
+ </method>
+
+ <method name="_setMenuitemAttributes">
+ <parameter name="aMenuitem"/>
+ <parameter name="aTab"/>
+ <body><![CDATA[
+ aMenuitem.setAttribute("label", aTab.label);
+ aMenuitem.setAttribute("crop", aTab.getAttribute("crop"));
+
+ if (aTab.hasAttribute("busy")) {
+ aMenuitem.setAttribute("busy", aTab.getAttribute("busy"));
+ aMenuitem.removeAttribute("image");
+ } else {
+ aMenuitem.setAttribute("image", aTab.getAttribute("image"));
+ aMenuitem.removeAttribute("busy");
+ }
+
+ if (aTab.hasAttribute("pending"))
+ aMenuitem.setAttribute("pending", aTab.getAttribute("pending"));
+ else
+ aMenuitem.removeAttribute("pending");
+
+ if (aTab.selected)
+ aMenuitem.setAttribute("selected", "true");
+ else
+ aMenuitem.removeAttribute("selected");
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="popupshowing">
+ <![CDATA[
+ var tabcontainer = gBrowser.tabContainer;
+
+ // Listen for changes in the tab bar.
+ tabcontainer.addEventListener("TabAttrModified", this, false);
+ tabcontainer.addEventListener("TabClose", this, false);
+ tabcontainer.mTabstrip.addEventListener("scroll", this, false);
+
+ let tabs = gBrowser.visibleTabs;
+ for (var i = 0; i < tabs.length; i++) {
+ if (!tabs[i].pinned)
+ this._createTabMenuItem(tabs[i]);
+ }
+ this._updateTabsVisibilityStatus();
+ ]]></handler>
+
+ <handler event="popuphidden">
+ <![CDATA[
+ // clear out the menu popup and remove the listeners
+ for (let i = this.childNodes.length - 1; i >= 0; i--) {
+ let menuItem = this.childNodes[i];
+ if (menuItem.tab) {
+ menuItem.tab.mCorrespondingMenuitem = null;
+ this.removeChild(menuItem);
+ }
+ }
+ var tabcontainer = gBrowser.tabContainer;
+ tabcontainer.mTabstrip.removeEventListener("scroll", this, false);
+ tabcontainer.removeEventListener("TabAttrModified", this, false);
+ tabcontainer.removeEventListener("TabClose", this, false);
+ ]]></handler>
+
+ <handler event="DOMMenuItemActive">
+ <![CDATA[
+ var tab = event.target.tab;
+ if (tab) {
+ let overLink = tab.linkedBrowser.currentURI.spec;
+ if (overLink == "about:blank")
+ overLink = "";
+ XULBrowserWindow.setOverLink(overLink, null);
+ }
+ ]]></handler>
+
+ <handler event="DOMMenuItemInactive">
+ <![CDATA[
+ XULBrowserWindow.setOverLink("", null);
+ ]]></handler>
+
+ <handler event="command"><![CDATA[
+ if (event.target.tab)
+ gBrowser.selectedTab = event.target.tab;
+ ]]></handler>
+
+ </handlers>
+ </binding>
+
+ <binding id="statuspanel" display="xul:hbox">
+ <content>
+ <xul:hbox class="statuspanel-inner">
+ <xul:label class="statuspanel-label"
+ role="status"
+ aria-live="off"
+ xbl:inherits="value=label,crop,mirror"
+ flex="1"
+ crop="end"/>
+ </xul:hbox>
+ </content>
+
+ <implementation implements="nsIDOMEventListener">
+ <constructor><![CDATA[
+ window.addEventListener("resize", this, false);
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ window.removeEventListener("resize", this, false);
+ MousePosTracker.removeListener(this);
+ ]]></destructor>
+
+ <property name="label">
+ <setter><![CDATA[
+ if (!this.label) {
+ this.removeAttribute("mirror");
+ this.removeAttribute("sizelimit");
+ }
+
+ this.style.minWidth = this.getAttribute("type") == "status" &&
+ this.getAttribute("previoustype") == "status"
+ ? getComputedStyle(this).width : "";
+
+ if (val) {
+ this.setAttribute("label", val);
+ this.removeAttribute("inactive");
+ this._calcMouseTargetRect();
+ MousePosTracker.addListener(this);
+ } else {
+ this.setAttribute("inactive", "true");
+ MousePosTracker.removeListener(this);
+ }
+
+ return val;
+ ]]></setter>
+ <getter>
+ return this.hasAttribute("inactive") ? "" : this.getAttribute("label");
+ </getter>
+ </property>
+
+ <method name="getMouseTargetRect">
+ <body><![CDATA[
+ return this._mouseTargetRect;
+ ]]></body>
+ </method>
+
+ <method name="onMouseEnter">
+ <body>
+ this._mirror();
+ </body>
+ </method>
+
+ <method name="onMouseLeave">
+ <body>
+ this._mirror();
+ </body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="event"/>
+ <body><![CDATA[
+ if (!this.label)
+ return;
+
+ switch (event.type) {
+ case "resize":
+ this._calcMouseTargetRect();
+ break;
+ }
+ ]]></body>
+ </method>
+
+ <method name="_calcMouseTargetRect">
+ <body><![CDATA[
+ let alignRight = false;
+
+ if (getComputedStyle(document.documentElement).direction == "rtl")
+ alignRight = !alignRight;
+
+ let rect = this.getBoundingClientRect();
+ this._mouseTargetRect = {
+ top: rect.top,
+ bottom: rect.bottom,
+ left: alignRight ? window.innerWidth - rect.width : 0,
+ right: alignRight ? window.innerWidth : rect.width
+ };
+ ]]></body>
+ </method>
+
+ <method name="_mirror">
+ <body>
+ if (this.hasAttribute("mirror"))
+ this.removeAttribute("mirror");
+ else
+ this.setAttribute("mirror", "true");
+
+ if (!this.hasAttribute("sizelimit")) {
+ this.setAttribute("sizelimit", "true");
+ this._calcMouseTargetRect();
+ }
+ </body>
+ </method>
+ </implementation>
+ </binding>
+
+</bindings>
diff --git a/browser/base/content/test/Makefile.in b/browser/base/content/test/Makefile.in
new file mode 100644
index 000000000..104ad5972
--- /dev/null
+++ b/browser/base/content/test/Makefile.in
@@ -0,0 +1,372 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEPTH = @DEPTH@
+topsrcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+relativesrcdir = @relativesrcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MOCHITEST_FILES = \
+ head_plain.js \
+ test_feed_discovery.html \
+ feed_discovery.html \
+ test_bug395533.html \
+ bug395533-data.txt \
+ ctxmenu-image.png \
+ video.ogg \
+ test_offlineNotification.html \
+ offlineChild.html \
+ offlineChild.cacheManifest \
+ offlineChild.cacheManifest^headers^ \
+ offlineChild2.html \
+ offlineChild2.cacheManifest \
+ offlineChild2.cacheManifest^headers^ \
+ offlineEvent.html \
+ offlineEvent.cacheManifest \
+ offlineEvent.cacheManifest^headers^ \
+ test_bug364677.html \
+ bug364677-data.xml \
+ bug364677-data.xml^headers^ \
+ test_offline_gzip.html \
+ gZipOfflineChild.html \
+ gZipOfflineChild.html^headers^ \
+ gZipOfflineChild.cacheManifest \
+ gZipOfflineChild.cacheManifest^headers^ \
+ $(NULL)
+
+# test_contextmenu.html is disabled on Linux due to bug 513558
+ifndef MOZ_WIDGET_GTK
+MOCHITEST_FILES += \
+ audio.ogg \
+ test_contextmenu.html \
+ subtst_contextmenu.html \
+ privateBrowsingMode.js \
+ $(NULL)
+endif
+
+# The following tests are disabled because they are unreliable:
+# browser_bug423833.js is bug 428712
+# browser_sanitize-download-history.js is bug 432425
+#
+# browser_sanitizeDialog_treeView.js is disabled until the tree view is added
+# back to the clear recent history dialog (sanitize.xul), if it ever is (bug
+# 480169)
+
+# browser_drag.js is disabled, as it needs to be updated for the new behavior from bug 320638.
+
+# browser_bug321000.js is disabled because newline handling is shaky (bug 592528)
+
+MOCHITEST_BROWSER_FILES = \
+ head.js \
+ browser_typeAheadFind.js \
+ browser_keywordSearch.js \
+ browser_keywordSearch_postData.js \
+ POSTSearchEngine.xml \
+ print_postdata.sjs \
+ browser_aboutHome.js \
+ browser_alltabslistener.js \
+ browser_bug304198.js \
+ title_test.svg \
+ browser_bug329212.js \
+ browser_bug356571.js \
+ browser_bug380960.js \
+ browser_bug386835.js \
+ browser_bug405137.js \
+ browser_bug406216.js \
+ browser_bug409481.js \
+ browser_bug409624.js \
+ browser_bug413915.js \
+ browser_bug416661.js \
+ browser_bug417483.js \
+ browser_bug419612.js \
+ browser_identity_UI.js \
+ browser_bug422590.js \
+ browser_bug424101.js \
+ browser_bug427559.js \
+ browser_bug432599.js \
+ browser_bug435035.js \
+ browser_bug435325.js \
+ browser_bug441778.js \
+ browser_bug455852.js \
+ browser_bug460146.js \
+ browser_bug462673.js \
+ browser_bug477014.js \
+ browser_bug479408.js \
+ browser_bug479408_sample.html \
+ browser_bug481560.js \
+ browser_bug484315.js \
+ browser_bug491431.js \
+ browser_bug495058.js \
+ browser_bug517902.js \
+ browser_bug519216.js \
+ browser_bug520538.js \
+ browser_bug521216.js \
+ browser_bug533232.js \
+ browser_bug537474.js \
+ browser_bug550565.js \
+ browser_bug553455.js \
+ browser_bug555224.js \
+ browser_bug555767.js \
+ browser_bug556061.js \
+ browser_bug559991.js \
+ browser_bug561623.js \
+ browser_bug561636.js \
+ browser_bug562649.js \
+ browser_bug563588.js \
+ browser_bug565575.js \
+ browser_bug567306.js \
+ browser_zbug569342.js \
+ browser_bug575561.js \
+ browser_bug575830.js \
+ browser_bug577121.js \
+ browser_bug578534.js \
+ browser_bug579872.js \
+ browser_bug580638.js \
+ browser_bug580956.js \
+ browser_bug581242.js \
+ browser_bug581253.js \
+ browser_bug581947.js \
+ browser_bug585558.js \
+ browser_bug585785.js \
+ browser_bug585830.js \
+ browser_bug590206.js \
+ browser_bug592338.js \
+ browser_bug594131.js \
+ browser_bug595507.js \
+ browser_bug596687.js \
+ browser_bug597218.js \
+ browser_bug598923.js \
+ browser_bug599325.js \
+ browser_bug609700.js \
+ browser_bug616836.js \
+ browser_bug623155.js \
+ browser_bug623893.js \
+ browser_bug624734.js \
+ browser_bug647886.js \
+ browser_bug655584.js \
+ browser_bug664672.js \
+ browser_bug678392.js \
+ browser_bug678392-1.html \
+ browser_bug678392-2.html \
+ browser_bug710878.js \
+ browser_bug719271.js \
+ browser_bug724239.js \
+ browser_bug735471.js \
+ browser_bug743421.js \
+ browser_bug749738.js \
+ browser_bug752516.js \
+ browser_bug763468_perwindowpb.js \
+ browser_bug767836_perwindowpb.js \
+ browser_bug771331.js \
+ browser_bug783614.js \
+ browser_bug797677.js \
+ browser_bug816527.js \
+ browser_bug817947.js \
+ browser_bug822367.js \
+ browser_bug902156.js \
+ browser_bug832435.js \
+ browser_bug839103.js \
+ browser_bug880101.js \
+ browser_canonizeURL.js \
+ browser_customize.js \
+ browser_findbarClose.js \
+ browser_homeDrop.js \
+ browser_keywordBookmarklets.js \
+ browser_contextSearchTabPosition.js \
+ browser_ctrlTab.js \
+ browser_customize_popupNotification.js \
+ browser_disablechrome.js \
+ browser_discovery.js \
+ browser_duplicateIDs.js \
+ browser_fullscreen-window-open.js \
+ file_fullscreen-window-open.html \
+ browser_gestureSupport.js \
+ browser_getshortcutoruri.js \
+ browser_hide_removing.js \
+ browser_overflowScroll.js \
+ browser_locationBarCommand.js \
+ browser_locationBarExternalLoad.js \
+ browser_page_style_menu.js \
+ browser_pinnedTabs.js \
+ browser_plainTextLinks.js \
+ browser_pluginnotification.js \
+ browser_plugins_added_dynamically.js \
+ browser_CTP_drag_drop.js \
+ browser_CTP_data_urls.js \
+ browser_pluginplaypreview.js \
+ browser_pluginplaypreview2.js \
+ browser_private_browsing_window.js \
+ browser_relatedTabs.js \
+ browser_removeTabsToTheEnd.js \
+ browser_sanitize-passwordDisabledHosts.js \
+ browser_sanitize-sitepermissions.js \
+ browser_sanitize-timespans.js \
+ browser_tabopen_reflows.js \
+ browser_clearplugindata.js \
+ browser_clearplugindata.html \
+ browser_clearplugindata_noage.html \
+ browser_popupUI.js \
+ browser_sanitizeDialog.js \
+ browser_save_link-perwindowpb.js \
+ browser_save_private_link_perwindowpb.js \
+ browser_save_video.js \
+ browser_tabMatchesInAwesomebar_perwindowpb.js \
+ browser_tab_drag_drop_perwindow.js \
+ bug564387.html \
+ bug564387_video1.ogv \
+ bug564387_video1.ogv^headers^ \
+ bug792517.html \
+ bug792517-2.html \
+ bug792517.sjs \
+ test_bug839103.html \
+ bug839103.css \
+ browser_scope.js \
+ browser_selectTabAtIndex.js \
+ browser_tab_dragdrop.js \
+ browser_tab_dragdrop2.js \
+ browser_tab_dragdrop2_frame1.xul \
+ browser_tabfocus.js \
+ browser_tabs_isActive.js \
+ browser_tabs_owner.js \
+ browser_unloaddialogs.js \
+ browser_urlbarAutoFillTrimURLs.js \
+ browser_urlbarCopying.js \
+ browser_urlbarEnter.js \
+ browser_urlbarRevert.js \
+ browser_urlbarStop.js \
+ browser_urlbarTrimURLs.js \
+ browser_urlbar_search_healthreport.js \
+ browser_urlHighlight.js \
+ browser_visibleFindSelection.js \
+ browser_visibleTabs.js \
+ browser_visibleTabs_contextMenu.js \
+ browser_visibleTabs_bookmarkAllPages.js \
+ browser_visibleTabs_bookmarkAllTabs.js \
+ browser_visibleTabs_tabPreview.js \
+ bug592338.html \
+ disablechrome.html \
+ discovery.html \
+ domplate_test.js \
+ file_bug822367_1.html \
+ file_bug822367_1.js \
+ file_bug822367_2.html \
+ file_bug822367_3.html \
+ file_bug822367_4.html \
+ file_bug822367_4.js \
+ file_bug822367_4B.html \
+ file_bug822367_5.html \
+ file_bug822367_6.html \
+ file_bug902156_1.html \
+ file_bug902156_2.html \
+ file_bug902156_3.html \
+ file_bug902156.js \
+ moz.png \
+ video.ogg \
+ test_bug435035.html \
+ test_bug462673.html \
+ page_style_sample.html \
+ plugin_unknown.html \
+ plugin_test.html \
+ plugin_test2.html \
+ plugin_test3.html \
+ plugin_alternate_content.html \
+ plugin_both.html \
+ plugin_both2.html \
+ plugin_add_dynamically.html \
+ plugin_clickToPlayAllow.html \
+ plugin_clickToPlayDeny.html \
+ plugin_bug744745.html \
+ plugin_bug749455.html \
+ plugin_bug752516.html \
+ plugin_bug787619.html \
+ plugin_bug797677.html \
+ plugin_bug820497.html \
+ plugin_hidden_to_visible.html \
+ plugin_two_types.html \
+ plugin_data_url.html \
+ alltabslistener.html \
+ zoom_test.html \
+ dummy_page.html \
+ file_bug550565_popup.html \
+ file_bug550565_favicon.ico \
+ app_bug575561.html \
+ app_subframe_bug575561.html \
+ browser_contentAreaClick.js \
+ browser_addon_bar_close_button.js \
+ browser_addon_bar_shortcut.js \
+ browser_addon_bar_aomlistener.js \
+ test_bug628179.html \
+ browser_wyciwyg_urlbarCopying.js \
+ test_wyciwyg_copying.html \
+ authenticate.sjs \
+ browser_minimize.js \
+ browser_aboutSyncProgress.js \
+ browser_middleMouse_inherit.js \
+ redirect_bug623155.sjs \
+ browser_tabDrop.js \
+ browser_lastAccessedTab.js \
+ browser_bug734076.js \
+ browser_bug744745.js \
+ browser_bug787619.js \
+ browser_bug812562.js \
+ browser_bug818118.js \
+ browser_bug820497.js \
+ blockPluginVulnerableUpdatable.xml \
+ blockPluginVulnerableNoUpdate.xml \
+ blockNoPlugins.xml \
+ blockPluginHard.xml \
+ browser_utilityOverlay.js \
+ browser_bug676619.js \
+ download_page.html \
+ browser_URLBarSetURI.js \
+ browser_pageInfo_plugins.js \
+ browser_pageInfo.js \
+ feed_tab.html \
+ browser_pluginCrashCommentAndURL.js \
+ pluginCrashCommentAndURL.html \
+ browser_private_no_prompt.js \
+ browser_blob-channelname.js \
+ healthreport_testRemoteCommands.html \
+ browser_offlineQuotaNotification.js \
+ offlineQuotaNotification.html \
+ offlineQuotaNotification.cacheManifest \
+ $(NULL)
+
+# Disable tests on Windows due to frequent failures (bugs 825739, 841341)
+ifneq (windows,$(MOZ_WIDGET_TOOLKIT))
+MOCHITEST_BROWSER_FILES += \
+ browser_bookmark_titles.js \
+ browser_popupNotification.js \
+ $(NULL)
+endif
+
+ifneq (cocoa,$(MOZ_WIDGET_TOOLKIT))
+MOCHITEST_BROWSER_FILES += \
+ browser_bug462289.js \
+ $(NULL)
+else
+MOCHITEST_BROWSER_FILES += \
+ browser_bug565667.js \
+ $(NULL)
+endif
+
+ifdef MOZ_DATA_REPORTING
+MOCHITEST_BROWSER_FILES += \
+ browser_datareporting_notification.js \
+ $(NULL)
+endif
+
+# browser_aboutHealthReport.js disabled for frequent failures on Linux (bug 924307)
+# browser_CTP_context_menu.js fails intermittently on Linux (bug 909342)
+ifndef MOZ_WIDGET_GTK
+MOCHITEST_BROWSER_FILES += \
+ browser_aboutHealthReport.js \
+ browser_CTP_context_menu.js \
+ $(NULL)
+endif
+
+include $(topsrcdir)/config/rules.mk
diff --git a/browser/base/content/test/POSTSearchEngine.xml b/browser/base/content/test/POSTSearchEngine.xml
new file mode 100644
index 000000000..85557d854
--- /dev/null
+++ b/browser/base/content/test/POSTSearchEngine.xml
@@ -0,0 +1,6 @@
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
+ <ShortName>POST Search</ShortName>
+ <Url type="text/html" method="POST" template="http://mochi.test:8888/browser/browser/base/content/test/print_postdata.sjs">
+ <Param name="searchterms" value="{searchTerms}"/>
+ </Url>
+</OpenSearchDescription>
diff --git a/browser/base/content/test/alltabslistener.html b/browser/base/content/test/alltabslistener.html
new file mode 100644
index 000000000..166c31037
--- /dev/null
+++ b/browser/base/content/test/alltabslistener.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+<title>Test page for bug 463387</title>
+</head>
+<body>
+<p>Test page for bug 463387</p>
+</body>
+</html>
diff --git a/browser/base/content/test/app_bug575561.html b/browser/base/content/test/app_bug575561.html
new file mode 100644
index 000000000..00f2e5488
--- /dev/null
+++ b/browser/base/content/test/app_bug575561.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=575561
+-->
+ <head>
+ <title>Test for links in app tabs</title>
+ </head>
+ <body>
+ <a href="http://example.com/browser/browser/base/content/test/dummy_page.html">same domain</a>
+ <a href="http://test1.example.com/browser/browser/base/content/test/dummy_page.html">same domain (different subdomain)</a>
+ <a href="http://example.org/browser/browser/base/content/test/dummy_page.html">different domain</a>
+ <a href="http://example.org/browser/browser/base/content/test/dummy_page.html" target="foo">different domain (with target)</a>
+ <a href="http://www.example.com/browser/browser/base/content/test/dummy_page.html">same domain (www prefix)</a>
+ <a href="data:text/html,<!DOCTYPE html><html><body>Another Page</body></html>">data: URI</a>
+ <a href="about:mozilla">about: URI</a>
+ <iframe src="app_subframe_bug575561.html"></iframe>
+ </body>
+</html>
diff --git a/browser/base/content/test/app_subframe_bug575561.html b/browser/base/content/test/app_subframe_bug575561.html
new file mode 100644
index 000000000..754f3806e
--- /dev/null
+++ b/browser/base/content/test/app_subframe_bug575561.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=575561
+-->
+ <head>
+ <title>Test for links in app tab subframes</title>
+ </head>
+ <body>
+ <a href="http://example.org/browser/browser/base/content/test/dummy_page.html">different domain</a>
+ </body>
+</html>
diff --git a/browser/base/content/test/audio.ogg b/browser/base/content/test/audio.ogg
new file mode 100644
index 000000000..7e6ef77ec
--- /dev/null
+++ b/browser/base/content/test/audio.ogg
Binary files differ
diff --git a/browser/base/content/test/authenticate.sjs b/browser/base/content/test/authenticate.sjs
new file mode 100644
index 000000000..58da655cf
--- /dev/null
+++ b/browser/base/content/test/authenticate.sjs
@@ -0,0 +1,220 @@
+function handleRequest(request, response)
+{
+ try {
+ reallyHandleRequest(request, response);
+ } catch (e) {
+ response.setStatusLine("1.0", 200, "AlmostOK");
+ response.write("Error handling request: " + e);
+ }
+}
+
+
+function reallyHandleRequest(request, response) {
+ var match;
+ var requestAuth = true, requestProxyAuth = true;
+
+ // Allow the caller to drive how authentication is processed via the query.
+ // Eg, http://localhost:8888/authenticate.sjs?user=foo&realm=bar
+ // The extra ? allows the user/pass/realm checks to succeed if the name is
+ // at the beginning of the query string.
+ var query = "?" + request.queryString;
+
+ var expected_user = "", expected_pass = "", realm = "mochitest";
+ var proxy_expected_user = "", proxy_expected_pass = "", proxy_realm = "mochi-proxy";
+ var huge = false, plugin = false, anonymous = false;
+ var authHeaderCount = 1;
+ // user=xxx
+ match = /[^_]user=([^&]*)/.exec(query);
+ if (match)
+ expected_user = match[1];
+
+ // pass=xxx
+ match = /[^_]pass=([^&]*)/.exec(query);
+ if (match)
+ expected_pass = match[1];
+
+ // realm=xxx
+ match = /[^_]realm=([^&]*)/.exec(query);
+ if (match)
+ realm = match[1];
+
+ // proxy_user=xxx
+ match = /proxy_user=([^&]*)/.exec(query);
+ if (match)
+ proxy_expected_user = match[1];
+
+ // proxy_pass=xxx
+ match = /proxy_pass=([^&]*)/.exec(query);
+ if (match)
+ proxy_expected_pass = match[1];
+
+ // proxy_realm=xxx
+ match = /proxy_realm=([^&]*)/.exec(query);
+ if (match)
+ proxy_realm = match[1];
+
+ // huge=1
+ match = /huge=1/.exec(query);
+ if (match)
+ huge = true;
+
+ // plugin=1
+ match = /plugin=1/.exec(query);
+ if (match)
+ plugin = true;
+
+ // multiple=1
+ match = /multiple=([^&]*)/.exec(query);
+ if (match)
+ authHeaderCount = match[1]+0;
+
+ // anonymous=1
+ match = /anonymous=1/.exec(query);
+ if (match)
+ anonymous = true;
+
+ // Look for an authentication header, if any, in the request.
+ //
+ // EG: Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
+ //
+ // This test only supports Basic auth. The value sent by the client is
+ // "username:password", obscured with base64 encoding.
+
+ var actual_user = "", actual_pass = "", authHeader, authPresent = false;
+ if (request.hasHeader("Authorization")) {
+ authPresent = true;
+ authHeader = request.getHeader("Authorization");
+ match = /Basic (.+)/.exec(authHeader);
+ if (match.length != 2)
+ throw "Couldn't parse auth header: " + authHeader;
+
+ var userpass = base64ToString(match[1]); // no atob() :-(
+ match = /(.*):(.*)/.exec(userpass);
+ if (match.length != 3)
+ throw "Couldn't decode auth header: " + userpass;
+ actual_user = match[1];
+ actual_pass = match[2];
+ }
+
+ var proxy_actual_user = "", proxy_actual_pass = "";
+ if (request.hasHeader("Proxy-Authorization")) {
+ authHeader = request.getHeader("Proxy-Authorization");
+ match = /Basic (.+)/.exec(authHeader);
+ if (match.length != 2)
+ throw "Couldn't parse auth header: " + authHeader;
+
+ var userpass = base64ToString(match[1]); // no atob() :-(
+ match = /(.*):(.*)/.exec(userpass);
+ if (match.length != 3)
+ throw "Couldn't decode auth header: " + userpass;
+ proxy_actual_user = match[1];
+ proxy_actual_pass = match[2];
+ }
+
+ // Don't request authentication if the credentials we got were what we
+ // expected.
+ if (expected_user == actual_user &&
+ expected_pass == actual_pass) {
+ requestAuth = false;
+ }
+ if (proxy_expected_user == proxy_actual_user &&
+ proxy_expected_pass == proxy_actual_pass) {
+ requestProxyAuth = false;
+ }
+
+ if (anonymous) {
+ if (authPresent) {
+ response.setStatusLine("1.0", 400, "Unexpected authorization header found");
+ } else {
+ response.setStatusLine("1.0", 200, "Authorization header not found");
+ }
+ } else {
+ if (requestProxyAuth) {
+ response.setStatusLine("1.0", 407, "Proxy authentication required");
+ for (i = 0; i < authHeaderCount; ++i)
+ response.setHeader("Proxy-Authenticate", "basic realm=\"" + proxy_realm + "\"", true);
+ } else if (requestAuth) {
+ response.setStatusLine("1.0", 401, "Authentication required");
+ for (i = 0; i < authHeaderCount; ++i)
+ response.setHeader("WWW-Authenticate", "basic realm=\"" + realm + "\"", true);
+ } else {
+ response.setStatusLine("1.0", 200, "OK");
+ }
+ }
+
+ response.setHeader("Content-Type", "application/xhtml+xml", false);
+ response.write("<html xmlns='http://www.w3.org/1999/xhtml'>");
+ response.write("<p>Login: <span id='ok'>" + (requestAuth ? "FAIL" : "PASS") + "</span></p>\n");
+ response.write("<p>Proxy: <span id='proxy'>" + (requestProxyAuth ? "FAIL" : "PASS") + "</span></p>\n");
+ response.write("<p>Auth: <span id='auth'>" + authHeader + "</span></p>\n");
+ response.write("<p>User: <span id='user'>" + actual_user + "</span></p>\n");
+ response.write("<p>Pass: <span id='pass'>" + actual_pass + "</span></p>\n");
+
+ if (huge) {
+ response.write("<div style='display: none'>");
+ for (i = 0; i < 100000; i++) {
+ response.write("123456789\n");
+ }
+ response.write("</div>");
+ response.write("<span id='footnote'>This is a footnote after the huge content fill</span>");
+ }
+
+ if (plugin) {
+ response.write("<embed id='embedtest' style='width: 400px; height: 100px;' " +
+ "type='application/x-test'></embed>\n");
+ }
+
+ response.write("</html>");
+}
+
+
+// base64 decoder
+//
+// Yoinked from extensions/xml-rpc/src/nsXmlRpcClient.js because btoa()
+// doesn't seem to exist. :-(
+/* Convert Base64 data to a string */
+const toBinaryTable = [
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
+ 52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1, 0,-1,-1,
+ -1, 0, 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,-1, -1,-1,-1,-1,
+ -1,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,-1, -1,-1,-1,-1
+];
+const base64Pad = '=';
+
+function base64ToString(data) {
+
+ var result = '';
+ var leftbits = 0; // number of bits decoded, but yet to be appended
+ var leftdata = 0; // bits decoded, but yet to be appended
+
+ // Convert one by one.
+ for (var i = 0; i < data.length; i++) {
+ var c = toBinaryTable[data.charCodeAt(i) & 0x7f];
+ var padding = (data[i] == base64Pad);
+ // Skip illegal characters and whitespace
+ if (c == -1) continue;
+
+ // Collect data into leftdata, update bitcount
+ leftdata = (leftdata << 6) | c;
+ leftbits += 6;
+
+ // If we have 8 or more bits, append 8 bits to the result
+ if (leftbits >= 8) {
+ leftbits -= 8;
+ // Append if not padding.
+ if (!padding)
+ result += String.fromCharCode((leftdata >> leftbits) & 0xff);
+ leftdata &= (1 << leftbits) - 1;
+ }
+ }
+
+ // If there are any bits left, the base64 string was corrupted
+ if (leftbits)
+ throw Components.Exception('Corrupted base64 string');
+
+ return result;
+}
diff --git a/browser/base/content/test/blockNoPlugins.xml b/browser/base/content/test/blockNoPlugins.xml
new file mode 100644
index 000000000..e4e191b37
--- /dev/null
+++ b/browser/base/content/test/blockNoPlugins.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0"?>
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1336406310001">
+ <emItems>
+ </emItems>
+ <pluginItems>
+ </pluginItems>
+</blocklist>
diff --git a/browser/base/content/test/blockPluginHard.xml b/browser/base/content/test/blockPluginHard.xml
new file mode 100644
index 000000000..24eb5bc6f
--- /dev/null
+++ b/browser/base/content/test/blockPluginHard.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1336406310000">
+ <emItems>
+ </emItems>
+ <pluginItems>
+ <pluginItem blockID="p9999">
+ <match name="filename" exp="libnptest\.so|nptest\.dll|Test\.plugin" />
+ <versionRange severity="2"></versionRange>
+ </pluginItem>
+ </pluginItems>
+</blocklist>
diff --git a/browser/base/content/test/blockPluginVulnerableNoUpdate.xml b/browser/base/content/test/blockPluginVulnerableNoUpdate.xml
new file mode 100644
index 000000000..bf8545afe
--- /dev/null
+++ b/browser/base/content/test/blockPluginVulnerableNoUpdate.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1336406310000">
+ <emItems>
+ </emItems>
+ <pluginItems>
+ <pluginItem blockID="p9999">
+ <match name="filename" exp="libnptest\.so|nptest\.dll|Test\.plugin" />
+ <versionRange severity="0" vulnerabilitystatus="2"></versionRange>
+ </pluginItem>
+ </pluginItems>
+</blocklist>
diff --git a/browser/base/content/test/blockPluginVulnerableUpdatable.xml b/browser/base/content/test/blockPluginVulnerableUpdatable.xml
new file mode 100644
index 000000000..5545162b1
--- /dev/null
+++ b/browser/base/content/test/blockPluginVulnerableUpdatable.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0"?>
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1336406310000">
+ <emItems>
+ </emItems>
+ <pluginItems>
+ <pluginItem blockID="p9999">
+ <match name="filename" exp="libnptest\.so|nptest\.dll|Test\.plugin" />
+ <versionRange severity="0" vulnerabilitystatus="1"></versionRange>
+ </pluginItem>
+ </pluginItems>
+</blocklist>
diff --git a/browser/base/content/test/browser_CTP_context_menu.js b/browser/base/content/test/browser_CTP_context_menu.js
new file mode 100644
index 000000000..3dcff2b8b
--- /dev/null
+++ b/browser/base/content/test/browser_CTP_context_menu.js
@@ -0,0 +1,103 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir;
+const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+
+var gTestBrowser = null;
+var gNextTest = null;
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+function test() {
+ waitForExplicitFinish();
+ registerCleanupFunction(function() {
+ clearAllPluginPermissions();
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ });
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+ let newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+ gTestBrowser.addEventListener("load", pageLoad, true);
+
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+
+ prepareTest(runAfterPluginBindingAttached(test1), gHttpTestRoot + "plugin_test.html");
+}
+
+function finishTest() {
+ clearAllPluginPermissions();
+ gTestBrowser.removeEventListener("load", pageLoad, true);
+ gBrowser.removeCurrentTab();
+ window.focus();
+ finish();
+}
+
+function pageLoad() {
+ // The plugin events are async dispatched and can come after the load event
+ // This just allows the events to fire before we then go on to test the states
+ executeSoon(gNextTest);
+}
+
+function prepareTest(nextTest, url) {
+ gNextTest = nextTest;
+ gTestBrowser.contentWindow.location = url;
+}
+
+// Due to layout being async, "PluginBindAttached" may trigger later.
+// This wraps a function to force a layout flush, thus triggering it,
+// and schedules the function execution so they're definitely executed
+// afterwards.
+function runAfterPluginBindingAttached(func) {
+ return function() {
+ let doc = gTestBrowser.contentDocument;
+ let elems = doc.getElementsByTagName('embed');
+ if (elems.length < 1) {
+ elems = doc.getElementsByTagName('object');
+ }
+ elems[0].clientTop;
+ executeSoon(func);
+ };
+}
+
+// Test that the activate action in content menus for CTP plugins works
+function test1() {
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "Test 1, Should have a click-to-play notification");
+
+ let plugin = gTestBrowser.contentDocument.getElementById("test");
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!objLoadingContent.activated, "Test 1, Plugin should not be activated");
+
+ window.document.addEventListener("popupshown", test2, false);
+ EventUtils.synthesizeMouseAtCenter(plugin,
+ { type: "contextmenu", button: 2 },
+ gTestBrowser.contentWindow);
+}
+
+function test2() {
+ window.document.removeEventListener("popupshown", test2, false);
+ let activate = window.document.getElementById("context-ctp-play");
+ ok(activate, "Test 2, Should have a context menu entry for activating the plugin");
+
+ // Trigger the click-to-play popup
+ activate.doCommand();
+
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 2, Should have a click-to-play notification");
+ ok(!notification.dismissed, "Test 2, The click-to-play notification should not be dismissed");
+
+ // Activate the plugin
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ let plugin = gTestBrowser.contentDocument.getElementById("test");
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ waitForCondition(() => objLoadingContent.activated, test3, "Waited too long for plugin to activate");
+}
+
+function test3() {
+ finishTest();
+}
diff --git a/browser/base/content/test/browser_CTP_data_urls.js b/browser/base/content/test/browser_CTP_data_urls.js
new file mode 100644
index 000000000..188f488ba
--- /dev/null
+++ b/browser/base/content/test/browser_CTP_data_urls.js
@@ -0,0 +1,256 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir;
+const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+
+var gTestBrowser = null;
+var gNextTest = null;
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+// This listens for the next opened tab and checks it is of the right url.
+// opencallback is called when the new tab is fully loaded
+// closecallback is called when the tab is closed
+function TabOpenListener(url, opencallback, closecallback) {
+ this.url = url;
+ this.opencallback = opencallback;
+ this.closecallback = closecallback;
+
+ gBrowser.tabContainer.addEventListener("TabOpen", this, false);
+}
+
+TabOpenListener.prototype = {
+ url: null,
+ opencallback: null,
+ closecallback: null,
+ tab: null,
+ browser: null,
+
+ handleEvent: function(event) {
+ if (event.type == "TabOpen") {
+ gBrowser.tabContainer.removeEventListener("TabOpen", this, false);
+ this.tab = event.originalTarget;
+ this.browser = this.tab.linkedBrowser;
+ gBrowser.addEventListener("pageshow", this, false);
+ } else if (event.type == "pageshow") {
+ if (event.target.location.href != this.url)
+ return;
+ gBrowser.removeEventListener("pageshow", this, false);
+ this.tab.addEventListener("TabClose", this, false);
+ var url = this.browser.contentDocument.location.href;
+ is(url, this.url, "Should have opened the correct tab");
+ this.opencallback(this.tab, this.browser.contentWindow);
+ } else if (event.type == "TabClose") {
+ if (event.originalTarget != this.tab)
+ return;
+ this.tab.removeEventListener("TabClose", this, false);
+ this.opencallback = null;
+ this.tab = null;
+ this.browser = null;
+ // Let the window close complete
+ executeSoon(this.closecallback);
+ this.closecallback = null;
+ }
+ }
+};
+
+function test() {
+ waitForExplicitFinish();
+ registerCleanupFunction(function() {
+ clearAllPluginPermissions();
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ });
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+ var newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+ gTestBrowser.addEventListener("load", pageLoad, true);
+
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+ getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+
+ prepareTest(test1a, gHttpTestRoot + "plugin_data_url.html");
+}
+
+function finishTest() {
+ clearAllPluginPermissions();
+ gTestBrowser.removeEventListener("load", pageLoad, true);
+ gBrowser.removeCurrentTab();
+ window.focus();
+ finish();
+}
+
+function pageLoad() {
+ // The plugin events are async dispatched and can come after the load event
+ // This just allows the events to fire before we then go on to test the states
+ executeSoon(gNextTest);
+}
+
+function prepareTest(nextTest, url) {
+ gNextTest = nextTest;
+ gTestBrowser.contentWindow.location = url;
+}
+
+// Due to layout being async, "PluginBindAttached" may trigger later.
+// This wraps a function to force a layout flush, thus triggering it,
+// and schedules the function execution so they're definitely executed
+// afterwards.
+function runAfterPluginBindingAttached(func) {
+ return function() {
+ let doc = gTestBrowser.contentDocument;
+ let elems = doc.getElementsByTagName('embed');
+ if (elems.length < 1) {
+ elems = doc.getElementsByTagName('object');
+ }
+ elems[0].clientTop;
+ executeSoon(func);
+ };
+}
+
+// Test that the click-to-play doorhanger still works when navigating to data URLs
+function test1a() {
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "Test 1a, Should have a click-to-play notification");
+
+ let plugin = gTestBrowser.contentDocument.getElementById("test");
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!objLoadingContent.activated, "Test 1a, Plugin should not be activated");
+
+ gNextTest = runAfterPluginBindingAttached(test1b);
+ gTestBrowser.contentDocument.getElementById("data-link-1").click();
+}
+
+function test1b() {
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "Test 1b, Should have a click-to-play notification");
+
+ let plugin = gTestBrowser.contentDocument.getElementById("test");
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!objLoadingContent.activated, "Test 1b, Plugin should not be activated");
+
+ // Simulate clicking the "Allow Always" button.
+ popupNotification.reshow();
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ let condition = function() objLoadingContent.activated;
+ waitForCondition(condition, test1c, "Test 1b, Waited too long for plugin to activate");
+}
+
+function test1c() {
+ clearAllPluginPermissions();
+ prepareTest(runAfterPluginBindingAttached(test2a), gHttpTestRoot + "plugin_data_url.html");
+}
+
+// Test that the click-to-play notification doesn't break when navigating to data URLs with multiple plugins
+function test2a() {
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "Test 2a, Should have a click-to-play notification");
+ let plugin = gTestBrowser.contentDocument.getElementById("test");
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!objLoadingContent.activated, "Test 2a, Plugin should not be activated");
+
+ gNextTest = runAfterPluginBindingAttached(test2b);
+ gTestBrowser.contentDocument.getElementById("data-link-2").click();
+}
+
+function test2b() {
+ let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 2b, Should have a click-to-play notification");
+
+ // Simulate choosing "Allow now" for the test plugin
+ notification.reshow();
+ is(notification.options.centerActions.length, 2, "Test 2b, Should have two types of plugin in the notification");
+
+ var centerAction = null;
+ for (var action of notification.options.centerActions) {
+ if (action.pluginName == "Test") {
+ centerAction = action;
+ break;
+ }
+ }
+ ok(centerAction, "Test 2b, found center action for the Test plugin");
+
+ var centerItem = null;
+ for (var item of PopupNotifications.panel.firstChild.childNodes) {
+ is(item.value, "block", "Test 2b, all plugins should start out blocked");
+ if (item.action == centerAction) {
+ centerItem = item;
+ break;
+ }
+ }
+ ok(centerItem, "Test 2b, found center item for the Test plugin");
+
+ // "click" the button to activate the Test plugin
+ centerItem.value = "allownow";
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ let plugin = gTestBrowser.contentDocument.getElementById("test1");
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ let condition = function() objLoadingContent.activated;
+ waitForCondition(condition, test2c, "Test 2b, Waited too long for plugin to activate");
+}
+
+function test2c() {
+ let plugin = gTestBrowser.contentDocument.getElementById("test1");
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.activated, "Test 2c, Plugin should be activated");
+
+ clearAllPluginPermissions();
+ prepareTest(runAfterPluginBindingAttached(test3a), gHttpTestRoot + "plugin_data_url.html");
+}
+
+// Test that when navigating to a data url, the plugin permission is inherited
+function test3a() {
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "Test 3a, Should have a click-to-play notification");
+ let plugin = gTestBrowser.contentDocument.getElementById("test");
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!objLoadingContent.activated, "Test 3a, Plugin should not be activated");
+
+ // Simulate clicking the "Allow Always" button.
+ popupNotification.reshow();
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ let condition = function() objLoadingContent.activated;
+ waitForCondition(condition, test3b, "Test 3a, Waited too long for plugin to activate");
+}
+
+function test3b() {
+ gNextTest = test3c;
+ gTestBrowser.contentDocument.getElementById("data-link-1").click();
+}
+
+function test3c() {
+ let plugin = gTestBrowser.contentDocument.getElementById("test");
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.activated, "Test 3c, Plugin should be activated");
+
+ clearAllPluginPermissions();
+ prepareTest(runAfterPluginBindingAttached(test4b),
+ 'data:text/html,<embed id="test" style="width: 200px; height: 200px" type="application/x-test"/>');
+}
+
+// Test that the click-to-play doorhanger still works when directly navigating to data URLs
+function test4a() {
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "Test 4a, Should have a click-to-play notification");
+ let plugin = gTestBrowser.contentDocument.getElementById("test");
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!objLoadingContent.activated, "Test 4a, Plugin should not be activated");
+
+ // Simulate clicking the "Allow Always" button.
+ popupNotification.reshow();
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ let condition = function() objLoadingContent.activated;
+ waitForCondition(condition, test4b, "Test 4a, Waited too long for plugin to activate");
+}
+
+function test4b() {
+ clearAllPluginPermissions();
+ finishTest();
+}
diff --git a/browser/base/content/test/browser_CTP_drag_drop.js b/browser/base/content/test/browser_CTP_drag_drop.js
new file mode 100644
index 000000000..85305bd94
--- /dev/null
+++ b/browser/base/content/test/browser_CTP_drag_drop.js
@@ -0,0 +1,104 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let gHttpTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+
+let gNextTest = null;
+let gNewWindow = null;
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+function test() {
+ waitForExplicitFinish();
+ registerCleanupFunction(function() {
+ clearAllPluginPermissions();
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ let plugin = getTestPlugin();
+ plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ });
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ let plugin = getTestPlugin();
+ plugin.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("PluginBindingAttached", handleEvent, true, true);
+ gNextTest = part1;
+ gBrowser.selectedBrowser.contentDocument.location = gHttpTestRoot + "plugin_test.html";
+}
+
+function handleEvent() {
+ gNextTest();
+}
+
+function part1() {
+ gBrowser.selectedBrowser.removeEventListener("PluginBindingAttached", handleEvent);
+ ok(PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser), "Should have a click-to-play notification in the initial tab");
+
+ gNextTest = part2;
+ gNewWindow = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
+ gNewWindow.addEventListener("load", handleEvent, true);
+}
+
+function part2() {
+ gNewWindow.removeEventListener("load", handleEvent);
+ let condition = function() PopupNotifications.getNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser);
+ waitForCondition(condition, part3, "Waited too long for click-to-play notification");
+}
+
+function part3() {
+ ok(PopupNotifications.getNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser), "Should have a click-to-play notification in the tab in the new window");
+ ok(!PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser), "Should not have a click-to-play notification in the old window now");
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.swapBrowsersAndCloseOther(gBrowser.selectedTab, gNewWindow.gBrowser.selectedTab);
+ let condition = function() PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser);
+ waitForCondition(condition, part4, "Waited too long for click-to-play notification");
+}
+
+function part4() {
+ ok(PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser), "Should have a click-to-play notification in the initial tab again");
+
+ gBrowser.selectedBrowser.addEventListener("PluginBindingAttached", handleEvent, true, true);
+ gNextTest = part5;
+ gBrowser.selectedBrowser.contentDocument.location = gHttpTestRoot + "plugin_test.html";
+}
+
+function part5() {
+ gBrowser.selectedBrowser.removeEventListener("PluginBindingAttached", handleEvent);
+ ok(PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser), "Should have a click-to-play notification in the initial tab");
+
+ gNextTest = part6;
+ gNewWindow = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
+ gNewWindow.addEventListener("load", handleEvent, true);
+}
+
+function part6() {
+ gNewWindow.removeEventListener("load", handleEvent);
+ let condition = function() PopupNotifications.getNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser);
+ waitForCondition(condition, part7, "Waited too long for click-to-play notification");
+}
+
+function part7() {
+ ok(PopupNotifications.getNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser), "Should have a click-to-play notification in the tab in the new window");
+ ok(!PopupNotifications.getNotification("click-to-play-plugins", gBrowser.selectedBrowser), "Should not have a click-to-play notification in the old window now");
+
+ let plugin = gNewWindow.gBrowser.selectedBrowser.contentDocument.getElementById("test");
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!objLoadingContent.activated, "plugin should not be activated");
+
+ EventUtils.synthesizeMouseAtCenter(plugin, {}, gNewWindow.gBrowser.selectedBrowser.contentWindow);
+ let condition = function() !PopupNotifications.getNotification("click-to-play-plugins", gNewWindow.gBrowser.selectedBrowser).dismissed && gNewWindow.PopupNotifications.panel.firstChild;
+ waitForCondition(condition, part8, "waited too long for plugin to activate");
+}
+
+function part8() {
+ // Click the activate button on doorhanger to make sure it works
+ gNewWindow.PopupNotifications.panel.firstChild._primaryButton.click();
+
+ let plugin = gNewWindow.gBrowser.selectedBrowser.contentDocument.getElementById("test");
+ let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.activated, "plugin should be activated now");
+
+ gNewWindow.close();
+ finish();
+}
diff --git a/browser/base/content/test/browser_URLBarSetURI.js b/browser/base/content/test/browser_URLBarSetURI.js
new file mode 100644
index 000000000..f98dbc5f0
--- /dev/null
+++ b/browser/base/content/test/browser_URLBarSetURI.js
@@ -0,0 +1,82 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ // avoid prompting about phishing
+ Services.prefs.setIntPref(phishyUserPassPref, 32);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(phishyUserPassPref);
+ });
+
+ nextTest();
+}
+
+const phishyUserPassPref = "network.http.phishy-userpass-length";
+
+function nextTest() {
+ let test = tests.shift();
+ if (test) {
+ test(function () {
+ executeSoon(nextTest);
+ });
+ } else {
+ executeSoon(finish);
+ }
+}
+
+let tests = [
+ function revert(next) {
+ loadTabInWindow(window, function (tab) {
+ gURLBar.handleRevert();
+ is(gURLBar.value, "example.com", "URL bar had user/pass stripped after reverting");
+ gBrowser.removeTab(tab);
+ next();
+ });
+ },
+ function customize(next) {
+ whenNewWindowLoaded(undefined, function (win) {
+ // Need to wait for delayedStartup for the customization part of the test,
+ // since that's where BrowserToolboxCustomizeDone is set.
+ whenDelayedStartupFinished(win, function () {
+ loadTabInWindow(win, function () {
+ openToolbarCustomizationUI(function () {
+ closeToolbarCustomizationUI(function () {
+ is(win.gURLBar.value, "example.com", "URL bar had user/pass stripped after customize");
+ win.close();
+ next();
+ }, win);
+ }, win);
+ });
+ });
+ });
+ },
+ function pageloaderror(next) {
+ loadTabInWindow(window, function (tab) {
+ // Load a new URL and then immediately stop it, to simulate a page load
+ // error.
+ tab.linkedBrowser.loadURI("http://test1.example.com");
+ tab.linkedBrowser.stop();
+ is(gURLBar.value, "example.com", "URL bar had user/pass stripped after load error");
+ gBrowser.removeTab(tab);
+ next();
+ });
+ }
+];
+
+function loadTabInWindow(win, callback) {
+ info("Loading tab");
+ let url = "http://user:pass@example.com/";
+ let tab = win.gBrowser.selectedTab = win.gBrowser.addTab(url);
+ tab.linkedBrowser.addEventListener("load", function listener() {
+ info("Tab loaded");
+ if (tab.linkedBrowser.currentURI.spec != url)
+ return;
+ tab.linkedBrowser.removeEventListener("load", listener, true);
+
+ is(win.gURLBar.value, "example.com", "URL bar had user/pass stripped initially");
+ callback(tab);
+ }, true);
+}
diff --git a/browser/base/content/test/browser_aboutHealthReport.js b/browser/base/content/test/browser_aboutHealthReport.js
new file mode 100644
index 000000000..b2e74140b
--- /dev/null
+++ b/browser/base/content/test/browser_aboutHealthReport.js
@@ -0,0 +1,105 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/commonjs/sdk/core/promise.js");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+registerCleanupFunction(function() {
+ // Ensure we don't pollute prefs for next tests.
+ try {
+ Services.prefs.clearUserPref("datareporting.healthreport.about.reportUrl");
+ let policy = Cc["@mozilla.org/datareporting/service;1"]
+ .getService(Ci.nsISupports)
+ .wrappedJSObject
+ .policy;
+ policy.recordHealthReportUploadEnabled(true,
+ "Resetting after tests.");
+ } catch (ex) {}
+});
+
+let gTests = [
+
+{
+ desc: "Test the remote commands",
+ setup: function ()
+ {
+ Services.prefs.setCharPref("datareporting.healthreport.about.reportUrl",
+ "https://example.com/browser/browser/base/content/test/healthreport_testRemoteCommands.html");
+ },
+ run: function ()
+ {
+ let deferred = Promise.defer();
+
+ let policy = Cc["@mozilla.org/datareporting/service;1"]
+ .getService(Ci.nsISupports)
+ .wrappedJSObject
+ .policy;
+
+ let results = 0;
+ try {
+ let win = gBrowser.contentWindow;
+ win.addEventListener("message", function testLoad(e) {
+ if (e.data.type == "testResult") {
+ ok(e.data.pass, e.data.info);
+ results++;
+ }
+ else if (e.data.type == "testsComplete") {
+ is(results, e.data.count, "Checking number of results received matches the number of tests that should have run");
+ win.removeEventListener("message", testLoad, false, true);
+ deferred.resolve();
+ }
+
+ }, false, true);
+
+ } catch(e) {
+ ok(false, "Failed to get all commands");
+ deferred.reject();
+ }
+ return deferred.promise;
+ }
+},
+
+
+]; // gTests
+
+function test()
+{
+ waitForExplicitFinish();
+
+ // xxxmpc leaving this here until we resolve bug 854038 and bug 854060
+ requestLongerTimeout(10);
+
+ Task.spawn(function () {
+ for (let test of gTests) {
+ info(test.desc);
+ test.setup();
+
+ yield promiseNewTabLoadEvent("about:healthreport");
+
+ yield test.run();
+
+ gBrowser.removeCurrentTab();
+ }
+
+ finish();
+ });
+}
+
+function promiseNewTabLoadEvent(aUrl, aEventType="load")
+{
+ let deferred = Promise.defer();
+ let tab = gBrowser.selectedTab = gBrowser.addTab(aUrl);
+ tab.linkedBrowser.addEventListener(aEventType, function load(event) {
+ tab.linkedBrowser.removeEventListener(aEventType, load, true);
+ let iframe = tab.linkedBrowser.contentDocument.getElementById("remote-report");
+ iframe.addEventListener("load", function frameLoad(e) {
+ iframe.removeEventListener("load", frameLoad, false);
+ deferred.resolve();
+ }, false);
+ }, true);
+ return deferred.promise;
+}
+
diff --git a/browser/base/content/test/browser_aboutHome.js b/browser/base/content/test/browser_aboutHome.js
new file mode 100644
index 000000000..3edbc5613
--- /dev/null
+++ b/browser/base/content/test/browser_aboutHome.js
@@ -0,0 +1,520 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/commonjs/sdk/core/promise.js");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AboutHomeUtils",
+ "resource:///modules/AboutHomeUtils.jsm");
+
+let gRightsVersion = Services.prefs.getIntPref("browser.rights.version");
+
+registerCleanupFunction(function() {
+ // Ensure we don't pollute prefs for next tests.
+ Services.prefs.clearUserPref("network.cookies.cookieBehavior");
+ Services.prefs.clearUserPref("network.cookie.lifetimePolicy");
+ Services.prefs.clearUserPref("browser.rights.override");
+ Services.prefs.clearUserPref("browser.rights." + gRightsVersion + ".shown");
+});
+
+let gTests = [
+
+{
+ desc: "Check that clearing cookies does not clear storage",
+ setup: function ()
+ {
+ Cc["@mozilla.org/observer-service;1"]
+ .getService(Ci.nsIObserverService)
+ .notifyObservers(null, "cookie-changed", "cleared");
+ },
+ run: function (aSnippetsMap)
+ {
+ isnot(aSnippetsMap.get("snippets-last-update"), null,
+ "snippets-last-update should have a value");
+ }
+},
+
+{
+ desc: "Check default snippets are shown",
+ setup: function () { },
+ run: function ()
+ {
+ let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
+ let snippetsElt = doc.getElementById("snippets");
+ ok(snippetsElt, "Found snippets element")
+ is(snippetsElt.getElementsByTagName("span").length, 1,
+ "A default snippet is present.");
+ }
+},
+
+{
+ desc: "Check default snippets are shown if snippets are invalid xml",
+ setup: function (aSnippetsMap)
+ {
+ // This must be some incorrect xhtml code.
+ aSnippetsMap.set("snippets", "<p><b></p></b>");
+ },
+ run: function (aSnippetsMap)
+ {
+ let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
+
+ let snippetsElt = doc.getElementById("snippets");
+ ok(snippetsElt, "Found snippets element");
+ is(snippetsElt.getElementsByTagName("span").length, 1,
+ "A default snippet is present.");
+
+ aSnippetsMap.delete("snippets");
+ }
+},
+
+{
+ desc: "Check that search engine logo has alt text",
+ setup: function () { },
+ run: function ()
+ {
+ let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
+
+ let searchEngineLogoElt = doc.getElementById("searchEngineLogo");
+ ok(searchEngineLogoElt, "Found search engine logo");
+
+ let altText = searchEngineLogoElt.alt;
+ ok(typeof altText == "string" && altText.length > 0,
+ "Search engine logo's alt text is a nonempty string");
+
+ isnot(altText, "undefined",
+ "Search engine logo's alt text shouldn't be the string 'undefined'");
+ }
+},
+
+// Disabled on Linux for intermittent issues with FHR, see Bug 945667.
+// Disabled always due to bug 992485
+{
+ desc: "Check that performing a search fires a search event and records to " +
+ "Firefox Health Report.",
+ setup: function () { },
+ run: function () {
+ // Skip this test always for now since it loads google.com and that causes bug 992485
+ return;
+
+ // Skip this test on Linux.
+ if (navigator.platform.indexOf("Linux") == 0) { return; }
+
+ try {
+ let cm = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
+ cm.getCategoryEntry("healthreport-js-provider-default", "SearchesProvider");
+ } catch (ex) {
+ // Health Report disabled, or no SearchesProvider.
+ return Promise.resolve();
+ }
+
+ let numSearchesBefore = 0;
+ let deferred = Promise.defer();
+ let doc = gBrowser.contentDocument;
+ let engineName = doc.documentElement.getAttribute("searchEngineName");
+
+ // We rely on the listener in browser.js being installed and fired before
+ // this one. If this ever changes, we should add an executeSoon() or similar.
+ doc.addEventListener("AboutHomeSearchEvent", function onSearch(e) {
+ is(e.detail, engineName, "Detail is search engine name");
+
+ getNumberOfSearches(engineName).then(num => {
+ is(num, numSearchesBefore + 1, "One more search recorded.");
+ deferred.resolve();
+ });
+ }, true, true);
+
+ // Get the current number of recorded searches.
+ getNumberOfSearches(engineName).then(num => {
+ numSearchesBefore = num;
+
+ info("Perform a search.");
+ doc.getElementById("searchText").value = "a search";
+ doc.getElementById("searchSubmit").click();
+ gBrowser.stop();
+ });
+
+ return deferred.promise;
+ }
+},
+
+{
+ desc: "Check snippets map is cleared if cached version is old",
+ setup: function (aSnippetsMap)
+ {
+ aSnippetsMap.set("snippets", "test");
+ aSnippetsMap.set("snippets-cached-version", 0);
+ },
+ run: function (aSnippetsMap)
+ {
+ ok(!aSnippetsMap.has("snippets"), "snippets have been properly cleared");
+ ok(!aSnippetsMap.has("snippets-cached-version"),
+ "cached-version has been properly cleared");
+ }
+},
+
+{
+ desc: "Check cached snippets are shown if cached version is current",
+ setup: function (aSnippetsMap)
+ {
+ aSnippetsMap.set("snippets", "test");
+ },
+ run: function (aSnippetsMap)
+ {
+ let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
+
+ let snippetsElt = doc.getElementById("snippets");
+ ok(snippetsElt, "Found snippets element");
+ is(snippetsElt.innerHTML, "test", "Cached snippet is present.");
+
+ is(aSnippetsMap.get("snippets"), "test", "snippets still cached");
+ is(aSnippetsMap.get("snippets-cached-version"),
+ AboutHomeUtils.snippetsVersion,
+ "cached-version is correct");
+ ok(aSnippetsMap.has("snippets-last-update"), "last-update still exists");
+ }
+},
+
+{
+ desc: "Check if the 'Know Your Rights default snippet is shown when 'browser.rights.override' pref is set",
+ beforeRun: function ()
+ {
+ Services.prefs.setBoolPref("browser.rights.override", false);
+ },
+ setup: function () { },
+ run: function (aSnippetsMap)
+ {
+ let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
+ let showRights = AboutHomeUtils.showKnowYourRights;
+
+ ok(showRights, "AboutHomeUtils.showKnowYourRights should be TRUE");
+
+ let snippetsElt = doc.getElementById("snippets");
+ ok(snippetsElt, "Found snippets element");
+ is(snippetsElt.getElementsByTagName("a")[0].href, "about:rights", "Snippet link is present.");
+
+ Services.prefs.clearUserPref("browser.rights.override");
+ }
+},
+
+{
+ desc: "Check if the 'Know Your Rights default snippet is NOT shown when 'browser.rights.override' pref is NOT set",
+ beforeRun: function ()
+ {
+ Services.prefs.setBoolPref("browser.rights.override", true);
+ },
+ setup: function () { },
+ run: function (aSnippetsMap)
+ {
+ let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
+ let rightsData = AboutHomeUtils.knowYourRightsData;
+
+ ok(!rightsData, "AboutHomeUtils.knowYourRightsData should be FALSE");
+
+ let snippetsElt = doc.getElementById("snippets");
+ ok(snippetsElt, "Found snippets element");
+ ok(snippetsElt.getElementsByTagName("a")[0].href != "about:rights", "Snippet link should not point to about:rights.");
+
+ Services.prefs.clearUserPref("browser.rights.override");
+ }
+},
+
+{
+ desc: "Check that the search UI/ action is updated when the search engine is changed",
+ setup: function() {},
+ run: function()
+ {
+ let currEngine = Services.search.currentEngine;
+ let unusedEngines = [].concat(Services.search.getVisibleEngines()).filter(x => x != currEngine);
+ let searchbar = document.getElementById("searchbar");
+
+ function checkSearchUI(engine) {
+ let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
+ let searchText = doc.getElementById("searchText");
+ let logoElt = doc.getElementById("searchEngineLogo");
+ let engineName = doc.documentElement.getAttribute("searchEngineName");
+
+ is(engineName, engine.name, "Engine name should've been updated");
+
+ if (!logoElt.parentNode.hidden) {
+ is(logoElt.alt, engineName, "Alt text of logo image should match search engine name")
+ } else {
+ is(searchText.placeholder, engineName, "Placeholder text should match search engine name");
+ }
+ }
+ // Do a sanity check that all attributes are correctly set to begin with
+ checkSearchUI(currEngine);
+
+ let deferred = Promise.defer();
+ promiseBrowserAttributes(gBrowser.selectedTab).then(function() {
+ // Test if the update propagated
+ checkSearchUI(unusedEngines[0]);
+ searchbar.currentEngine = currEngine;
+ deferred.resolve();
+ });
+
+ // The following cleanup function will set currentEngine back to the previous
+ // engine if we fail to do so above.
+ registerCleanupFunction(function() {
+ searchbar.currentEngine = currEngine;
+ });
+ // Set the current search engine to an unused one
+ searchbar.currentEngine = unusedEngines[0];
+ searchbar.select();
+ return deferred.promise;
+ }
+},
+
+{
+ desc: "Check POST search engine support",
+ setup: function() {},
+ run: function()
+ {
+ let deferred = Promise.defer();
+ let currEngine = Services.search.defaultEngine;
+ let searchObserver = function search_observer(aSubject, aTopic, aData) {
+ let engine = aSubject.QueryInterface(Ci.nsISearchEngine);
+ info("Observer: " + aData + " for " + engine.name);
+
+ if (aData != "engine-added")
+ return;
+
+ if (engine.name != "POST Search")
+ return;
+
+ Services.search.defaultEngine = engine;
+
+ registerCleanupFunction(function() {
+ Services.search.removeEngine(engine);
+ Services.search.defaultEngine = currEngine;
+ });
+
+ let needle = "Search for something awesome.";
+
+ // Ready to execute the tests!
+ promiseBrowserAttributes(gBrowser.selectedTab).then(function() {
+ let document = gBrowser.selectedTab.linkedBrowser.contentDocument;
+ let searchText = document.getElementById("searchText");
+
+ waitForLoad(function() {
+ let loadedText = gBrowser.contentDocument.body.textContent;
+ ok(loadedText, "search page loaded");
+ is(loadedText, "searchterms=" + escape(needle.replace(/\s/g, "+")),
+ "Search text should arrive correctly");
+ deferred.resolve();
+ });
+
+ searchText.value = needle;
+ searchText.focus();
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ });
+ };
+ Services.obs.addObserver(searchObserver, "browser-search-engine-modified", false);
+ registerCleanupFunction(function () {
+ Services.obs.removeObserver(searchObserver, "browser-search-engine-modified");
+ });
+ Services.search.addEngine("http://test:80/browser/browser/base/content/test/POSTSearchEngine.xml",
+ Ci.nsISearchEngine.DATA_XML, null, false);
+ return deferred.promise;
+ }
+}
+
+];
+
+function test()
+{
+ waitForExplicitFinish();
+ requestLongerTimeout(2);
+ ignoreAllUncaughtExceptions();
+
+ Task.spawn(function () {
+ for (let test of gTests) {
+ info(test.desc);
+
+ if (test.beforeRun)
+ yield test.beforeRun();
+
+ let tab = yield promiseNewTabLoadEvent("about:home", "DOMContentLoaded");
+
+ // Must wait for both the snippets map and the browser attributes, since
+ // can't guess the order they will happen.
+ // So, start listening now, but verify the promise is fulfilled only
+ // after the snippets map setup.
+ let promise = promiseBrowserAttributes(tab);
+ // Prepare the snippets map with default values, then run the test setup.
+ let snippetsMap = yield promiseSetupSnippetsMap(tab, test.setup);
+ // Ensure browser has set attributes already, or wait for them.
+ yield promise;
+ info("Running test");
+ yield test.run(snippetsMap);
+ info("Cleanup");
+ gBrowser.removeCurrentTab();
+ }
+ }).then(finish, ex => {
+ ok(false, "Unexpected Exception: " + ex);
+ finish();
+ });
+}
+
+/**
+ * Creates a new tab and waits for a load event.
+ *
+ * @param aUrl
+ * The url to load in a new tab.
+ * @param aEvent
+ * The load event type to wait for. Defaults to "load".
+ * @return {Promise} resolved when the event is handled. Gets the new tab.
+ */
+function promiseNewTabLoadEvent(aUrl, aEventType="load")
+{
+ let deferred = Promise.defer();
+ let tab = gBrowser.selectedTab = gBrowser.addTab(aUrl);
+ info("Wait tab event: " + aEventType);
+ tab.linkedBrowser.addEventListener(aEventType, function load(event) {
+ if (event.originalTarget != tab.linkedBrowser.contentDocument ||
+ event.target.location.href == "about:blank") {
+ info("skipping spurious load event");
+ return;
+ }
+ tab.linkedBrowser.removeEventListener(aEventType, load, true);
+ info("Tab event received: " + aEventType);
+ deferred.resolve(tab);
+ }, true);
+ return deferred.promise;
+}
+
+/**
+ * Cleans up snippets and ensures that by default we don't try to check for
+ * remote snippets since that may cause network bustage or slowness.
+ *
+ * @param aTab
+ * The tab containing about:home.
+ * @param aSetupFn
+ * The setup function to be run.
+ * @return {Promise} resolved when the snippets are ready. Gets the snippets map.
+ */
+function promiseSetupSnippetsMap(aTab, aSetupFn)
+{
+ let deferred = Promise.defer();
+ let cw = aTab.linkedBrowser.contentWindow.wrappedJSObject;
+ info("Waiting for snippets map");
+ cw.ensureSnippetsMapThen(function (aSnippetsMap) {
+ info("Got snippets map: " +
+ "{ last-update: " + aSnippetsMap.get("snippets-last-update") +
+ ", cached-version: " + aSnippetsMap.get("snippets-cached-version") +
+ " }");
+ // Don't try to update.
+ aSnippetsMap.set("snippets-last-update", Date.now());
+ aSnippetsMap.set("snippets-cached-version", AboutHomeUtils.snippetsVersion);
+ // Clear snippets.
+ aSnippetsMap.delete("snippets");
+ aSetupFn(aSnippetsMap);
+ // Must be sure to continue after the page snippets map setup.
+ executeSoon(function() deferred.resolve(aSnippetsMap));
+ });
+ return deferred.promise;
+}
+
+/**
+ * Waits for the attributes being set by browser.js and overwrites snippetsURL
+ * to ensure we won't try to hit the network and we can force xhr to throw.
+ *
+ * @param aTab
+ * The tab containing about:home.
+ * @return {Promise} resolved when the attributes are ready.
+ */
+function promiseBrowserAttributes(aTab)
+{
+ let deferred = Promise.defer();
+
+ let docElt = aTab.linkedBrowser.contentDocument.documentElement;
+ //docElt.setAttribute("snippetsURL", "nonexistent://test");
+ let observer = new MutationObserver(function (mutations) {
+ for (let mutation of mutations) {
+ info("Got attribute mutation: " + mutation.attributeName +
+ " from " + mutation.oldValue);
+ if (mutation.attributeName == "snippetsURL" &&
+ docElt.getAttribute("snippetsURL") != "nonexistent://test") {
+ docElt.setAttribute("snippetsURL", "nonexistent://test");
+ }
+
+ // Now we just have to wait for the last attribute.
+ if (mutation.attributeName == "searchEngineURL") {
+ info("Remove attributes observer");
+ observer.disconnect();
+ // Must be sure to continue after the page mutation observer.
+ executeSoon(function() deferred.resolve());
+ break;
+ }
+ }
+ });
+ info("Add attributes observer");
+ observer.observe(docElt, { attributes: true });
+
+ return deferred.promise;
+}
+
+/**
+ * Retrieves the number of about:home searches recorded for the current day.
+ *
+ * @param aEngineName
+ * name of the setup search engine.
+ *
+ * @return {Promise} Returns a promise resolving to the number of searches.
+ */
+function getNumberOfSearches(aEngineName) {
+ let reporter = Components.classes["@mozilla.org/datareporting/service;1"]
+ .getService()
+ .wrappedJSObject
+ .healthReporter;
+ ok(reporter, "Health Reporter instance available.");
+
+ return reporter.onInit().then(function onInit() {
+ let provider = reporter.getProvider("org.mozilla.searches");
+ ok(provider, "Searches provider is available.");
+
+ let m = provider.getMeasurement("counts", 2);
+ return m.getValues().then(data => {
+ let now = new Date();
+ let yday = new Date(now);
+ yday.setDate(yday.getDate() - 1);
+
+ // Add the number of searches recorded yesterday to the number of searches
+ // recorded today. This makes the test not fail intermittently when it is
+ // run at midnight and we accidentally compare the number of searches from
+ // different days. Tests are always run with an empty profile so there
+ // are no searches from yesterday, normally. Should the test happen to run
+ // past midnight we make sure to count them in as well.
+ return getNumberOfSearchesByDate(aEngineName, data, now) +
+ getNumberOfSearchesByDate(aEngineName, data, yday);
+ });
+ });
+}
+
+function getNumberOfSearchesByDate(aEngineName, aData, aDate) {
+ if (aData.days.hasDay(aDate)) {
+ let id = Services.search.getEngineByName(aEngineName).identifier;
+
+ let day = aData.days.getDay(aDate);
+ let field = id + ".abouthome";
+
+ if (day.has(field)) {
+ return day.get(field) || 0;
+ }
+ }
+
+ return 0; // No records found.
+}
+
+function waitForLoad(cb) {
+ let browser = gBrowser.selectedBrowser;
+ browser.addEventListener("load", function listener() {
+ if (browser.currentURI.spec == "about:blank")
+ return;
+ info("Page loaded: " + browser.currentURI.spec);
+ browser.removeEventListener("load", listener, true);
+
+ cb();
+ }, true);
+}
diff --git a/browser/base/content/test/browser_aboutSyncProgress.js b/browser/base/content/test/browser_aboutSyncProgress.js
new file mode 100644
index 000000000..49a2fd803
--- /dev/null
+++ b/browser/base/content/test/browser_aboutSyncProgress.js
@@ -0,0 +1,102 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+Cu.import("resource://services-sync/main.js");
+
+let gTests = [ {
+ desc: "Makes sure the progress bar appears if firstSync pref is set",
+ setup: function () {
+ Services.prefs.setCharPref("services.sync.firstSync", "newAccount");
+ },
+ run: function () {
+ let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
+ let progressBar = doc.getElementById("uploadProgressBar");
+
+ let win = doc.defaultView;
+ isnot(win.getComputedStyle(progressBar).display, "none", "progress bar should be visible");
+ executeSoon(runNextTest);
+ }
+},
+
+{
+ desc: "Makes sure the progress bar is hidden if firstSync pref is not set",
+ setup: function () {
+ Services.prefs.clearUserPref("services.sync.firstSync");
+ is(Services.prefs.getPrefType("services.sync.firstSync"),
+ Ci.nsIPrefBranch.PREF_INVALID, "pref DNE" );
+ },
+ run: function () {
+ let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
+ let progressBar = doc.getElementById("uploadProgressBar");
+
+ let win = doc.defaultView;
+ is(win.getComputedStyle(progressBar).display, "none",
+ "progress bar should not be visible");
+ executeSoon(runNextTest);
+ }
+},
+{
+ desc: "Makes sure the observer updates are reflected in the progress bar",
+ setup: function () {
+ },
+ run: function () {
+ let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
+ let progressBar = doc.getElementById("uploadProgressBar");
+
+ Services.obs.notifyObservers(null, "weave:engine:sync:finish", null);
+ Services.obs.notifyObservers(null, "weave:engine:sync:error", null);
+
+ let received = progressBar.getAttribute("value");
+
+ is(received, 2, "progress bar received correct notifications");
+ executeSoon(runNextTest);
+ }
+},
+{
+ desc: "Close button should close tab",
+ setup: function (){
+ },
+ run: function () {
+ function onTabClosed() {
+ ok(true, "received TabClose notification");
+ gBrowser.tabContainer.removeEventListener("TabClose", onTabClosed, false);
+ executeSoon(runNextTest);
+ }
+ let doc = gBrowser.selectedTab.linkedBrowser.contentDocument;
+ let button = doc.getElementById('closeButton');
+ let window = doc.defaultView;
+ gBrowser.tabContainer.addEventListener("TabClose", onTabClosed, false);
+ EventUtils.sendMouseEvent({type: "click"}, button, window);
+ }
+},
+];
+
+function test () {
+ waitForExplicitFinish();
+ executeSoon(runNextTest);
+}
+
+function runNextTest()
+{
+ while (gBrowser.tabs.length > 1) {
+ gBrowser.removeCurrentTab();
+ }
+
+ if (gTests.length) {
+ let test = gTests.shift();
+ info(test.desc);
+ test.setup();
+ let tab = gBrowser.selectedTab = gBrowser.addTab("about:sync-progress");
+ tab.linkedBrowser.addEventListener("load", function (event) {
+ tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+ // Some part of the page is populated on load, so enqueue on it.
+ executeSoon(test.run);
+ }, true);
+ }
+ else {
+ finish();
+ }
+}
+
diff --git a/browser/base/content/test/browser_addon_bar.js b/browser/base/content/test/browser_addon_bar.js
new file mode 100644
index 000000000..3607eb122
--- /dev/null
+++ b/browser/base/content/test/browser_addon_bar.js
@@ -0,0 +1,63 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ let addonbar = document.getElementById("addon-bar");
+ ok(addonbar.collapsed, "addon bar is collapsed by default");
+
+ let topMenu, toolbarMenu;
+
+ function onTopMenuShown(event) {
+ ok(1, "top menu popupshown listener called");
+ event.currentTarget.removeEventListener("popupshown", arguments.callee, false);
+ // open the customize or toolbars menu
+ toolbarMenu = document.getElementById("appmenu_customizeMenu") ||
+ document.getElementById("viewToolbarsMenu").firstElementChild;
+ toolbarMenu.addEventListener("popupshown", onToolbarMenuShown, false);
+ toolbarMenu.addEventListener("popuphidden", onToolbarMenuHidden, false);
+ toolbarMenu.openPopup();
+ }
+
+ function onTopMenuHidden(event) {
+ ok(1, "top menu popuphidden listener called");
+ event.currentTarget.removeEventListener("popuphidden", arguments.callee, false);
+ finish();
+ }
+
+ function onToolbarMenuShown(event) {
+ ok(1, "sub menu popupshown listener called");
+ event.currentTarget.removeEventListener("popupshown", arguments.callee, false);
+
+ // test the menu item's default state
+ let menuitem = document.getElementById("toggle_addon-bar");
+ ok(menuitem, "found the menu item");
+ is(menuitem.getAttribute("checked"), "false", "menuitem is not checked by default");
+
+ // click on the menu item
+ // TODO: there's got to be a way to check+command in one shot
+ menuitem.setAttribute("checked", "true");
+ menuitem.click();
+
+ // now the addon bar should be visible and the menu checked
+ is(addonbar.getAttribute("collapsed"), "false", "addon bar is visible after executing the command");
+ is(menuitem.getAttribute("checked"), "true", "menuitem is checked after executing the command");
+
+ toolbarMenu.hidePopup();
+ }
+
+ function onToolbarMenuHidden(event) {
+ ok(1, "toolbar menu popuphidden listener called");
+ event.currentTarget.removeEventListener("popuphidden", arguments.callee, false);
+ topMenu.hidePopup();
+ }
+
+ // open the appmenu or view menu
+ topMenu = document.getElementById("appmenu-popup") ||
+ document.getElementById("menu_viewPopup");
+ topMenu.addEventListener("popupshown", onTopMenuShown, false);
+ topMenu.addEventListener("popuphidden", onTopMenuHidden, false);
+ topMenu.openPopup();
+}
diff --git a/browser/base/content/test/browser_addon_bar_aomlistener.js b/browser/base/content/test/browser_addon_bar_aomlistener.js
new file mode 100644
index 000000000..75a539e07
--- /dev/null
+++ b/browser/base/content/test/browser_addon_bar_aomlistener.js
@@ -0,0 +1,67 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+
+ let addonbar = document.getElementById("addon-bar");
+ ok(addonbar.collapsed, "addon bar is collapsed by default");
+
+ function addItem(id) {
+ let button = document.createElement("toolbarbutton");
+ button.id = id;
+ let palette = document.getElementById("navigator-toolbox").palette;
+ palette.appendChild(button);
+ addonbar.insertItem(id, null, null, false);
+ }
+
+ // call onInstalling
+ AddonsMgrListener.onInstalling();
+
+ // add item to the bar
+ let id = "testbutton";
+ addItem(id);
+
+ // call onInstalled
+ AddonsMgrListener.onInstalled();
+
+ // confirm bar is visible
+ ok(!addonbar.collapsed, "addon bar is not collapsed after toggle");
+
+ // call onUninstalling
+ AddonsMgrListener.onUninstalling();
+
+ // remove item from the bar
+ addonbar.currentSet = addonbar.currentSet.replace("," + id, "");
+
+ // call onUninstalled
+ AddonsMgrListener.onUninstalled();
+
+ // confirm bar is not visible
+ ok(addonbar.collapsed, "addon bar is collapsed after toggle");
+
+ // call onEnabling
+ AddonsMgrListener.onEnabling();
+
+ // add item to the bar
+ let id = "testbutton";
+ addItem(id);
+
+ // call onEnabled
+ AddonsMgrListener.onEnabled();
+
+ // confirm bar is visible
+ ok(!addonbar.collapsed, "addon bar is not collapsed after toggle");
+
+ // call onDisabling
+ AddonsMgrListener.onDisabling();
+
+ // remove item from the bar
+ addonbar.currentSet = addonbar.currentSet.replace("," + id, "");
+
+ // call onDisabled
+ AddonsMgrListener.onDisabled();
+
+ // confirm bar is not visible
+ ok(addonbar.collapsed, "addon bar is collapsed after toggle");
+}
diff --git a/browser/base/content/test/browser_addon_bar_close_button.js b/browser/base/content/test/browser_addon_bar_close_button.js
new file mode 100644
index 000000000..7d3afb333
--- /dev/null
+++ b/browser/base/content/test/browser_addon_bar_close_button.js
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ let addonbar = document.getElementById("addon-bar");
+ ok(addonbar.collapsed, "addon bar is collapsed by default");
+
+ // make add-on bar visible
+ setToolbarVisibility(addonbar, true);
+ ok(!addonbar.collapsed, "addon bar is not collapsed after toggle");
+
+ // click the close button
+ let closeButton = document.getElementById("addonbar-closebutton");
+ EventUtils.synthesizeMouseAtCenter(closeButton, {});
+
+ // confirm addon bar is closed
+ ok(addonbar.collapsed, "addon bar is collapsed after clicking close button");
+}
diff --git a/browser/base/content/test/browser_addon_bar_shortcut.js b/browser/base/content/test/browser_addon_bar_shortcut.js
new file mode 100644
index 000000000..847f96721
--- /dev/null
+++ b/browser/base/content/test/browser_addon_bar_shortcut.js
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ let addonbar = document.getElementById("addon-bar");
+ ok(addonbar.collapsed, "addon bar is collapsed by default");
+
+ // show the add-on bar
+ EventUtils.synthesizeKey("/", { accelKey: true }, window);
+ ok(!addonbar.collapsed, "addon bar is not collapsed after toggle");
+
+ // hide the add-on bar
+ EventUtils.synthesizeKey("/", { accelKey: true }, window);
+
+ // confirm addon bar is closed
+ ok(addonbar.collapsed, "addon bar is collapsed after toggle");
+}
diff --git a/browser/base/content/test/browser_allTabsPanel.js b/browser/base/content/test/browser_allTabsPanel.js
new file mode 100644
index 000000000..eb82c0d1a
--- /dev/null
+++ b/browser/base/content/test/browser_allTabsPanel.js
@@ -0,0 +1,162 @@
+function test() {
+ waitForExplicitFinish();
+
+ Services.prefs.setBoolPref(allTabs.prefName, true);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(allTabs.prefName);
+ });
+
+ allTabs.init();
+ nextSequence();
+}
+
+var sequences = 3;
+var chars = "ABCDEFGHI";
+var closedTabs;
+var history;
+var steps;
+var whenOpen = [
+ startSearch,
+ clearSearch, clearSearch,
+ closeTab,
+ moveTab,
+ closePanel,
+];
+var whenClosed = [
+ openPanel, openPanel, openPanel, openPanel, openPanel, openPanel,
+ closeTab, closeTab, closeTab,
+ moveTab, moveTab, moveTab,
+ selectTab, selectTab,
+ undoCloseTab,
+ openTab,
+];
+
+function rand(min, max) {
+ return min + Math.floor(Math.random() * (max - min + 1));
+}
+function pickOne(array) {
+ return array[rand(0, array.length - 1)];
+}
+function pickOneTab() {
+ var tab = pickOne(gBrowser.tabs);
+ return [tab, Array.indexOf(gBrowser.tabs, tab)];
+}
+function nextSequence() {
+ while (gBrowser.browsers.length > 1)
+ gBrowser.removeCurrentTab();
+ if (sequences-- <= 0) {
+ allTabs.close();
+ gBrowser.addTab();
+ gBrowser.removeCurrentTab();
+ finish();
+ return;
+ }
+ closedTabs = 0;
+ steps = rand(10, 20);
+ var initialTabs = "";
+ while (gBrowser.browsers.length < rand(3, 20)) {
+ let tabChar = pickOne(chars);
+ initialTabs += tabChar;
+ gBrowser.addTab("data:text/plain," + tabChar);
+ }
+ history = [initialTabs];
+ gBrowser.removeCurrentTab();
+ next();
+}
+function next() {
+ executeSoon(function () {
+ is(allTabs.previews.length, gBrowser.browsers.length,
+ history.join(", "));
+ if (steps-- <= 0) {
+ nextSequence();
+ return;
+ }
+ var step;
+ var rv;
+ do {
+ step = pickOne(allTabs.isOpen ? whenOpen : whenClosed);
+ info(step.name);
+ rv = step();
+ } while (rv === false);
+ history.push(step.name + (rv !== true && rv !== undefined ? " " + rv : ""));
+ });
+}
+
+function openPanel() {
+ if (allTabs.isOpen)
+ return false;
+ allTabs.panel.addEventListener("popupshown", function () {
+ allTabs.panel.removeEventListener("popupshown", arguments.callee, false);
+ next();
+ }, false);
+ allTabs.open();
+ return true;
+}
+
+function closePanel() {
+ allTabs.panel.addEventListener("popuphidden", function () {
+ allTabs.panel.removeEventListener("popuphidden", arguments.callee, false);
+ next();
+ }, false);
+ allTabs.close();
+}
+
+function closeTab() {
+ if (gBrowser.browsers.length == 1)
+ return false;
+ var [tab, index] = pickOneTab();
+ gBrowser.removeTab(tab);
+ closedTabs++;
+ next();
+ return index;
+}
+
+function startSearch() {
+ allTabs.filterField.value = pickOne(chars);
+ info(allTabs.filterField.value);
+ allTabs.filter();
+ next();
+ return allTabs.filterField.value;
+}
+
+function clearSearch() {
+ if (!allTabs.filterField.value)
+ return false;
+ allTabs.filterField.value = "";
+ allTabs.filter();
+ next();
+ return true;
+}
+
+function undoCloseTab() {
+ if (!closedTabs)
+ return false;
+ window.undoCloseTab(0);
+ closedTabs--;
+ next();
+ return true;
+}
+
+function selectTab() {
+ var [tab, index] = pickOneTab();
+ gBrowser.selectedTab = tab;
+ next();
+ return index;
+}
+
+function openTab() {
+ BrowserOpenTab();
+ next();
+}
+
+function moveTab() {
+ if (gBrowser.browsers.length == 1)
+ return false;
+ var [tab, currentIndex] = pickOneTab();
+ do {
+ var [, newIndex] = pickOneTab();
+ } while (newIndex == currentIndex);
+ gBrowser.moveTabTo(tab, newIndex);
+ next();
+ return currentIndex + "->" + newIndex;
+}
diff --git a/browser/base/content/test/browser_alltabslistener.js b/browser/base/content/test/browser_alltabslistener.js
new file mode 100644
index 000000000..4d7d11435
--- /dev/null
+++ b/browser/base/content/test/browser_alltabslistener.js
@@ -0,0 +1,204 @@
+const Ci = Components.interfaces;
+
+const gCompleteState = Ci.nsIWebProgressListener.STATE_STOP +
+ Ci.nsIWebProgressListener.STATE_IS_NETWORK;
+
+var gFrontProgressListener = {
+ onProgressChange: function (aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress) {
+ },
+
+ onStateChange: function (aWebProgress, aRequest, aStateFlags, aStatus) {
+ var state = "onStateChange";
+ info("FrontProgress: " + state + " 0x" + aStateFlags.toString(16));
+ ok(gFrontNotificationsPos < gFrontNotifications.length, "Got an expected notification for the front notifications listener");
+ is(state, gFrontNotifications[gFrontNotificationsPos], "Got a notification for the front notifications listener");
+ gFrontNotificationsPos++;
+ },
+
+ onLocationChange: function (aWebProgress, aRequest, aLocationURI, aFlags) {
+ var state = "onLocationChange";
+ info("FrontProgress: " + state + " " + aLocationURI.spec);
+ ok(gFrontNotificationsPos < gFrontNotifications.length, "Got an expected notification for the front notifications listener");
+ is(state, gFrontNotifications[gFrontNotificationsPos], "Got a notification for the front notifications listener");
+ gFrontNotificationsPos++;
+ },
+
+ onStatusChange: function (aWebProgress, aRequest, aStatus, aMessage) {
+ },
+
+ onSecurityChange: function (aWebProgress, aRequest, aState) {
+ var state = "onSecurityChange";
+ info("FrontProgress: " + state + " 0x" + aState.toString(16));
+ ok(gFrontNotificationsPos < gFrontNotifications.length, "Got an expected notification for the front notifications listener");
+ is(state, gFrontNotifications[gFrontNotificationsPos], "Got a notification for the front notifications listener");
+ gFrontNotificationsPos++;
+ }
+}
+
+var gAllProgressListener = {
+ onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
+ var state = "onStateChange";
+ info("AllProgress: " + state + " 0x" + aStateFlags.toString(16));
+ ok(aBrowser == gTestBrowser, state + " notification came from the correct browser");
+ ok(gAllNotificationsPos < gAllNotifications.length, "Got an expected notification for the all notifications listener");
+ is(state, gAllNotifications[gAllNotificationsPos], "Got a notification for the all notifications listener");
+ gAllNotificationsPos++;
+
+ if ((aStateFlags & gCompleteState) == gCompleteState) {
+ ok(gAllNotificationsPos == gAllNotifications.length, "Saw the expected number of notifications");
+ ok(gFrontNotificationsPos == gFrontNotifications.length, "Saw the expected number of frontnotifications");
+ executeSoon(gNextTest);
+ }
+ },
+
+ onLocationChange: function (aBrowser, aWebProgress, aRequest, aLocationURI,
+ aFlags) {
+ var state = "onLocationChange";
+ info("AllProgress: " + state + " " + aLocationURI.spec);
+ ok(aBrowser == gTestBrowser, state + " notification came from the correct browser");
+ ok(gAllNotificationsPos < gAllNotifications.length, "Got an expected notification for the all notifications listener");
+ is(state, gAllNotifications[gAllNotificationsPos], "Got a notification for the all notifications listener");
+ gAllNotificationsPos++;
+ },
+
+ onStatusChange: function (aBrowser, aWebProgress, aRequest, aStatus, aMessage) {
+ var state = "onStatusChange";
+ ok(aBrowser == gTestBrowser, state + " notification came from the correct browser");
+ },
+
+ onSecurityChange: function (aBrowser, aWebProgress, aRequest, aState) {
+ var state = "onSecurityChange";
+ info("AllProgress: " + state + " 0x" + aState.toString(16));
+ ok(aBrowser == gTestBrowser, state + " notification came from the correct browser");
+ ok(gAllNotificationsPos < gAllNotifications.length, "Got an expected notification for the all notifications listener");
+ is(state, gAllNotifications[gAllNotificationsPos], "Got a notification for the all notifications listener");
+ gAllNotificationsPos++;
+ }
+}
+
+var gFrontNotifications, gAllNotifications, gFrontNotificationsPos, gAllNotificationsPos;
+var gBackgroundTab, gForegroundTab, gBackgroundBrowser, gForegroundBrowser, gTestBrowser;
+var gTestPage = "/browser/browser/base/content/test/alltabslistener.html";
+var gNextTest;
+
+function test() {
+ waitForExplicitFinish();
+
+ gBackgroundTab = gBrowser.addTab("about:blank");
+ gForegroundTab = gBrowser.addTab("about:blank");
+ gBackgroundBrowser = gBrowser.getBrowserForTab(gBackgroundTab);
+ gForegroundBrowser = gBrowser.getBrowserForTab(gForegroundTab);
+ gBrowser.selectedTab = gForegroundTab;
+
+ // We must wait until the about:blank page has completed loading before
+ // starting tests or we get notifications from that
+ gForegroundBrowser.addEventListener("load", startTests, true);
+}
+
+function runTest(browser, url, next) {
+ gFrontNotificationsPos = 0;
+ gAllNotificationsPos = 0;
+ gNextTest = next;
+ gTestBrowser = browser;
+ browser.loadURI(url);
+}
+
+function startTests() {
+ gForegroundBrowser.removeEventListener("load", startTests, true);
+ executeSoon(startTest1);
+}
+
+function startTest1() {
+ info("\nTest 1");
+ gBrowser.addProgressListener(gFrontProgressListener);
+ gBrowser.addTabsProgressListener(gAllProgressListener);
+
+ gAllNotifications = [
+ "onStateChange",
+ "onLocationChange",
+ "onSecurityChange",
+ "onStateChange"
+ ];
+ gFrontNotifications = gAllNotifications;
+ runTest(gForegroundBrowser, "http://example.org" + gTestPage, startTest2);
+}
+
+function startTest2() {
+ info("\nTest 2");
+ gAllNotifications = [
+ "onStateChange",
+ "onLocationChange",
+ "onSecurityChange",
+ "onSecurityChange",
+ "onStateChange"
+ ];
+ gFrontNotifications = gAllNotifications;
+ runTest(gForegroundBrowser, "https://example.com" + gTestPage, startTest3);
+}
+
+function startTest3() {
+ info("\nTest 3");
+ gAllNotifications = [
+ "onStateChange",
+ "onLocationChange",
+ "onSecurityChange",
+ "onStateChange"
+ ];
+ gFrontNotifications = [];
+ runTest(gBackgroundBrowser, "http://example.org" + gTestPage, startTest4);
+}
+
+function startTest4() {
+ info("\nTest 4");
+ gAllNotifications = [
+ "onStateChange",
+ "onLocationChange",
+ "onSecurityChange",
+ "onSecurityChange",
+ "onStateChange"
+ ];
+ gFrontNotifications = [];
+ runTest(gBackgroundBrowser, "https://example.com" + gTestPage, startTest5);
+}
+
+function startTest5() {
+ info("\nTest 5");
+ // Switch the foreground browser
+ [gForegroundBrowser, gBackgroundBrowser] = [gBackgroundBrowser, gForegroundBrowser];
+ [gForegroundTab, gBackgroundTab] = [gBackgroundTab, gForegroundTab];
+ // Avoid the onLocationChange this will fire
+ gBrowser.removeProgressListener(gFrontProgressListener);
+ gBrowser.selectedTab = gForegroundTab;
+ gBrowser.addProgressListener(gFrontProgressListener);
+
+ gAllNotifications = [
+ "onStateChange",
+ "onLocationChange",
+ "onSecurityChange",
+ "onStateChange"
+ ];
+ gFrontNotifications = gAllNotifications;
+ runTest(gForegroundBrowser, "http://example.org" + gTestPage, startTest6);
+}
+
+function startTest6() {
+ info("\nTest 6");
+ gAllNotifications = [
+ "onStateChange",
+ "onLocationChange",
+ "onSecurityChange",
+ "onStateChange"
+ ];
+ gFrontNotifications = [];
+ runTest(gBackgroundBrowser, "http://example.org" + gTestPage, finishTest);
+}
+
+function finishTest() {
+ gBrowser.removeProgressListener(gFrontProgressListener);
+ gBrowser.removeTabsProgressListener(gAllProgressListener);
+ gBrowser.removeTab(gBackgroundTab);
+ gBrowser.removeTab(gForegroundTab);
+ finish();
+}
diff --git a/browser/base/content/test/browser_blob-channelname.js b/browser/base/content/test/browser_blob-channelname.js
new file mode 100644
index 000000000..fbeaeeb21
--- /dev/null
+++ b/browser/base/content/test/browser_blob-channelname.js
@@ -0,0 +1,11 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+function test() {
+ var file = new File(new Blob(['test'], {type: 'text/plain'}), {name: 'test-name'});
+ var url = URL.createObjectURL(file);
+ var channel = NetUtil.newChannel(url);
+
+ is(channel.contentDispositionFilename, 'test-name', "filename matches");
+}
diff --git a/browser/base/content/test/browser_bookmark_titles.js b/browser/base/content/test/browser_bookmark_titles.js
new file mode 100644
index 000000000..5f7c11053
--- /dev/null
+++ b/browser/base/content/test/browser_bookmark_titles.js
@@ -0,0 +1,86 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file is tests for the default titles that new bookmarks get.
+
+let tests = [
+ // Common page.
+ ['http://example.com/browser/browser/base/content/test/dummy_page.html',
+ 'Dummy test page'],
+ // Data URI.
+ ['data:text/html;charset=utf-8,<title>test%20data:%20url</title>',
+ 'test data: url'],
+ // about:neterror
+ ['data:application/vnd.mozilla.xul+xml,',
+ 'data:application/vnd.mozilla.xul+xml,'],
+ // about:certerror
+ ['https://untrusted.example.com/somepage.html',
+ 'https://untrusted.example.com/somepage.html']
+];
+
+function generatorTest() {
+ gBrowser.selectedTab = gBrowser.addTab();
+ let browser = gBrowser.selectedBrowser;
+ browser.stop(); // stop the about:blank load.
+
+ browser.addEventListener("DOMContentLoaded", event => {
+ if (event.originalTarget != browser.contentDocument ||
+ event.target.location.href == "about:blank") {
+ info("skipping spurious load event");
+ return;
+ }
+ nextStep();
+ }, true);
+ registerCleanupFunction(function () {
+ browser.removeEventListener("DOMContentLoaded", nextStep, true);
+ gBrowser.removeCurrentTab();
+ });
+
+ // Test that a bookmark of each URI gets the corresponding default title.
+ for (let i = 0; i < tests.length; ++i) {
+ let [uri, title] = tests[i];
+ content.location = uri;
+ yield;
+ checkBookmark(uri, title);
+ }
+
+ // Network failure test: now that dummy_page.html is in history, bookmarking
+ // it should give the last known page title as the default bookmark title.
+
+ // Simulate a network outage with offline mode. (Localhost is still
+ // accessible in offline mode, so disable the test proxy as well.)
+ BrowserOffline.toggleOfflineStatus();
+ let proxy = Services.prefs.getIntPref('network.proxy.type');
+ Services.prefs.setIntPref('network.proxy.type', 0);
+ registerCleanupFunction(function () {
+ BrowserOffline.toggleOfflineStatus();
+ Services.prefs.setIntPref('network.proxy.type', proxy);
+ });
+
+ // LOAD_FLAGS_BYPASS_CACHE isn't good enough. So clear the cache.
+ Services.cache.evictEntries(Services.cache.STORE_ANYWHERE);
+
+ let [uri, title] = tests[0];
+ content.location = uri;
+ yield;
+ // The offline mode test is only good if the page failed to load.
+ is(content.document.documentURI.substring(0, 14), 'about:neterror',
+ "Offline mode successfully simulated network outage.");
+ checkBookmark(uri, title);
+}
+
+// Bookmark the current page and confirm that the new bookmark has the expected
+// title. (Then delete the bookmark.)
+function checkBookmark(uri, expected_title) {
+ is(gBrowser.selectedBrowser.currentURI.spec, uri,
+ "Trying to bookmark the expected uri");
+ PlacesCommandHook.bookmarkCurrentPage(false);
+
+ let id = PlacesUtils.getMostRecentBookmarkForURI(PlacesUtils._uri(uri));
+ ok(id > 0, "Found the expected bookmark");
+ let title = PlacesUtils.bookmarks.getItemTitle(id);
+ is(title, expected_title, "Bookmark got a good default title.");
+
+ PlacesUtils.bookmarks.removeItem(id);
+}
diff --git a/browser/base/content/test/browser_bug304198.js b/browser/base/content/test/browser_bug304198.js
new file mode 100644
index 000000000..a40034148
--- /dev/null
+++ b/browser/base/content/test/browser_bug304198.js
@@ -0,0 +1,125 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ let charsToDelete, deletedURLTab, fullURLTab, partialURLTab, testPartialURL, testURL;
+
+ charsToDelete = 5;
+ deletedURLTab = gBrowser.addTab();
+ fullURLTab = gBrowser.addTab();
+ partialURLTab = gBrowser.addTab();
+ testURL = "http://example.org/browser/browser/base/content/test/dummy_page.html";
+
+ function cleanUp() {
+ gBrowser.removeTab(fullURLTab);
+ gBrowser.removeTab(partialURLTab);
+ gBrowser.removeTab(deletedURLTab);
+ }
+
+ function cycleTabs() {
+ gBrowser.selectedTab = fullURLTab;
+ is(gURLBar.value, testURL, 'gURLBar.value should be testURL after switching back to fullURLTab');
+
+ gBrowser.selectedTab = partialURLTab;
+ is(gURLBar.value, testPartialURL, 'gURLBar.value should be testPartialURL after switching back to partialURLTab');
+
+ gBrowser.selectedTab = deletedURLTab;
+ is(gURLBar.value, '', 'gURLBar.value should be "" after switching back to deletedURLTab');
+
+ gBrowser.selectedTab = fullURLTab;
+ is(gURLBar.value, testURL, 'gURLBar.value should be testURL after switching back to fullURLTab');
+ }
+
+ // function borrowed from browser_bug386835.js
+ function load(tab, url, cb) {
+ tab.linkedBrowser.addEventListener("load", function (event) {
+ event.currentTarget.removeEventListener("load", arguments.callee, true);
+ cb();
+ }, true);
+ tab.linkedBrowser.loadURI(url);
+ }
+
+ function urlbarBackspace(cb) {
+ gBrowser.selectedBrowser.focus();
+ gURLBar.addEventListener("focus", function () {
+ gURLBar.removeEventListener("focus", arguments.callee, false);
+ gURLBar.addEventListener("input", function () {
+ gURLBar.removeEventListener("input", arguments.callee, false);
+ cb();
+ }, false);
+ executeSoon(function () {
+ EventUtils.synthesizeKey("VK_BACK_SPACE", {});
+ });
+ }, false);
+ gURLBar.focus();
+ }
+
+ function prepareDeletedURLTab(cb) {
+ gBrowser.selectedTab = deletedURLTab;
+ is(gURLBar.value, testURL, 'gURLBar.value should be testURL after initial switch to deletedURLTab');
+
+ // simulate the user removing the whole url from the location bar
+ gPrefService.setBoolPref("browser.urlbar.clickSelectsAll", true);
+
+ urlbarBackspace(function () {
+ is(gURLBar.value, "", 'gURLBar.value should be "" (just set)');
+ if (gPrefService.prefHasUserValue("browser.urlbar.clickSelectsAll"))
+ gPrefService.clearUserPref("browser.urlbar.clickSelectsAll");
+ cb();
+ });
+ }
+
+ function prepareFullURLTab(cb) {
+ gBrowser.selectedTab = fullURLTab;
+ is(gURLBar.value, testURL, 'gURLBar.value should be testURL after initial switch to fullURLTab');
+ cb();
+ }
+
+ function preparePartialURLTab(cb) {
+ gBrowser.selectedTab = partialURLTab;
+ is(gURLBar.value, testURL, 'gURLBar.value should be testURL after initial switch to partialURLTab');
+
+ // simulate the user removing part of the url from the location bar
+ gPrefService.setBoolPref("browser.urlbar.clickSelectsAll", false);
+
+ var deleted = 0;
+ urlbarBackspace(function () {
+ deleted++;
+ if (deleted < charsToDelete) {
+ urlbarBackspace(arguments.callee);
+ } else {
+ is(gURLBar.value, testPartialURL, "gURLBar.value should be testPartialURL (just set)");
+ if (gPrefService.prefHasUserValue("browser.urlbar.clickSelectsAll"))
+ gPrefService.clearUserPref("browser.urlbar.clickSelectsAll");
+ cb();
+ }
+ });
+ }
+
+ function runTests() {
+ testURL = gURLBar.trimValue(testURL);
+ testPartialURL = testURL.substr(0, (testURL.length - charsToDelete));
+
+ // prepare the three tabs required by this test
+ prepareFullURLTab(function () {
+ preparePartialURLTab(function () {
+ prepareDeletedURLTab(function () {
+ // now cycle the tabs and make sure everything looks good
+ cycleTabs();
+ cleanUp();
+ finish();
+ });
+ });
+ });
+ }
+
+ load(deletedURLTab, testURL, function() {
+ load(fullURLTab, testURL, function() {
+ load(partialURLTab, testURL, runTests);
+ });
+ });
+}
+
diff --git a/browser/base/content/test/browser_bug321000.js b/browser/base/content/test/browser_bug321000.js
new file mode 100644
index 000000000..99c1d51a5
--- /dev/null
+++ b/browser/base/content/test/browser_bug321000.js
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const kTestString = " hello hello \n world\nworld ";
+
+var gTests = [
+
+ { desc: "Urlbar strips newlines and surrounding whitespace",
+ element: gURLBar,
+ expected: kTestString.replace(/\s*\n\s*/g,'')
+ },
+
+ { desc: "Searchbar replaces newlines with spaces",
+ element: document.getElementById('searchbar'),
+ expected: kTestString.replace('\n',' ','g')
+ },
+
+];
+
+// Test for bug 23485 and bug 321000.
+// Urlbar should strip newlines,
+// search bar should replace newlines with spaces.
+function test() {
+ waitForExplicitFinish();
+
+ let cbHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].
+ getService(Ci.nsIClipboardHelper);
+
+ // Put a multi-line string in the clipboard.
+ // Setting the clipboard value is an async OS operation, so we need to poll
+ // the clipboard for valid data before going on.
+ waitForClipboard(kTestString, function() { cbHelper.copyString(kTestString, document); },
+ next_test, finish);
+}
+
+function next_test() {
+ if (gTests.length)
+ test_paste(gTests.shift());
+ else
+ finish();
+}
+
+function test_paste(aCurrentTest) {
+ var element = aCurrentTest.element;
+
+ // Register input listener.
+ var inputListener = {
+ test: aCurrentTest,
+ handleEvent: function(event) {
+ element.removeEventListener(event.type, this, false);
+
+ is(element.value, this.test.expected, this.test.desc);
+
+ // Clear the field and go to next test.
+ element.value = "";
+ setTimeout(next_test, 0);
+ }
+ }
+ element.addEventListener("input", inputListener, false);
+
+ // Focus the window.
+ window.focus();
+ gBrowser.selectedBrowser.focus();
+
+ // Focus the element and wait for focus event.
+ info("About to focus " + element.id);
+ element.addEventListener("focus", function() {
+ element.removeEventListener("focus", arguments.callee, false);
+ executeSoon(function() {
+ // Pasting is async because the Accel+V codepath ends up going through
+ // nsDocumentViewer::FireClipboardEvent.
+ info("Pasting into " + element.id);
+ EventUtils.synthesizeKey("v", { accelKey: true });
+ });
+ }, false);
+ element.focus();
+}
diff --git a/browser/base/content/test/browser_bug329212.js b/browser/base/content/test/browser_bug329212.js
new file mode 100644
index 000000000..5dc22c4aa
--- /dev/null
+++ b/browser/base/content/test/browser_bug329212.js
@@ -0,0 +1,43 @@
+function test () {
+
+ waitForExplicitFinish();
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", function () {
+ gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+ let doc = gBrowser.contentDocument;
+ let tooltip = document.getElementById("aHTMLTooltip");
+
+ ok(tooltip.fillInPageTooltip(doc.getElementById("svg1")), "should get title");
+ is(tooltip.getAttribute("label"), "This is a non-root SVG element title");
+
+ ok(tooltip.fillInPageTooltip(doc.getElementById("text1")), "should get title");
+ is(tooltip.getAttribute("label"), "\n\n\n This is a title\n\n ");
+
+ ok(!tooltip.fillInPageTooltip(doc.getElementById("text2")), "should not get title");
+
+ ok(!tooltip.fillInPageTooltip(doc.getElementById("text3")), "should not get title");
+
+ ok(tooltip.fillInPageTooltip(doc.getElementById("link1")), "should get title");
+ is(tooltip.getAttribute("label"), "\n This is a title\n ");
+ ok(tooltip.fillInPageTooltip(doc.getElementById("text4")), "should get title");
+ is(tooltip.getAttribute("label"), "\n This is a title\n ");
+
+ ok(!tooltip.fillInPageTooltip(doc.getElementById("link2")), "should not get title");
+
+ ok(tooltip.fillInPageTooltip(doc.getElementById("link3")), "should get title");
+ isnot(tooltip.getAttribute("label"), "");
+
+ ok(tooltip.fillInPageTooltip(doc.getElementById("link4")), "should get title");
+ is(tooltip.getAttribute("label"), "This is an xlink:title attribute");
+
+ ok(!tooltip.fillInPageTooltip(doc.getElementById("text5")), "should not get title");
+
+ gBrowser.removeCurrentTab();
+ finish();
+ }, true);
+
+ content.location =
+ "http://mochi.test:8888/browser/browser/base/content/test/title_test.svg";
+}
+
diff --git a/browser/base/content/test/browser_bug356571.js b/browser/base/content/test/browser_bug356571.js
new file mode 100644
index 000000000..aeb07c785
--- /dev/null
+++ b/browser/base/content/test/browser_bug356571.js
@@ -0,0 +1,91 @@
+// Bug 356571 - loadOneOrMoreURIs gives up if one of the URLs has an unknown protocol
+
+const Cr = Components.results;
+const Cm = Components.manager;
+
+// Set to true when docShell alerts for unknown protocol error
+var didFail = false;
+
+// Override Alert to avoid blocking the test due to unknown protocol error
+const kPromptServiceUUID = "{6cc9c9fe-bc0b-432b-a410-253ef8bcc699}";
+const kPromptServiceContractID = "@mozilla.org/embedcomp/prompt-service;1";
+
+// Save original prompt service factory
+const kPromptServiceFactory = Cm.getClassObject(Cc[kPromptServiceContractID],
+ Ci.nsIFactory);
+
+let fakePromptServiceFactory = {
+ createInstance: function(aOuter, aIid) {
+ if (aOuter != null)
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ return promptService.QueryInterface(aIid);
+ }
+};
+
+let promptService = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIPromptService]),
+ alert: function() {
+ didFail = true;
+ }
+};
+
+/* FIXME
+Cm.QueryInterface(Ci.nsIComponentRegistrar)
+ .registerFactory(Components.ID(kPromptServiceUUID), "Prompt Service",
+ kPromptServiceContractID, fakePromptServiceFactory);
+*/
+
+const kCompleteState = Ci.nsIWebProgressListener.STATE_STOP +
+ Ci.nsIWebProgressListener.STATE_IS_NETWORK;
+
+const kDummyPage = "http://example.org/browser/browser/base/content/test/dummy_page.html";
+const kURIs = [
+ "bad://www.mozilla.org/",
+ kDummyPage,
+ kDummyPage,
+];
+
+var gProgressListener = {
+ _runCount: 0,
+ onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
+ if ((aStateFlags & kCompleteState) == kCompleteState) {
+ if (++this._runCount != kURIs.length)
+ return;
+ // Check we failed on unknown protocol (received an alert from docShell)
+ ok(didFail, "Correctly failed on unknown protocol");
+ // Check we opened all tabs
+ ok(gBrowser.tabs.length == kURIs.length, "Correctly opened all expected tabs");
+ finishTest();
+ }
+ }
+}
+
+function test() {
+ todo(false, "temp. disabled");
+ return; /* FIXME */
+ waitForExplicitFinish();
+ // Wait for all tabs to finish loading
+ gBrowser.addTabsProgressListener(gProgressListener);
+ loadOneOrMoreURIs(kURIs.join("|"));
+}
+
+function finishTest() {
+ // Unregister the factory so we do not leak
+ Cm.QueryInterface(Ci.nsIComponentRegistrar)
+ .unregisterFactory(Components.ID(kPromptServiceUUID),
+ fakePromptServiceFactory);
+
+ // Restore the original factory
+ Cm.QueryInterface(Ci.nsIComponentRegistrar)
+ .registerFactory(Components.ID(kPromptServiceUUID), "Prompt Service",
+ kPromptServiceContractID, kPromptServiceFactory);
+
+ // Remove the listener
+ gBrowser.removeTabsProgressListener(gProgressListener);
+
+ // Close opened tabs
+ for (var i = gBrowser.tabs.length-1; i > 0; i--)
+ gBrowser.removeTab(gBrowser.tabs[i]);
+
+ finish();
+}
diff --git a/browser/base/content/test/browser_bug380960.js b/browser/base/content/test/browser_bug380960.js
new file mode 100644
index 000000000..e5be919b1
--- /dev/null
+++ b/browser/base/content/test/browser_bug380960.js
@@ -0,0 +1,91 @@
+function test() {
+ gBrowser.tabContainer.addEventListener("TabOpen", tabAdded, false);
+
+ var tab = gBrowser.addTab("about:blank", { skipAnimation: true });
+ gBrowser.removeTab(tab);
+ is(tab.parentNode, null, "tab removed immediately");
+
+ tab = gBrowser.addTab("about:blank", { skipAnimation: true });
+ gBrowser.removeTab(tab, { animate: true });
+ gBrowser.removeTab(tab);
+ is(tab.parentNode, null, "tab removed immediately when calling removeTab again after the animation was kicked off");
+
+ waitForExplicitFinish();
+
+ Services.prefs.setBoolPref("browser.tabs.animate", true);
+
+// preperForNextText();
+ todo(false, "async tests disabled because of intermittent failures (bug 585361)");
+ cleanup();
+}
+
+function tabAdded() {
+ info("tab added");
+}
+
+function cleanup() {
+ if (Services.prefs.prefHasUserValue("browser.tabs.animate"))
+ Services.prefs.clearUserPref("browser.tabs.animate");
+ gBrowser.tabContainer.removeEventListener("TabOpen", tabAdded, false);
+ finish();
+}
+
+var asyncTests = [
+ function (tab) {
+ info("closing tab with middle click");
+ EventUtils.synthesizeMouse(tab, 2, 2, { button: 1 });
+ },
+ function (tab) {
+ info("closing tab with accel+w");
+ gBrowser.selectedTab = tab;
+ gBrowser.selectedBrowser.focus();
+ EventUtils.synthesizeKey("w", { accelKey: true });
+ },
+ function (tab) {
+ info("closing tab by clicking the tab close button");
+ gBrowser.selectedTab = tab;
+ var button = document.getAnonymousElementByAttribute(tab, "anonid", "close-button");
+ EventUtils.synthesizeMouse(button, 2, 2, {});
+ }
+];
+
+function preperForNextText() {
+ info("tests left: " + asyncTests.length + "; starting next");
+ var tab = gBrowser.addTab("about:blank", { skipAnimation: true });
+ executeSoon(function () {
+ nextAsyncText(tab);
+ });
+}
+
+function nextAsyncText(tab) {
+ var gotCloseEvent = false;
+
+ tab.addEventListener("TabClose", function () {
+ tab.removeEventListener("TabClose", arguments.callee, false);
+ info("got TabClose event");
+ gotCloseEvent = true;
+
+ const DEFAULT_ANIMATION_LENGTH = 250;
+ const MAX_WAIT_TIME = DEFAULT_ANIMATION_LENGTH * 7;
+ var polls = Math.ceil(MAX_WAIT_TIME / DEFAULT_ANIMATION_LENGTH);
+ var pollTabRemoved = setInterval(function () {
+ --polls;
+ if (tab.parentNode && polls > 0)
+ return;
+ clearInterval(pollTabRemoved);
+
+ is(tab.parentNode, null, "tab removed after at most " + MAX_WAIT_TIME + " ms");
+
+ if (asyncTests.length)
+ preperForNextText();
+ else
+ cleanup();
+ }, DEFAULT_ANIMATION_LENGTH);
+ }, false);
+
+ asyncTests.shift()(tab);
+
+ ok(gotCloseEvent, "got the close event syncronously");
+
+ is(tab.parentNode, gBrowser.tabContainer, "tab still exists when it's about to be removed asynchronously");
+}
diff --git a/browser/base/content/test/browser_bug386835.js b/browser/base/content/test/browser_bug386835.js
new file mode 100644
index 000000000..0f0bae2aa
--- /dev/null
+++ b/browser/base/content/test/browser_bug386835.js
@@ -0,0 +1,89 @@
+var gTestPage = "http://example.org/browser/browser/base/content/test/dummy_page.html";
+var gTestImage = "http://example.org/browser/browser/base/content/test/moz.png";
+var gTab1, gTab2, gTab3;
+var gLevel;
+const BACK = 0;
+const FORWARD = 1;
+
+function test() {
+ waitForExplicitFinish();
+
+ Task.spawn(function () {
+ gTab1 = gBrowser.addTab(gTestPage);
+ gTab2 = gBrowser.addTab();
+ gTab3 = gBrowser.addTab();
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
+ yield FullZoomHelper.load(gTab1, gTestPage);
+ yield FullZoomHelper.load(gTab2, gTestPage);
+ }).then(secondPageLoaded, FullZoomHelper.failAndContinue(finish));
+}
+
+function secondPageLoaded() {
+ Task.spawn(function () {
+ FullZoomHelper.zoomTest(gTab1, 1, "Initial zoom of tab 1 should be 1");
+ FullZoomHelper.zoomTest(gTab2, 1, "Initial zoom of tab 2 should be 1");
+ FullZoomHelper.zoomTest(gTab3, 1, "Initial zoom of tab 3 should be 1");
+
+ // Now have three tabs, two with the test page, one blank. Tab 1 is selected
+ // Zoom tab 1
+ FullZoom.enlarge();
+ gLevel = ZoomManager.getZoomForBrowser(gBrowser.getBrowserForTab(gTab1));
+
+ ok(gLevel > 1, "New zoom for tab 1 should be greater than 1");
+ FullZoomHelper.zoomTest(gTab2, 1, "Zooming tab 1 should not affect tab 2");
+ FullZoomHelper.zoomTest(gTab3, 1, "Zooming tab 1 should not affect tab 3");
+
+ yield FullZoomHelper.load(gTab3, gTestPage);
+ }).then(thirdPageLoaded, FullZoomHelper.failAndContinue(finish));
+}
+
+function thirdPageLoaded() {
+ Task.spawn(function () {
+ FullZoomHelper.zoomTest(gTab1, gLevel, "Tab 1 should still be zoomed");
+ FullZoomHelper.zoomTest(gTab2, 1, "Tab 2 should still not be affected");
+ FullZoomHelper.zoomTest(gTab3, gLevel, "Tab 3 should have zoomed as it was loading in the background");
+
+ // Switching to tab 2 should update its zoom setting.
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2);
+ FullZoomHelper.zoomTest(gTab1, gLevel, "Tab 1 should still be zoomed");
+ FullZoomHelper.zoomTest(gTab2, gLevel, "Tab 2 should be zoomed now");
+ FullZoomHelper.zoomTest(gTab3, gLevel, "Tab 3 should still be zoomed");
+
+ yield FullZoomHelper.load(gTab1, gTestImage);
+ }).then(imageLoaded, FullZoomHelper.failAndContinue(finish));
+}
+
+function imageLoaded() {
+ Task.spawn(function () {
+ FullZoomHelper.zoomTest(gTab1, 1, "Zoom should be 1 when image was loaded in the background");
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
+ FullZoomHelper.zoomTest(gTab1, 1, "Zoom should still be 1 when tab with image is selected");
+ }).then(imageZoomSwitch, FullZoomHelper.failAndContinue(finish));
+}
+
+function imageZoomSwitch() {
+ Task.spawn(function () {
+ yield FullZoomHelper.navigate(BACK);
+ yield FullZoomHelper.navigate(FORWARD);
+ FullZoomHelper.zoomTest(gTab1, 1, "Tab 1 should not be zoomed when an image loads");
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2);
+ FullZoomHelper.zoomTest(gTab1, 1, "Tab 1 should still not be zoomed when deselected");
+ }).then(finishTest, FullZoomHelper.failAndContinue(finish));
+}
+
+var finishTestStarted = false;
+function finishTest() {
+ Task.spawn(function () {
+ ok(!finishTestStarted, "finishTest called more than once");
+ finishTestStarted = true;
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
+ FullZoom.reset();
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab1);
+ FullZoom.reset();
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab2);
+ FullZoom.reset();
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab3);
+ }).then(finish, FullZoomHelper.failAndContinue(finish));
+}
diff --git a/browser/base/content/test/browser_bug405137.js b/browser/base/content/test/browser_bug405137.js
new file mode 100644
index 000000000..a28d1c030
--- /dev/null
+++ b/browser/base/content/test/browser_bug405137.js
@@ -0,0 +1,5 @@
+function test(){
+ var tab = gBrowser.addTab();
+ ok(tab.getAttribute("closetabtext") != "", "tab has non-empty closetabtext");
+ gBrowser.removeTab(tab);
+}
diff --git a/browser/base/content/test/browser_bug406216.js b/browser/base/content/test/browser_bug406216.js
new file mode 100644
index 000000000..db3b1bffa
--- /dev/null
+++ b/browser/base/content/test/browser_bug406216.js
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * "TabClose" event is possibly used for closing related tabs of the current.
+ * "removeTab" method should work correctly even if the number of tabs are
+ * changed while "TabClose" event.
+ */
+
+var count = 0;
+const URIS = ["about:config",
+ "about:plugins",
+ "about:buildconfig",
+ "data:text/html,<title>OK</title>"];
+
+function test() {
+ waitForExplicitFinish();
+ URIS.forEach(addTab);
+}
+
+function addTab(aURI, aIndex) {
+ var tab = gBrowser.addTab(aURI);
+ if (aIndex == 0)
+ gBrowser.removeTab(gBrowser.tabs[0]);
+
+ tab.linkedBrowser.addEventListener("load", function (event) {
+ event.currentTarget.removeEventListener("load", arguments.callee, true);
+ if (++count == URIS.length)
+ executeSoon(doTabsTest);
+ }, true);
+}
+
+function doTabsTest() {
+ is(gBrowser.tabs.length, URIS.length, "Correctly opened all expected tabs");
+
+ // sample of "close related tabs" feature
+ gBrowser.tabContainer.addEventListener("TabClose", function (event) {
+ event.currentTarget.removeEventListener("TabClose", arguments.callee, true);
+ var closedTab = event.originalTarget;
+ var scheme = closedTab.linkedBrowser.currentURI.scheme;
+ Array.slice(gBrowser.tabs).forEach(function (aTab) {
+ if (aTab != closedTab && aTab.linkedBrowser.currentURI.scheme == scheme)
+ gBrowser.removeTab(aTab);
+ });
+ }, true);
+
+ gBrowser.removeTab(gBrowser.tabs[0]);
+ is(gBrowser.tabs.length, 1, "Related tabs are not closed unexpectedly");
+
+ gBrowser.addTab("about:blank");
+ gBrowser.removeTab(gBrowser.tabs[0]);
+ finish();
+}
diff --git a/browser/base/content/test/browser_bug409481.js b/browser/base/content/test/browser_bug409481.js
new file mode 100644
index 000000000..ade9d2099
--- /dev/null
+++ b/browser/base/content/test/browser_bug409481.js
@@ -0,0 +1,83 @@
+function test() {
+ waitForExplicitFinish();
+
+ // XXX This looks a bit odd, but is needed to avoid throwing when removing the
+ // event listeners below. See bug 310955.
+ document.getElementById("sidebar").addEventListener("load", delayedOpenUrl, true);
+ toggleSidebar("viewWebPanelsSidebar", true);
+}
+
+function delayedOpenUrl() {
+ ok(true, "Ran delayedOpenUrl");
+ setTimeout(openPanelUrl, 100);
+}
+
+function openPanelUrl(event) {
+ ok(!document.getElementById("sidebar-box").hidden, "Sidebar showing");
+
+ var sidebar = document.getElementById("sidebar");
+ var root = sidebar.contentDocument.documentElement;
+ ok(root.nodeName != "parsererror", "Sidebar is well formed");
+
+ sidebar.removeEventListener("load", delayedOpenUrl, true);
+ // XXX See comment above
+ sidebar.contentDocument.addEventListener("load", delayedRunTest, true);
+ var url = 'data:text/html,<div%20id="test_bug409481">Content!</div><a id="link" href="http://www.example.com/ctest">Link</a><input id="textbox">';
+ sidebar.contentWindow.loadWebPanel(url);
+}
+
+function delayedRunTest() {
+ ok(true, "Ran delayedRunTest");
+ setTimeout(runTest, 100);
+}
+
+function runTest(event) {
+ var sidebar = document.getElementById("sidebar");
+ sidebar.contentDocument.removeEventListener("load", delayedRunTest, true);
+
+ var browser = sidebar.contentDocument.getElementById("web-panels-browser");
+ var div = browser && browser.contentDocument.getElementById("test_bug409481");
+ ok(div && div.textContent == "Content!", "Sidebar content loaded");
+
+ var link = browser && browser.contentDocument.getElementById("link");
+ sidebar.contentDocument.addEventListener("popupshown", contextMenuOpened, false);
+
+ EventUtils.synthesizeMouseAtCenter(link, { type: "contextmenu", button: 2 }, browser.contentWindow);
+}
+
+function contextMenuOpened()
+{
+ var sidebar = document.getElementById("sidebar");
+ sidebar.contentDocument.removeEventListener("popupshown", contextMenuOpened, false);
+
+ var copyLinkCommand = sidebar.contentDocument.getElementById("context-copylink");
+ copyLinkCommand.addEventListener("command", copyLinkCommandExecuted, false);
+ copyLinkCommand.doCommand();
+}
+
+function copyLinkCommandExecuted(event)
+{
+ event.target.removeEventListener("command", copyLinkCommandExecuted, false);
+
+ var sidebar = document.getElementById("sidebar");
+ var browser = sidebar.contentDocument.getElementById("web-panels-browser");
+ var textbox = browser && browser.contentDocument.getElementById("textbox");
+ textbox.focus();
+ document.commandDispatcher.getControllerForCommand("cmd_paste").doCommand("cmd_paste");
+ is(textbox.value, "http://www.example.com/ctest", "copy link command");
+
+ sidebar.contentDocument.addEventListener("popuphidden", contextMenuClosed, false);
+ event.target.parentNode.hidePopup();
+}
+
+function contextMenuClosed()
+{
+ var sidebar = document.getElementById("sidebar");
+ sidebar.contentDocument.removeEventListener("popuphidden", contextMenuClosed, false);
+
+ toggleSidebar("viewWebPanelsSidebar");
+
+ ok(document.getElementById("sidebar-box").hidden, "Sidebar successfully hidden");
+
+ finish();
+}
diff --git a/browser/base/content/test/browser_bug409624.js b/browser/base/content/test/browser_bug409624.js
new file mode 100644
index 000000000..2ea177794
--- /dev/null
+++ b/browser/base/content/test/browser_bug409624.js
@@ -0,0 +1,73 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
+ "resource://gre/modules/FormHistory.jsm");
+
+function test() {
+ waitForExplicitFinish();
+
+ // This test relies on the form history being empty to start with delete
+ // all the items first.
+ FormHistory.update({ op: "remove" },
+ { handleError: function (error) {
+ do_throw("Error occurred updating form history: " + error);
+ },
+ handleCompletion: function (reason) { if (!reason) test2(); },
+ });
+}
+
+function test2()
+{
+ let prefService = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch2);
+
+ let findBar = gFindBar;
+ let textbox = gFindBar.getElement("findbar-textbox");
+
+ let tempScope = {};
+ Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://browser/content/sanitize.js", tempScope);
+ let Sanitizer = tempScope.Sanitizer;
+ let s = new Sanitizer();
+ s.prefDomain = "privacy.cpd.";
+ let prefBranch = prefService.getBranch(s.prefDomain);
+
+ prefBranch.setBoolPref("cache", false);
+ prefBranch.setBoolPref("cookies", false);
+ prefBranch.setBoolPref("downloads", false);
+ prefBranch.setBoolPref("formdata", true);
+ prefBranch.setBoolPref("history", false);
+ prefBranch.setBoolPref("offlineApps", false);
+ prefBranch.setBoolPref("passwords", false);
+ prefBranch.setBoolPref("sessions", false);
+ prefBranch.setBoolPref("siteSettings", false);
+
+ // Sanitize now so we can test that canClear is correct. Formdata is cleared asynchronously.
+ s.sanitize().then(function() {
+ s.canClearItem("formdata", clearDone1, s);
+ });
+}
+
+function clearDone1(aItemName, aResult, aSanitizer)
+{
+ ok(!aResult, "pre-test baseline for sanitizer");
+ gFindBar.getElement("findbar-textbox").value = "m";
+ aSanitizer.canClearItem("formdata", inputEntered, aSanitizer);
+}
+
+function inputEntered(aItemName, aResult, aSanitizer)
+{
+ ok(aResult, "formdata can be cleared after input");
+ aSanitizer.sanitize().then(function() {
+ aSanitizer.canClearItem("formdata", clearDone2);
+ });
+}
+
+function clearDone2(aItemName, aResult)
+{
+ is(gFindBar.getElement("findbar-textbox").value, "", "findBar textbox should be empty after sanitize");
+ ok(!aResult, "canClear now false after sanitize");
+ finish();
+}
diff --git a/browser/base/content/test/browser_bug413915.js b/browser/base/content/test/browser_bug413915.js
new file mode 100644
index 000000000..1f22043b9
--- /dev/null
+++ b/browser/base/content/test/browser_bug413915.js
@@ -0,0 +1,59 @@
+function test() {
+ var exampleUri = makeURI("http://example.com/");
+ var secman = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager);
+ var principal = secman.getSimpleCodebasePrincipal(exampleUri);
+
+ function testIsFeed(aTitle, aHref, aType, aKnown) {
+ var link = { title: aTitle, href: aHref, type: aType };
+ return isValidFeed(link, principal, aKnown);
+ }
+
+ var href = "http://example.com/feed/";
+ var atomType = "application/atom+xml";
+ var funkyAtomType = " aPPLICAtion/Atom+XML ";
+ var rssType = "application/rss+xml";
+ var funkyRssType = " Application/RSS+XML ";
+ var rdfType = "application/rdf+xml";
+ var texmlType = "text/xml";
+ var appxmlType = "application/xml";
+ var noRss = "Foo";
+ var rss = "RSS";
+
+ // things that should be valid
+ ok(testIsFeed(noRss, href, atomType, false) == atomType,
+ "detect Atom feed");
+ ok(testIsFeed(noRss, href, funkyAtomType, false) == atomType,
+ "clean up and detect Atom feed");
+ ok(testIsFeed(noRss, href, rssType, false) == rssType,
+ "detect RSS feed");
+ ok(testIsFeed(noRss, href, funkyRssType, false) == rssType,
+ "clean up and detect RSS feed");
+
+ // things that should not be feeds
+ ok(testIsFeed(noRss, href, rdfType, false) == null,
+ "should not detect RDF non-feed");
+ ok(testIsFeed(rss, href, rdfType, false) == null,
+ "should not detect RDF feed from type and title");
+ ok(testIsFeed(noRss, href, texmlType, false) == null,
+ "should not detect text/xml non-feed");
+ ok(testIsFeed(rss, href, texmlType, false) == null,
+ "should not detect text/xml feed from type and title");
+ ok(testIsFeed(noRss, href, appxmlType, false) == null,
+ "should not detect application/xml non-feed");
+ ok(testIsFeed(rss, href, appxmlType, false) == null,
+ "should not detect application/xml feed from type and title");
+
+ // security check only, returns cleaned up type or "application/rss+xml"
+ ok(testIsFeed(noRss, href, atomType, true) == atomType,
+ "feed security check should return Atom type");
+ ok(testIsFeed(noRss, href, funkyAtomType, true) == atomType,
+ "feed security check should return cleaned up Atom type");
+ ok(testIsFeed(noRss, href, rssType, true) == rssType,
+ "feed security check should return RSS type");
+ ok(testIsFeed(noRss, href, funkyRssType, true) == rssType,
+ "feed security check should return cleaned up RSS type");
+ ok(testIsFeed(noRss, href, "", true) == rssType,
+ "feed security check without type should return RSS type");
+ ok(testIsFeed(noRss, href, "garbage", true) == "garbage",
+ "feed security check with garbage type should return garbage");
+}
diff --git a/browser/base/content/test/browser_bug416661.js b/browser/base/content/test/browser_bug416661.js
new file mode 100644
index 000000000..0324ce0f3
--- /dev/null
+++ b/browser/base/content/test/browser_bug416661.js
@@ -0,0 +1,43 @@
+var tabElm, zoomLevel;
+function start_test_prefNotSet() {
+ Task.spawn(function () {
+ is(ZoomManager.zoom, 1, "initial zoom level should be 1");
+ FullZoom.enlarge();
+
+ //capture the zoom level to test later
+ zoomLevel = ZoomManager.zoom;
+ isnot(zoomLevel, 1, "zoom level should have changed");
+
+ yield FullZoomHelper.load(gBrowser.selectedTab, "http://mochi.test:8888/browser/browser/base/content/test/moz.png");
+ }).then(continue_test_prefNotSet, FullZoomHelper.failAndContinue(finish));
+}
+
+function continue_test_prefNotSet () {
+ Task.spawn(function () {
+ is(ZoomManager.zoom, 1, "zoom level pref should not apply to an image");
+ FullZoom.reset();
+
+ yield FullZoomHelper.load(gBrowser.selectedTab, "http://mochi.test:8888/browser/browser/base/content/test/zoom_test.html");
+ }).then(end_test_prefNotSet, FullZoomHelper.failAndContinue(finish));
+}
+
+function end_test_prefNotSet() {
+ Task.spawn(function () {
+ is(ZoomManager.zoom, zoomLevel, "the zoom level should have persisted");
+
+ // Reset the zoom so that other tests have a fresh zoom level
+ FullZoom.reset();
+ yield FullZoomHelper.removeTabAndWaitForLocationChange();
+ finish();
+ });
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ Task.spawn(function () {
+ tabElm = gBrowser.addTab();
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(tabElm);
+ yield FullZoomHelper.load(tabElm, "http://mochi.test:8888/browser/browser/base/content/test/zoom_test.html");
+ }).then(start_test_prefNotSet, FullZoomHelper.failAndContinue(finish));
+}
diff --git a/browser/base/content/test/browser_bug417483.js b/browser/base/content/test/browser_bug417483.js
new file mode 100644
index 000000000..ab6d73ae5
--- /dev/null
+++ b/browser/base/content/test/browser_bug417483.js
@@ -0,0 +1,26 @@
+function test() {
+ waitForExplicitFinish();
+
+ var htmlContent = "data:text/html, <iframe src='data:text/html,text text'></iframe>";
+ gBrowser.addEventListener("pageshow", onPageShow, false);
+ gBrowser.loadURI(htmlContent);
+}
+
+function onPageShow() {
+ gBrowser.removeEventListener("pageshow", onPageShow, false);
+ var frame = content.frames[0];
+ var sel = frame.getSelection();
+ var range = frame.document.createRange();
+ var tn = frame.document.body.childNodes[0];
+ range.setStart(tn , 4);
+ range.setEnd(tn , 5);
+ sel.addRange(range);
+ frame.focus();
+
+ document.popupNode = frame.document.body;
+ var contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+ var contextMenu = new nsContextMenu(contentAreaContextMenu);
+
+ ok(document.getElementById("frame-sep").hidden, "'frame-sep' should be hidden if the selection contains only spaces");
+ finish();
+}
diff --git a/browser/base/content/test/browser_bug419612.js b/browser/base/content/test/browser_bug419612.js
new file mode 100644
index 000000000..1dee59ece
--- /dev/null
+++ b/browser/base/content/test/browser_bug419612.js
@@ -0,0 +1,32 @@
+function test() {
+ waitForExplicitFinish();
+
+ Task.spawn(function () {
+ let testPage = "http://example.org/browser/browser/base/content/test/dummy_page.html";
+ let tab1 = gBrowser.addTab();
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(tab1);
+ yield FullZoomHelper.load(tab1, testPage);
+
+ let tab2 = gBrowser.addTab();
+ yield FullZoomHelper.load(tab2, testPage);
+
+ FullZoom.enlarge();
+ let tab1Zoom = ZoomManager.getZoomForBrowser(tab1.linkedBrowser);
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(tab2);
+ let tab2Zoom = ZoomManager.getZoomForBrowser(tab2.linkedBrowser);
+ is(tab2Zoom, tab1Zoom, "Zoom should affect background tabs");
+
+ gPrefService.setBoolPref("browser.zoom.updateBackgroundTabs", false);
+ FullZoom.reset();
+ gBrowser.selectedTab = tab1;
+ tab1Zoom = ZoomManager.getZoomForBrowser(tab1.linkedBrowser);
+ tab2Zoom = ZoomManager.getZoomForBrowser(tab2.linkedBrowser);
+ isnot(tab1Zoom, tab2Zoom, "Zoom should not affect background tabs");
+
+ if (gPrefService.prefHasUserValue("browser.zoom.updateBackgroundTabs"))
+ gPrefService.clearUserPref("browser.zoom.updateBackgroundTabs");
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(tab1);
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(tab2);
+ }).then(finish, FullZoomHelper.failAndContinue(finish));
+}
diff --git a/browser/base/content/test/browser_bug422590.js b/browser/base/content/test/browser_bug422590.js
new file mode 100644
index 000000000..2ecb4c0b9
--- /dev/null
+++ b/browser/base/content/test/browser_bug422590.js
@@ -0,0 +1,50 @@
+function test() {
+ waitForExplicitFinish();
+ // test the main (normal) browser window
+ testCustomize(window, testChromeless);
+}
+
+function testChromeless() {
+ // test a chromeless window
+ var newWin = openDialog(getBrowserURL(), "_blank",
+ "chrome,dialog=no,toolbar=no", "about:blank");
+ ok(newWin, "got new window");
+
+ whenDelayedStartupFinished(newWin, function () {
+ // Check that the search bar is hidden
+ var searchBar = newWin.BrowserSearch.searchBar;
+ ok(searchBar, "got search bar");
+
+ var searchBarBO = searchBar.boxObject;
+ is(searchBarBO.width, 0, "search bar hidden");
+ is(searchBarBO.height, 0, "search bar hidden");
+
+ testCustomize(newWin, function () {
+ newWin.close();
+ finish();
+ });
+ });
+}
+
+function testCustomize(aWindow, aCallback) {
+ var fileMenu = aWindow.document.getElementById("file-menu");
+ ok(fileMenu, "got file menu");
+ is(fileMenu.disabled, false, "file menu initially enabled");
+
+ openToolbarCustomizationUI(function () {
+ // Can't use the property, since the binding may have since been removed
+ // if the element is hidden (see bug 422590)
+ is(fileMenu.getAttribute("disabled"), "true",
+ "file menu is disabled during toolbar customization");
+
+ closeToolbarCustomizationUI(onClose, aWindow);
+ }, aWindow);
+
+ function onClose() {
+ is(fileMenu.getAttribute("disabled"), "false",
+ "file menu is enabled after toolbar customization");
+
+ if (aCallback)
+ aCallback();
+ }
+}
diff --git a/browser/base/content/test/browser_bug423833.js b/browser/base/content/test/browser_bug423833.js
new file mode 100644
index 000000000..d4069338b
--- /dev/null
+++ b/browser/base/content/test/browser_bug423833.js
@@ -0,0 +1,138 @@
+/* Tests for proper behaviour of "Show this frame" context menu options */
+
+// Two frames, one with text content, the other an error page
+var invalidPage = 'http://127.0.0.1:55555/';
+var validPage = 'http://example.com/';
+var testPage = 'data:text/html,<frameset cols="400,400"><frame src="' + validPage + '"><frame src="' + invalidPage + '"></frameset>';
+
+// Store the tab and window created in tests 2 and 3 respectively
+var test2tab;
+var test3window;
+
+// We use setInterval instead of setTimeout to avoid race conditions on error doc loads
+var intervalID;
+
+function test() {
+ waitForExplicitFinish();
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", test1Setup, true);
+ content.location = testPage;
+}
+
+function test1Setup() {
+ if (content.frames.length < 2 ||
+ content.frames[1].location != invalidPage)
+ // The error frame hasn't loaded yet
+ return;
+
+ gBrowser.selectedBrowser.removeEventListener("load", test1Setup, true);
+
+ var badFrame = content.frames[1];
+ document.popupNode = badFrame.document.firstChild;
+
+ var contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+ var contextMenu = new nsContextMenu(contentAreaContextMenu);
+
+ // We'd like to use another load listener here, but error pages don't fire load events
+ contextMenu.showOnlyThisFrame();
+ intervalID = setInterval(testShowOnlyThisFrame, 3000);
+}
+
+function testShowOnlyThisFrame() {
+ if (content.location.href == testPage)
+ // This is a stale event from the original page loading
+ return;
+
+ // We should now have loaded the error page frame content directly
+ // in the tab, make sure the URL is right.
+ clearInterval(intervalID);
+
+ is(content.location.href, invalidPage, "Should navigate to page url, not about:neterror");
+
+ // Go back to the frames page
+ gBrowser.addEventListener("load", test2Setup, true);
+ content.location = testPage;
+}
+
+function test2Setup() {
+ if (content.frames.length < 2 ||
+ content.frames[1].location != invalidPage)
+ // The error frame hasn't loaded yet
+ return;
+
+ gBrowser.removeEventListener("load", test2Setup, true);
+
+ // Now let's do the whole thing again, but this time for "Open frame in new tab"
+ var badFrame = content.frames[1];
+
+ document.popupNode = badFrame.document.firstChild;
+
+ var contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+ var contextMenu = new nsContextMenu(contentAreaContextMenu);
+
+ gBrowser.tabContainer.addEventListener("TabOpen", function (event) {
+ test2tab = event.target;
+ gBrowser.tabContainer.removeEventListener("TabOpen", arguments.callee, false);
+ }, false);
+ contextMenu.openFrameInTab();
+ ok(test2tab, "openFrameInTab() opened a tab");
+
+ gBrowser.selectedTab = test2tab;
+
+ intervalID = setInterval(testOpenFrameInTab, 3000);
+}
+
+function testOpenFrameInTab() {
+ if (gBrowser.contentDocument.location.href == "about:blank")
+ // Wait another cycle
+ return;
+
+ clearInterval(intervalID);
+
+ // We should now have the error page in a new, active tab.
+ is(gBrowser.contentDocument.location.href, invalidPage, "New tab should have page url, not about:neterror");
+
+ // Clear up the new tab, and punt to test 3
+ gBrowser.removeCurrentTab();
+
+ test3Setup();
+}
+
+function test3Setup() {
+ // One more time, for "Open frame in new window"
+ var badFrame = content.frames[1];
+ document.popupNode = badFrame.document.firstChild;
+
+ var contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+ var contextMenu = new nsContextMenu(contentAreaContextMenu);
+
+ Services.ww.registerNotification(function (aSubject, aTopic, aData) {
+ if (aTopic == "domwindowopened")
+ test3window = aSubject;
+ Services.ww.unregisterNotification(arguments.callee);
+ });
+
+ contextMenu.openFrame();
+
+ intervalID = setInterval(testOpenFrame, 3000);
+}
+
+function testOpenFrame() {
+ if (!test3window || test3window.content.location.href == "about:blank") {
+ info("testOpenFrame: Wait another cycle");
+ return;
+ }
+
+ clearInterval(intervalID);
+
+ is(test3window.content.location.href, invalidPage, "New window should have page url, not about:neterror");
+
+ test3window.close();
+ cleanup();
+}
+
+function cleanup() {
+ gBrowser.removeCurrentTab();
+ finish();
+}
diff --git a/browser/base/content/test/browser_bug424101.js b/browser/base/content/test/browser_bug424101.js
new file mode 100644
index 000000000..7c9599e69
--- /dev/null
+++ b/browser/base/content/test/browser_bug424101.js
@@ -0,0 +1,53 @@
+/* Make sure that the context menu appears on form elements */
+
+function test() {
+ waitForExplicitFinish();
+
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ gBrowser.selectedBrowser.addEventListener("load", function() {
+ gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+ let doc = gBrowser.contentDocument;
+ let testInput = function(type, expected) {
+ let element = doc.createElement("input");
+ element.setAttribute("type", type);
+ doc.body.appendChild(element);
+ document.popupNode = element;
+
+ let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+ let contextMenu = new nsContextMenu(contentAreaContextMenu);
+
+ is(contextMenu.shouldDisplay, expected, "context menu behavior for <input type=" + type + "> is wrong");
+ };
+ let testElement = function(tag, expected) {
+ let element = doc.createElement(tag);
+ doc.body.appendChild(element);
+ document.popupNode = element;
+
+ let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+ let contextMenu = new nsContextMenu(contentAreaContextMenu);
+
+ is(contextMenu.shouldDisplay, expected, "context menu behavior for <" + tag + "> is wrong");
+ };
+
+ testInput("text", true);
+ testInput("password", true);
+ testInput("image", true);
+ testInput("button", true);
+ testInput("submit", true);
+ testInput("reset", true);
+ testInput("checkbox", true);
+ testInput("radio", true);
+ testElement("button", true);
+ testElement("select", true);
+ testElement("option", true);
+ testElement("optgroup", true);
+
+ // cleanup
+ document.popupNode = null;
+ gBrowser.removeCurrentTab();
+ finish();
+ }, true);
+ content.location = "data:text/html,test";
+}
diff --git a/browser/base/content/test/browser_bug427559.js b/browser/base/content/test/browser_bug427559.js
new file mode 100644
index 000000000..50993f9b9
--- /dev/null
+++ b/browser/base/content/test/browser_bug427559.js
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test bug 427559 to make sure focused elements that are no longer on the page
+ * will have focus transferred to the window when changing tabs back to that
+ * tab with the now-gone element.
+ */
+
+// Default focus on a button and have it kill itself on blur
+let testPage = 'data:text/html,<body><button onblur="this.parentNode.removeChild(this);"><script>document.body.firstChild.focus();</script></body>';
+
+function test() {
+ waitForExplicitFinish();
+
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ gBrowser.selectedBrowser.addEventListener("load", function () {
+ gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+ setTimeout(function () {
+ var testPageWin = content;
+
+ // The test page loaded, so open an empty tab, select it, then restore
+ // the test tab. This causes the test page's focused element to be removed
+ // from its document.
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.removeCurrentTab();
+
+ // Make sure focus is given to the window because the element is now gone
+ is(document.commandDispatcher.focusedWindow, testPageWin,
+ "content window is focused");
+
+ gBrowser.removeCurrentTab();
+ finish();
+ }, 0);
+ }, true);
+
+ content.location = testPage;
+}
diff --git a/browser/base/content/test/browser_bug432599.js b/browser/base/content/test/browser_bug432599.js
new file mode 100644
index 000000000..d23df4bbe
--- /dev/null
+++ b/browser/base/content/test/browser_bug432599.js
@@ -0,0 +1,127 @@
+function invokeUsingCtrlD(phase) {
+ switch (phase) {
+ case 1:
+ EventUtils.synthesizeKey("d", { accelKey: true });
+ break;
+ case 2:
+ case 4:
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ break;
+ case 3:
+ EventUtils.synthesizeKey("d", { accelKey: true });
+ EventUtils.synthesizeKey("d", { accelKey: true });
+ break;
+ }
+}
+
+function invokeUsingStarButton(phase) {
+ switch (phase) {
+ case 1:
+ EventUtils.synthesizeMouseAtCenter(BookmarkingUI.star, {});
+ break;
+ case 2:
+ case 4:
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ break;
+ case 3:
+ EventUtils.synthesizeMouseAtCenter(BookmarkingUI.star,
+ { clickCount: 2 });
+ break;
+ }
+}
+
+var testURL = "data:text/plain,Content";
+var bookmarkId;
+
+function add_bookmark(aURI, aTitle) {
+ return PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
+ aURI, PlacesUtils.bookmarks.DEFAULT_INDEX,
+ aTitle);
+}
+
+// test bug 432599
+function test() {
+ waitForExplicitFinish();
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
+ gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
+ waitForStarChange(false, initTest);
+ }, true);
+
+ content.location = testURL;
+}
+
+function initTest() {
+ // First, bookmark the page.
+ bookmarkId = add_bookmark(makeURI(testURL), "Bug 432599 Test");
+
+ checkBookmarksPanel(invokers[currentInvoker], 1);
+}
+
+function waitForStarChange(aValue, aCallback) {
+ let expectedStatus = aValue ? BookmarkingUI.STATUS_STARRED
+ : BookmarkingUI.STATUS_UNSTARRED;
+ if (BookmarkingUI.status == BookmarkingUI.STATUS_UPDATING ||
+ BookmarkingUI.status != expectedStatus) {
+ info("Waiting for star button change.");
+ setTimeout(waitForStarChange, 50, aValue, aCallback);
+ return;
+ }
+ aCallback();
+}
+
+let invokers = [invokeUsingStarButton, invokeUsingCtrlD];
+let currentInvoker = 0;
+
+let initialValue;
+let initialRemoveHidden;
+
+let popupElement = document.getElementById("editBookmarkPanel");
+let titleElement = document.getElementById("editBookmarkPanelTitle");
+let removeElement = document.getElementById("editBookmarkPanelRemoveButton");
+
+function checkBookmarksPanel(invoker, phase)
+{
+ let onPopupShown = function(aEvent) {
+ if (aEvent.originalTarget == popupElement) {
+ popupElement.removeEventListener("popupshown", arguments.callee, false);
+ checkBookmarksPanel(invoker, phase + 1);
+ }
+ };
+ let onPopupHidden = function(aEvent) {
+ if (aEvent.originalTarget == popupElement) {
+ popupElement.removeEventListener("popuphidden", arguments.callee, false);
+ if (phase < 4) {
+ checkBookmarksPanel(invoker, phase + 1);
+ } else {
+ ++currentInvoker;
+ if (currentInvoker < invokers.length) {
+ checkBookmarksPanel(invokers[currentInvoker], 1);
+ } else {
+ gBrowser.removeCurrentTab();
+ PlacesUtils.bookmarks.removeItem(bookmarkId);
+ executeSoon(finish);
+ }
+ }
+ }
+ };
+
+ switch (phase) {
+ case 1:
+ case 3:
+ popupElement.addEventListener("popupshown", onPopupShown, false);
+ break;
+ case 2:
+ popupElement.addEventListener("popuphidden", onPopupHidden, false);
+ initialValue = titleElement.value;
+ initialRemoveHidden = removeElement.hidden;
+ break;
+ case 4:
+ popupElement.addEventListener("popuphidden", onPopupHidden, false);
+ is(titleElement.value, initialValue, "The bookmark panel's title should be the same");
+ is(removeElement.hidden, initialRemoveHidden, "The bookmark panel's visibility should not change");
+ break;
+ }
+ invoker(phase);
+}
diff --git a/browser/base/content/test/browser_bug435035.js b/browser/base/content/test/browser_bug435035.js
new file mode 100644
index 000000000..ae865ef12
--- /dev/null
+++ b/browser/base/content/test/browser_bug435035.js
@@ -0,0 +1,16 @@
+function test() {
+ waitForExplicitFinish();
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", function () {
+ gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+ is(document.getElementById("identity-box").className,
+ gIdentityHandler.IDENTITY_MODE_MIXED_CONTENT,
+ "identity box has class name for mixed content");
+
+ gBrowser.removeCurrentTab();
+ finish();
+ }, true);
+
+ content.location = "https://example.com/browser/browser/base/content/test/test_bug435035.html";
+}
diff --git a/browser/base/content/test/browser_bug435325.js b/browser/base/content/test/browser_bug435325.js
new file mode 100644
index 000000000..fe05c757a
--- /dev/null
+++ b/browser/base/content/test/browser_bug435325.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Ensure that clicking the button in the Offline mode neterror page makes the browser go online. See bug 435325. */
+
+let proxyPrefValue;
+
+function test() {
+ waitForExplicitFinish();
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+
+ // Go offline and disable the proxy and cache, then try to load the test URL.
+ Services.io.offline = true;
+
+ // Tests always connect to localhost, and per bug 87717, localhost is now
+ // reachable in offline mode. To avoid this, disable any proxy.
+ proxyPrefValue = Services.prefs.getIntPref("network.proxy.type");
+ Services.prefs.setIntPref("network.proxy.type", 0);
+
+ Services.prefs.setBoolPref("browser.cache.disk.enable", false);
+ Services.prefs.setBoolPref("browser.cache.memory.enable", false);
+ content.location = "http://example.com/";
+
+ window.addEventListener("DOMContentLoaded", function load() {
+ if (content.location == "about:blank") {
+ info("got about:blank, which is expected once, so return");
+ return;
+ }
+ window.removeEventListener("DOMContentLoaded", load, false);
+
+ let observer = new MutationObserver(function (mutations) {
+ for (let mutation of mutations) {
+ if (mutation.attributeName == "hasBrowserHandlers") {
+ observer.disconnect();
+ checkPage();
+ return;
+ }
+ }
+ });
+ let docElt = tab.linkedBrowser.contentDocument.documentElement;
+ observer.observe(docElt, { attributes: true });
+ }, false);
+}
+
+function checkPage() {
+ ok(Services.io.offline, "Setting Services.io.offline to true.");
+ is(gBrowser.contentDocument.documentURI.substring(0,27),
+ "about:neterror?e=netOffline", "Loading the Offline mode neterror page.");
+
+ // Now press the "Try Again" button
+ ok(gBrowser.contentDocument.getElementById("errorTryAgain"),
+ "The error page has got a #errorTryAgain element");
+
+ // Re-enable the proxy so example.com is resolved to localhost, rather than
+ // the actual example.com.
+ Services.prefs.setIntPref("network.proxy.type", proxyPrefValue);
+
+ gBrowser.contentDocument.getElementById("errorTryAgain").click();
+
+ ok(!Services.io.offline, "After clicking the Try Again button, we're back " +
+ "online.");
+
+ finish();
+}
+
+registerCleanupFunction(function() {
+ Services.prefs.setBoolPref("browser.cache.disk.enable", true);
+ Services.prefs.setBoolPref("browser.cache.memory.enable", true);
+ Services.io.offline = false;
+ gBrowser.removeCurrentTab();
+});
diff --git a/browser/base/content/test/browser_bug441778.js b/browser/base/content/test/browser_bug441778.js
new file mode 100644
index 000000000..ef68018a0
--- /dev/null
+++ b/browser/base/content/test/browser_bug441778.js
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Test the fix for bug 441778 to ensure site-specific page zoom doesn't get
+ * modified by sub-document loads of content from a different domain.
+ */
+
+function test() {
+ waitForExplicitFinish();
+
+ const TEST_PAGE_URL = 'data:text/html,<body><iframe src=""></iframe></body>';
+ const TEST_IFRAME_URL = "http://test2.example.org/";
+
+ Task.spawn(function () {
+ // Prepare the test tab
+ let tab = gBrowser.addTab();
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(tab);
+
+ let testBrowser = tab.linkedBrowser;
+
+ yield FullZoomHelper.load(tab, TEST_PAGE_URL);
+
+ // Change the zoom level and then save it so we can compare it to the level
+ // after loading the sub-document.
+ FullZoom.enlarge();
+ var zoomLevel = ZoomManager.zoom;
+
+ // Start the sub-document load.
+ let deferred = Promise.defer();
+ executeSoon(function () {
+ testBrowser.addEventListener("load", function (e) {
+ testBrowser.removeEventListener("load", arguments.callee, true);
+
+ is(e.target.defaultView.location, TEST_IFRAME_URL, "got the load event for the iframe");
+ is(ZoomManager.zoom, zoomLevel, "zoom is retained after sub-document load");
+
+ FullZoomHelper.removeTabAndWaitForLocationChange().
+ then(() => deferred.resolve());
+ }, true);
+ content.document.querySelector("iframe").src = TEST_IFRAME_URL;
+ });
+ yield deferred.promise;
+ }).then(finish, FullZoomHelper.failAndContinue(finish));
+}
diff --git a/browser/base/content/test/browser_bug455852.js b/browser/base/content/test/browser_bug455852.js
new file mode 100644
index 000000000..52798f102
--- /dev/null
+++ b/browser/base/content/test/browser_bug455852.js
@@ -0,0 +1,16 @@
+function test() {
+ is(gBrowser.tabs.length, 1, "one tab is open");
+
+ gBrowser.selectedBrowser.focus();
+ isnot(document.activeElement, gURLBar.inputField, "location bar is not focused");
+
+ var tab = gBrowser.selectedTab;
+ gPrefService.setBoolPref("browser.tabs.closeWindowWithLastTab", false);
+ EventUtils.synthesizeKey("w", { accelKey: true });
+ is(tab.parentNode, null, "ctrl+w removes the tab");
+ is(gBrowser.tabs.length, 1, "a new tab has been opened");
+ is(document.activeElement, gURLBar.inputField, "location bar is focused for the new tab");
+
+ if (gPrefService.prefHasUserValue("browser.tabs.closeWindowWithLastTab"))
+ gPrefService.clearUserPref("browser.tabs.closeWindowWithLastTab");
+}
diff --git a/browser/base/content/test/browser_bug460146.js b/browser/base/content/test/browser_bug460146.js
new file mode 100644
index 000000000..3ddaae97e
--- /dev/null
+++ b/browser/base/content/test/browser_bug460146.js
@@ -0,0 +1,51 @@
+/* Check proper image url retrieval from all kinds of elements/styles */
+
+function test() {
+ waitForExplicitFinish();
+
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ gBrowser.selectedBrowser.addEventListener("load", function () {
+ gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+ var doc = gBrowser.contentDocument;
+ var pageInfo = BrowserPageInfo(doc, "mediaTab");
+
+ pageInfo.addEventListener("load", function () {
+ pageInfo.removeEventListener("load", arguments.callee, true);
+ pageInfo.onFinished.push(function () {
+ executeSoon(function () {
+ var imageTree = pageInfo.document.getElementById("imagetree");
+ var imageRowsNum = imageTree.view.rowCount;
+
+ ok(imageTree, "Image tree is null (media tab is broken)");
+
+ ok(imageRowsNum == 7, "Number of images listed: " +
+ imageRowsNum + ", should be 7");
+
+ pageInfo.close();
+ gBrowser.removeCurrentTab();
+ finish();
+ });
+ });
+ }, true);
+ }, true);
+
+ content.location =
+ "data:text/html," +
+ "<html>" +
+ " <head>" +
+ " <title>Test for media tab</title>" +
+ " <link rel='shortcut icon' href='file:///dummy_icon.ico'>" + // Icon
+ " </head>" +
+ " <body style='background-image:url(about:logo?a);'>" + // Background
+ " <img src='file:///dummy_image.gif'>" + // Image
+ " <ul>" +
+ " <li style='list-style:url(about:logo?b);'>List Item 1</li>" + // Bullet
+ " </ul> " +
+ " <div style='-moz-border-image: url(about:logo?c) 20 20 20 20;'>test</div>" + // Border
+ " <a href='' style='cursor: url(about:logo?d),default;'>test link</a>" + // Cursor
+ " <object type='image/svg+xml' width=20 height=20 data='file:///dummy_object.svg'></object>" + // Object
+ " </body>" +
+ "</html>";
+}
diff --git a/browser/base/content/test/browser_bug462289.js b/browser/base/content/test/browser_bug462289.js
new file mode 100644
index 000000000..fcde120ff
--- /dev/null
+++ b/browser/base/content/test/browser_bug462289.js
@@ -0,0 +1,85 @@
+var tab1, tab2;
+
+function focus_in_navbar()
+{
+ var parent = document.activeElement.parentNode;
+ while (parent && parent.id != "nav-bar")
+ parent = parent.parentNode;
+
+ return parent != null;
+}
+
+function test()
+{
+ waitForExplicitFinish();
+
+ tab1 = gBrowser.addTab("about:blank", {skipAnimation: true});
+ tab2 = gBrowser.addTab("about:blank", {skipAnimation: true});
+
+ EventUtils.synthesizeMouseAtCenter(tab1, {});
+ setTimeout(step2, 0);
+}
+
+function step2()
+{
+ is(gBrowser.selectedTab, tab1, "1st click on tab1 selects tab");
+ isnot(document.activeElement, tab1, "1st click on tab1 does not activate tab");
+
+ EventUtils.synthesizeMouseAtCenter(tab1, {});
+ setTimeout(step3, 0);
+}
+
+function step3()
+{
+ is(gBrowser.selectedTab, tab1, "2nd click on selected tab1 keeps tab selected");
+ isnot(document.activeElement, tab1, "2nd click on selected tab1 does not activate tab");
+
+ if (gNavToolbox.getAttribute("tabsontop") == "true") {
+ ok(true, "[tabsontop=true] focusing URLBar then sending 1 Shift+Tab.");
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_TAB", {shiftKey: true});
+ } else {
+ ok(true, "[tabsontop=false] focusing SearchBar then sending Tab(s) until out of nav-bar.");
+ document.getElementById("searchbar").focus();
+ while (focus_in_navbar())
+ EventUtils.synthesizeKey("VK_TAB", { });
+ }
+ is(gBrowser.selectedTab, tab1, "tab key to selected tab1 keeps tab selected");
+ is(document.activeElement, tab1, "tab key to selected tab1 activates tab");
+
+ EventUtils.synthesizeMouseAtCenter(tab1, {});
+ setTimeout(step4, 0);
+}
+
+function step4()
+{
+ is(gBrowser.selectedTab, tab1, "3rd click on activated tab1 keeps tab selected");
+ is(document.activeElement, tab1, "3rd click on activated tab1 keeps tab activated");
+
+ EventUtils.synthesizeMouseAtCenter(tab2, {});
+ setTimeout(step5, 0);
+}
+
+function step5()
+{
+ // The tabbox selects a tab within a setTimeout in a bubbling mousedown event
+ // listener, and focuses the current tab if another tab previously had focus.
+ is(gBrowser.selectedTab, tab2, "click on tab2 while tab1 is activated selects tab");
+ is(document.activeElement, tab2, "click on tab2 while tab1 is activated activates tab");
+
+ ok(true, "focusing content then sending middle-button mousedown to tab2.");
+ gBrowser.selectedBrowser.focus();
+ EventUtils.synthesizeMouseAtCenter(tab2, {button: 1, type: "mousedown"});
+ setTimeout(step6, 0);
+}
+
+function step6()
+{
+ is(gBrowser.selectedTab, tab2, "middle-button mousedown on selected tab2 keeps tab selected");
+ isnot(document.activeElement, tab2, "middle-button mousedown on selected tab2 does not activate tab");
+
+ gBrowser.removeTab(tab2);
+ gBrowser.removeTab(tab1);
+
+ finish();
+}
diff --git a/browser/base/content/test/browser_bug462673.js b/browser/base/content/test/browser_bug462673.js
new file mode 100644
index 000000000..83bec6f6f
--- /dev/null
+++ b/browser/base/content/test/browser_bug462673.js
@@ -0,0 +1,53 @@
+var runs = [
+ function (win, tabbrowser, tab) {
+ is(tabbrowser.browsers.length, 2, "test_bug462673.html has opened a second tab");
+ is(tabbrowser.selectedTab, tab.nextSibling, "dependent tab is selected");
+ tabbrowser.removeTab(tab);
+ ok(win.closed, "Window is closed");
+ },
+ function (win, tabbrowser, tab) {
+ var newTab = tabbrowser.addTab();
+ var newBrowser = newTab.linkedBrowser;
+ tabbrowser.removeTab(tab);
+ ok(!win.closed, "Window stays open");
+ if (!win.closed) {
+ is(tabbrowser.tabContainer.childElementCount, 1, "Window has one tab");
+ is(tabbrowser.browsers.length, 1, "Window has one browser");
+ is(tabbrowser.selectedTab, newTab, "Remaining tab is selected");
+ is(tabbrowser.selectedBrowser, newBrowser, "Browser for remaining tab is selected");
+ is(tabbrowser.mTabBox.selectedPanel, newBrowser.parentNode.parentNode.parentNode.parentNode, "Panel for remaining tab is selected");
+ }
+ }
+];
+
+function test() {
+ waitForExplicitFinish();
+ runOneTest();
+}
+
+function runOneTest() {
+ var win = openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no");
+
+ win.addEventListener("load", function () {
+ win.removeEventListener("load", arguments.callee, false);
+
+ var tab = win.gBrowser.tabContainer.firstChild;
+ var browser = tab.linkedBrowser;
+
+ browser.addEventListener("load", function () {
+ browser.removeEventListener("load", arguments.callee, true);
+
+ executeSoon(function () {
+ runs.shift()(win, win.gBrowser, tab);
+ win.close();
+ if (runs.length)
+ runOneTest();
+ else
+ finish();
+ });
+ }, true);
+
+ var rootDir = getRootDirectory(gTestPath);
+ browser.contentWindow.location = rootDir + "test_bug462673.html"
+ }, false);
+}
diff --git a/browser/base/content/test/browser_bug477014.js b/browser/base/content/test/browser_bug477014.js
new file mode 100644
index 000000000..77770e199
--- /dev/null
+++ b/browser/base/content/test/browser_bug477014.js
@@ -0,0 +1,55 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// That's a gecko!
+const iconURLSpec = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==";
+var testPage="data:text/plain,test bug 477014";
+
+function test() {
+ waitForExplicitFinish();
+
+ var newWindow;
+ var tabToDetach;
+ var documentToDetach;
+
+ function onPageShow(event) {
+ // we get here if the test is executed before the pageshow
+ // event for the window's first tab
+ if (!tabToDetach || documentToDetach != event.target)
+ return;
+
+ event.currentTarget.removeEventListener("pageshow", onPageShow, false);
+
+ if (!newWindow) {
+ // prepare the tab (set icon and busy state)
+ // we have to set these only after onState* notification, otherwise
+ // they're overriden
+ setTimeout(function() {
+ gBrowser.setIcon(tabToDetach, iconURLSpec);
+ tabToDetach.setAttribute("busy", "true");
+
+ // detach and set the listener on the new window
+ newWindow = gBrowser.replaceTabWithWindow(tabToDetach);
+ // wait for gBrowser to come along
+ newWindow.addEventListener("load", function () {
+ newWindow.removeEventListener("load", arguments.callee, false);
+ newWindow.gBrowser.addEventListener("pageshow", onPageShow, false);
+ }, false);
+ }, 0);
+ return;
+ }
+
+ is(newWindow.gBrowser.selectedTab.hasAttribute("busy"), true);
+ is(newWindow.gBrowser.getIcon(), iconURLSpec);
+ newWindow.close();
+ finish();
+ }
+
+ tabToDetach = gBrowser.addTab(testPage);
+ tabToDetach.linkedBrowser.addEventListener("load", function onLoad() {
+ tabToDetach.linkedBrowser.removeEventListener("load", onLoad, true);
+ documentToDetach = tabToDetach.linkedBrowser.contentDocument;
+ gBrowser.addEventListener("pageshow", onPageShow, false);
+ }, true);
+}
diff --git a/browser/base/content/test/browser_bug479408.js b/browser/base/content/test/browser_bug479408.js
new file mode 100644
index 000000000..0a14e2259
--- /dev/null
+++ b/browser/base/content/test/browser_bug479408.js
@@ -0,0 +1,17 @@
+function test() {
+ waitForExplicitFinish();
+ let tab = gBrowser.selectedTab = gBrowser.addTab(
+ "http://mochi.test:8888/browser/browser/base/content/test/browser_bug479408_sample.html");
+
+ gBrowser.addEventListener("DOMLinkAdded", function(aEvent) {
+ gBrowser.removeEventListener("DOMLinkAdded", arguments.callee, true);
+
+ executeSoon(function() {
+ ok(!tab.linkedBrowser.engines,
+ "the subframe's search engine wasn't detected");
+
+ gBrowser.removeTab(tab);
+ finish();
+ });
+ }, true);
+}
diff --git a/browser/base/content/test/browser_bug479408_sample.html b/browser/base/content/test/browser_bug479408_sample.html
new file mode 100644
index 000000000..f83f02bb9
--- /dev/null
+++ b/browser/base/content/test/browser_bug479408_sample.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<title>Testcase for bug 479408</title>
+
+<iframe src='data:text/html,<link%20rel="search"%20type="application/opensearchdescription+xml"%20title="Search%20bug%20479408"%20href="http://example.com/search.xml">'>
diff --git a/browser/base/content/test/browser_bug481560.js b/browser/base/content/test/browser_bug481560.js
new file mode 100644
index 000000000..e3d281b1b
--- /dev/null
+++ b/browser/base/content/test/browser_bug481560.js
@@ -0,0 +1,27 @@
+function test() {
+ waitForExplicitFinish();
+
+ var win = openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no");
+
+ win.addEventListener("load", function () {
+ win.removeEventListener("load", arguments.callee, false);
+
+ win.content.addEventListener("focus", function () {
+ win.content.removeEventListener("focus", arguments.callee, false);
+
+ function onTabClose() {
+ ok(false, "shouldn't have gotten the TabClose event for the last tab");
+ }
+ var tab = win.gBrowser.selectedTab;
+ tab.addEventListener("TabClose", onTabClose, false);
+
+ EventUtils.synthesizeKey("w", { accelKey: true }, win);
+
+ ok(win.closed, "accel+w closed the window immediately");
+
+ tab.removeEventListener("TabClose", onTabClose, false);
+
+ finish();
+ }, false);
+ }, false);
+}
diff --git a/browser/base/content/test/browser_bug484315.js b/browser/base/content/test/browser_bug484315.js
new file mode 100644
index 000000000..fb23ae33a
--- /dev/null
+++ b/browser/base/content/test/browser_bug484315.js
@@ -0,0 +1,23 @@
+function test() {
+ var contentWin = window.open("about:blank", "", "width=100,height=100");
+ var enumerator = Services.wm.getEnumerator("navigator:browser");
+
+ while (enumerator.hasMoreElements()) {
+ let win = enumerator.getNext();
+ if (win.content == contentWin) {
+ gPrefService.setBoolPref("browser.tabs.closeWindowWithLastTab", false);
+ win.gBrowser.removeCurrentTab();
+ ok(win.closed, "popup is closed");
+
+ // clean up
+ if (!win.closed)
+ win.close();
+ if (gPrefService.prefHasUserValue("browser.tabs.closeWindowWithLastTab"))
+ gPrefService.clearUserPref("browser.tabs.closeWindowWithLastTab");
+
+ return;
+ }
+ }
+
+ throw "couldn't find the content window";
+}
diff --git a/browser/base/content/test/browser_bug491431.js b/browser/base/content/test/browser_bug491431.js
new file mode 100644
index 000000000..357e55bfc
--- /dev/null
+++ b/browser/base/content/test/browser_bug491431.js
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+let testPage = "data:text/plain,test bug 491431 Page";
+
+function test() {
+ waitForExplicitFinish();
+
+ let newWin, tabA, tabB;
+
+ // test normal close
+ tabA = gBrowser.addTab(testPage);
+ gBrowser.tabContainer.addEventListener("TabClose", function(aEvent) {
+ gBrowser.tabContainer.removeEventListener("TabClose", arguments.callee, true);
+ ok(!aEvent.detail, "This was a normal tab close");
+
+ // test tab close by moving
+ tabB = gBrowser.addTab(testPage);
+ gBrowser.tabContainer.addEventListener("TabClose", function(aEvent) {
+ gBrowser.tabContainer.removeEventListener("TabClose", arguments.callee, true);
+ executeSoon(function() {
+ ok(aEvent.detail, "This was a tab closed by moving");
+
+ // cleanup
+ newWin.close();
+ executeSoon(finish);
+ });
+ }, true);
+ newWin = gBrowser.replaceTabWithWindow(tabB);
+ }, true);
+ gBrowser.removeTab(tabA);
+}
+
diff --git a/browser/base/content/test/browser_bug495058.js b/browser/base/content/test/browser_bug495058.js
new file mode 100644
index 000000000..0d4680058
--- /dev/null
+++ b/browser/base/content/test/browser_bug495058.js
@@ -0,0 +1,43 @@
+function test() {
+ waitForExplicitFinish();
+ next();
+}
+
+var uris = [
+ "about:blank",
+ "about:sessionrestore",
+ "about:privatebrowsing",
+];
+
+function next() {
+ var tab = gBrowser.addTab();
+ var uri = uris.shift();
+
+ if (uri == "about:blank") {
+ detach();
+ } else {
+ let browser = tab.linkedBrowser;
+ browser.addEventListener("load", function () {
+ browser.removeEventListener("load", arguments.callee, true);
+ detach();
+ }, true);
+ browser.loadURI(uri);
+ }
+
+ function detach() {
+ var win = gBrowser.replaceTabWithWindow(tab);
+
+ whenDelayedStartupFinished(win, function () {
+ is(win.gBrowser.currentURI.spec, uri, uri + ": uri loaded in detached tab");
+ is(win.document.activeElement, win.gBrowser.selectedBrowser, uri + ": browser is focused");
+ is(win.gURLBar.value, "", uri + ": urlbar is empty");
+ ok(win.gURLBar.placeholder, uri + ": placeholder text is present");
+
+ win.close();
+ if (uris.length)
+ next();
+ else
+ executeSoon(finish);
+ });
+ }
+}
diff --git a/browser/base/content/test/browser_bug517902.js b/browser/base/content/test/browser_bug517902.js
new file mode 100644
index 000000000..525b68aab
--- /dev/null
+++ b/browser/base/content/test/browser_bug517902.js
@@ -0,0 +1,41 @@
+/* Make sure that "View Image Info" loads the correct image data */
+
+function test() {
+ waitForExplicitFinish();
+
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ gBrowser.selectedBrowser.addEventListener("load", function () {
+ gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+ var doc = gBrowser.contentDocument;
+ var testImg = doc.getElementById("test-image");
+ var pageInfo = BrowserPageInfo(doc, "mediaTab", testImg);
+
+ pageInfo.addEventListener("load", function () {
+ pageInfo.removeEventListener("load", arguments.callee, true);
+ pageInfo.onImagePreviewShown.push(function () {
+ executeSoon(function () {
+ var pageInfoImg = pageInfo.document.getElementById("thepreviewimage");
+
+ is(pageInfoImg.src, testImg.src, "selected image has the correct source");
+ is(pageInfoImg.width, testImg.width, "selected image has the correct width");
+ is(pageInfoImg.height, testImg.height, "selected image has the correct height");
+
+ pageInfo.close();
+ gBrowser.removeCurrentTab();
+ finish();
+ });
+ });
+ }, true);
+ }, true);
+
+ content.location =
+ "data:text/html," +
+ "<style type='text/css'>%23test-image,%23not-test-image {background-image: url('about:logo?c');}</style>" +
+ "<img src='about:logo?b' height=300 width=350 alt=2 id='not-test-image'>" +
+ "<img src='about:logo?b' height=300 width=350 alt=2>" +
+ "<img src='about:logo?a' height=200 width=250>" +
+ "<img src='about:logo?b' height=200 width=250 alt=1>" +
+ "<img src='about:logo?b' height=100 width=150 alt=2 id='test-image'>";
+}
diff --git a/browser/base/content/test/browser_bug519216.js b/browser/base/content/test/browser_bug519216.js
new file mode 100644
index 000000000..a924f7a09
--- /dev/null
+++ b/browser/base/content/test/browser_bug519216.js
@@ -0,0 +1,50 @@
+function test() {
+ waitForExplicitFinish();
+ gBrowser.stop();
+ gBrowser.addProgressListener(progressListener1);
+ gBrowser.addProgressListener(progressListener2);
+ gBrowser.addProgressListener(progressListener3);
+ gBrowser.loadURI("data:text/plain,bug519216");
+}
+
+var calledListener1 = false;
+var progressListener1 = {
+ onLocationChange: function onLocationChange() {
+ calledListener1 = true;
+ gBrowser.removeProgressListener(this);
+ }
+};
+
+var calledListener2 = false;
+var progressListener2 = {
+ onLocationChange: function onLocationChange() {
+ ok(calledListener1, "called progressListener1 before progressListener2");
+ calledListener2 = true;
+ gBrowser.removeProgressListener(this);
+ }
+};
+
+var progressListener3 = {
+ onLocationChange: function onLocationChange() {
+ ok(calledListener2, "called progressListener2 before progressListener3");
+ gBrowser.removeProgressListener(this);
+ gBrowser.addProgressListener(progressListener4);
+ executeSoon(function () {
+ expectListener4 = true;
+ gBrowser.reload();
+ });
+ }
+};
+
+var expectListener4 = false;
+var progressListener4 = {
+ onLocationChange: function onLocationChange() {
+ ok(expectListener4, "didn't call progressListener4 for the first location change");
+ gBrowser.removeProgressListener(this);
+ executeSoon(function () {
+ gBrowser.addTab();
+ gBrowser.removeCurrentTab();
+ finish();
+ });
+ }
+};
diff --git a/browser/base/content/test/browser_bug520538.js b/browser/base/content/test/browser_bug520538.js
new file mode 100644
index 000000000..4489b64c3
--- /dev/null
+++ b/browser/base/content/test/browser_bug520538.js
@@ -0,0 +1,15 @@
+function test() {
+ var tabCount = gBrowser.tabs.length;
+ gBrowser.selectedBrowser.focus();
+ browserDOMWindow.openURI(makeURI("about:blank"),
+ null,
+ Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
+ Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
+ is(gBrowser.tabs.length, tabCount + 1,
+ "'-new-tab about:blank' opens a new tab");
+ is(gBrowser.selectedTab, gBrowser.tabs[tabCount],
+ "'-new-tab about:blank' selects the new tab");
+ is(document.activeElement, gURLBar.inputField,
+ "'-new-tab about:blank' focuses the location bar");
+ gBrowser.removeCurrentTab();
+}
diff --git a/browser/base/content/test/browser_bug521216.js b/browser/base/content/test/browser_bug521216.js
new file mode 100644
index 000000000..c39ff741f
--- /dev/null
+++ b/browser/base/content/test/browser_bug521216.js
@@ -0,0 +1,47 @@
+var expected = ["TabOpen", "onStateChange", "onLocationChange", "onLinkIconAvailable"];
+var actual = [];
+var tabIndex = -1;
+this.__defineGetter__("tab", function () gBrowser.tabs[tabIndex]);
+
+function test() {
+ waitForExplicitFinish();
+ tabIndex = gBrowser.tabs.length;
+ gBrowser.addTabsProgressListener(progressListener);
+ gBrowser.tabContainer.addEventListener("TabOpen", TabOpen, false);
+ gBrowser.addTab("data:text/html,<html><head><link href='about:logo' rel='shortcut icon'>");
+}
+
+function record(aName) {
+ info("got " + aName);
+ if (actual.indexOf(aName) == -1)
+ actual.push(aName);
+ if (actual.length == expected.length) {
+ is(actual.toString(), expected.toString(),
+ "got events and progress notifications in expected order");
+ gBrowser.removeTab(tab);
+ gBrowser.removeTabsProgressListener(progressListener);
+ gBrowser.tabContainer.removeEventListener("TabOpen", TabOpen, false);
+ finish();
+ }
+}
+
+function TabOpen(aEvent) {
+ if (aEvent.target == tab)
+ record(arguments.callee.name);
+}
+
+var progressListener = {
+ onLocationChange: function onLocationChange(aBrowser) {
+ if (aBrowser == tab.linkedBrowser)
+ record(arguments.callee.name);
+ },
+ onStateChange: function onStateChange(aBrowser) {
+ if (aBrowser == tab.linkedBrowser)
+ record(arguments.callee.name);
+ },
+ onLinkIconAvailable: function onLinkIconAvailable(aBrowser, aIconURL) {
+ if (aBrowser == tab.linkedBrowser &&
+ aIconURL == "about:logo")
+ record(arguments.callee.name);
+ }
+};
diff --git a/browser/base/content/test/browser_bug533232.js b/browser/base/content/test/browser_bug533232.js
new file mode 100644
index 000000000..fdee75ba2
--- /dev/null
+++ b/browser/base/content/test/browser_bug533232.js
@@ -0,0 +1,36 @@
+function test() {
+ var tab1 = gBrowser.selectedTab;
+ var tab2 = gBrowser.addTab();
+ var childTab1;
+ var childTab2;
+
+ childTab1 = gBrowser.addTab("about:blank", { relatedToCurrent: true });
+ gBrowser.selectedTab = childTab1;
+ gBrowser.removeCurrentTab();
+ is(idx(gBrowser.selectedTab), idx(tab1),
+ "closing a tab next to its parent selects the parent");
+
+ childTab1 = gBrowser.addTab("about:blank", { relatedToCurrent: true });
+ gBrowser.selectedTab = tab2;
+ gBrowser.selectedTab = childTab1;
+ gBrowser.removeCurrentTab();
+ is(idx(gBrowser.selectedTab), idx(tab2),
+ "closing a tab next to its parent doesn't select the parent if another tab had been selected ad interim");
+
+ gBrowser.selectedTab = tab1;
+ childTab1 = gBrowser.addTab("about:blank", { relatedToCurrent: true });
+ childTab2 = gBrowser.addTab("about:blank", { relatedToCurrent: true });
+ gBrowser.selectedTab = childTab1;
+ gBrowser.removeCurrentTab();
+ is(idx(gBrowser.selectedTab), idx(childTab2),
+ "closing a tab next to its parent selects the next tab with the same parent");
+ gBrowser.removeCurrentTab();
+ is(idx(gBrowser.selectedTab), idx(tab2),
+ "closing the last tab in a set of child tabs doesn't go back to the parent");
+
+ gBrowser.removeTab(tab2);
+}
+
+function idx(tab) {
+ return Array.indexOf(gBrowser.tabs, tab);
+}
diff --git a/browser/base/content/test/browser_bug537474.js b/browser/base/content/test/browser_bug537474.js
new file mode 100644
index 000000000..3c471e1d2
--- /dev/null
+++ b/browser/base/content/test/browser_bug537474.js
@@ -0,0 +1,8 @@
+function test() {
+ var currentWin = content;
+ var newWin =
+ browserDOMWindow.openURI(makeURI("about:"), null,
+ Ci.nsIBrowserDOMWindow.OPEN_CURRENTWINDOW, null)
+ is(newWin, currentWin, "page loads in the current content window");
+ gBrowser.stop();
+}
diff --git a/browser/base/content/test/browser_bug550565.js b/browser/base/content/test/browser_bug550565.js
new file mode 100644
index 000000000..0dfa4ed4a
--- /dev/null
+++ b/browser/base/content/test/browser_bug550565.js
@@ -0,0 +1,21 @@
+function test() {
+ waitForExplicitFinish();
+
+ let testPath = getRootDirectory(gTestPath);
+
+ let tab = gBrowser.addTab(testPath + "file_bug550565_popup.html");
+
+ tab.linkedBrowser.addEventListener("DOMContentLoaded", function() {
+ tab.linkedBrowser.removeEventListener("DOMContentLoaded", arguments.callee, true);
+
+ let expectedIcon = testPath + "file_bug550565_favicon.ico";
+
+ is(gBrowser.getIcon(tab), expectedIcon, "Correct icon before pushState.");
+ tab.linkedBrowser.contentWindow.history.pushState("page2", "page2", "page2");
+ is(gBrowser.getIcon(tab), expectedIcon, "Correct icon after pushState.");
+
+ gBrowser.removeTab(tab);
+
+ finish();
+ }, true);
+}
diff --git a/browser/base/content/test/browser_bug553455.js b/browser/base/content/test/browser_bug553455.js
new file mode 100644
index 000000000..ddfba9fd3
--- /dev/null
+++ b/browser/base/content/test/browser_bug553455.js
@@ -0,0 +1,903 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const TESTROOT = "http://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/";
+const TESTROOT2 = "http://example.org/browser/toolkit/mozapps/extensions/test/xpinstall/";
+const SECUREROOT = "https://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/";
+const XPINSTALL_URL = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
+const PREF_INSTALL_REQUIREBUILTINCERTS = "extensions.install.requireBuiltInCerts";
+
+var rootDir = getRootDirectory(gTestPath);
+var path = rootDir.split('/');
+var chromeName = path[0] + '//' + path[2];
+var croot = chromeName + "/content/browser/toolkit/mozapps/extensions/test/xpinstall/";
+var jar = getJar(croot);
+if (jar) {
+ var tmpdir = extractJarToTmp(jar);
+ croot = 'file://' + tmpdir.path + '/';
+}
+const CHROMEROOT = croot;
+
+var gApp = document.getElementById("bundle_brand").getString("brandShortName");
+var gVersion = Services.appinfo.version;
+var check_notification;
+
+function wait_for_notification(aCallback) {
+ info("Waiting for notification");
+ check_notification = function() {
+ PopupNotifications.panel.removeEventListener("popupshown", check_notification, false);
+ info("Saw notification");
+ is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification");
+ aCallback(PopupNotifications.panel);
+ };
+ PopupNotifications.panel.addEventListener("popupshown", check_notification, false);
+}
+
+function wait_for_notification_close(aCallback) {
+ info("Waiting for notification to close");
+ PopupNotifications.panel.addEventListener("popuphidden", function() {
+ PopupNotifications.panel.removeEventListener("popuphidden", arguments.callee, false);
+ aCallback();
+ }, false);
+}
+
+function wait_for_install_dialog(aCallback) {
+ info("Waiting for install dialog");
+ Services.wm.addListener({
+ onOpenWindow: function(aXULWindow) {
+ info("Install dialog opened, waiting for focus");
+ Services.wm.removeListener(this);
+
+ var domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ waitForFocus(function() {
+ info("Saw install dialog");
+ is(domwindow.document.location.href, XPINSTALL_URL, "Should have seen the right window open");
+
+ // Override the countdown timer on the accept button
+ var button = domwindow.document.documentElement.getButton("accept");
+ button.disabled = false;
+
+ aCallback(domwindow);
+ }, domwindow);
+ },
+
+ onCloseWindow: function(aXULWindow) {
+ },
+
+ onWindowTitleChange: function(aXULWindow, aNewTitle) {
+ }
+ });
+}
+
+function wait_for_single_notification(aCallback) {
+ function inner_waiter() {
+ info("Waiting for single notification");
+ // Notification should never close while we wait
+ ok(PopupNotifications.isPanelOpen, "Notification should still be open");
+ if (PopupNotifications.panel.childNodes.length == 2) {
+ executeSoon(inner_waiter);
+ return;
+ }
+
+ aCallback();
+ }
+
+ executeSoon(inner_waiter);
+}
+
+function setup_redirect(aSettings) {
+ var url = "https://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/redirect.sjs?mode=setup";
+ for (var name in aSettings) {
+ url += "&" + name + "=" + aSettings[name];
+ }
+
+ var req = new XMLHttpRequest();
+ req.open("GET", url, false);
+ req.send(null);
+}
+
+var TESTS = [
+function test_disabled_install() {
+ Services.prefs.setBoolPref("xpinstall.enabled", false);
+
+ // Wait for the disabled notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "xpinstall-disabled-notification", "Should have seen installs disabled");
+ is(notification.button.label, "Enable", "Should have seen the right button");
+ is(notification.getAttribute("label"),
+ "Software installation is currently disabled. Click Enable and try again.");
+
+ wait_for_notification_close(function() {
+ try {
+ ok(Services.prefs.getBoolPref("xpinstall.enabled"), "Installation should be enabled");
+ }
+ catch (e) {
+ ok(false, "xpinstall.enabled should be set");
+ }
+
+ gBrowser.removeTab(gBrowser.selectedTab);
+
+ AddonManager.getAllInstalls(function(aInstalls) {
+ is(aInstalls.length, 1, "Should have been one install created");
+ aInstalls[0].cancel();
+
+ runNextTest();
+ });
+ });
+
+ // Click on Enable
+ EventUtils.synthesizeMouseAtCenter(notification.button, {});
+ });
+
+ var triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "unsigned.xpi"
+ }));
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+},
+
+function test_blocked_install() {
+ // Wait for the blocked notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-install-blocked-notification", "Should have seen the install blocked");
+ is(notification.button.label, "Allow", "Should have seen the right button");
+ is(notification.getAttribute("label"),
+ gApp + " prevented this site (example.com) from asking you to install " +
+ "software on your computer.",
+ "Should have seen the right message");
+
+ // Wait for the install confirmation dialog
+ wait_for_install_dialog(function(aWindow) {
+ // Wait for the complete notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-install-complete-notification", "Should have seen the install complete");
+ is(notification.button.label, "Restart Now", "Should have seen the right button");
+ is(notification.getAttribute("label"),
+ "XPI Test will be installed after you restart " + gApp + ".",
+ "Should have seen the right message");
+
+ AddonManager.getAllInstalls(function(aInstalls) {
+ is(aInstalls.length, 1, "Should be one pending install");
+ aInstalls[0].cancel();
+
+ wait_for_notification_close(runNextTest);
+ gBrowser.removeTab(gBrowser.selectedTab);
+ });
+ });
+
+ aWindow.document.documentElement.acceptDialog();
+ });
+
+ // Click on Allow
+ EventUtils.synthesizeMouse(notification.button, 20, 10, {});
+
+ // Notification should have changed to progress notification
+ ok(PopupNotifications.isPanelOpen, "Notification should still be open");
+ notification = aPanel.childNodes[0];
+ is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
+
+ });
+
+ var triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "unsigned.xpi"
+ }));
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+},
+
+function test_whitelisted_install() {
+ // Wait for the progress notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
+
+ // Wait for the install confirmation dialog
+ wait_for_install_dialog(function(aWindow) {
+ // Wait for the complete notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-install-complete-notification", "Should have seen the install complete");
+ is(notification.button.label, "Restart Now", "Should have seen the right button");
+ is(notification.getAttribute("label"),
+ "XPI Test will be installed after you restart " + gApp + ".",
+ "Should have seen the right message");
+
+ AddonManager.getAllInstalls(function(aInstalls) {
+ is(aInstalls.length, 1, "Should be one pending install");
+ aInstalls[0].cancel();
+
+ Services.perms.remove("example.com", "install");
+ wait_for_notification_close(runNextTest);
+ gBrowser.removeTab(gBrowser.selectedTab);
+ });
+ });
+
+ aWindow.document.documentElement.acceptDialog();
+ });
+ });
+
+ var pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ var triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "unsigned.xpi"
+ }));
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+},
+
+function test_failed_download() {
+ // Wait for the progress notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
+
+ // Wait for the failed notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-install-failed-notification", "Should have seen the install fail");
+ is(notification.getAttribute("label"),
+ "The add-on could not be downloaded because of a connection failure " +
+ "on example.com.",
+ "Should have seen the right message");
+
+ Services.perms.remove("example.com", "install");
+ wait_for_notification_close(runNextTest);
+ gBrowser.removeTab(gBrowser.selectedTab);
+ });
+ });
+
+ var pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ var triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "missing.xpi"
+ }));
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+},
+
+function test_corrupt_file() {
+ // Wait for the progress notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
+
+ // Wait for the failed notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-install-failed-notification", "Should have seen the install fail");
+ is(notification.getAttribute("label"),
+ "The add-on downloaded from example.com could not be installed " +
+ "because it appears to be corrupt.",
+ "Should have seen the right message");
+
+ Services.perms.remove("example.com", "install");
+ wait_for_notification_close(runNextTest);
+ gBrowser.removeTab(gBrowser.selectedTab);
+ });
+ });
+
+ var pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ var triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "corrupt.xpi"
+ }));
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+},
+
+function test_incompatible() {
+ // Wait for the progress notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
+
+ // Wait for the failed notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-install-failed-notification", "Should have seen the install fail");
+ is(notification.getAttribute("label"),
+ "XPI Test could not be installed because it is not compatible with " +
+ gApp + " " + gVersion + ".",
+ "Should have seen the right message");
+
+ Services.perms.remove("example.com", "install");
+ wait_for_notification_close(runNextTest);
+ gBrowser.removeTab(gBrowser.selectedTab);
+ });
+ });
+
+ var pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ var triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "incompatible.xpi"
+ }));
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+},
+
+function test_restartless() {
+ // Wait for the progress notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
+
+ // Wait for the install confirmation dialog
+ wait_for_install_dialog(function(aWindow) {
+ // Wait for the complete notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-install-complete-notification", "Should have seen the install complete");
+ is(notification.getAttribute("label"),
+ "XPI Test has been installed successfully.",
+ "Should have seen the right message");
+
+ AddonManager.getAllInstalls(function(aInstalls) {
+ is(aInstalls.length, 0, "Should be no pending installs");
+
+ AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org", function(aAddon) {
+ aAddon.uninstall();
+
+ Services.perms.remove("example.com", "install");
+ wait_for_notification_close(runNextTest);
+ gBrowser.removeTab(gBrowser.selectedTab);
+ });
+ });
+ });
+
+ aWindow.document.documentElement.acceptDialog();
+ });
+ });
+
+ var pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ var triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "restartless.xpi"
+ }));
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+},
+
+function test_multiple() {
+ // Wait for the progress notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
+
+ // Wait for the install confirmation dialog
+ wait_for_install_dialog(function(aWindow) {
+ // Wait for the complete notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-install-complete-notification", "Should have seen the install complete");
+ is(notification.button.label, "Restart Now", "Should have seen the right button");
+ is(notification.getAttribute("label"),
+ "2 add-ons will be installed after you restart " + gApp + ".",
+ "Should have seen the right message");
+
+ AddonManager.getAllInstalls(function(aInstalls) {
+ is(aInstalls.length, 1, "Should be one pending install");
+ aInstalls[0].cancel();
+
+ AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org", function(aAddon) {
+ aAddon.uninstall();
+
+ Services.perms.remove("example.com", "install");
+ wait_for_notification_close(runNextTest);
+ gBrowser.removeTab(gBrowser.selectedTab);
+ });
+ });
+ });
+
+ aWindow.document.documentElement.acceptDialog();
+ });
+ });
+
+ var pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ var triggers = encodeURIComponent(JSON.stringify({
+ "Unsigned XPI": "unsigned.xpi",
+ "Restartless XPI": "restartless.xpi"
+ }));
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+},
+
+function test_url() {
+ // Wait for the progress notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
+
+ // Wait for the install confirmation dialog
+ wait_for_install_dialog(function(aWindow) {
+ // Wait for the complete notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-install-complete-notification", "Should have seen the install complete");
+ is(notification.button.label, "Restart Now", "Should have seen the right button");
+ is(notification.getAttribute("label"),
+ "XPI Test will be installed after you restart " + gApp + ".",
+ "Should have seen the right message");
+
+ AddonManager.getAllInstalls(function(aInstalls) {
+ is(aInstalls.length, 1, "Should be one pending install");
+ aInstalls[0].cancel();
+
+ wait_for_notification_close(runNextTest);
+ gBrowser.removeTab(gBrowser.selectedTab);
+ });
+ });
+
+ aWindow.document.documentElement.acceptDialog();
+ });
+ });
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.loadURI(TESTROOT + "unsigned.xpi");
+},
+
+function test_localfile() {
+ // Wait for the install to fail
+ Services.obs.addObserver(function() {
+ Services.obs.removeObserver(arguments.callee, "addon-install-failed");
+
+ // Wait for the browser code to add the failure notification
+ wait_for_single_notification(function() {
+ let notification = PopupNotifications.panel.childNodes[0];
+ is(notification.id, "addon-install-failed-notification", "Should have seen the install fail");
+ is(notification.getAttribute("label"),
+ "This add-on could not be installed because it appears to be corrupt.",
+ "Should have seen the right message");
+
+ wait_for_notification_close(runNextTest);
+ gBrowser.removeTab(gBrowser.selectedTab);
+ });
+ }, "addon-install-failed", false);
+
+ var cr = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
+ .getService(Components.interfaces.nsIChromeRegistry);
+ try {
+ var path = cr.convertChromeURL(makeURI(CHROMEROOT + "corrupt.xpi")).spec;
+ } catch (ex) {
+ var path = CHROMEROOT + "corrupt.xpi";
+ }
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.loadURI(path);
+},
+
+function test_wronghost() {
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.addEventListener("load", function() {
+ if (gBrowser.currentURI.spec != TESTROOT2 + "enabled.html")
+ return;
+
+ gBrowser.removeEventListener("load", arguments.callee, true);
+
+ // Wait for the progress notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
+ // Wait for the complete notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-install-failed-notification", "Should have seen the install fail");
+ is(notification.getAttribute("label"),
+ "The add-on downloaded from example.com could not be installed " +
+ "because it appears to be corrupt.",
+ "Should have seen the right message");
+
+ wait_for_notification_close(runNextTest);
+ gBrowser.removeTab(gBrowser.selectedTab);
+ });
+ });
+
+ gBrowser.loadURI(TESTROOT + "corrupt.xpi");
+ }, true);
+ gBrowser.loadURI(TESTROOT2 + "enabled.html");
+},
+
+function test_reload() {
+ // Wait for the progress notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
+
+ // Wait for the install confirmation dialog
+ wait_for_install_dialog(function(aWindow) {
+ // Wait for the complete notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-install-complete-notification", "Should have seen the install complete");
+ is(notification.button.label, "Restart Now", "Should have seen the right button");
+ is(notification.getAttribute("label"),
+ "XPI Test will be installed after you restart " + gApp + ".",
+ "Should have seen the right message");
+
+ function test_fail() {
+ ok(false, "Reloading should not have hidden the notification");
+ }
+
+ PopupNotifications.panel.addEventListener("popuphiding", test_fail, false);
+
+ gBrowser.addEventListener("load", function() {
+ if (gBrowser.currentURI.spec != TESTROOT2 + "enabled.html")
+ return;
+
+ gBrowser.removeEventListener("load", arguments.callee, true);
+
+ PopupNotifications.panel.removeEventListener("popuphiding", test_fail, false);
+
+ AddonManager.getAllInstalls(function(aInstalls) {
+ is(aInstalls.length, 1, "Should be one pending install");
+ aInstalls[0].cancel();
+
+ Services.perms.remove("example.com", "install");
+ wait_for_notification_close(runNextTest);
+ gBrowser.removeTab(gBrowser.selectedTab);
+ });
+ }, true);
+ gBrowser.loadURI(TESTROOT2 + "enabled.html");
+ });
+
+ aWindow.document.documentElement.acceptDialog();
+ });
+ });
+
+ var pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ var triggers = encodeURIComponent(JSON.stringify({
+ "Unsigned XPI": "unsigned.xpi"
+ }));
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+},
+
+function test_theme() {
+ // Wait for the progress notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
+
+ // Wait for the install confirmation dialog
+ wait_for_install_dialog(function(aWindow) {
+ // Wait for the complete notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-install-complete-notification", "Should have seen the install complete");
+ is(notification.button.label, "Restart Now", "Should have seen the right button");
+ is(notification.getAttribute("label"),
+ "Theme Test will be installed after you restart " + gApp + ".",
+ "Should have seen the right message");
+
+ AddonManager.getAddonByID("{972ce4c6-7e08-4474-a285-3208198ce6fd}", function(aAddon) {
+ ok(aAddon.userDisabled, "Should be switching away from the default theme.");
+ // Undo the pending theme switch
+ aAddon.userDisabled = false;
+
+ AddonManager.getAddonByID("theme-xpi@tests.mozilla.org", function(aAddon) {
+ isnot(aAddon, null, "Test theme will have been installed");
+ aAddon.uninstall();
+
+ Services.perms.remove("example.com", "install");
+ wait_for_notification_close(runNextTest);
+ gBrowser.removeTab(gBrowser.selectedTab);
+ });
+ });
+ });
+
+ aWindow.document.documentElement.acceptDialog();
+ });
+ });
+
+ var pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ var triggers = encodeURIComponent(JSON.stringify({
+ "Theme XPI": "theme.xpi"
+ }));
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+},
+
+function test_renotify_blocked() {
+ // Wait for the blocked notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-install-blocked-notification", "Should have seen the install blocked");
+
+ wait_for_notification_close(function () {
+ info("Timeouts after this probably mean bug 589954 regressed");
+ executeSoon(function () {
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-install-blocked-notification",
+ "Should have seen the install blocked - 2nd time");
+
+ AddonManager.getAllInstalls(function(aInstalls) {
+ is(aInstalls.length, 2, "Should be two pending installs");
+ aInstalls[0].cancel();
+ aInstalls[1].cancel();
+
+ info("Closing browser tab");
+ wait_for_notification_close(runNextTest);
+ gBrowser.removeTab(gBrowser.selectedTab);
+ });
+ });
+
+ gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+ });
+ });
+
+ // hide the panel (this simulates the user dismissing it)
+ aPanel.hidePopup();
+ });
+
+ var triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "unsigned.xpi"
+ }));
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+},
+
+function test_renotify_installed() {
+ // Wait for the progress notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
+
+ // Wait for the install confirmation dialog
+ wait_for_install_dialog(function(aWindow) {
+ // Wait for the complete notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-install-complete-notification", "Should have seen the install complete");
+
+ // Dismiss the notification
+ wait_for_notification_close(function () {
+ // Install another
+ executeSoon(function () {
+ // Wait for the progress notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
+
+ // Wait for the install confirmation dialog
+ wait_for_install_dialog(function(aWindow) {
+ info("Timeouts after this probably mean bug 589954 regressed");
+
+ // Wait for the complete notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-install-complete-notification", "Should have seen the second install complete");
+
+ AddonManager.getAllInstalls(function(aInstalls) {
+ is(aInstalls.length, 1, "Should be one pending installs");
+ aInstalls[0].cancel();
+
+ Services.perms.remove("example.com", "install");
+ wait_for_notification_close(runNextTest);
+ gBrowser.removeTab(gBrowser.selectedTab);
+ });
+ });
+
+ aWindow.document.documentElement.acceptDialog();
+ });
+ });
+
+ gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+ });
+ });
+
+ // hide the panel (this simulates the user dismissing it)
+ aPanel.hidePopup();
+ });
+
+ aWindow.document.documentElement.acceptDialog();
+ });
+ });
+
+ var pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ var triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "unsigned.xpi"
+ }));
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+},
+
+function test_cancel_restart() {
+ // Wait for the progress notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
+
+ // Close the notification
+ let anchor = document.getElementById("addons-notification-icon");
+ anchor.click();
+ // Reopen the notification
+ anchor.click();
+
+ ok(PopupNotifications.isPanelOpen, "Notification should still be open");
+ is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification");
+ isnot(notification, aPanel.childNodes[0], "Should have reconstructed the notification UI");
+ notification = aPanel.childNodes[0];
+ is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
+ let button = document.getAnonymousElementByAttribute(notification, "anonid", "cancel");
+
+ // Cancel the download
+ EventUtils.synthesizeMouse(button, 2, 2, {});
+
+ // Notification should have changed to cancelled
+ notification = aPanel.childNodes[0];
+ is(notification.id, "addon-install-cancelled-notification", "Should have seen the cancelled notification");
+
+ // Wait for the install confirmation dialog
+ wait_for_install_dialog(function(aWindow) {
+ // Wait for the complete notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-install-complete-notification", "Should have seen the install complete");
+ is(notification.button.label, "Restart Now", "Should have seen the right button");
+ is(notification.getAttribute("label"),
+ "XPI Test will be installed after you restart " + gApp + ".",
+ "Should have seen the right message");
+
+ AddonManager.getAllInstalls(function(aInstalls) {
+ is(aInstalls.length, 1, "Should be one pending install");
+ aInstalls[0].cancel();
+
+ Services.perms.remove("example.com", "install");
+ wait_for_notification_close(runNextTest);
+ gBrowser.removeTab(gBrowser.selectedTab);
+ });
+ });
+
+ aWindow.document.documentElement.acceptDialog();
+ });
+
+ // Restart the download
+ EventUtils.synthesizeMouse(notification.button, 20, 10, {});
+
+ // Should be back to a progress notification
+ ok(PopupNotifications.isPanelOpen, "Notification should still be open");
+ is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification");
+ notification = aPanel.childNodes[0];
+ is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
+ });
+
+ var pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ var triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "unsigned.xpi"
+ }));
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+},
+
+function test_failed_security() {
+ Services.prefs.setBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, false);
+
+ setup_redirect({
+ "Location": TESTROOT + "unsigned.xpi"
+ });
+
+ // Wait for the blocked notification
+ wait_for_notification(function(aPanel) {
+ let notification = aPanel.childNodes[0];
+ is(notification.id, "addon-install-blocked-notification", "Should have seen the install blocked");
+
+ // Click on Allow
+ EventUtils.synthesizeMouse(notification.button, 20, 10, {});
+
+ // Notification should have changed to progress notification
+ ok(PopupNotifications.isPanelOpen, "Notification should still be open");
+ is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification");
+ notification = aPanel.childNodes[0];
+ is(notification.id, "addon-progress-notification", "Should have seen the progress notification");
+
+ // Wait for it to fail
+ Services.obs.addObserver(function() {
+ Services.obs.removeObserver(arguments.callee, "addon-install-failed");
+
+ // Allow the browser code to add the failure notification and then wait
+ // for the progress notification to dismiss itself
+ wait_for_single_notification(function() {
+ is(PopupNotifications.panel.childNodes.length, 1, "Should be only one notification");
+ notification = aPanel.childNodes[0];
+ is(notification.id, "addon-install-failed-notification", "Should have seen the install fail");
+
+ Services.prefs.setBoolPref(PREF_INSTALL_REQUIREBUILTINCERTS, true);
+ wait_for_notification_close(runNextTest);
+ gBrowser.removeTab(gBrowser.selectedTab);
+ });
+ }, "addon-install-failed", false);
+ });
+
+ var triggers = encodeURIComponent(JSON.stringify({
+ "XPI": "redirect.sjs?mode=redirect"
+ }));
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.loadURI(SECUREROOT + "installtrigger.html?" + triggers);
+}
+];
+
+var gTestStart = null;
+
+function runNextTest() {
+ if (gTestStart)
+ info("Test part took " + (Date.now() - gTestStart) + "ms");
+
+ ok(!PopupNotifications.isPanelOpen, "Notification should be closed");
+
+ AddonManager.getAllInstalls(function(aInstalls) {
+ is(aInstalls.length, 0, "Should be no active installs");
+
+ if (TESTS.length == 0) {
+ finish();
+ return;
+ }
+
+ info("Running " + TESTS[0].name);
+ gTestStart = Date.now();
+ TESTS.shift()();
+ });
+};
+
+var XPInstallObserver = {
+ observe: function (aSubject, aTopic, aData) {
+ var installInfo = aSubject.QueryInterface(Components.interfaces.amIWebInstallInfo);
+ info("Observed " + aTopic + " for " + installInfo.installs.length + " installs");
+ installInfo.installs.forEach(function(aInstall) {
+ info("Install of " + aInstall.sourceURI.spec + " was in state " + aInstall.state);
+ });
+ }
+};
+
+function test() {
+ requestLongerTimeout(4);
+ waitForExplicitFinish();
+
+ Services.prefs.setBoolPref("extensions.logging.enabled", true);
+ Services.prefs.setBoolPref("extensions.strictCompatibility", true);
+
+ Services.obs.addObserver(XPInstallObserver, "addon-install-started", false);
+ Services.obs.addObserver(XPInstallObserver, "addon-install-blocked", false);
+ Services.obs.addObserver(XPInstallObserver, "addon-install-failed", false);
+ Services.obs.addObserver(XPInstallObserver, "addon-install-complete", false);
+
+ registerCleanupFunction(function() {
+ // Make sure no more test parts run in case we were timed out
+ TESTS = [];
+ PopupNotifications.panel.removeEventListener("popupshown", check_notification, false);
+
+ AddonManager.getAllInstalls(function(aInstalls) {
+ aInstalls.forEach(function(aInstall) {
+ aInstall.cancel();
+ });
+ });
+
+ Services.prefs.clearUserPref("extensions.logging.enabled");
+ Services.prefs.clearUserPref("extensions.strictCompatibility");
+
+ Services.obs.removeObserver(XPInstallObserver, "addon-install-started");
+ Services.obs.removeObserver(XPInstallObserver, "addon-install-blocked");
+ Services.obs.removeObserver(XPInstallObserver, "addon-install-failed");
+ Services.obs.removeObserver(XPInstallObserver, "addon-install-complete");
+ });
+
+ runNextTest();
+}
diff --git a/browser/base/content/test/browser_bug555224.js b/browser/base/content/test/browser_bug555224.js
new file mode 100644
index 000000000..dbf3464a5
--- /dev/null
+++ b/browser/base/content/test/browser_bug555224.js
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+const TEST_PAGE = "/browser/browser/base/content/test/dummy_page.html";
+var gTestTab, gBgTab, gTestZoom;
+
+function testBackgroundLoad() {
+ Task.spawn(function () {
+ is(ZoomManager.zoom, gTestZoom, "opening a background tab should not change foreground zoom");
+
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(gBgTab);
+
+ FullZoom.reset();
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(gTestTab);
+ finish();
+ });
+}
+
+function testInitialZoom() {
+ Task.spawn(function () {
+ is(ZoomManager.zoom, 1, "initial zoom level should be 1");
+ FullZoom.enlarge();
+
+ gTestZoom = ZoomManager.zoom;
+ isnot(gTestZoom, 1, "zoom level should have changed");
+
+ gBgTab = gBrowser.addTab();
+ yield FullZoomHelper.load(gBgTab, "http://mochi.test:8888" + TEST_PAGE);
+ }).then(testBackgroundLoad, FullZoomHelper.failAndContinue(finish));
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ Task.spawn(function () {
+ gTestTab = gBrowser.addTab();
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTestTab);
+ yield FullZoomHelper.load(gTestTab, "http://example.org" + TEST_PAGE);
+ }).then(testInitialZoom, FullZoomHelper.failAndContinue(finish));
+}
diff --git a/browser/base/content/test/browser_bug555767.js b/browser/base/content/test/browser_bug555767.js
new file mode 100644
index 000000000..cfd1904a0
--- /dev/null
+++ b/browser/base/content/test/browser_bug555767.js
@@ -0,0 +1,68 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ let testURL = "http://example.org/browser/browser/base/content/test/dummy_page.html";
+ let tabSelected = false;
+
+ // Open the base tab
+ let baseTab = gBrowser.addTab(testURL);
+ baseTab.linkedBrowser.addEventListener("load", function() {
+ // Wait for the tab to be fully loaded so matching happens correctly
+ if (baseTab.linkedBrowser.currentURI.spec == "about:blank")
+ return;
+ baseTab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+ let testTab = gBrowser.addTab();
+
+ // Select the testTab
+ gBrowser.selectedTab = testTab;
+
+ // Ensure that this tab has no history entries
+ ok(testTab.linkedBrowser.sessionHistory.count < 2,
+ "The test tab has 1 or less history entries");
+ // Ensure that this tab is on about:blank
+ is(testTab.linkedBrowser.currentURI.spec, "about:blank",
+ "The test tab is on about:blank");
+ // Ensure that this tab's document has no child nodes
+ ok(!testTab.linkedBrowser.contentDocument.body.hasChildNodes(),
+ "The test tab has no child nodes");
+ ok(!testTab.hasAttribute("busy"),
+ "The test tab doesn't have the busy attribute");
+
+ // Set the urlbar to include the moz-action
+ gURLBar.value = "moz-action:switchtab," + testURL;
+ // Focus the urlbar so we can press enter
+ gURLBar.focus();
+
+ // Functions for TabClose and TabSelect
+ function onTabClose(aEvent) {
+ gBrowser.tabContainer.removeEventListener("TabClose", onTabClose, false);
+ // Make sure we get the TabClose event for testTab
+ is(aEvent.originalTarget, testTab, "Got the TabClose event for the right tab");
+ // Confirm that we did select the tab
+ ok(tabSelected, "Confirming that the tab was selected");
+ gBrowser.removeTab(baseTab);
+ finish();
+ }
+ function onTabSelect(aEvent) {
+ gBrowser.tabContainer.removeEventListener("TabSelect", onTabSelect, false);
+ // Make sure we got the TabSelect event for baseTab
+ is(aEvent.originalTarget, baseTab, "Got the TabSelect event for the right tab");
+ // Confirm that the selected tab is in fact base tab
+ is(gBrowser.selectedTab, baseTab, "We've switched to the correct tab");
+ tabSelected = true;
+ }
+
+ // Add the TabClose, TabSelect event listeners before we press enter
+ gBrowser.tabContainer.addEventListener("TabClose", onTabClose, false);
+ gBrowser.tabContainer.addEventListener("TabSelect", onTabSelect, false);
+
+ // Press enter!
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ }, true);
+}
+
diff --git a/browser/base/content/test/browser_bug556061.js b/browser/base/content/test/browser_bug556061.js
new file mode 100644
index 000000000..21046d079
--- /dev/null
+++ b/browser/base/content/test/browser_bug556061.js
@@ -0,0 +1,96 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+let testURL = "http://example.org/browser/browser/base/content/test/dummy_page.html";
+let testActionURL = "moz-action:switchtab," + testURL;
+testURL = gURLBar.trimValue(testURL);
+let testTab;
+
+function runNextTest() {
+ if (tests.length) {
+ let t = tests.shift();
+ waitForClipboard(t.expected, t.setup, function() {
+ t.success();
+ runNextTest();
+ }, cleanup);
+ }
+ else {
+ cleanup();
+ }
+}
+
+function cleanup() {
+ gBrowser.removeTab(testTab);
+ finish();
+}
+
+let tests = [
+ {
+ expected: testURL,
+ setup: function() {
+ gURLBar.value = testActionURL;
+ gURLBar.valueIsTyped = true;
+ is(gURLBar.value, testActionURL, "gURLBar.value starts with correct value");
+
+ // Focus the urlbar so we can select it all & copy
+ gURLBar.focus();
+ gURLBar.select();
+ goDoCommand("cmd_copy");
+ },
+ success: function() {
+ is(gURLBar.value, testActionURL, "gURLBar.value didn't change when copying");
+ }
+ },
+ {
+ expected: testURL.substring(0, 10),
+ setup: function() {
+ // Set selectionStart/End manually and make sure it matches the substring
+ gURLBar.selectionStart = 0;
+ gURLBar.selectionEnd = 10;
+ goDoCommand("cmd_copy");
+ },
+ success: function() {
+ is(gURLBar.value, testActionURL, "gURLBar.value didn't change when copying");
+ }
+ },
+ {
+ expected: testURL,
+ setup: function() {
+ // Setup for cut test...
+ // Select all
+ gURLBar.select();
+ goDoCommand("cmd_cut");
+ },
+ success: function() {
+ is(gURLBar.value, "", "gURLBar.value is now empty");
+ }
+ },
+ {
+ expected: testURL.substring(testURL.length - 10, testURL.length),
+ setup: function() {
+ // Reset urlbar value
+ gURLBar.value = testActionURL;
+ gURLBar.valueIsTyped = true;
+ // Sanity check that we have the right value
+ is(gURLBar.value, testActionURL, "gURLBar.value starts with correct value");
+
+ // Now just select part of the value & cut that.
+ gURLBar.selectionStart = testURL.length - 10;
+ gURLBar.selectionEnd = testURL.length;
+ goDoCommand("cmd_cut");
+ },
+ success: function() {
+ is(gURLBar.value, testURL.substring(0, testURL.length - 10), "gURLBar.value has the correct value");
+ }
+ }
+];
+
+function test() {
+ waitForExplicitFinish();
+ testTab = gBrowser.addTab();
+ gBrowser.selectedTab = testTab;
+
+ // Kick off the testing
+ runNextTest();
+}
diff --git a/browser/base/content/test/browser_bug559991.js b/browser/base/content/test/browser_bug559991.js
new file mode 100644
index 000000000..60b2621d0
--- /dev/null
+++ b/browser/base/content/test/browser_bug559991.js
@@ -0,0 +1,42 @@
+var tab;
+
+function test() {
+
+ // ----------
+ // Test setup
+
+ waitForExplicitFinish();
+
+ gPrefService.setBoolPref("browser.zoom.updateBackgroundTabs", true);
+ gPrefService.setBoolPref("browser.zoom.siteSpecific", true);
+
+ let uri = "http://example.org/browser/browser/base/content/test/dummy_page.html";
+
+ Task.spawn(function () {
+ tab = gBrowser.addTab();
+ yield FullZoomHelper.load(tab, uri);
+
+ // -------------------------------------------------------------------
+ // Test - Trigger a tab switch that should update the zoom level
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(tab);
+ ok(true, "applyPrefToSetting was called");
+ }).then(endTest, FullZoomHelper.failAndContinue(endTest));
+}
+
+// -------------
+// Test clean-up
+function endTest() {
+ Task.spawn(function () {
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(tab);
+
+ tab = null;
+
+ if (gPrefService.prefHasUserValue("browser.zoom.updateBackgroundTabs"))
+ gPrefService.clearUserPref("browser.zoom.updateBackgroundTabs");
+
+ if (gPrefService.prefHasUserValue("browser.zoom.siteSpecific"))
+ gPrefService.clearUserPref("browser.zoom.siteSpecific");
+
+ finish();
+ });
+}
diff --git a/browser/base/content/test/browser_bug561623.js b/browser/base/content/test/browser_bug561623.js
new file mode 100644
index 000000000..6f7fc85c7
--- /dev/null
+++ b/browser/base/content/test/browser_bug561623.js
@@ -0,0 +1,29 @@
+function test() {
+ waitForExplicitFinish();
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", function () {
+ gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+ let doc = gBrowser.contentDocument;
+ let tooltip = document.getElementById("aHTMLTooltip");
+ let i = doc.getElementById("i");
+
+ ok(!tooltip.fillInPageTooltip(i),
+ "No tooltip should be shown when @title is null");
+
+ i.title = "foo";
+ ok(tooltip.fillInPageTooltip(i),
+ "A tooltip should be shown when @title is not the empty string");
+
+ i.pattern = "bar";
+ ok(tooltip.fillInPageTooltip(i),
+ "A tooltip should be shown when @title is not the empty string");
+
+ gBrowser.removeCurrentTab();
+ finish();
+ }, true);
+
+ content.location =
+ "data:text/html,<!DOCTYPE html><html><body><input id='i'></body></html>";
+}
+
diff --git a/browser/base/content/test/browser_bug561636.js b/browser/base/content/test/browser_bug561636.js
new file mode 100644
index 000000000..1cf689390
--- /dev/null
+++ b/browser/base/content/test/browser_bug561636.js
@@ -0,0 +1,474 @@
+var gInvalidFormPopup = document.getElementById('invalid-form-popup');
+ok(gInvalidFormPopup,
+ "The browser should have a popup to show when a form is invalid");
+
+function checkPopupShow()
+{
+ ok(gInvalidFormPopup.state == 'showing' || gInvalidFormPopup.state == 'open',
+ "[Test " + testId + "] The invalid form popup should be shown");
+}
+
+function checkPopupHide()
+{
+ ok(gInvalidFormPopup.state != 'showing' && gInvalidFormPopup.state != 'open',
+ "[Test " + testId + "] The invalid form popup should not be shown");
+}
+
+function checkPopupMessage(doc)
+{
+ is(gInvalidFormPopup.firstChild.textContent,
+ doc.getElementById('i').validationMessage,
+ "[Test " + testId + "] The panel should show the message from validationMessage");
+}
+
+let gObserver = {
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormSubmitObserver]),
+
+ notifyInvalidSubmit : function (aFormElement, aInvalidElements)
+ {
+ }
+};
+
+var testId = -1;
+
+function nextTest()
+{
+ testId++;
+ if (testId >= tests.length) {
+ finish();
+ return;
+ }
+ executeSoon(tests[testId]);
+}
+
+function test()
+{
+ waitForExplicitFinish();
+ waitForFocus(nextTest);
+}
+
+var tests = [
+
+/**
+ * In this test, we check that no popup appears if the form is valid.
+ */
+function()
+{
+ let uri = "data:text/html,<html><body><iframe name='t'></iframe><form target='t' action='data:text/html,'><input><input id='s' type='submit'></form></body></html>";
+ let tab = gBrowser.addTab();
+
+ tab.linkedBrowser.addEventListener("load", function(aEvent) {
+ tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+ let doc = gBrowser.contentDocument;
+
+ doc.getElementById('s').click();
+
+ executeSoon(function() {
+ checkPopupHide();
+
+ // Clean-up
+ gBrowser.removeTab(gBrowser.selectedTab);
+ nextTest();
+ });
+ }, true);
+
+ gBrowser.selectedTab = tab;
+ gBrowser.selectedTab.linkedBrowser.loadURI(uri);
+},
+
+/**
+ * In this test, we check that, when an invalid form is submitted,
+ * the invalid element is focused and a popup appears.
+ */
+function()
+{
+ let uri = "data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input required id='i'><input id='s' type='submit'></form>";
+ let tab = gBrowser.addTab();
+
+ gInvalidFormPopup.addEventListener("popupshown", function() {
+ gInvalidFormPopup.removeEventListener("popupshown", arguments.callee, false);
+
+ let doc = gBrowser.contentDocument;
+ is(doc.activeElement, doc.getElementById('i'),
+ "First invalid element should be focused");
+
+ checkPopupShow();
+ checkPopupMessage(doc);
+
+ // Clean-up and next test.
+ gBrowser.removeTab(gBrowser.selectedTab);
+ nextTest();
+ }, false);
+
+ tab.linkedBrowser.addEventListener("load", function(aEvent) {
+ tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+ gBrowser.contentDocument.getElementById('s').click();
+ }, true);
+
+ gBrowser.selectedTab = tab;
+ gBrowser.selectedTab.linkedBrowser.loadURI(uri);
+},
+
+/**
+ * In this test, we check that, when an invalid form is submitted,
+ * the first invalid element is focused and a popup appears.
+ */
+function()
+{
+ let uri = "data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input><input id='i' required><input required><input id='s' type='submit'></form>";
+ let tab = gBrowser.addTab();
+
+ gInvalidFormPopup.addEventListener("popupshown", function() {
+ gInvalidFormPopup.removeEventListener("popupshown", arguments.callee, false);
+
+ let doc = gBrowser.contentDocument;
+ is(doc.activeElement, doc.getElementById('i'),
+ "First invalid element should be focused");
+
+ checkPopupShow();
+ checkPopupMessage(doc);
+
+ // Clean-up and next test.
+ gBrowser.removeTab(gBrowser.selectedTab);
+ nextTest();
+ }, false);
+
+ tab.linkedBrowser.addEventListener("load", function(aEvent) {
+ tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+ gBrowser.contentDocument.getElementById('s').click();
+ }, true);
+
+ gBrowser.selectedTab = tab;
+ gBrowser.selectedTab.linkedBrowser.loadURI(uri);
+},
+
+/**
+ * In this test, we check that, we hide the popup by interacting with the
+ * invalid element if the element becomes valid.
+ */
+function()
+{
+ let uri = "data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>";
+ let tab = gBrowser.addTab();
+
+ gInvalidFormPopup.addEventListener("popupshown", function() {
+ gInvalidFormPopup.removeEventListener("popupshown", arguments.callee, false);
+
+ let doc = gBrowser.contentDocument;
+ is(doc.activeElement, doc.getElementById('i'),
+ "First invalid element should be focused");
+
+ checkPopupShow();
+ checkPopupMessage(doc);
+
+ EventUtils.synthesizeKey("a", {});
+
+ executeSoon(function () {
+ checkPopupHide();
+
+ // Clean-up and next test.
+ gBrowser.removeTab(gBrowser.selectedTab);
+ nextTest();
+ });
+ }, false);
+
+ tab.linkedBrowser.addEventListener("load", function(aEvent) {
+ tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+ gBrowser.contentDocument.getElementById('s').click();
+ }, true);
+
+ gBrowser.selectedTab = tab;
+ gBrowser.selectedTab.linkedBrowser.loadURI(uri);
+},
+
+/**
+ * In this test, we check that, we don't hide the popup by interacting with the
+ * invalid element if the element is still invalid.
+ */
+function()
+{
+ let uri = "data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input type='email' id='i' required><input id='s' type='submit'></form>";
+ let tab = gBrowser.addTab();
+
+ gInvalidFormPopup.addEventListener("popupshown", function() {
+ gInvalidFormPopup.removeEventListener("popupshown", arguments.callee, false);
+
+ let doc = gBrowser.contentDocument;
+ is(doc.activeElement, doc.getElementById('i'),
+ "First invalid element should be focused");
+
+ checkPopupShow();
+ checkPopupMessage(doc);
+
+ EventUtils.synthesizeKey("a", {});
+
+ executeSoon(function () {
+ checkPopupShow();
+
+ // Clean-up and next test.
+ gBrowser.removeTab(gBrowser.selectedTab);
+ nextTest();
+ });
+ }, false);
+
+ tab.linkedBrowser.addEventListener("load", function(aEvent) {
+ tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+ gBrowser.contentDocument.getElementById('s').click();
+ }, true);
+
+ gBrowser.selectedTab = tab;
+ gBrowser.selectedTab.linkedBrowser.loadURI(uri);
+},
+
+/**
+ * In this test, we check that we can hide the popup by blurring the invalid
+ * element.
+ */
+function()
+{
+ let uri = "data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>";
+ let tab = gBrowser.addTab();
+
+ gInvalidFormPopup.addEventListener("popupshown", function() {
+ gInvalidFormPopup.removeEventListener("popupshown", arguments.callee, false);
+
+ let doc = gBrowser.contentDocument;
+ is(doc.activeElement, doc.getElementById('i'),
+ "First invalid element should be focused");
+
+ checkPopupShow();
+ checkPopupMessage(doc);
+
+ doc.getElementById('i').blur();
+
+ executeSoon(function () {
+ checkPopupHide();
+
+ // Clean-up and next test.
+ gBrowser.removeTab(gBrowser.selectedTab);
+ nextTest();
+ });
+ }, false);
+
+ tab.linkedBrowser.addEventListener("load", function(aEvent) {
+ tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+ gBrowser.contentDocument.getElementById('s').click();
+ }, true);
+
+ gBrowser.selectedTab = tab;
+ gBrowser.selectedTab.linkedBrowser.loadURI(uri);
+},
+
+/**
+ * In this test, we check that we can hide the popup by pressing TAB.
+ */
+function()
+{
+ let uri = "data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>";
+ let tab = gBrowser.addTab();
+
+ gInvalidFormPopup.addEventListener("popupshown", function() {
+ gInvalidFormPopup.removeEventListener("popupshown", arguments.callee, false);
+
+ let doc = gBrowser.contentDocument;
+ is(doc.activeElement, doc.getElementById('i'),
+ "First invalid element should be focused");
+
+ checkPopupShow();
+ checkPopupMessage(doc);
+
+ EventUtils.synthesizeKey("VK_TAB", {});
+
+ executeSoon(function () {
+ checkPopupHide();
+
+ // Clean-up and next test.
+ gBrowser.removeTab(gBrowser.selectedTab);
+ nextTest();
+ });
+ }, false);
+
+ tab.linkedBrowser.addEventListener("load", function(aEvent) {
+ tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+ gBrowser.contentDocument.getElementById('s').click();
+ }, true);
+
+ gBrowser.selectedTab = tab;
+ gBrowser.selectedTab.linkedBrowser.loadURI(uri);
+},
+
+/**
+ * In this test, we check that the popup will hide if we move to another tab.
+ */
+function()
+{
+ let uri = "data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>";
+ let tab = gBrowser.addTab();
+
+ gInvalidFormPopup.addEventListener("popupshown", function() {
+ gInvalidFormPopup.removeEventListener("popupshown", arguments.callee, false);
+
+ let doc = gBrowser.contentDocument;
+ is(doc.activeElement, doc.getElementById('i'),
+ "First invalid element should be focused");
+
+ checkPopupShow();
+ checkPopupMessage(doc);
+
+ // Create a new tab and move to it.
+ gBrowser.selectedTab = gBrowser.addTab("about:blank", {skipAnimation: true});
+
+ executeSoon(function() {
+ checkPopupHide();
+
+ // Clean-up and next test.
+ gBrowser.removeTab(gBrowser.selectedTab);
+ gBrowser.removeTab(gBrowser.selectedTab);
+ nextTest();
+ });
+ }, false);
+
+ tab.linkedBrowser.addEventListener("load", function(aEvent) {
+ tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+ gBrowser.contentDocument.getElementById('s').click();
+ }, true);
+
+ gBrowser.selectedTab = tab;
+ gBrowser.selectedTab.linkedBrowser.loadURI(uri);
+},
+
+/**
+ * In this test, we check that nothing happen (no focus nor popup) if the
+ * invalid form is submitted in another tab than the current focused one
+ * (submitted in background).
+ */
+function()
+{
+ let uri = "data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input id='i' required><input id='s' type='submit'></form>";
+ let tab = gBrowser.addTab();
+
+ gObserver.notifyInvalidSubmit = function() {
+ executeSoon(function() {
+ let doc = tab.linkedBrowser.contentDocument;
+ isnot(doc.activeElement, doc.getElementById('i'),
+ "We should not focus the invalid element when the form is submitted in background");
+
+ checkPopupHide();
+
+ // Clean-up
+ Services.obs.removeObserver(gObserver, "invalidformsubmit");
+ gObserver.notifyInvalidSubmit = function () {};
+ gBrowser.removeTab(tab);
+
+ nextTest();
+ });
+ };
+
+ Services.obs.addObserver(gObserver, "invalidformsubmit", false);
+
+ tab.linkedBrowser.addEventListener("load", function(e) {
+ // Ignore load events from the iframe.
+ if (tab.linkedBrowser.contentDocument == e.target) {
+ let browser = e.currentTarget;
+ browser.removeEventListener("load", arguments.callee, true);
+
+ isnot(gBrowser.selectedTab.linkedBrowser, browser,
+ "This tab should have been loaded in background");
+ browser.contentDocument.getElementById('s').click();
+ }
+ }, true);
+
+ tab.linkedBrowser.loadURI(uri);
+},
+
+/**
+ * In this test, we check that the author defined error message is shown.
+ */
+function()
+{
+ let uri = "data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input x-moz-errormessage='foo' required id='i'><input id='s' type='submit'></form>";
+ let tab = gBrowser.addTab();
+
+ gInvalidFormPopup.addEventListener("popupshown", function() {
+ gInvalidFormPopup.removeEventListener("popupshown", arguments.callee, false);
+
+ let doc = gBrowser.contentDocument;
+ is(doc.activeElement, doc.getElementById('i'),
+ "First invalid element should be focused");
+
+ checkPopupShow();
+
+ is(gInvalidFormPopup.firstChild.textContent, "foo",
+ "The panel should show the author defined error message");
+
+ // Clean-up and next test.
+ gBrowser.removeTab(gBrowser.selectedTab);
+ nextTest();
+ }, false);
+
+ tab.linkedBrowser.addEventListener("load", function(aEvent) {
+ tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+ gBrowser.contentDocument.getElementById('s').click();
+ }, true);
+
+ gBrowser.selectedTab = tab;
+ gBrowser.selectedTab.linkedBrowser.loadURI(uri);
+},
+
+/**
+ * In this test, we check that the message is correctly updated when it changes.
+ */
+function()
+{
+ let uri = "data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input type='email' required id='i'><input id='s' type='submit'></form>";
+ let tab = gBrowser.addTab();
+
+ gInvalidFormPopup.addEventListener("popupshown", function() {
+ gInvalidFormPopup.removeEventListener("popupshown", arguments.callee, false);
+
+ let doc = gBrowser.contentDocument;
+ let input = doc.getElementById('i');
+ is(doc.activeElement, input, "First invalid element should be focused");
+
+ checkPopupShow();
+
+ is(gInvalidFormPopup.firstChild.textContent, input.validationMessage,
+ "The panel should show the current validation message");
+
+ input.addEventListener('input', function() {
+ input.removeEventListener('input', arguments.callee, false);
+
+ executeSoon(function() {
+ // Now, the element suffers from another error, the message should have
+ // been updated.
+ is(gInvalidFormPopup.firstChild.textContent, input.validationMessage,
+ "The panel should show the current validation message");
+
+ // Clean-up and next test.
+ gBrowser.removeTab(gBrowser.selectedTab);
+ nextTest();
+ });
+ }, false);
+
+ EventUtils.synthesizeKey('f', {});
+ }, false);
+
+ tab.linkedBrowser.addEventListener("load", function(aEvent) {
+ tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+ gBrowser.contentDocument.getElementById('s').click();
+ }, true);
+
+ gBrowser.selectedTab = tab;
+ gBrowser.selectedTab.linkedBrowser.loadURI(uri);
+},
+
+];
diff --git a/browser/base/content/test/browser_bug562649.js b/browser/base/content/test/browser_bug562649.js
new file mode 100644
index 000000000..af7cdaeb1
--- /dev/null
+++ b/browser/base/content/test/browser_bug562649.js
@@ -0,0 +1,27 @@
+function test() {
+ const URI = "data:text/plain,bug562649";
+ browserDOMWindow.openURI(makeURI(URI),
+ null,
+ Ci.nsIBrowserDOMWindow.OPEN_NEWTAB,
+ Ci.nsIBrowserDOMWindow.OPEN_EXTERNAL);
+
+ ok(XULBrowserWindow.isBusy, "window is busy loading a page");
+ is(gBrowser.userTypedValue, URI, "userTypedValue matches test URI");
+ is(gURLBar.value, URI, "location bar value matches test URI");
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.removeCurrentTab();
+ is(gBrowser.userTypedValue, URI, "userTypedValue matches test URI after switching tabs");
+ is(gURLBar.value, URI, "location bar value matches test URI after switching tabs");
+
+ waitForExplicitFinish();
+ gBrowser.selectedBrowser.addEventListener("load", function () {
+ gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+ is(gBrowser.userTypedValue, null, "userTypedValue is null as the page has loaded");
+ is(gURLBar.value, URI, "location bar value matches test URI as the page has loaded");
+
+ gBrowser.removeCurrentTab();
+ finish();
+ }, true);
+}
diff --git a/browser/base/content/test/browser_bug563588.js b/browser/base/content/test/browser_bug563588.js
new file mode 100644
index 000000000..a1774fb7e
--- /dev/null
+++ b/browser/base/content/test/browser_bug563588.js
@@ -0,0 +1,30 @@
+function press(key, expectedPos) {
+ var originalSelectedTab = gBrowser.selectedTab;
+ EventUtils.synthesizeKey("VK_" + key.toUpperCase(), { accelKey: true });
+ is(gBrowser.selectedTab, originalSelectedTab,
+ "accel+" + key + " doesn't change which tab is selected");
+ is(gBrowser.tabContainer.selectedIndex, expectedPos,
+ "accel+" + key + " moves the tab to the expected position");
+ is(document.activeElement, gBrowser.selectedTab,
+ "accel+" + key + " leaves the selected tab focused");
+}
+
+function test() {
+ gBrowser.addTab();
+ gBrowser.addTab();
+ is(gBrowser.tabs.length, 3, "got three tabs");
+ is(gBrowser.tabs[0], gBrowser.selectedTab, "first tab is selected");
+
+ gBrowser.selectedTab.focus();
+ is(document.activeElement, gBrowser.selectedTab, "selected tab is focused");
+
+ press("right", 1);
+ press("down", 2);
+ press("left", 1);
+ press("up", 0);
+ press("end", 2);
+ press("home", 0);
+
+ gBrowser.removeCurrentTab();
+ gBrowser.removeCurrentTab();
+}
diff --git a/browser/base/content/test/browser_bug565575.js b/browser/base/content/test/browser_bug565575.js
new file mode 100644
index 000000000..a6a227010
--- /dev/null
+++ b/browser/base/content/test/browser_bug565575.js
@@ -0,0 +1,13 @@
+function test() {
+ gBrowser.selectedBrowser.focus();
+ BrowserOpenTab();
+ ok(gURLBar.focused, "location bar is focused for a new tab");
+
+ gBrowser.selectedTab = gBrowser.tabs[0];
+ ok(!gURLBar.focused, "location bar isn't focused for the previously selected tab");
+
+ gBrowser.selectedTab = gBrowser.tabs[1];
+ ok(gURLBar.focused, "location bar is re-focused when selecting the new tab");
+
+ gBrowser.removeCurrentTab();
+}
diff --git a/browser/base/content/test/browser_bug565667.js b/browser/base/content/test/browser_bug565667.js
new file mode 100644
index 000000000..6fac026c8
--- /dev/null
+++ b/browser/base/content/test/browser_bug565667.js
@@ -0,0 +1,59 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
+
+function test() {
+ waitForExplicitFinish();
+ // Open the javascript console. It has the mac menu overlay, so browser.js is
+ // loaded in it.
+ let consoleWin = window.open("chrome://global/content/console.xul", "_blank",
+ "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar");
+ testWithOpenWindow(consoleWin);
+}
+
+function testWithOpenWindow(consoleWin) {
+ // Add a tab so we don't open the url into the current tab
+ let newTab = gBrowser.addTab("http://example.com");
+ gBrowser.selectedTab = newTab;
+
+ let numTabs = gBrowser.tabs.length;
+
+ waitForFocus(function() {
+ // Sanity check
+ is(fm.activeWindow, consoleWin,
+ "the console window is focused");
+
+ gBrowser.tabContainer.addEventListener("TabOpen", function(aEvent) {
+ gBrowser.tabContainer.removeEventListener("TabOpen", arguments.callee, true);
+ let browser = aEvent.originalTarget.linkedBrowser;
+ browser.addEventListener("pageshow", function(event) {
+ if (event.target.location.href != "about:addons")
+ return;
+ browser.removeEventListener("pageshow", arguments.callee, true);
+
+ is(fm.activeWindow, window,
+ "the browser window was focused");
+ is(browser.currentURI.spec, "about:addons",
+ "about:addons was loaded in the window");
+ is(gBrowser.tabs.length, numTabs + 1,
+ "a new tab was added");
+
+ // Cleanup.
+ executeSoon(function() {
+ consoleWin.close();
+ gBrowser.removeTab(gBrowser.selectedTab);
+ gBrowser.removeTab(newTab);
+ finish();
+ });
+ }, true);
+ }, true);
+
+ // Open the addons manager, uses switchToTabHavingURI.
+ consoleWin.BrowserOpenAddonsMgr();
+ }, consoleWin);
+}
+
+// Ideally we'd also check that the case for no open windows works, but we can't
+// due to limitations with the testing framework.
diff --git a/browser/base/content/test/browser_bug567306.js b/browser/base/content/test/browser_bug567306.js
new file mode 100644
index 000000000..1fa35efa8
--- /dev/null
+++ b/browser/base/content/test/browser_bug567306.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+let Ci = Components.interfaces;
+
+function test() {
+ waitForExplicitFinish();
+
+ whenNewWindowLoaded(undefined, function (win) {
+ whenDelayedStartupFinished(win, function () {
+ let selectedBrowser = win.gBrowser.selectedBrowser;
+ selectedBrowser.addEventListener("pageshow", function() {
+ selectedBrowser.removeEventListener("pageshow", arguments.callee, true);
+ ok(true, "pageshow listener called: " + win.content.location);
+ waitForFocus(function () {
+ onFocus(win);
+ }, selectedBrowser.contentWindow);
+ }, true);
+ selectedBrowser.loadURI("data:text/html,<h1 id='h1'>Select Me</h1>");
+ });
+ });
+}
+
+function selectText(win) {
+ let elt = win.document.getElementById("h1");
+ let selection = win.getSelection();
+ let range = win.document.createRange();
+ range.setStart(elt, 0);
+ range.setEnd(elt, 1);
+ selection.removeAllRanges();
+ selection.addRange(range);
+}
+
+function onFocus(win) {
+ ok(!win.gFindBarInitialized, "find bar is not yet initialized");
+ let findBar = win.gFindBar;
+ selectText(win.content);
+ findBar.onFindCommand();
+ is(findBar._findField.value, "Select Me", "Findbar is initialized with selection");
+ findBar.close();
+ win.close();
+ finish();
+}
diff --git a/browser/base/content/test/browser_bug575561.js b/browser/base/content/test/browser_bug575561.js
new file mode 100644
index 000000000..61c2676e4
--- /dev/null
+++ b/browser/base/content/test/browser_bug575561.js
@@ -0,0 +1,90 @@
+function test() {
+ waitForExplicitFinish();
+
+ // Pinned: Link to the same domain should not open a new tab
+ // Tests link to http://example.com/browser/browser/base/content/test/dummy_page.html
+ testLink(0, true, false, function() {
+ // Pinned: Link to a different subdomain should open a new tab
+ // Tests link to http://test1.example.com/browser/browser/base/content/test/dummy_page.html
+ testLink(1, true, true, function() {
+ // Pinned: Link to a different domain should open a new tab
+ // Tests link to http://example.org/browser/browser/base/content/test/dummy_page.html
+ testLink(2, true, true, function() {
+ // Not Pinned: Link to a different domain should not open a new tab
+ // Tests link to http://example.org/browser/browser/base/content/test/dummy_page.html
+ testLink(2, false, false, function() {
+ // Pinned: Targetted link should open a new tab
+ // Tests link to http://example.org/browser/browser/base/content/test/dummy_page.html with target="foo"
+ testLink(3, true, true, function() {
+ // Pinned: Link in a subframe should not open a new tab
+ // Tests link to http://example.org/browser/browser/base/content/test/dummy_page.html in subframe
+ testLink(0, true, false, function() {
+ // Pinned: Link to the same domain (with www prefix) should not open a new tab
+ // Tests link to http://www.example.com/browser/browser/base/content/test/dummy_page.html
+ testLink(4, true, false, function() {
+ // Pinned: Link to a data: URI should not open a new tab
+ // Tests link to data:text/html,<!DOCTYPE html><html><body>Another Page</body></html>
+ testLink(5, true, false, function() {
+ // Pinned: Link to an about: URI should not open a new tab
+ // Tests link to about:mozilla
+ testLink(6, true, false, finish);
+ });
+ });
+ }, true);
+ });
+ });
+ });
+ });
+ });
+}
+
+function testLink(aLinkIndex, pinTab, expectNewTab, nextTest, testSubFrame) {
+ let appTab = gBrowser.addTab("http://example.com/browser/browser/base/content/test/app_bug575561.html", {skipAnimation: true});
+ if (pinTab)
+ gBrowser.pinTab(appTab);
+ gBrowser.selectedTab = appTab;
+ appTab.linkedBrowser.addEventListener("load", onLoad, true);
+
+ let loadCount = 0;
+ function onLoad() {
+ loadCount++;
+ if (loadCount < 2)
+ return;
+
+ appTab.linkedBrowser.removeEventListener("load", onLoad, true);
+
+ let browser = gBrowser.getBrowserForTab(appTab);
+ if (testSubFrame)
+ browser = browser.contentDocument.getElementsByTagName("iframe")[0];
+
+ let links = browser.contentDocument.getElementsByTagName("a");
+
+ if (expectNewTab)
+ gBrowser.tabContainer.addEventListener("TabOpen", onTabOpen, true);
+ else
+ browser.addEventListener("load", onPageLoad, true);
+
+ info("Clicking " + links[aLinkIndex].textContent);
+ EventUtils.sendMouseEvent({type:"click"}, links[aLinkIndex], browser.contentWindow);
+ let linkLocation = links[aLinkIndex].href;
+
+ function onPageLoad() {
+ browser.removeEventListener("load", onPageLoad, true);
+ is(browser.contentDocument.location.href, linkLocation, "Link should not open in a new tab");
+ executeSoon(function(){
+ gBrowser.removeTab(appTab);
+ nextTest();
+ });
+ }
+
+ function onTabOpen(event) {
+ gBrowser.tabContainer.removeEventListener("TabOpen", onTabOpen, true);
+ ok(true, "Link should open a new tab");
+ executeSoon(function(){
+ gBrowser.removeTab(appTab);
+ gBrowser.removeCurrentTab();
+ nextTest();
+ });
+ }
+ }
+}
diff --git a/browser/base/content/test/browser_bug575830.js b/browser/base/content/test/browser_bug575830.js
new file mode 100644
index 000000000..506da4a17
--- /dev/null
+++ b/browser/base/content/test/browser_bug575830.js
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+function test() {
+ let tab1, tab2;
+ const TEST_IMAGE = "http://example.org/browser/browser/base/content/test/moz.png";
+
+ waitForExplicitFinish();
+
+ Task.spawn(function () {
+ tab1 = gBrowser.addTab();
+ tab2 = gBrowser.addTab();
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(tab1);
+ yield FullZoomHelper.load(tab1, TEST_IMAGE);
+
+ is(ZoomManager.zoom, 1, "initial zoom level for first should be 1");
+
+ FullZoom.enlarge();
+ let zoom = ZoomManager.zoom;
+ isnot(zoom, 1, "zoom level should have changed");
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(tab2);
+ is(ZoomManager.zoom, 1, "initial zoom level for second tab should be 1");
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(tab1);
+ is(ZoomManager.zoom, zoom, "zoom level for first tab should not have changed");
+
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(tab1);
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(tab2);
+ }).then(finish, FullZoomHelper.failAndContinue(finish));
+}
diff --git a/browser/base/content/test/browser_bug577121.js b/browser/base/content/test/browser_bug577121.js
new file mode 100644
index 000000000..94d169be0
--- /dev/null
+++ b/browser/base/content/test/browser_bug577121.js
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ // Open 2 other tabs, and pin the second one. Like that, the initial tab
+ // should get closed.
+ let testTab1 = gBrowser.addTab();
+ let testTab2 = gBrowser.addTab();
+ gBrowser.pinTab(testTab2);
+
+ // Now execute "Close other Tabs" on the first manually opened tab (tab1).
+ // -> tab2 ist pinned, tab1 should remain open and the initial tab should
+ // get closed.
+ gBrowser.removeAllTabsBut(testTab1);
+
+ is(gBrowser.tabs.length, 2, "there are two remaining tabs open");
+ is(gBrowser.tabs[0], testTab2, "pinned tab2 stayed open");
+ is(gBrowser.tabs[1], testTab1, "tab1 stayed open");
+
+ // Cleanup. Close only one tab because we need an opened tab at the end of
+ // the test.
+ gBrowser.removeTab(testTab2);
+}
diff --git a/browser/base/content/test/browser_bug578534.js b/browser/base/content/test/browser_bug578534.js
new file mode 100644
index 000000000..5cf83cc66
--- /dev/null
+++ b/browser/base/content/test/browser_bug578534.js
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ let uriString = "http://example.com/";
+ let cookieBehavior = "network.cookie.cookieBehavior";
+ let uriObj = Services.io.newURI(uriString, null, null)
+ let cp = Components.classes["@mozilla.org/cookie/permission;1"]
+ .getService(Components.interfaces.nsICookiePermission);
+
+ Services.prefs.setIntPref(cookieBehavior, 2);
+
+ cp.setAccess(uriObj, cp.ACCESS_ALLOW);
+ gBrowser.selectedTab = gBrowser.addTab(uriString);
+ waitForExplicitFinish();
+ gBrowser.selectedBrowser.addEventListener("load", onTabLoaded, true);
+
+ function onTabLoaded() {
+ is(gBrowser.selectedBrowser.contentWindow.navigator.cookieEnabled, true,
+ "navigator.cookieEnabled should be true");
+ // Clean up
+ gBrowser.selectedBrowser.removeEventListener("load", onTabLoaded, true);
+ gBrowser.removeTab(gBrowser.selectedTab);
+ Services.prefs.setIntPref(cookieBehavior, 0);
+ cp.setAccess(uriObj, cp.ACCESS_DEFAULT);
+ finish();
+ }
+}
diff --git a/browser/base/content/test/browser_bug579872.js b/browser/base/content/test/browser_bug579872.js
new file mode 100644
index 000000000..63f64598d
--- /dev/null
+++ b/browser/base/content/test/browser_bug579872.js
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ let newTab = gBrowser.addTab();
+ waitForExplicitFinish();
+ newTab.linkedBrowser.addEventListener("load", mainPart, true);
+
+ function mainPart() {
+ gBrowser.pinTab(newTab);
+ gBrowser.selectedTab = newTab;
+
+ openUILinkIn("javascript:var x=0;", "current");
+ is(gBrowser.tabs.length, 2, "Should open in current tab");
+
+ openUILinkIn("http://example.com/1", "current");
+ is(gBrowser.tabs.length, 2, "Should open in current tab");
+
+ openUILinkIn("http://example.org/", "current");
+ is(gBrowser.tabs.length, 3, "Should open in new tab");
+
+ newTab.removeEventListener("load", mainPart, true);
+ gBrowser.removeTab(newTab);
+ gBrowser.removeTab(gBrowser.tabs[1]); // example.org tab
+ finish();
+ }
+ newTab.linkedBrowser.loadURI("http://example.com");
+}
diff --git a/browser/base/content/test/browser_bug580638.js b/browser/base/content/test/browser_bug580638.js
new file mode 100644
index 000000000..ead51105e
--- /dev/null
+++ b/browser/base/content/test/browser_bug580638.js
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ function testState(aPinned) {
+ function elemAttr(id, attr) document.getElementById(id).getAttribute(attr);
+
+ if (aPinned) {
+ is(elemAttr("key_close", "disabled"), "true",
+ "key_close should be disabled when a pinned-tab is selected");
+ is(elemAttr("menu_close", "key"), "",
+ "menu_close shouldn't have a key set when a pinned is selected");
+ }
+ else {
+ is(elemAttr("key_close", "disabled"), "",
+ "key_closed shouldn't have disabled state set when a non-pinned tab is selected");
+ is(elemAttr("menu_close", "key"), "key_close",
+ "menu_close should have key_close set as its key when a non-pinned tab is selected");
+ }
+ }
+
+ let lastSelectedTab = gBrowser.selectedTab;
+ ok(!lastSelectedTab.pinned, "We should have started with a regular tab selected");
+
+ testState(false);
+
+ let pinnedTab = gBrowser.addTab("about:blank");
+ gBrowser.pinTab(pinnedTab);
+
+ // Just pinning the tab shouldn't change the key state.
+ testState(false);
+
+ // Test updating key state after selecting a tab.
+ gBrowser.selectedTab = pinnedTab;
+ testState(true);
+
+ gBrowser.selectedTab = lastSelectedTab;
+ testState(false);
+
+ gBrowser.selectedTab = pinnedTab;
+ testState(true);
+
+ // Test updating the key state after un/pinning the tab.
+ gBrowser.unpinTab(pinnedTab);
+ testState(false);
+
+ gBrowser.pinTab(pinnedTab);
+ testState(true);
+
+ // Test updating the key state after removing the tab.
+ gBrowser.removeTab(pinnedTab);
+ testState(false);
+
+ finish();
+}
diff --git a/browser/base/content/test/browser_bug580956.js b/browser/base/content/test/browser_bug580956.js
new file mode 100644
index 000000000..f7cc3c3fe
--- /dev/null
+++ b/browser/base/content/test/browser_bug580956.js
@@ -0,0 +1,29 @@
+function numClosedTabs()
+ Cc["@mozilla.org/browser/sessionstore;1"].
+ getService(Ci.nsISessionStore).
+ getClosedTabCount(window);
+
+function isUndoCloseEnabled() {
+ updateTabContextMenu();
+ return !document.getElementById("context_undoCloseTab").disabled;
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ gPrefService.setIntPref("browser.sessionstore.max_tabs_undo", 0);
+ gPrefService.clearUserPref("browser.sessionstore.max_tabs_undo");
+ is(numClosedTabs(), 0, "There should be 0 closed tabs.");
+ ok(!isUndoCloseEnabled(), "Undo Close Tab should be disabled.");
+
+ var tab = gBrowser.addTab("http://mochi.test:8888/");
+ var browser = gBrowser.getBrowserForTab(tab);
+ browser.addEventListener("load", function() {
+ browser.removeEventListener("load", arguments.callee, true);
+
+ gBrowser.removeTab(tab);
+ ok(isUndoCloseEnabled(), "Undo Close Tab should be enabled.");
+
+ finish();
+ }, true);
+}
diff --git a/browser/base/content/test/browser_bug581242.js b/browser/base/content/test/browser_bug581242.js
new file mode 100644
index 000000000..668c0cd41
--- /dev/null
+++ b/browser/base/content/test/browser_bug581242.js
@@ -0,0 +1,21 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ // Create a new tab and load about:addons
+ let blanktab = gBrowser.addTab();
+ gBrowser.selectedTab = blanktab;
+ BrowserOpenAddonsMgr();
+
+ is(blanktab, gBrowser.selectedTab, "Current tab should be blank tab");
+ // Verify that about:addons loads
+ waitForExplicitFinish();
+ gBrowser.selectedBrowser.addEventListener("load", function() {
+ gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+ let browser = blanktab.linkedBrowser;
+ is(browser.currentURI.spec, "about:addons", "about:addons should load into blank tab.");
+ gBrowser.removeTab(blanktab);
+ finish();
+ }, true);
+}
diff --git a/browser/base/content/test/browser_bug581253.js b/browser/base/content/test/browser_bug581253.js
new file mode 100644
index 000000000..3d2575118
--- /dev/null
+++ b/browser/base/content/test/browser_bug581253.js
@@ -0,0 +1,102 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+let testURL = "data:text/plain,nothing but plain text";
+let testTag = "581253_tag";
+let timerID = -1;
+
+function test() {
+ registerCleanupFunction(function() {
+ PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
+ if (timerID > 0) {
+ clearTimeout(timerID);
+ }
+ });
+ waitForExplicitFinish();
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+ tab.linkedBrowser.addEventListener("load", (function(event) {
+ tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+ let uri = makeURI(testURL);
+ let bmTxn =
+ new PlacesCreateBookmarkTransaction(uri,
+ PlacesUtils.unfiledBookmarksFolderId,
+ -1, "", null, []);
+ PlacesUtils.transactionManager.doTransaction(bmTxn);
+
+ ok(PlacesUtils.bookmarks.isBookmarked(uri), "the test url is bookmarked");
+ waitForStarChange(true, onStarred);
+ }), true);
+
+ content.location = testURL;
+}
+
+function waitForStarChange(aValue, aCallback) {
+ let expectedStatus = aValue ? BookmarkingUI.STATUS_STARRED
+ : BookmarkingUI.STATUS_UNSTARRED;
+ if (BookmarkingUI.status == BookmarkingUI.STATUS_UPDATING ||
+ BookmarkingUI.status != expectedStatus) {
+ info("Waiting for star button change.");
+ setTimeout(waitForStarChange, 50, aValue, aCallback);
+ return;
+ }
+ aCallback();
+}
+
+function onStarred() {
+ is(BookmarkingUI.status, BookmarkingUI.STATUS_STARRED,
+ "star button indicates that the page is bookmarked");
+
+ let uri = makeURI(testURL);
+ let tagTxn = new PlacesTagURITransaction(uri, [testTag]);
+ PlacesUtils.transactionManager.doTransaction(tagTxn);
+
+ StarUI.panel.addEventListener("popupshown", onPanelShown, false);
+ BookmarkingUI.star.click();
+}
+
+function onPanelShown(aEvent) {
+ if (aEvent.target == StarUI.panel) {
+ StarUI.panel.removeEventListener("popupshown", arguments.callee, false);
+ let tagsField = document.getElementById("editBMPanel_tagsField");
+ ok(tagsField.value == testTag, "tags field value was set");
+ tagsField.focus();
+
+ StarUI.panel.addEventListener("popuphidden", onPanelHidden, false);
+ let removeButton = document.getElementById("editBookmarkPanelRemoveButton");
+ removeButton.click();
+ }
+}
+
+/**
+ * Clears history invoking callback when done.
+ */
+function waitForClearHistory(aCallback)
+{
+ let observer = {
+ observe: function(aSubject, aTopic, aData)
+ {
+ Services.obs.removeObserver(this, PlacesUtils.TOPIC_EXPIRATION_FINISHED);
+ aCallback(aSubject, aTopic, aData);
+ }
+ };
+ Services.obs.addObserver(observer, PlacesUtils.TOPIC_EXPIRATION_FINISHED, false);
+ PlacesUtils.bhistory.removeAllPages();
+}
+
+function onPanelHidden(aEvent) {
+ if (aEvent.target == StarUI.panel) {
+ StarUI.panel.removeEventListener("popuphidden", arguments.callee, false);
+
+ executeSoon(function() {
+ ok(!PlacesUtils.bookmarks.isBookmarked(makeURI(testURL)),
+ "the bookmark for the test url has been removed");
+ is(BookmarkingUI.status, BookmarkingUI.STATUS_UNSTARRED,
+ "star button indicates that the bookmark has been removed");
+ gBrowser.removeCurrentTab();
+ waitForClearHistory(finish);
+ });
+ }
+}
diff --git a/browser/base/content/test/browser_bug581947.js b/browser/base/content/test/browser_bug581947.js
new file mode 100644
index 000000000..458cb7473
--- /dev/null
+++ b/browser/base/content/test/browser_bug581947.js
@@ -0,0 +1,90 @@
+function check(aElementName, aBarred) {
+ let doc = gBrowser.contentDocument;
+ let tooltip = document.getElementById("aHTMLTooltip");
+ let content = doc.getElementById('content');
+
+ let e = doc.createElement(aElementName);
+ content.appendChild(e);
+
+ ok(!tooltip.fillInPageTooltip(e),
+ "No tooltip should be shown when the element is valid");
+
+ e.setCustomValidity('foo');
+ if (aBarred) {
+ ok(!tooltip.fillInPageTooltip(e),
+ "No tooltip should be shown when the element is barred from constraint validation");
+ } else {
+ ok(tooltip.fillInPageTooltip(e),
+ e.tagName + " " +"A tooltip should be shown when the element isn't valid");
+ }
+
+ e.setAttribute('title', '');
+ ok (!tooltip.fillInPageTooltip(e),
+ "No tooltip should be shown if the title attribute is set");
+
+ e.removeAttribute('title');
+ content.setAttribute('novalidate', '');
+ ok (!tooltip.fillInPageTooltip(e),
+ "No tooltip should be shown if the novalidate attribute is set on the form owner");
+ content.removeAttribute('novalidate');
+
+ content.removeChild(e);
+}
+
+function todo_check(aElementName, aBarred) {
+ let doc = gBrowser.contentDocument;
+ let tooltip = document.getElementById("aHTMLTooltip");
+ let content = doc.getElementById('content');
+
+ let e = doc.createElement(aElementName);
+ content.appendChild(e);
+
+ let cought = false;
+ try {
+ e.setCustomValidity('foo');
+ } catch (e) {
+ cought = true;
+ }
+
+ todo(!cought, "setCustomValidity should exist for " + aElementName);
+
+ content.removeChild(e);
+}
+
+function test () {
+ waitForExplicitFinish();
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", function () {
+ gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+ let testData = [
+ /* element name, barred */
+ [ 'input', false ],
+ [ 'textarea', false ],
+ [ 'button', true ],
+ [ 'select', false ],
+ [ 'output', true ],
+ [ 'fieldset', true ],
+ [ 'object', true ],
+ ];
+
+ for each (let data in testData) {
+ check(data[0], data[1]);
+ }
+
+ let todo_testData = [
+ [ 'keygen', 'false' ],
+ ];
+
+ for each(let data in todo_testData) {
+ todo_check(data[0], data[1]);
+ }
+
+ gBrowser.removeCurrentTab();
+ finish();
+ }, true);
+
+ content.location =
+ "data:text/html,<!DOCTYPE html><html><body><form id='content'></form></body></html>";
+}
+
diff --git a/browser/base/content/test/browser_bug585558.js b/browser/base/content/test/browser_bug585558.js
new file mode 100644
index 000000000..f2ed7fd94
--- /dev/null
+++ b/browser/base/content/test/browser_bug585558.js
@@ -0,0 +1,152 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+let tabs = [];
+
+function addTab(aURL) {
+ tabs.push(gBrowser.addTab(aURL, {skipAnimation: true}));
+}
+
+function testAttrib(elem, attrib, attribValue, msg) {
+ is(elem.hasAttribute(attrib), attribValue, msg);
+}
+
+function test() {
+ waitForExplicitFinish();
+ is(gBrowser.tabs.length, 1, "one tab is open initially");
+
+ // Add several new tabs in sequence, hiding some, to ensure that the
+ // correct attributes get set
+
+ addTab("http://mochi.test:8888/#0");
+ addTab("http://mochi.test:8888/#1");
+ addTab("http://mochi.test:8888/#2");
+ addTab("http://mochi.test:8888/#3");
+
+ gBrowser.selectedTab = gBrowser.tabs[0];
+ testAttrib(gBrowser.tabs[0], "first-visible-tab", true,
+ "First tab marked first-visible-tab!");
+ testAttrib(gBrowser.tabs[4], "last-visible-tab", true,
+ "Fifth tab marked last-visible-tab!");
+ testAttrib(gBrowser.tabs[0], "selected", true, "First tab marked selected!");
+ testAttrib(gBrowser.tabs[0], "afterselected-visible", false,
+ "First tab not marked afterselected-visible!");
+ testAttrib(gBrowser.tabs[1], "afterselected-visible", true,
+ "Second tab marked afterselected-visible!");
+ gBrowser.hideTab(gBrowser.tabs[1]);
+ executeSoon(test_hideSecond);
+}
+
+function test_hideSecond() {
+ testAttrib(gBrowser.tabs[2], "afterselected-visible", true,
+ "Third tab marked afterselected-visible!");
+ gBrowser.showTab(gBrowser.tabs[1])
+ executeSoon(test_showSecond);
+}
+
+function test_showSecond() {
+ testAttrib(gBrowser.tabs[1], "afterselected-visible", true,
+ "Second tab marked afterselected-visible!");
+ testAttrib(gBrowser.tabs[2], "afterselected-visible", false,
+ "Third tab not marked as afterselected-visible!");
+ gBrowser.selectedTab = gBrowser.tabs[1];
+ gBrowser.hideTab(gBrowser.tabs[0]);
+ executeSoon(test_hideFirst);
+}
+
+function test_hideFirst() {
+ testAttrib(gBrowser.tabs[0], "first-visible-tab", false,
+ "Hidden first tab not marked first-visible-tab!");
+ testAttrib(gBrowser.tabs[1], "first-visible-tab", true,
+ "Second tab marked first-visible-tab!");
+ gBrowser.showTab(gBrowser.tabs[0]);
+ executeSoon(test_showFirst);
+}
+
+function test_showFirst() {
+ testAttrib(gBrowser.tabs[0], "first-visible-tab", true,
+ "First tab marked first-visible-tab!");
+ gBrowser.selectedTab = gBrowser.tabs[2];
+ testAttrib(gBrowser.tabs[3], "afterselected-visible", true,
+ "Fourth tab marked afterselected-visible!");
+
+ gBrowser.moveTabTo(gBrowser.selectedTab, 1);
+ executeSoon(test_movedLower);
+}
+
+function test_movedLower() {
+ testAttrib(gBrowser.tabs[2], "afterselected-visible", true,
+ "Third tab marked afterselected-visible!");
+ test_hoverOne();
+}
+
+function test_hoverOne() {
+ EventUtils.synthesizeMouseAtCenter(gBrowser.tabs[4], { type: "mousemove" });
+ testAttrib(gBrowser.tabs[3], "beforehovered", true, "Fourth tab marked beforehovered");
+ EventUtils.synthesizeMouseAtCenter(gBrowser.tabs[3], { type: "mousemove" });
+ testAttrib(gBrowser.tabs[2], "beforehovered", true, "Third tab marked beforehovered!");
+ testAttrib(gBrowser.tabs[2], "afterhovered", false, "Third tab not marked afterhovered!");
+ testAttrib(gBrowser.tabs[4], "afterhovered", true, "Fifth tab marked afterhovered!");
+ testAttrib(gBrowser.tabs[4], "beforehovered", false, "Fifth tab not marked beforehovered!");
+ testAttrib(gBrowser.tabs[0], "beforehovered", false, "First tab not marked beforehovered!");
+ testAttrib(gBrowser.tabs[0], "afterhovered", false, "First tab not marked afterhovered!");
+ testAttrib(gBrowser.tabs[1], "beforehovered", false, "Second tab not marked beforehovered!");
+ testAttrib(gBrowser.tabs[1], "afterhovered", false, "Second tab not marked afterhovered!");
+ testAttrib(gBrowser.tabs[3], "beforehovered", false, "Fourth tab not marked beforehovered!");
+ testAttrib(gBrowser.tabs[3], "afterhovered", false, "Fourth tab not marked afterhovered!");
+ gBrowser.removeTab(tabs.pop());
+ executeSoon(test_hoverStatePersistence);
+}
+
+function test_hoverStatePersistence() {
+ // Test that the afterhovered and beforehovered attributes are still there when
+ // a tab is selected and then unselected again. See bug 856107.
+
+ function assertState() {
+ testAttrib(gBrowser.tabs[0], "beforehovered", true, "First tab still marked beforehovered!");
+ testAttrib(gBrowser.tabs[0], "afterhovered", false, "First tab not marked afterhovered!");
+ testAttrib(gBrowser.tabs[2], "afterhovered", true, "Third tab still marked afterhovered!");
+ testAttrib(gBrowser.tabs[2], "beforehovered", false, "Third tab not marked afterhovered!");
+ testAttrib(gBrowser.tabs[1], "beforehovered", false, "Second tab not marked beforehovered!");
+ testAttrib(gBrowser.tabs[1], "afterhovered", false, "Second tab not marked afterhovered!");
+ testAttrib(gBrowser.tabs[3], "beforehovered", false, "Fourth tab not marked beforehovered!");
+ testAttrib(gBrowser.tabs[3], "afterhovered", false, "Fourth tab not marked afterhovered!");
+ }
+
+ gBrowser.selectedTab = gBrowser.tabs[3];
+ EventUtils.synthesizeMouseAtCenter(gBrowser.tabs[1], { type: "mousemove" });
+ assertState();
+ gBrowser.selectedTab = gBrowser.tabs[1];
+ assertState();
+ gBrowser.selectedTab = gBrowser.tabs[3];
+ assertState();
+ executeSoon(test_pinning);
+}
+
+function test_pinning() {
+ gBrowser.selectedTab = gBrowser.tabs[3];
+ testAttrib(gBrowser.tabs[3], "last-visible-tab", true,
+ "Fourth tab marked last-visible-tab!");
+ testAttrib(gBrowser.tabs[3], "selected", true, "Fourth tab marked selected!");
+ testAttrib(gBrowser.tabs[3], "afterselected-visible", false,
+ "Fourth tab not marked afterselected-visible!");
+ // Causes gBrowser.tabs to change indices
+ gBrowser.pinTab(gBrowser.tabs[3]);
+ testAttrib(gBrowser.tabs[3], "last-visible-tab", true,
+ "Fourth tab marked last-visible-tab!");
+ testAttrib(gBrowser.tabs[1], "afterselected-visible", true,
+ "Second tab marked afterselected-visible!");
+ testAttrib(gBrowser.tabs[0], "first-visible-tab", true,
+ "First tab marked first-visible-tab!");
+ testAttrib(gBrowser.tabs[0], "selected", true, "First tab marked selected!");
+ gBrowser.selectedTab = gBrowser.tabs[1];
+ testAttrib(gBrowser.tabs[2], "afterselected-visible", true,
+ "Third tab marked afterselected-visible!");
+ test_cleanUp();
+}
+
+function test_cleanUp() {
+ tabs.forEach(gBrowser.removeTab, gBrowser);
+ finish();
+}
diff --git a/browser/base/content/test/browser_bug585785.js b/browser/base/content/test/browser_bug585785.js
new file mode 100644
index 000000000..adbb5a47e
--- /dev/null
+++ b/browser/base/content/test/browser_bug585785.js
@@ -0,0 +1,35 @@
+var tab;
+
+function test() {
+ waitForExplicitFinish();
+
+ tab = gBrowser.addTab();
+ isnot(tab.getAttribute("fadein"), "true", "newly opened tab is yet to fade in");
+
+ // Try to remove the tab right before the opening animation's first frame
+ window.mozRequestAnimationFrame(checkAnimationState);
+}
+
+function checkAnimationState() {
+ is(tab.getAttribute("fadein"), "true", "tab opening animation initiated");
+
+ info(window.getComputedStyle(tab).maxWidth);
+ gBrowser.removeTab(tab, { animate: true });
+ if (!tab.parentNode) {
+ ok(true, "tab removed synchronously since the opening animation hasn't moved yet");
+ finish();
+ return;
+ }
+
+ info("tab didn't close immediately, so the tab opening animation must have started moving");
+ info("waiting for the tab to close asynchronously");
+ tab.addEventListener("transitionend", function (event) {
+ if (event.propertyName == "max-width") {
+ tab.removeEventListener("transitionend", arguments.callee, false);
+ executeSoon(function () {
+ ok(!tab.parentNode, "tab removed asynchronously");
+ finish();
+ });
+ }
+ }, false);
+}
diff --git a/browser/base/content/test/browser_bug585830.js b/browser/base/content/test/browser_bug585830.js
new file mode 100644
index 000000000..bf99c2c90
--- /dev/null
+++ b/browser/base/content/test/browser_bug585830.js
@@ -0,0 +1,25 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ let tab1 = gBrowser.selectedTab;
+ let tab2 = gBrowser.addTab("about:blank", {skipAnimation: true});
+ let tab3 = gBrowser.addTab();
+ gBrowser.selectedTab = tab2;
+
+ gBrowser.removeCurrentTab({animate: true});
+ gBrowser.tabContainer.advanceSelectedTab(-1, true);
+ is(gBrowser.selectedTab, tab1, "First tab should be selected");
+ gBrowser.removeTab(tab2);
+
+ // test for "null has no properties" fix. See Bug 585830 Comment 13
+ gBrowser.removeCurrentTab({animate: true});
+ try {
+ gBrowser.tabContainer.advanceSelectedTab(-1, false);
+ } catch(err) {
+ ok(false, "Shouldn't throw");
+ }
+
+ gBrowser.removeTab(tab1);
+}
diff --git a/browser/base/content/test/browser_bug590206.js b/browser/base/content/test/browser_bug590206.js
new file mode 100644
index 000000000..dc9e48ad8
--- /dev/null
+++ b/browser/base/content/test/browser_bug590206.js
@@ -0,0 +1,136 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const DUMMY = "browser/browser/base/content/test/dummy_page.html";
+
+function loadNewTab(aURL, aCallback) {
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.loadURI(aURL);
+
+ gBrowser.selectedBrowser.addEventListener("load", function() {
+ if (gBrowser.selectedBrowser.currentURI.spec != aURL)
+ return;
+ gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+ aCallback(gBrowser.selectedTab);
+ }, true);
+}
+
+function getIdentityMode() {
+ return document.getElementById("identity-box").className;
+}
+
+var TESTS = [
+function test_webpage() {
+ let oldTab = gBrowser.selectedTab;
+
+ loadNewTab("http://example.com/" + DUMMY, function(aNewTab) {
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = oldTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = aNewTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.removeTab(aNewTab);
+
+ runNextTest();
+ });
+},
+
+function test_blank() {
+ let oldTab = gBrowser.selectedTab;
+
+ loadNewTab("about:blank", function(aNewTab) {
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = oldTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = aNewTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.removeTab(aNewTab);
+
+ runNextTest();
+ });
+},
+
+function test_chrome() {
+ let oldTab = gBrowser.selectedTab;
+
+ loadNewTab("chrome://mozapps/content/extensions/extensions.xul", function(aNewTab) {
+ is(getIdentityMode(), "chromeUI", "Identity should be chrome");
+
+ gBrowser.selectedTab = oldTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = aNewTab;
+ is(getIdentityMode(), "chromeUI", "Identity should be chrome");
+
+ gBrowser.removeTab(aNewTab);
+
+ runNextTest();
+ });
+},
+
+function test_https() {
+ let oldTab = gBrowser.selectedTab;
+
+ loadNewTab("https://example.com/" + DUMMY, function(aNewTab) {
+ is(getIdentityMode(), "verifiedDomain", "Identity should be verified");
+
+ gBrowser.selectedTab = oldTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = aNewTab;
+ is(getIdentityMode(), "verifiedDomain", "Identity should be verified");
+
+ gBrowser.removeTab(aNewTab);
+
+ runNextTest();
+ });
+},
+
+function test_addons() {
+ let oldTab = gBrowser.selectedTab;
+
+ loadNewTab("about:addons", function(aNewTab) {
+ is(getIdentityMode(), "chromeUI", "Identity should be chrome");
+
+ gBrowser.selectedTab = oldTab;
+ is(getIdentityMode(), "unknownIdentity", "Identity should be unknown");
+
+ gBrowser.selectedTab = aNewTab;
+ is(getIdentityMode(), "chromeUI", "Identity should be chrome");
+
+ gBrowser.removeTab(aNewTab);
+
+ runNextTest();
+ });
+}
+];
+
+var gTestStart = null;
+
+function runNextTest() {
+ if (gTestStart)
+ info("Test part took " + (Date.now() - gTestStart) + "ms");
+
+ if (TESTS.length == 0) {
+ finish();
+ return;
+ }
+
+ info("Running " + TESTS[0].name);
+ gTestStart = Date.now();
+ TESTS.shift()();
+};
+
+function test() {
+ waitForExplicitFinish();
+
+ runNextTest();
+}
diff --git a/browser/base/content/test/browser_bug592338.js b/browser/base/content/test/browser_bug592338.js
new file mode 100644
index 000000000..a9ec62566
--- /dev/null
+++ b/browser/base/content/test/browser_bug592338.js
@@ -0,0 +1,136 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const TESTROOT = "http://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/";
+
+var tempScope = {};
+Components.utils.import("resource://gre/modules/LightweightThemeManager.jsm", tempScope);
+var LightweightThemeManager = tempScope.LightweightThemeManager;
+
+function wait_for_notification(aCallback) {
+ PopupNotifications.panel.addEventListener("popupshown", function() {
+ PopupNotifications.panel.removeEventListener("popupshown", arguments.callee, false);
+ aCallback(PopupNotifications.panel);
+ }, false);
+}
+
+var TESTS = [
+function test_install_lwtheme() {
+ is(LightweightThemeManager.currentTheme, null, "Should be no lightweight theme selected");
+
+ var pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ gBrowser.selectedTab = gBrowser.addTab("http://example.com/browser/browser/base/content/test/bug592338.html");
+ gBrowser.selectedBrowser.addEventListener("pageshow", function() {
+ if (gBrowser.contentDocument.location.href == "about:blank")
+ return;
+
+ gBrowser.selectedBrowser.removeEventListener("pageshow", arguments.callee, false);
+
+ executeSoon(function() {
+ var link = gBrowser.contentDocument.getElementById("theme-install");
+ EventUtils.synthesizeMouse(link, 2, 2, {}, gBrowser.contentWindow);
+
+ is(LightweightThemeManager.currentTheme.id, "test", "Should have installed the test theme");
+
+ LightweightThemeManager.currentTheme = null;
+ gBrowser.removeTab(gBrowser.selectedTab);
+
+ Services.perms.remove("example.com", "install");
+
+ runNextTest();
+ });
+ }, false);
+},
+
+function test_lwtheme_switch_theme() {
+ is(LightweightThemeManager.currentTheme, null, "Should be no lightweight theme selected");
+
+ AddonManager.getAddonByID("theme-xpi@tests.mozilla.org", function(aAddon) {
+ aAddon.userDisabled = false;
+ ok(aAddon.isActive, "Theme should have immediately enabled");
+ Services.prefs.setBoolPref("extensions.dss.enabled", false);
+
+ var pm = Services.perms;
+ pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
+
+ gBrowser.selectedTab = gBrowser.addTab("http://example.com/browser/browser/base/content/test/bug592338.html");
+ gBrowser.selectedBrowser.addEventListener("pageshow", function() {
+ if (gBrowser.contentDocument.location.href == "about:blank")
+ return;
+
+ gBrowser.selectedBrowser.removeEventListener("pageshow", arguments.callee, false);
+
+ executeSoon(function() {
+ var link = gBrowser.contentDocument.getElementById("theme-install");
+ wait_for_notification(function(aPanel) {
+ is(LightweightThemeManager.currentTheme, null, "Should not have installed the test lwtheme");
+ ok(aAddon.isActive, "Test theme should still be active");
+
+ let notification = aPanel.childNodes[0];
+ is(notification.button.label, "Restart Now", "Should have seen the right button");
+
+ ok(aAddon.userDisabled, "Should be waiting to disable the test theme");
+ aAddon.userDisabled = false;
+ Services.prefs.setBoolPref("extensions.dss.enabled", true);
+
+ gBrowser.removeTab(gBrowser.selectedTab);
+
+ Services.perms.remove("example.com", "install");
+
+ runNextTest();
+ });
+ EventUtils.synthesizeMouse(link, 2, 2, {}, gBrowser.contentWindow);
+ });
+ }, false);
+ });
+}
+];
+
+function runNextTest() {
+ AddonManager.getAllInstalls(function(aInstalls) {
+ is(aInstalls.length, 0, "Should be no active installs");
+
+ if (TESTS.length == 0) {
+ AddonManager.getAddonByID("theme-xpi@tests.mozilla.org", function(aAddon) {
+ aAddon.uninstall();
+
+ Services.prefs.setBoolPref("extensions.logging.enabled", false);
+ Services.prefs.setBoolPref("extensions.dss.enabled", false);
+
+ finish();
+ });
+ return;
+ }
+
+ info("Running " + TESTS[0].name);
+ TESTS.shift()();
+ });
+};
+
+function test() {
+ waitForExplicitFinish();
+
+ Services.prefs.setBoolPref("extensions.logging.enabled", true);
+
+ AddonManager.getInstallForURL(TESTROOT + "theme.xpi", function(aInstall) {
+ aInstall.addListener({
+ onInstallEnded: function(aInstall, aAddon) {
+ AddonManager.getAddonByID("theme-xpi@tests.mozilla.org", function(aAddon) {
+ isnot(aAddon, null, "Should have installed the test theme.");
+
+ // In order to switch themes while the test is running we turn on dynamic
+ // theme switching. This means the test isn't exactly correct but should
+ // do some good
+ Services.prefs.setBoolPref("extensions.dss.enabled", true);
+
+ runNextTest();
+ });
+ }
+ });
+
+ aInstall.install();
+ }, "application/x-xpinstall");
+}
diff --git a/browser/base/content/test/browser_bug594131.js b/browser/base/content/test/browser_bug594131.js
new file mode 100644
index 000000000..d68d1979a
--- /dev/null
+++ b/browser/base/content/test/browser_bug594131.js
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ let newTab = gBrowser.addTab("http://example.com");
+ waitForExplicitFinish();
+ newTab.linkedBrowser.addEventListener("load", mainPart, true);
+
+ function mainPart() {
+ newTab.linkedBrowser.removeEventListener("load", mainPart, true);
+
+ gBrowser.pinTab(newTab);
+ gBrowser.selectedTab = newTab;
+
+ openUILinkIn("http://example.org/", "current", { inBackground: true });
+ isnot(gBrowser.selectedTab, newTab, "shouldn't load in background");
+
+ gBrowser.removeTab(newTab);
+ gBrowser.removeTab(gBrowser.tabs[1]); // example.org tab
+ finish();
+ }
+}
diff --git a/browser/base/content/test/browser_bug595507.js b/browser/base/content/test/browser_bug595507.js
new file mode 100644
index 000000000..65a10eace
--- /dev/null
+++ b/browser/base/content/test/browser_bug595507.js
@@ -0,0 +1,39 @@
+var gInvalidFormPopup = document.getElementById('invalid-form-popup');
+ok(gInvalidFormPopup,
+ "The browser should have a popup to show when a form is invalid");
+
+/**
+ * Make sure that the form validation error message shows even if the form is in an iframe.
+ */
+function test()
+{
+ waitForExplicitFinish();
+
+ let uri = "data:text/html,<iframe src=\"data:text/html,<iframe name='t'></iframe><form target='t' action='data:text/html,'><input required id='i'><input id='s' type='submit'></form>\"</iframe>";
+ let tab = gBrowser.addTab();
+
+ gInvalidFormPopup.addEventListener("popupshown", function() {
+ gInvalidFormPopup.removeEventListener("popupshown", arguments.callee, false);
+
+ let doc = gBrowser.contentDocument.getElementsByTagName('iframe')[0].contentDocument;
+ is(doc.activeElement, doc.getElementById('i'),
+ "First invalid element should be focused");
+
+ ok(gInvalidFormPopup.state == 'showing' || gInvalidFormPopup.state == 'open',
+ "The invalid form popup should be shown");
+
+ // Clean-up and next test.
+ gBrowser.removeTab(gBrowser.selectedTab, {animate: false});
+ executeSoon(finish);
+ }, false);
+
+ tab.linkedBrowser.addEventListener("load", function(aEvent) {
+ tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+ gBrowser.contentDocument.getElementsByTagName('iframe')[0].contentDocument
+ .getElementById('s').click();
+ }, true);
+
+ gBrowser.selectedTab = tab;
+ gBrowser.selectedTab.linkedBrowser.loadURI(uri);
+}
diff --git a/browser/base/content/test/browser_bug596687.js b/browser/base/content/test/browser_bug596687.js
new file mode 100644
index 000000000..ccf6f5e35
--- /dev/null
+++ b/browser/base/content/test/browser_bug596687.js
@@ -0,0 +1,26 @@
+function test() {
+ var tab = gBrowser.addTab(null, {skipAnimation: true});
+ gBrowser.selectedTab = tab;
+
+ var gotTabAttrModified = false;
+ var gotTabClose = false;
+
+ function onTabClose() {
+ gotTabClose = true;
+ tab.addEventListener("TabAttrModified", onTabAttrModified, false);
+ }
+
+ function onTabAttrModified() {
+ gotTabAttrModified = true;
+ }
+
+ tab.addEventListener("TabClose", onTabClose, false);
+
+ gBrowser.removeTab(tab);
+
+ ok(gotTabClose, "should have got the TabClose event");
+ ok(!gotTabAttrModified, "shouldn't have got the TabAttrModified event after TabClose");
+
+ tab.removeEventListener("TabClose", onTabClose, false);
+ tab.removeEventListener("TabAttrModified", onTabAttrModified, false);
+}
diff --git a/browser/base/content/test/browser_bug597218.js b/browser/base/content/test/browser_bug597218.js
new file mode 100644
index 000000000..f00e99f72
--- /dev/null
+++ b/browser/base/content/test/browser_bug597218.js
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ // establish initial state
+ is(gBrowser.tabs.length, 1, "we start with one tab");
+
+ // create a tab
+ let tab = gBrowser.loadOneTab("about:blank");
+ ok(!tab.hidden, "tab starts out not hidden");
+ is(gBrowser.tabs.length, 2, "we now have two tabs");
+
+ // make sure .hidden is read-only
+ tab.hidden = true;
+ ok(!tab.hidden, "can't set .hidden directly");
+
+ // hide the tab
+ gBrowser.hideTab(tab);
+ ok(tab.hidden, "tab is hidden");
+
+ // now pin it and make sure it gets unhidden
+ gBrowser.pinTab(tab);
+ ok(tab.pinned, "tab was pinned");
+ ok(!tab.hidden, "tab was unhidden");
+
+ // try hiding it now that it's pinned; shouldn't be able to
+ gBrowser.hideTab(tab);
+ ok(!tab.hidden, "tab did not hide");
+
+ // clean up
+ gBrowser.removeTab(tab);
+ is(gBrowser.tabs.length, 1, "we finish with one tab");
+
+ finish();
+}
diff --git a/browser/base/content/test/browser_bug598923.js b/browser/base/content/test/browser_bug598923.js
new file mode 100644
index 000000000..35e9c09f0
--- /dev/null
+++ b/browser/base/content/test/browser_bug598923.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test:
+// * if add-on is installed to the add-on bar, the bar is made visible.
+// * if add-on is uninstalled from the add-on bar, and no more add-ons there,
+// the bar is hidden.
+
+function test() {
+ let aml = AddonsMgrListener;
+ ok(aml, "AddonsMgrListener exists");
+ // check is hidden
+ is(aml.addonBar.collapsed, true, "add-on bar is hidden initially");
+ // aob gets the count
+ AddonsMgrListener.onInstalling();
+ // add an item
+ let element = document.createElement("toolbaritem");
+ element.id = "bug598923-addon-item";
+ aml.addonBar.appendChild(element);
+ // aob checks the count, makes visible
+ AddonsMgrListener.onInstalled();
+ // check is visible
+ is(aml.addonBar.collapsed, false, "add-on bar has been made visible");
+ // aob gets the count
+ AddonsMgrListener.onUninstalling();
+ // remove an item
+ aml.addonBar.removeChild(element);
+ // aob checks the count, makes hidden
+ AddonsMgrListener.onUninstalled();
+ // check is hidden
+ is(aml.addonBar.collapsed, true, "add-on bar is hidden again");
+}
diff --git a/browser/base/content/test/browser_bug599325.js b/browser/base/content/test/browser_bug599325.js
new file mode 100644
index 000000000..d721fc663
--- /dev/null
+++ b/browser/base/content/test/browser_bug599325.js
@@ -0,0 +1,21 @@
+function test() {
+ waitForExplicitFinish();
+
+ let addonBar = document.getElementById("addon-bar");
+ ok(addonBar, "got addon bar");
+ ok(!isElementVisible(addonBar), "addon bar initially hidden");
+
+ openToolbarCustomizationUI(function () {
+ ok(isElementVisible(addonBar),
+ "add-on bar is visible during toolbar customization");
+
+ closeToolbarCustomizationUI(onClose);
+ });
+
+ function onClose() {
+ ok(!isElementVisible(addonBar),
+ "addon bar is hidden after toolbar customization");
+
+ finish();
+ }
+}
diff --git a/browser/base/content/test/browser_bug609700.js b/browser/base/content/test/browser_bug609700.js
new file mode 100644
index 000000000..8b4f1ea91
--- /dev/null
+++ b/browser/base/content/test/browser_bug609700.js
@@ -0,0 +1,20 @@
+function test() {
+ waitForExplicitFinish();
+
+ Services.ww.registerNotification(function (aSubject, aTopic, aData) {
+ if (aTopic == "domwindowopened") {
+ Services.ww.unregisterNotification(arguments.callee);
+
+ ok(true, "duplicateTabIn opened a new window");
+
+ whenDelayedStartupFinished(aSubject, function () {
+ executeSoon(function () {
+ aSubject.close();
+ finish();
+ });
+ }, false);
+ }
+ });
+
+ duplicateTabIn(gBrowser.selectedTab, "window");
+}
diff --git a/browser/base/content/test/browser_bug616836.js b/browser/base/content/test/browser_bug616836.js
new file mode 100644
index 000000000..efaaa837a
--- /dev/null
+++ b/browser/base/content/test/browser_bug616836.js
@@ -0,0 +1,4 @@
+function test() {
+ is(document.querySelectorAll("#appmenu-popup [accesskey]").length, 0,
+ "there should be no items with access keys in the app menu popup");
+}
diff --git a/browser/base/content/test/browser_bug623155.js b/browser/base/content/test/browser_bug623155.js
new file mode 100644
index 000000000..52ee73b07
--- /dev/null
+++ b/browser/base/content/test/browser_bug623155.js
@@ -0,0 +1,136 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const REDIRECT_FROM = "https://example.com/browser/browser/base/content/test/" +
+ "redirect_bug623155.sjs";
+
+const REDIRECT_TO = "https://www.bank1.com/"; // Bad-cert host.
+
+function isRedirectedURISpec(aURISpec) {
+ return isRedirectedURI(Services.io.newURI(aURISpec, null, null));
+}
+
+function isRedirectedURI(aURI) {
+ // Compare only their before-hash portion.
+ return Services.io.newURI(REDIRECT_TO, null, null)
+ .equalsExceptRef(aURI);
+}
+
+/*
+ Test.
+
+1. Load
+https://example.com/browser/browser/base/content/test/redirect_bug623155.sjs#BG
+ in a background tab.
+
+2. The redirected URI is <https://www.bank1.com/#BG>, which displayes a cert
+ error page.
+
+3. Switch the tab to foreground.
+
+4. Check the URLbar's value, expecting <https://www.bank1.com/#BG>
+
+5. Load
+https://example.com/browser/browser/base/content/test/redirect_bug623155.sjs#FG
+ in the foreground tab.
+
+6. The redirected URI is <https://www.bank1.com/#FG>. And this is also
+ a cert-error page.
+
+7. Check the URLbar's value, expecting <https://www.bank1.com/#FG>
+
+8. End.
+
+ */
+
+var gNewTab;
+
+function test() {
+ waitForExplicitFinish();
+
+ // Load a URI in the background.
+ gNewTab = gBrowser.addTab(REDIRECT_FROM + "#BG");
+ gBrowser.getBrowserForTab(gNewTab)
+ .webProgress
+ .addProgressListener(gWebProgressListener,
+ Components.interfaces.nsIWebProgress
+ .NOTIFY_LOCATION);
+}
+
+var gWebProgressListener = {
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
+ aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
+ aIID.equals(Components.interfaces.nsISupports))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ },
+
+ // ---------------------------------------------------------------------------
+ // NOTIFY_LOCATION mode should work fine without these methods.
+ //
+ //onStateChange: function() {},
+ //onStatusChange: function() {},
+ //onProgressChange: function() {},
+ //onSecurityChange: function() {},
+ //----------------------------------------------------------------------------
+
+ onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags) {
+ if (!aRequest) {
+ // This is bug 673752, or maybe initial "about:blank".
+ return;
+ }
+
+ ok(gNewTab, "There is a new tab.");
+ ok(isRedirectedURI(aLocation),
+ "onLocationChange catches only redirected URI.");
+
+ if (aLocation.ref == "BG") {
+ // This is background tab's request.
+ isnot(gNewTab, gBrowser.selectedTab, "This is a background tab.");
+ } else if (aLocation.ref == "FG") {
+ // This is foreground tab's request.
+ is(gNewTab, gBrowser.selectedTab, "This is a foreground tab.");
+ }
+ else {
+ // We shonuld not reach here.
+ ok(false, "This URI hash is not expected:" + aLocation.ref);
+ }
+
+ let isSelectedTab = gNewTab.selected;
+ setTimeout(delayed, 0, isSelectedTab);
+ }
+};
+
+function delayed(aIsSelectedTab) {
+ // Switch tab and confirm URL bar.
+ if (!aIsSelectedTab) {
+ gBrowser.selectedTab = gNewTab;
+ }
+
+ ok(isRedirectedURISpec(content.location.href),
+ "The content area is redirected. aIsSelectedTab:" + aIsSelectedTab);
+ is(gURLBar.value, content.location.href,
+ "The URL bar shows the content URI. aIsSelectedTab:" + aIsSelectedTab);
+
+ if (!aIsSelectedTab) {
+ // If this was a background request, go on a foreground request.
+ content.location = REDIRECT_FROM + "#FG";
+ }
+ else {
+ // Othrewise, nothing to do remains.
+ finish();
+ }
+}
+
+/* Cleanup */
+registerCleanupFunction(function() {
+ if (gNewTab) {
+ gBrowser.getBrowserForTab(gNewTab)
+ .webProgress
+ .removeProgressListener(gWebProgressListener);
+
+ gBrowser.removeTab(gNewTab);
+ }
+ gNewTab = null;
+});
diff --git a/browser/base/content/test/browser_bug623893.js b/browser/base/content/test/browser_bug623893.js
new file mode 100644
index 000000000..800b8fad5
--- /dev/null
+++ b/browser/base/content/test/browser_bug623893.js
@@ -0,0 +1,46 @@
+function test() {
+ waitForExplicitFinish();
+
+ loadAndWait("data:text/plain,1", function () {
+ loadAndWait("data:text/plain,2", function () {
+ loadAndWait("data:text/plain,3", runTests);
+ });
+ });
+}
+
+function runTests() {
+ duplicate(0, "maintained the original index", function () {
+ gBrowser.removeCurrentTab();
+
+ duplicate(-1, "went back", function () {
+ duplicate(1, "went forward", function () {
+ gBrowser.removeCurrentTab();
+ gBrowser.removeCurrentTab();
+ gBrowser.addTab();
+ gBrowser.removeCurrentTab();
+ finish();
+ });
+ });
+ });
+}
+
+function duplicate(delta, msg, cb) {
+ var start = gBrowser.sessionHistory.index;
+
+ duplicateTabIn(gBrowser.selectedTab, "tab", delta);
+
+ gBrowser.selectedBrowser.addEventListener("pageshow", function () {
+ gBrowser.selectedBrowser.removeEventListener("pageshow", arguments.callee, false);
+ is(gBrowser.sessionHistory.index, start + delta, msg);
+ executeSoon(cb);
+ }, false);
+}
+
+function loadAndWait(url, cb) {
+ gBrowser.selectedBrowser.addEventListener("load", function () {
+ gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+ executeSoon(cb);
+ }, true);
+
+ gBrowser.loadURI(url);
+}
diff --git a/browser/base/content/test/browser_bug624734.js b/browser/base/content/test/browser_bug624734.js
new file mode 100644
index 000000000..13369a310
--- /dev/null
+++ b/browser/base/content/test/browser_bug624734.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Bug 624734 - Star UI has no tooltip until bookmarked page is visited
+
+function test() {
+ waitForExplicitFinish();
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+ tab.linkedBrowser.addEventListener("load", (function(event) {
+ tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+ is(BookmarkingUI.star.getAttribute("tooltiptext"),
+ BookmarkingUI._unstarredTooltip,
+ "Star icon should have the unstarred tooltip text");
+
+ gBrowser.removeCurrentTab();
+ finish();
+ }), true);
+
+ tab.linkedBrowser.loadURI("http://example.com/browser/browser/base/content/test/dummy_page.html");
+}
diff --git a/browser/base/content/test/browser_bug647886.js b/browser/base/content/test/browser_bug647886.js
new file mode 100644
index 000000000..4a41fc6ad
--- /dev/null
+++ b/browser/base/content/test/browser_bug647886.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+ waitForExplicitFinish();
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", function () {
+ gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+ content.history.pushState({}, "2", "2.html");
+
+ testBackButton();
+ }, true);
+
+ loadURI("http://example.com");
+}
+
+function testBackButton() {
+ var backButton = document.getElementById("back-button");
+ var rect = backButton.getBoundingClientRect();
+
+ info("waiting for the history menu to open");
+
+ backButton.addEventListener("popupshown", function (event) {
+ backButton.removeEventListener("popupshown", arguments.callee, false);
+
+ ok(true, "history menu opened");
+ event.target.hidePopup();
+ gBrowser.removeTab(gBrowser.selectedTab);
+ finish();
+ }, false);
+
+ EventUtils.synthesizeMouseAtCenter(backButton, {type: "mousedown"});
+ EventUtils.synthesizeMouse(backButton, rect.width / 2, rect.height, {type: "mouseup"});
+}
diff --git a/browser/base/content/test/browser_bug655584.js b/browser/base/content/test/browser_bug655584.js
new file mode 100644
index 000000000..2fb1b4b43
--- /dev/null
+++ b/browser/base/content/test/browser_bug655584.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Bug 655584 - awesomebar suggestions don't update after tab is closed
+
+function test() {
+ var tab1 = gBrowser.addTab();
+ var tab2 = gBrowser.addTab();
+
+ // When urlbar in a new tab is focused, and a tab switch occurs,
+ // the urlbar popup should be closed
+ gBrowser.selectedTab = tab2;
+ gURLBar.focus(); // focus the urlbar in the tab we will switch to
+ gBrowser.selectedTab = tab1;
+ gURLBar.openPopup();
+ gBrowser.selectedTab = tab2;
+ ok(!gURLBar.popupOpen, "urlbar focused in tab to switch to, close popup");
+
+ // cleanup
+ gBrowser.removeCurrentTab();
+ gBrowser.removeCurrentTab();
+}
diff --git a/browser/base/content/test/browser_bug664672.js b/browser/base/content/test/browser_bug664672.js
new file mode 100644
index 000000000..2064f77d0
--- /dev/null
+++ b/browser/base/content/test/browser_bug664672.js
@@ -0,0 +1,19 @@
+function test() {
+ waitForExplicitFinish();
+
+ var tab = gBrowser.addTab();
+
+ tab.addEventListener("TabClose", function () {
+ tab.removeEventListener("TabClose", arguments.callee, false);
+
+ ok(tab.linkedBrowser, "linkedBrowser should still exist during the TabClose event");
+
+ executeSoon(function () {
+ ok(!tab.linkedBrowser, "linkedBrowser should be gone after the TabClose event");
+
+ finish();
+ });
+ }, false);
+
+ gBrowser.removeTab(tab);
+}
diff --git a/browser/base/content/test/browser_bug676619.js b/browser/base/content/test/browser_bug676619.js
new file mode 100644
index 000000000..23dd66e87
--- /dev/null
+++ b/browser/base/content/test/browser_bug676619.js
@@ -0,0 +1,121 @@
+function test () {
+ waitForExplicitFinish();
+
+ var isHTTPS = false;
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", function () {
+ if (isHTTPS) {
+ gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+ }
+ let doc = gBrowser.contentDocument;
+
+
+ function testLocation(link, url, next) {
+ var tabOpenListener = new TabOpenListener(url, function () {
+ gBrowser.removeTab(this.tab);
+ }, function () {
+ next();
+ });
+
+ doc.getElementById(link).click();
+ }
+
+ function testLink(link, name, next) {
+ addWindowListener("chrome://mozapps/content/downloads/unknownContentType.xul", function (win) {
+ is(doc.getElementById("unload-flag").textContent, "Okay", "beforeunload shouldn't have fired");
+ is(win.document.getElementById("location").value, name, "file name should match");
+ win.close();
+ next();
+ });
+
+ doc.getElementById(link).click();
+ }
+
+ testLink("link1", "test.txt",
+ testLink.bind(null, "link2", "video.ogg",
+ testLink.bind(null, "link3", "just some video",
+ testLink.bind(null, "link4", "with-target.txt",
+ testLink.bind(null, "link5", "javascript.txt",
+ testLink.bind(null, "link6", "test.blob",
+ testLocation.bind(null, "link7", "http://example.com/",
+ function () {
+ if (isHTTPS) {
+ gBrowser.removeCurrentTab();
+ finish();
+ } else {
+ // same test again with https:
+ isHTTPS = true;
+ content.location = "https://example.com:443/browser/browser/base/content/test/download_page.html";
+ }
+ })))))));
+
+ }, true);
+
+ content.location = "http://mochi.test:8888/browser/browser/base/content/test/download_page.html";
+}
+
+
+function addWindowListener(aURL, aCallback) {
+ Services.wm.addListener({
+ onOpenWindow: function(aXULWindow) {
+ info("window opened, waiting for focus");
+ Services.wm.removeListener(this);
+
+ var domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ waitForFocus(function() {
+ is(domwindow.document.location.href, aURL, "should have seen the right window open");
+ aCallback(domwindow);
+ }, domwindow);
+ },
+ onCloseWindow: function(aXULWindow) { },
+ onWindowTitleChange: function(aXULWindow, aNewTitle) { }
+ });
+}
+
+// This listens for the next opened tab and checks it is of the right url.
+// opencallback is called when the new tab is fully loaded
+// closecallback is called when the tab is closed
+function TabOpenListener(url, opencallback, closecallback) {
+ this.url = url;
+ this.opencallback = opencallback;
+ this.closecallback = closecallback;
+
+ gBrowser.tabContainer.addEventListener("TabOpen", this, false);
+}
+
+TabOpenListener.prototype = {
+ url: null,
+ opencallback: null,
+ closecallback: null,
+ tab: null,
+ browser: null,
+
+ handleEvent: function(event) {
+ if (event.type == "TabOpen") {
+ gBrowser.tabContainer.removeEventListener("TabOpen", this, false);
+ this.tab = event.originalTarget;
+ this.browser = this.tab.linkedBrowser;
+ gBrowser.addEventListener("pageshow", this, false);
+ } else if (event.type == "pageshow") {
+ if (event.target.location.href != this.url)
+ return;
+ gBrowser.removeEventListener("pageshow", this, false);
+ this.tab.addEventListener("TabClose", this, false);
+ var url = this.browser.contentDocument.location.href;
+ is(url, this.url, "Should have opened the correct tab");
+ this.opencallback(this.tab, this.browser.contentWindow);
+ } else if (event.type == "TabClose") {
+ if (event.originalTarget != this.tab)
+ return;
+ this.tab.removeEventListener("TabClose", this, false);
+ this.opencallback = null;
+ this.tab = null;
+ this.browser = null;
+ // Let the window close complete
+ executeSoon(this.closecallback);
+ this.closecallback = null;
+ }
+ }
+};
diff --git a/browser/base/content/test/browser_bug678392-1.html b/browser/base/content/test/browser_bug678392-1.html
new file mode 100644
index 000000000..c3b235dd0
--- /dev/null
+++ b/browser/base/content/test/browser_bug678392-1.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html PUBLIC"-//W3C//DTD XHTML 1.0 Strict//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html>
+ <head>
+ <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+ <meta content="utf-8" http-equiv="encoding">
+ <title>bug678392 - 1</title>
+ </head>
+ <body>
+bug 678392 test page 1
+ </body>
+</html> \ No newline at end of file
diff --git a/browser/base/content/test/browser_bug678392-2.html b/browser/base/content/test/browser_bug678392-2.html
new file mode 100644
index 000000000..9b18efcf7
--- /dev/null
+++ b/browser/base/content/test/browser_bug678392-2.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html PUBLIC"-//W3C//DTD XHTML 1.0 Strict//EN"
+"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html>
+ <head>
+ <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+ <meta content="utf-8" http-equiv="encoding">
+ <title>bug678392 - 2</title>
+ </head>
+ <body>
+bug 678392 test page 2
+ </body>
+</html> \ No newline at end of file
diff --git a/browser/base/content/test/browser_bug678392.js b/browser/base/content/test/browser_bug678392.js
new file mode 100644
index 000000000..3b670dc4a
--- /dev/null
+++ b/browser/base/content/test/browser_bug678392.js
@@ -0,0 +1,192 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+let HTTPROOT = "http://example.com/browser/browser/base/content/test/";
+
+function maxSnapshotOverride() {
+ return 5;
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ BrowserOpenTab();
+ let tab = gBrowser.selectedTab;
+ registerCleanupFunction(function () { gBrowser.removeTab(tab); });
+
+ ok(gHistorySwipeAnimation, "gHistorySwipeAnimation exists.");
+
+ if (!gHistorySwipeAnimation._isSupported()) {
+ is(gHistorySwipeAnimation.active, false, "History swipe animation is not " +
+ "active when not supported by the platform.");
+ finish();
+ return;
+ }
+
+ gHistorySwipeAnimation._getMaxSnapshots = maxSnapshotOverride;
+ gHistorySwipeAnimation.init();
+
+ is(gHistorySwipeAnimation.active, true, "History swipe animation support " +
+ "was successfully initialized when supported.");
+
+ cleanupArray();
+ load(gBrowser.selectedTab, HTTPROOT + "browser_bug678392-2.html", test0);
+}
+
+function load(aTab, aUrl, aCallback) {
+ aTab.linkedBrowser.addEventListener("load", function onload(aEvent) {
+ aEvent.currentTarget.removeEventListener("load", onload, true);
+ waitForFocus(aCallback, content);
+ }, true);
+ aTab.linkedBrowser.loadURI(aUrl);
+}
+
+function cleanupArray() {
+ let arr = gHistorySwipeAnimation._trackedSnapshots;
+ while (arr.length > 0) {
+ delete arr[0].browser.snapshots[arr[0].index]; // delete actual snapshot
+ arr.splice(0, 1);
+ }
+}
+
+function testArrayCleanup() {
+ // Test cleanup of array of tracked snapshots.
+ let arr = gHistorySwipeAnimation._trackedSnapshots;
+ is(arr.length, 0, "Snapshots were removed correctly from the array of " +
+ "tracked snapshots.");
+}
+
+function test0() {
+ // Test growing of array of tracked snapshots.
+ let tab = gBrowser.selectedTab;
+
+ load(tab, HTTPROOT + "browser_bug678392-1.html", function() {
+ ok(gHistorySwipeAnimation._trackedSnapshots, "Array for snapshot " +
+ "tracking is initialized.");
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 1, "Snapshot array " +
+ "has correct length of 1 after loading one page.");
+ load(tab, HTTPROOT + "browser_bug678392-2.html", function() {
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 2, "Snapshot array " +
+ " has correct length of 2 after loading two pages.");
+ load(tab, HTTPROOT + "browser_bug678392-1.html", function() {
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 3, "Snapshot " +
+ "array has correct length of 3 after loading three pages.");
+ load(tab, HTTPROOT + "browser_bug678392-2.html", function() {
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 4, "Snapshot " +
+ "array has correct length of 4 after loading four pages.");
+ cleanupArray();
+ testArrayCleanup();
+ test1();
+ });
+ });
+ });
+ });
+}
+
+function verifyRefRemoved(aIndex, aBrowser) {
+ let wasFound = false;
+ let arr = gHistorySwipeAnimation._trackedSnapshots;
+ for (let i = 0; i < arr.length; i++) {
+ if (arr[i].index == aIndex && arr[i].browser == aBrowser)
+ wasFound = true;
+ }
+ is(wasFound, false, "The reference that was previously removed was " +
+ "still found in the array of tracked snapshots.");
+}
+
+function test1() {
+ // Test presence of snpashots in per-tab array of snapshots and removal of
+ // individual snapshots (and corresponding references in the array of
+ // tracked snapshots).
+ let tab = gBrowser.selectedTab;
+
+ load(tab, HTTPROOT + "browser_bug678392-1.html", function() {
+ var historyIndex = gBrowser.webNavigation.sessionHistory.index - 1;
+ load(tab, HTTPROOT + "browser_bug678392-2.html", function() {
+ load(tab, HTTPROOT + "browser_bug678392-1.html", function() {
+ load(tab, HTTPROOT + "browser_bug678392-2.html", function() {
+ let browser = gBrowser.selectedBrowser;
+ ok(browser.snapshots, "Array of snapshots exists in browser.");
+ ok(browser.snapshots[historyIndex], "First page exists in snapshot " +
+ "array.");
+ ok(browser.snapshots[historyIndex + 1], "Second page exists in " +
+ "snapshot array.");
+ ok(browser.snapshots[historyIndex + 2], "Third page exists in " +
+ "snapshot array.");
+ ok(browser.snapshots[historyIndex + 3], "Fourth page exists in " +
+ "snapshot array.");
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 4, "Length of " +
+ "array of tracked snapshots is equal to 4 after loading four " +
+ "pages.");
+
+ // Test removal of reference in the middle of the array.
+ gHistorySwipeAnimation._removeTrackedSnapshot(historyIndex + 1,
+ browser);
+ verifyRefRemoved(historyIndex + 1, browser);
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 3, "Length of " +
+ "array of tracked snapshots is equal to 3 after removing one" +
+ "reference from the array with length 4.");
+
+ // Test removal of reference at end of array.
+ gHistorySwipeAnimation._removeTrackedSnapshot(historyIndex + 3,
+ browser);
+ verifyRefRemoved(historyIndex + 3, browser);
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 2, "Length of " +
+ "array of tracked snapshots is equal to 2 after removing two" +
+ "references from the array with length 4.");
+
+ // Test removal of reference at head of array.
+ gHistorySwipeAnimation._removeTrackedSnapshot(historyIndex,
+ browser);
+ verifyRefRemoved(historyIndex, browser);
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 1, "Length of " +
+ "array of tracked snapshots is equal to 1 after removing three" +
+ "references from the array with length 4.");
+
+ cleanupArray();
+ test2();
+ });
+ });
+ });
+ });
+}
+
+function test2() {
+ // Test growing of snapshot array across tabs.
+ let tab = gBrowser.selectedTab;
+
+ load(tab, HTTPROOT + "browser_bug678392-1.html", function() {
+ var historyIndex = gBrowser.webNavigation.sessionHistory.index - 1;
+ load(tab, HTTPROOT + "browser_bug678392-2.html", function() {
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 2, "Length of " +
+ "snapshot array is equal to 2 after loading two pages");
+ let prevTab = tab;
+ tab = gBrowser.addTab("about:newtab");
+ gBrowser.selectedTab = tab;
+ load(tab, HTTPROOT + "browser_bug678392-2.html" /* initial page */,
+ function() {
+ load(tab, HTTPROOT + "browser_bug678392-1.html", function() {
+ load(tab, HTTPROOT + "browser_bug678392-2.html", function() {
+ is(gHistorySwipeAnimation._trackedSnapshots.length, 4, "Length " +
+ "of snapshot array is equal to 4 after loading two pages in " +
+ "two tabs each.");
+ gBrowser.removeCurrentTab();
+ gBrowser.selectedTab = prevTab;
+ cleanupArray();
+ test3();
+ });
+ });
+ });
+ });
+ });
+}
+
+function test3() {
+ // Test uninit of gHistorySwipeAnimation.
+ // This test MUST be the last one to execute.
+ gHistorySwipeAnimation.uninit();
+ is(gHistorySwipeAnimation.active, false, "History swipe animation support " +
+ "was successfully uninitialized");
+ finish();
+}
diff --git a/browser/base/content/test/browser_bug710878.js b/browser/base/content/test/browser_bug710878.js
new file mode 100644
index 000000000..aaec82787
--- /dev/null
+++ b/browser/base/content/test/browser_bug710878.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test()
+{
+ waitForExplicitFinish();
+
+ let doc;
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", function onload() {
+ gBrowser.selectedBrowser.removeEventListener("load", onload, true);
+ doc = content.document;
+ waitForFocus(performTest, content);
+ }, true);
+
+ content.location = "data:text/html,<a href='%23xxx'><span>word1 <span> word2 </span></span><span> word3</span></a>";
+
+ function performTest()
+ {
+ let link = doc.querySelector("a");;
+ let text = gatherTextUnder(link);
+ is(text, "word1 word2 word3", "Text under link is correctly computed.");
+ doc = null;
+ gBrowser.removeCurrentTab();
+ finish();
+ }
+}
+
diff --git a/browser/base/content/test/browser_bug719271.js b/browser/base/content/test/browser_bug719271.js
new file mode 100644
index 000000000..1c0076ac1
--- /dev/null
+++ b/browser/base/content/test/browser_bug719271.js
@@ -0,0 +1,89 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const TEST_PAGE = "http://example.org/browser/browser/base/content/test/zoom_test.html";
+const TEST_VIDEO = "http://example.org/browser/browser/base/content/test/video.ogg";
+
+var gTab1, gTab2, gLevel1, gLevel2;
+
+function test() {
+ waitForExplicitFinish();
+
+ Task.spawn(function () {
+ gTab1 = gBrowser.addTab();
+ gTab2 = gBrowser.addTab();
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
+ yield FullZoomHelper.load(gTab1, TEST_PAGE);
+ yield FullZoomHelper.load(gTab2, TEST_VIDEO);
+ }).then(zoomTab1, FullZoomHelper.failAndContinue(finish));
+}
+
+function zoomTab1() {
+ Task.spawn(function () {
+ is(gBrowser.selectedTab, gTab1, "Tab 1 is selected");
+ FullZoomHelper.zoomTest(gTab1, 1, "Initial zoom of tab 1 should be 1");
+ FullZoomHelper.zoomTest(gTab2, 1, "Initial zoom of tab 2 should be 1");
+
+ FullZoom.enlarge();
+ gLevel1 = ZoomManager.getZoomForBrowser(gBrowser.getBrowserForTab(gTab1));
+
+ ok(gLevel1 > 1, "New zoom for tab 1 should be greater than 1");
+ FullZoomHelper.zoomTest(gTab2, 1, "Zooming tab 1 should not affect tab 2");
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2);
+ FullZoomHelper.zoomTest(gTab2, 1, "Tab 2 is still unzoomed after it is selected");
+ FullZoomHelper.zoomTest(gTab1, gLevel1, "Tab 1 is still zoomed");
+ }).then(zoomTab2, FullZoomHelper.failAndContinue(finish));
+}
+
+function zoomTab2() {
+ Task.spawn(function () {
+ is(gBrowser.selectedTab, gTab2, "Tab 2 is selected");
+
+ FullZoom.reduce();
+ let gLevel2 = ZoomManager.getZoomForBrowser(gBrowser.getBrowserForTab(gTab2));
+
+ ok(gLevel2 < 1, "New zoom for tab 2 should be less than 1");
+ FullZoomHelper.zoomTest(gTab1, gLevel1, "Zooming tab 2 should not affect tab 1");
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
+ FullZoomHelper.zoomTest(gTab1, gLevel1, "Tab 1 should have the same zoom after it's selected");
+ }).then(testNavigation, FullZoomHelper.failAndContinue(finish));
+}
+
+function testNavigation() {
+ Task.spawn(function () {
+ yield FullZoomHelper.load(gTab1, TEST_VIDEO);
+ FullZoomHelper.zoomTest(gTab1, 1, "Zoom should be 1 when a video was loaded");
+ yield waitForNextTurn(); // trying to fix orange bug 806046
+ yield FullZoomHelper.navigate(FullZoomHelper.BACK);
+ FullZoomHelper.zoomTest(gTab1, gLevel1, "Zoom should be restored when a page is loaded");
+ yield waitForNextTurn(); // trying to fix orange bug 806046
+ yield FullZoomHelper.navigate(FullZoomHelper.FORWARD);
+ FullZoomHelper.zoomTest(gTab1, 1, "Zoom should be 1 again when navigating back to a video");
+ }).then(finishTest, FullZoomHelper.failAndContinue(finish));
+}
+
+function waitForNextTurn() {
+ let deferred = Promise.defer();
+ setTimeout(function () deferred.resolve(), 0);
+ return deferred.promise;
+}
+
+var finishTestStarted = false;
+function finishTest() {
+ Task.spawn(function () {
+ ok(!finishTestStarted, "finishTest called more than once");
+ finishTestStarted = true;
+
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab1);
+ FullZoom.reset();
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab1);
+ yield FullZoomHelper.selectTabAndWaitForLocationChange(gTab2);
+ FullZoom.reset();
+ yield FullZoomHelper.removeTabAndWaitForLocationChange(gTab2);
+ }).then(finish, FullZoomHelper.failAndContinue(finish));
+}
diff --git a/browser/base/content/test/browser_bug724239.js b/browser/base/content/test/browser_bug724239.js
new file mode 100644
index 000000000..766002eca
--- /dev/null
+++ b/browser/base/content/test/browser_bug724239.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+ waitForExplicitFinish();
+ BrowserOpenTab();
+
+ let tab = gBrowser.selectedTab;
+ let browser = tab.linkedBrowser;
+
+ registerCleanupFunction(function () { gBrowser.removeTab(tab); });
+
+ whenBrowserLoaded(browser, function () {
+ browser.loadURI("http://example.com/");
+
+ whenBrowserLoaded(browser, function () {
+ ok(!gBrowser.canGoBack, "about:newtab wasn't added to the session history");
+ finish();
+ });
+ });
+}
+
+function whenBrowserLoaded(aBrowser, aCallback) {
+ if (aBrowser.contentDocument.readyState == "complete") {
+ executeSoon(aCallback);
+ return;
+ }
+
+ aBrowser.addEventListener("load", function onLoad() {
+ aBrowser.removeEventListener("load", onLoad, true);
+ executeSoon(aCallback);
+ }, true);
+}
diff --git a/browser/base/content/test/browser_bug734076.js b/browser/base/content/test/browser_bug734076.js
new file mode 100644
index 000000000..3e1cae716
--- /dev/null
+++ b/browser/base/content/test/browser_bug734076.js
@@ -0,0 +1,107 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+ waitForExplicitFinish();
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+ registerCleanupFunction(function () {
+ gBrowser.removeTab(tab);
+ });
+
+ let browser = tab.linkedBrowser;
+ browser.stop(); // stop the about:blank load
+
+ let writeDomainURL = encodeURI("data:text/html,<script>document.write(document.domain);</script>");
+ let tests = [
+ {
+ name: "view background image",
+ url: "http://mochi.test:8888/",
+ go: function (cb) {
+ let contentBody = browser.contentDocument.body;
+ contentBody.style.backgroundImage = "url('" + writeDomainURL + "')";
+ doOnLoad(function () {
+ let domain = browser.contentDocument.body.textContent;
+ is(domain, "", "no domain was inherited for view background image");
+ cb();
+ });
+
+ let contextMenu = initContextMenu(contentBody);
+ contextMenu.viewBGImage();
+ }
+ },
+ {
+ name: "view image",
+ url: "http://mochi.test:8888/",
+ go: function (cb) {
+ doOnLoad(function () {
+ let domain = browser.contentDocument.body.textContent;
+ is(domain, "", "no domain was inherited for view image");
+ cb();
+ });
+
+ let doc = browser.contentDocument;
+ let img = doc.createElement("img");
+ img.setAttribute("src", writeDomainURL);
+ doc.body.appendChild(img);
+
+ let contextMenu = initContextMenu(img);
+ contextMenu.viewMedia();
+ }
+ },
+ {
+ name: "show only this frame",
+ url: "http://mochi.test:8888/",
+ go: function (cb) {
+ doOnLoad(function () {
+ let domain = browser.contentDocument.body.textContent;
+ is(domain, "", "no domain was inherited for 'show only this frame'");
+ cb();
+ });
+
+ let doc = browser.contentDocument;
+ let iframe = doc.createElement("iframe");
+ iframe.setAttribute("src", writeDomainURL);
+ doc.body.appendChild(iframe);
+
+ iframe.addEventListener("load", function onload() {
+ let contextMenu = initContextMenu(iframe.contentDocument.body);
+ contextMenu.showOnlyThisFrame();
+ }, false);
+ }
+ }
+ ];
+
+ function doOnLoad(cb) {
+ browser.addEventListener("load", function onLoad(e) {
+ if (e.target != browser.contentDocument)
+ return;
+ browser.removeEventListener("load", onLoad, true);
+ cb();
+ }, true);
+ }
+
+ function doNext() {
+ let test = tests.shift();
+ if (test) {
+ info("Running test: " + test.name);
+ doOnLoad(function () {
+ test.go(function () {
+ executeSoon(doNext);
+ });
+ });
+ browser.contentDocument.location = test.url;
+ } else {
+ executeSoon(finish);
+ }
+ }
+
+ doNext();
+}
+
+function initContextMenu(aNode) {
+ document.popupNode = aNode;
+ let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+ let contextMenu = new nsContextMenu(contentAreaContextMenu);
+ return contextMenu;
+}
diff --git a/browser/base/content/test/browser_bug735471.js b/browser/base/content/test/browser_bug735471.js
new file mode 100644
index 000000000..eb9e7d338
--- /dev/null
+++ b/browser/base/content/test/browser_bug735471.js
@@ -0,0 +1,59 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public License,
+ * v. 2.0. If a copy of the MPL was not distributed with this file, You can
+ * obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+
+function test() {
+ waitForExplicitFinish();
+ registerCleanupFunction(function() {
+ // Reset pref to its default
+ Services.prefs.clearUserPref("browser.preferences.inContent");
+ });
+
+ // Verify that about:preferences tab is displayed when
+ // browser.preferences.inContent is set to true
+ Services.prefs.setBoolPref("browser.preferences.inContent", true);
+
+ gBrowser.tabContainer.addEventListener("TabOpen", function(aEvent) {
+
+ gBrowser.tabContainer.removeEventListener("TabOpen", arguments.callee, true);
+ let browser = aEvent.originalTarget.linkedBrowser;
+ browser.addEventListener("load", function(aEvent) {
+ browser.removeEventListener("load", arguments.callee, true);
+
+ is(Services.prefs.getBoolPref("browser.preferences.inContent"), true, "In-content prefs are enabled");
+ is(browser.contentWindow.location.href, "about:preferences", "Checking if the preferences tab was opened");
+
+ gBrowser.removeCurrentTab();
+ Services.prefs.setBoolPref("browser.preferences.inContent", false);
+ openPreferences();
+
+ }, true);
+ }, true);
+
+
+ let observer = {
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic == "domwindowopened") {
+ windowWatcher.unregisterNotification(observer);
+
+ let win = aSubject.QueryInterface(Components.interfaces.nsIDOMWindow);
+ win.addEventListener("load", function() {
+ win.removeEventListener("load", arguments.callee, false);
+ is(Services.prefs.getBoolPref("browser.preferences.inContent"), false, "In-content prefs are disabled");
+ is(win.location.href, "chrome://browser/content/preferences/preferences.xul", "Checking if the preferences window was opened");
+ win.close();
+ finish();
+ }, false);
+ }
+ }
+ }
+
+ var windowWatcher = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
+ .getService(Components.interfaces.nsIWindowWatcher);
+ windowWatcher.registerNotification(observer);
+
+ openPreferences();
+}
diff --git a/browser/base/content/test/browser_bug743421.js b/browser/base/content/test/browser_bug743421.js
new file mode 100644
index 000000000..38437589d
--- /dev/null
+++ b/browser/base/content/test/browser_bug743421.js
@@ -0,0 +1,118 @@
+const gTestRoot = "http://mochi.test:8888/browser/browser/base/content/test/";
+
+var gTestBrowser = null;
+var gNextTest = null;
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+function test() {
+ waitForExplicitFinish();
+ registerCleanupFunction(function() {
+ clearAllPluginPermissions();
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ var plugin = getTestPlugin();
+ plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ });
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ var plugin = getTestPlugin();
+ plugin.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+
+ var newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+ gTestBrowser.addEventListener("load", pageLoad, true);
+ prepareTest(test1a, gTestRoot + "plugin_add_dynamically.html");
+}
+
+function finishTest() {
+ gTestBrowser.removeEventListener("load", pageLoad, true);
+ gBrowser.removeCurrentTab();
+ window.focus();
+ finish();
+}
+
+function pageLoad() {
+ // The plugin events are async dispatched and can come after the load event
+ // This just allows the events to fire before we then go on to test the states
+ executeSoon(gNextTest);
+}
+
+function prepareTest(nextTest, url) {
+ gNextTest = nextTest;
+ gTestBrowser.contentWindow.location = url;
+}
+
+// Tests that navigation within the page and the window.history API doesn't break click-to-play state.
+function test1a() {
+ var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(!popupNotification, "Test 1a, Should not have a click-to-play notification");
+ var plugin = new XPCNativeWrapper(XPCNativeWrapper.unwrap(gTestBrowser.contentWindow).addPlugin());
+
+ var condition = function() PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ waitForCondition(condition, test1b, "Test 1a, Waited too long for plugin notification");
+}
+
+function test1b() {
+ var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "Test 1b, Should have a click-to-play notification");
+ var plugin = gTestBrowser.contentDocument.getElementsByTagName("embed")[0];
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!objLoadingContent.activated, "Test 1b, Plugin should not be activated");
+
+ // Click the activate button on doorhanger to make sure it works
+ popupNotification.reshow();
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ ok(objLoadingContent.activated, "Test 1b, Doorhanger should activate plugin");
+
+ test1c();
+}
+
+function test1c() {
+ var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "Test 1c, Should still have a click-to-play notification");
+ var plugin = new XPCNativeWrapper(XPCNativeWrapper.unwrap(gTestBrowser.contentWindow).addPlugin());
+
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ var condition = function() objLoadingContent.activated;
+ waitForCondition(condition, test1d, "Test 1c, Waited too long for plugin activation");
+}
+
+function test1d() {
+ var plugin = gTestBrowser.contentDocument.getElementsByTagName("embed")[1];
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.activated, "Test 1d, Plugin should be activated");
+
+ gNextTest = test1e;
+ gTestBrowser.contentWindow.addEventListener("hashchange", test1e, false);
+ gTestBrowser.contentWindow.location += "#anchorNavigation";
+}
+
+function test1e() {
+ gTestBrowser.contentWindow.removeEventListener("hashchange", test1e, false);
+
+ var plugin = new XPCNativeWrapper(XPCNativeWrapper.unwrap(gTestBrowser.contentWindow).addPlugin());
+
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ var condition = function() objLoadingContent.activated;
+ waitForCondition(condition, test1f, "Test 1e, Waited too long for plugin activation");
+}
+
+function test1f() {
+ var plugin = gTestBrowser.contentDocument.getElementsByTagName("embed")[2];
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.activated, "Test 1f, Plugin should be activated");
+
+ gTestBrowser.contentWindow.history.replaceState({}, "", "replacedState");
+ var plugin = new XPCNativeWrapper(XPCNativeWrapper.unwrap(gTestBrowser.contentWindow).addPlugin());
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ var condition = function() objLoadingContent.activated;
+ waitForCondition(condition, test1g, "Test 1f, Waited too long for plugin activation");
+}
+
+function test1g() {
+ var plugin = gTestBrowser.contentDocument.getElementsByTagName("embed")[3];
+ var objLoadingContent2 = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent2.activated, "Test 1g, Plugin should be activated");
+ finishTest();
+}
diff --git a/browser/base/content/test/browser_bug744745.js b/browser/base/content/test/browser_bug744745.js
new file mode 100644
index 000000000..916480c50
--- /dev/null
+++ b/browser/base/content/test/browser_bug744745.js
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var gTestBrowser = null;
+var gNumPluginBindingsAttached = 0;
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+function test() {
+ waitForExplicitFinish();
+ registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ var plugin = getTestPlugin();
+ plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ gTestBrowser.removeEventListener("PluginBindingAttached", pluginBindingAttached, true, true);
+ gBrowser.removeCurrentTab();
+ window.focus();
+ });
+
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ var plugin = getTestPlugin();
+ plugin.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+ gTestBrowser.addEventListener("PluginBindingAttached", pluginBindingAttached, true, true);
+ var gHttpTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+ gTestBrowser.contentWindow.location = gHttpTestRoot + "plugin_bug744745.html";
+}
+
+function pluginBindingAttached() {
+ gNumPluginBindingsAttached++;
+
+ if (gNumPluginBindingsAttached == 1) {
+ var doc = gTestBrowser.contentDocument;
+ var testplugin = doc.getElementById("test");
+ ok(testplugin, "should have test plugin");
+ var style = getComputedStyle(testplugin);
+ ok('opacity' in style, "style should have opacity set");
+ is(style.opacity, 1, "opacity should be 1");
+ finish();
+ } else {
+ ok(false, "if we've gotten here, something is quite wrong");
+ }
+}
diff --git a/browser/base/content/test/browser_bug749738.js b/browser/base/content/test/browser_bug749738.js
new file mode 100644
index 000000000..2372e7cb3
--- /dev/null
+++ b/browser/base/content/test/browser_bug749738.js
@@ -0,0 +1,36 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const DUMMY_PAGE = "http://example.org/browser/browser/base/content/test/dummy_page.html";
+
+function test() {
+ waitForExplicitFinish();
+
+ let tab = gBrowser.addTab();
+ gBrowser.selectedTab = tab;
+
+ load(tab, DUMMY_PAGE, function() {
+ gFindBar.onFindCommand();
+ EventUtils.sendString("Dummy");
+ gBrowser.removeTab(tab);
+
+ try {
+ gFindBar.close();
+ ok(true, "findbar.close should not throw an exception");
+ } catch(e) {
+ ok(false, "findbar.close threw exception: " + e);
+ }
+ finish();
+ });
+}
+
+function load(aTab, aUrl, aCallback) {
+ aTab.linkedBrowser.addEventListener("load", function onload(aEvent) {
+ aEvent.currentTarget.removeEventListener("load", onload, true);
+ waitForFocus(aCallback, content);
+ }, true);
+ aTab.linkedBrowser.loadURI(aUrl);
+}
diff --git a/browser/base/content/test/browser_bug752516.js b/browser/base/content/test/browser_bug752516.js
new file mode 100644
index 000000000..8cd69767c
--- /dev/null
+++ b/browser/base/content/test/browser_bug752516.js
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var gTestBrowser = null;
+
+function test() {
+ waitForExplicitFinish();
+ registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ let plugin = getTestPlugin();
+ plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ gBrowser.removeCurrentTab();
+ window.focus();
+ });
+
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ let plugin = getTestPlugin();
+ plugin.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+ let gHttpTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+ gTestBrowser.contentWindow.location = gHttpTestRoot + "plugin_bug752516.html";
+
+ gTestBrowser.addEventListener("load", tabLoad, true);
+}
+
+function tabLoad() {
+ // Due to layout being async, "PluginBindAttached" may trigger later.
+ // This forces a layout flush, thus triggering it, and schedules the
+ // test so it is definitely executed afterwards.
+ gTestBrowser.contentDocument.getElementById('test').clientTop;
+ executeSoon(actualTest);
+}
+
+function actualTest() {
+ let doc = gTestBrowser.contentDocument;
+ let plugin = doc.getElementById("test");
+ ok(!plugin.activated, "Plugin should not be activated");
+ ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed, "Doorhanger should not be open");
+
+ EventUtils.synthesizeMouseAtCenter(plugin, {}, gTestBrowser.contentWindow);
+ let condition = function() !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed;
+ waitForCondition(condition, finish, "Waited too long for plugin doorhanger to activate");
+}
diff --git a/browser/base/content/test/browser_bug763468_perwindowpb.js b/browser/base/content/test/browser_bug763468_perwindowpb.js
new file mode 100644
index 000000000..bdd6943d9
--- /dev/null
+++ b/browser/base/content/test/browser_bug763468_perwindowpb.js
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This test makes sure that opening a new tab in private browsing mode opens about:privatebrowsing
+function test() {
+ // initialization
+ waitForExplicitFinish();
+ let windowsToClose = [];
+ let newTab;
+ let newTabPrefName = "browser.newtab.url";
+ let newTabURL;
+ let mode;
+
+ function doTest(aIsPrivateMode, aWindow, aCallback) {
+ whenNewTabLoaded(aWindow, function () {
+ if (aIsPrivateMode) {
+ mode = "per window private browsing";
+ newTabURL = "about:privatebrowsing";
+ } else {
+ mode = "normal";
+ newTabURL = Services.prefs.getCharPref(newTabPrefName) || "about:blank";
+ }
+
+ is(aWindow.gBrowser.currentURI.spec, newTabURL,
+ "URL of NewTab should be " + newTabURL + " in " + mode + " mode");
+
+ aWindow.gBrowser.removeTab(aWindow.gBrowser.selectedTab);
+ aCallback()
+ });
+ };
+
+ function testOnWindow(aOptions, aCallback) {
+ whenNewWindowLoaded(aOptions, function(aWin) {
+ windowsToClose.push(aWin);
+ // execute should only be called when need, like when you are opening
+ // web pages on the test. If calling executeSoon() is not necesary, then
+ // call whenNewWindowLoaded() instead of testOnWindow() on your test.
+ executeSoon(function() aCallback(aWin));
+ });
+ };
+
+ // this function is called after calling finish() on the test.
+ registerCleanupFunction(function() {
+ windowsToClose.forEach(function(aWin) {
+ aWin.close();
+ });
+ });
+
+ // test first when not on private mode
+ testOnWindow({}, function(aWin) {
+ doTest(false, aWin, function() {
+ // then test when on private mode
+ testOnWindow({private: true}, function(aWin) {
+ doTest(true, aWin, function() {
+ // then test again when not on private mode
+ testOnWindow({}, function(aWin) {
+ doTest(false, aWin, finish);
+ });
+ });
+ });
+ });
+ });
+}
diff --git a/browser/base/content/test/browser_bug767836_perwindowpb.js b/browser/base/content/test/browser_bug767836_perwindowpb.js
new file mode 100644
index 000000000..aef355e61
--- /dev/null
+++ b/browser/base/content/test/browser_bug767836_perwindowpb.js
@@ -0,0 +1,83 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ //initialization
+ waitForExplicitFinish();
+ let newTabPrefName = "browser.newtab.url";
+ let newTabURL;
+ let testURL = "http://example.com/";
+ let mode;
+
+ function doTest(aIsPrivateMode, aWindow, aCallback) {
+ openNewTab(aWindow, function () {
+ if (aIsPrivateMode) {
+ mode = "per window private browsing";
+ newTabURL = "about:privatebrowsing";
+ } else {
+ mode = "normal";
+ newTabURL = Services.prefs.getCharPref(newTabPrefName) || "about:blank";
+ }
+
+ // Check the new tab opened while in normal/private mode
+ is(aWindow.gBrowser.selectedBrowser.currentURI.spec, newTabURL,
+ "URL of NewTab should be " + newTabURL + " in " + mode + " mode");
+ // Set the custom newtab url
+ Services.prefs.setCharPref(newTabPrefName, testURL);
+ ok(Services.prefs.prefHasUserValue(newTabPrefName), "Custom newtab url is set");
+
+ // Open a newtab after setting the custom newtab url
+ openNewTab(aWindow, function () {
+ is(aWindow.gBrowser.selectedBrowser.currentURI.spec, testURL,
+ "URL of NewTab should be the custom url");
+
+ // clear the custom url preference
+ Services.prefs.clearUserPref(newTabPrefName);
+ ok(!Services.prefs.prefHasUserValue(newTabPrefName), "No custom newtab url is set");
+
+ aWindow.gBrowser.removeTab(aWindow.gBrowser.selectedTab);
+ aWindow.gBrowser.removeTab(aWindow.gBrowser.selectedTab);
+ aWindow.close();
+ aCallback()
+ });
+ });
+ }
+
+ function testOnWindow(aIsPrivate, aCallback) {
+ whenNewWindowLoaded({private: aIsPrivate}, function(win) {
+ executeSoon(function() aCallback(win));
+ });
+ }
+
+ // check whether any custom new tab url has been configured
+ ok(!Services.prefs.prefHasUserValue(newTabPrefName), "No custom newtab url is set");
+
+ // test normal mode
+ testOnWindow(false, function(aWindow) {
+ doTest(false, aWindow, function() {
+ // test private mode
+ testOnWindow(true, function(aWindow) {
+ doTest(true, aWindow, function() {
+ finish();
+ });
+ });
+ });
+ });
+}
+
+function openNewTab(aWindow, aCallback) {
+ // Open a new tab
+ aWindow.BrowserOpenTab();
+
+ let browser = aWindow.gBrowser.selectedBrowser;
+ if (browser.contentDocument.readyState == "complete") {
+ executeSoon(aCallback);
+ return;
+ }
+
+ browser.addEventListener("load", function onLoad() {
+ browser.removeEventListener("load", onLoad, true);
+ executeSoon(aCallback);
+ }, true);
+} \ No newline at end of file
diff --git a/browser/base/content/test/browser_bug771331.js b/browser/base/content/test/browser_bug771331.js
new file mode 100644
index 000000000..3b1ab552c
--- /dev/null
+++ b/browser/base/content/test/browser_bug771331.js
@@ -0,0 +1,82 @@
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+
+const INPUT_ID = "input1";
+const FORM1_ID = "form1";
+const FORM2_ID = "form2";
+const CHANGE_INPUT_ID = "input2";
+
+function test() {
+ waitForExplicitFinish();
+ let tab = gBrowser.selectedTab =
+ gBrowser.addTab("data:text/html;charset=utf-8," +
+ "<html><body>" +
+ "<form id='" + FORM1_ID + "'><input id='" + CHANGE_INPUT_ID + "'></form>" +
+ "<form id='" + FORM2_ID + "'></form>" +
+ "</body></html>");
+ tab.linkedBrowser.addEventListener("load", tabLoad, true);
+}
+
+function unexpectedContentEvent(evt) {
+ ok(false, "Received a " + evt.type + " event on content");
+}
+
+var gDoc = null;
+
+function tabLoad() {
+ let tab = gBrowser.selectedTab;
+ tab.linkedBrowser.removeEventListener("load", tabLoad, true);
+ gDoc = gBrowser.selectedBrowser.contentDocument;
+ // These events shouldn't escape to content.
+ gDoc.addEventListener("DOMFormHasPassword", unexpectedContentEvent, false);
+ gDoc.defaultView.setTimeout(test_inputAdd, 0);
+}
+
+function test_inputAdd() {
+ gBrowser.addEventListener("DOMFormHasPassword", test_inputAddHandler, false);
+ let input = gDoc.createElementNS(HTML_NS, "input");
+ input.setAttribute("type", "password");
+ input.setAttribute("id", INPUT_ID);
+ input.setAttribute("data-test", "unique-attribute");
+ gDoc.getElementById(FORM1_ID).appendChild(input);
+ info("Done appending the input element");
+}
+
+function test_inputAddHandler(evt) {
+ gBrowser.removeEventListener(evt.type, test_inputAddHandler, false);
+ is(evt.target.id, FORM1_ID,
+ evt.type + " event targets correct form element (added password element)");
+ gDoc.defaultView.setTimeout(test_inputChangeForm, 0);
+}
+
+function test_inputChangeForm() {
+ gBrowser.addEventListener("DOMFormHasPassword", test_inputChangeFormHandler, false);
+ let input = gDoc.getElementById(INPUT_ID);
+ input.setAttribute("form", FORM2_ID);
+}
+
+function test_inputChangeFormHandler(evt) {
+ gBrowser.removeEventListener(evt.type, test_inputChangeFormHandler, false);
+ is(evt.target.id, FORM2_ID,
+ evt.type + " event targets correct form element (changed form)");
+ gDoc.defaultView.setTimeout(test_inputChangesType, 0);
+}
+
+function test_inputChangesType() {
+ gBrowser.addEventListener("DOMFormHasPassword", test_inputChangesTypeHandler, false);
+ let input = gDoc.getElementById(CHANGE_INPUT_ID);
+ input.setAttribute("type", "password");
+}
+
+function test_inputChangesTypeHandler(evt) {
+ gBrowser.removeEventListener(evt.type, test_inputChangesTypeHandler, false);
+ is(evt.target.id, FORM1_ID,
+ evt.type + " event targets correct form element (changed type)");
+ gDoc.defaultView.setTimeout(completeTest, 0);
+}
+
+function completeTest() {
+ ok(true, "Test completed");
+ gDoc.removeEventListener("DOMFormHasPassword", unexpectedContentEvent, false);
+ gBrowser.removeCurrentTab();
+ finish();
+}
diff --git a/browser/base/content/test/browser_bug783614.js b/browser/base/content/test/browser_bug783614.js
new file mode 100644
index 000000000..ebc62e8fa
--- /dev/null
+++ b/browser/base/content/test/browser_bug783614.js
@@ -0,0 +1,13 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ gURLBar.focus();
+ gURLBar.inputField.value = "https://example.com/";
+ gURLBar.selectionStart = 4;
+ gURLBar.selectionEnd = 5;
+ goDoCommand("cmd_cut");
+ is(gURLBar.inputField.value, "http://example.com/", "location bar value after cutting 's' from https");
+ gURLBar.handleRevert();
+}
diff --git a/browser/base/content/test/browser_bug787619.js b/browser/base/content/test/browser_bug787619.js
new file mode 100644
index 000000000..1917100eb
--- /dev/null
+++ b/browser/base/content/test/browser_bug787619.js
@@ -0,0 +1,52 @@
+const gHttpTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+
+let gTestBrowser = null;
+let gWrapperClickCount = 0;
+
+function test() {
+ waitForExplicitFinish();
+ registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ let plugin = getTestPlugin();
+ plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ });
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ let plugin = getTestPlugin();
+ plugin.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+ gTestBrowser.addEventListener("load", pageLoad, true);
+ gTestBrowser.contentWindow.location = gHttpTestRoot + "plugin_bug787619.html";
+}
+
+function pageLoad() {
+ // Due to layout being async, "PluginBindAttached" may trigger later.
+ // This forces a layout flush, thus triggering it, and schedules the
+ // test so it is definitely executed afterwards.
+ gTestBrowser.contentDocument.getElementById('plugin').clientTop;
+ executeSoon(part1);
+}
+
+function part1() {
+ let wrapper = gTestBrowser.contentDocument.getElementById('wrapper');
+ wrapper.addEventListener('click', function() ++gWrapperClickCount, false);
+
+ let plugin = gTestBrowser.contentDocument.getElementById('plugin');
+ ok(plugin, 'got plugin element');
+ ok(!plugin.activated, 'plugin should not be activated');
+ ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed, "Doorhanger should not be open");
+
+ EventUtils.synthesizeMouseAtCenter(plugin, {}, gTestBrowser.contentWindow);
+ let condition = function() !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed;
+ waitForCondition(condition, part2,
+ 'waited too long for plugin to activate');
+}
+
+function part2() {
+ is(gWrapperClickCount, 0, 'wrapper should not have received any clicks');
+ gTestBrowser.removeEventListener("load", pageLoad, true);
+ gBrowser.removeCurrentTab();
+ window.focus();
+ finish();
+}
diff --git a/browser/base/content/test/browser_bug797677.js b/browser/base/content/test/browser_bug797677.js
new file mode 100644
index 000000000..ff9637306
--- /dev/null
+++ b/browser/base/content/test/browser_bug797677.js
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var rootDir = getRootDirectory(gTestPath);
+const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+var gTestBrowser = null;
+var gConsoleErrors = 0;
+
+function test() {
+ waitForExplicitFinish();
+ var newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+ gTestBrowser.addEventListener("PluginBindingAttached", pluginBindingAttached, true, true);
+ var consoleService = Cc["@mozilla.org/consoleservice;1"]
+ .getService(Ci.nsIConsoleService);
+ var errorListener = {
+ observe: function(aMessage) {
+ if (aMessage.message.contains("NS_ERROR"))
+ gConsoleErrors++;
+ }
+ };
+ consoleService.registerListener(errorListener);
+ registerCleanupFunction(function() {
+ gTestBrowser.removeEventListener("PluginBindingAttached", pluginBindingAttached, true);
+ consoleService.unregisterListener(errorListener);
+ gBrowser.removeCurrentTab();
+ window.focus();
+ });
+ gTestBrowser.contentWindow.location = gHttpTestRoot + "plugin_bug797677.html";
+}
+
+function pluginBindingAttached() {
+ // Let browser-plugins.js handle the PluginNotFound event, then run the test
+ executeSoon(runTest);
+}
+
+function runTest() {
+ var doc = gTestBrowser.contentDocument;
+ var plugin = doc.getElementById("plugin");
+ ok(plugin, "plugin should be in the page");
+ is(gConsoleErrors, 0, "should have no console errors");
+ finish();
+}
diff --git a/browser/base/content/test/browser_bug812562.js b/browser/base/content/test/browser_bug812562.js
new file mode 100644
index 000000000..677558cbe
--- /dev/null
+++ b/browser/base/content/test/browser_bug812562.js
@@ -0,0 +1,96 @@
+var gHttpTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gTestBrowser = null;
+var gNextTest = null;
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+function test() {
+ waitForExplicitFinish();
+ registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ var plugin = getTestPlugin();
+ plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ });
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ var plugin = getTestPlugin();
+ plugin.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+
+ var newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+ gTestBrowser.addEventListener("load", pageLoad, true);
+ setAndUpdateBlocklist(gHttpTestRoot + "blockPluginVulnerableUpdatable.xml",
+ function() {
+ prepareTest(function() {
+ // Due to layout being async, "PluginBindAttached" may trigger later.
+ // This forces a layout flush, thus triggering it, and schedules the
+ // test so it is definitely executed afterwards.
+ gTestBrowser.contentDocument.getElementById('test').clientTop;
+ testPart1();
+ },
+ gHttpTestRoot + "plugin_test.html");
+ });
+}
+
+function finishTest() {
+ gTestBrowser.removeEventListener("load", pageLoad, true);
+ gBrowser.removeCurrentTab();
+ window.focus();
+ setAndUpdateBlocklist(gHttpTestRoot + "blockNoPlugins.xml",
+ function() {
+ resetBlocklist();
+ finish();
+ });
+}
+
+function pageLoad(aEvent) {
+ // The plugin events are async dispatched and can come after the load event
+ // This just allows the events to fire before we then go on to test the states
+ if (gNextTest != null)
+ executeSoon(gNextTest);
+}
+
+function prepareTest(nextTest, url) {
+ gNextTest = nextTest;
+ gTestBrowser.contentWindow.location = url;
+}
+
+// Tests that the going back will reshow the notification for click-to-play
+// blocklisted plugins (part 1/4)
+function testPart1() {
+ var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "test part 1: Should have a click-to-play notification");
+ var plugin = gTestBrowser.contentDocument.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE, "test part 1: plugin fallback type should be PLUGIN_VULNERABLE_UPDATABLE");
+ ok(!objLoadingContent.activated, "test part 1: plugin should not be activated");
+
+ prepareTest(testPart2, "about:blank");
+}
+
+function testPart2() {
+ var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(!popupNotification, "test part 2: Should not have a click-to-play notification");
+ var plugin = gTestBrowser.contentDocument.getElementById("test");
+ ok(!plugin, "test part 2: Should not have a plugin in this page");
+
+ Services.obs.addObserver(testPart3, "PopupNotifications-updateNotShowing", false);
+ gTestBrowser.contentWindow.history.back();
+}
+
+function testPart3() {
+ Services.obs.removeObserver(testPart3, "PopupNotifications-updateNotShowing");
+ var condition = function() PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ waitForCondition(condition, testPart4, "test part 3: waited too long for click-to-play-plugin notification");
+}
+
+function testPart4() {
+ var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "test part 4: Should have a click-to-play notification");
+ var plugin = gTestBrowser.contentDocument.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE, "test part 4: plugin fallback type should be PLUGIN_VULNERABLE_UPDATABLE");
+ ok(!objLoadingContent.activated, "test part 4: plugin should not be activated");
+
+ finishTest();
+}
diff --git a/browser/base/content/test/browser_bug816527.js b/browser/base/content/test/browser_bug816527.js
new file mode 100644
index 000000000..30a4090f2
--- /dev/null
+++ b/browser/base/content/test/browser_bug816527.js
@@ -0,0 +1,122 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ let testURL = "http://example.org/browser/browser/base/content/test/dummy_page.html";
+
+ function testOnWindow(aOptions, aCallback) {
+ whenNewWindowLoaded(aOptions, function(aWin) {
+ // execute should only be called when need, like when you are opening
+ // web pages on the test. If calling executeSoon() is not necesary, then
+ // call whenNewWindowLoaded() instead of testOnWindow() on your test.
+ executeSoon(function() aCallback(aWin));
+ });
+ };
+
+ testOnWindow({}, function(aNormalWindow) {
+ testOnWindow({private: true}, function(aPrivateWindow) {
+ runTest(aNormalWindow, aPrivateWindow, false, function() {
+ aNormalWindow.close();
+ aPrivateWindow.close();
+ testOnWindow({}, function(aNormalWindow) {
+ testOnWindow({private: true}, function(aPrivateWindow) {
+ runTest(aPrivateWindow, aNormalWindow, false, function() {
+ aNormalWindow.close();
+ aPrivateWindow.close();
+ testOnWindow({private: true}, function(aPrivateWindow) {
+ runTest(aPrivateWindow, aPrivateWindow, false, function() {
+ aPrivateWindow.close();
+ testOnWindow({}, function(aNormalWindow) {
+ runTest(aNormalWindow, aNormalWindow, true, function() {
+ aNormalWindow.close();
+ finish();
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+
+ function runTest(aSourceWindow, aDestWindow, aExpectSuccess, aCallback) {
+ // Open the base tab
+ let baseTab = aSourceWindow.gBrowser.addTab(testURL);
+ baseTab.linkedBrowser.addEventListener("load", function() {
+ // Wait for the tab to be fully loaded so matching happens correctly
+ if (baseTab.linkedBrowser.currentURI.spec == "about:blank")
+ return;
+ baseTab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+ let testTab = aDestWindow.gBrowser.addTab();
+
+ waitForFocus(function() {
+ // Select the testTab
+ aDestWindow.gBrowser.selectedTab = testTab;
+
+ // Ensure that this tab has no history entries
+ ok(testTab.linkedBrowser.sessionHistory.count < 2,
+ "The test tab has 1 or less history entries");
+ // Ensure that this tab is on about:blank
+ is(testTab.linkedBrowser.currentURI.spec, "about:blank",
+ "The test tab is on about:blank");
+ // Ensure that this tab's document has no child nodes
+ ok(!testTab.linkedBrowser.contentDocument.body.hasChildNodes(),
+ "The test tab has no child nodes");
+ ok(!testTab.hasAttribute("busy"),
+ "The test tab doesn't have the busy attribute");
+
+ // Set the urlbar to include the moz-action
+ aDestWindow.gURLBar.value = "moz-action:switchtab," + testURL;
+ // Focus the urlbar so we can press enter
+ aDestWindow.gURLBar.focus();
+
+ // We want to see if the switchtab action works. If it does, the
+ // current tab will get closed, and that's what we detect with the
+ // TabClose handler. If pressing enter triggers a load in that tab,
+ // then the load handler will get called. Neither of these are
+ // the desired effect here. So if the test goes successfully, it is
+ // the timeout handler which gets called.
+ //
+ // The reason that we can't avoid the timeout here is because we are
+ // trying to test something which should not happen, so we just need
+ // to wait for a while and then check whether any bad things have
+ // happened.
+
+ function onTabClose(aEvent) {
+ aDestWindow.gBrowser.tabContainer.removeEventListener("TabClose", onTabClose, false);
+ aDestWindow.gBrowser.removeEventListener("load", onLoad, false);
+ clearTimeout(timeout);
+ // Should only happen when we expect success
+ ok(aExpectSuccess, "Tab closed as expected");
+ aCallback();
+ }
+ function onLoad(aEvent) {
+ aDestWindow.gBrowser.tabContainer.removeEventListener("TabClose", onTabClose, false);
+ aDestWindow.gBrowser.removeEventListener("load", onLoad, false);
+ clearTimeout(timeout);
+ // Should only happen when we expect success
+ ok(aExpectSuccess, "Tab loaded as expected");
+ aCallback();
+ }
+
+ aDestWindow.gBrowser.tabContainer.addEventListener("TabClose", onTabClose, false);
+ aDestWindow.gBrowser.addEventListener("load", onLoad, false);
+ let timeout = setTimeout(function() {
+ aDestWindow.gBrowser.tabContainer.removeEventListener("TabClose", onTabClose, false);
+ aDestWindow.gBrowser.removeEventListener("load", onLoad, false);
+ aCallback();
+ }, 500);
+
+ // Press enter!
+ EventUtils.synthesizeKey("VK_RETURN", {});
+ }, aDestWindow);
+ }, true);
+ }
+}
+
diff --git a/browser/base/content/test/browser_bug817947.js b/browser/base/content/test/browser_bug817947.js
new file mode 100644
index 000000000..061cd8d18
--- /dev/null
+++ b/browser/base/content/test/browser_bug817947.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const ss = Cc["@mozilla.org/browser/sessionstore;1"]
+ .getService(Ci.nsISessionStore);
+
+const URL = "http://mochi.test:8888/browser/";
+const PREF = "browser.sessionstore.restore_on_demand";
+
+function test() {
+ waitForExplicitFinish();
+
+ Services.prefs.setBoolPref(PREF, true);
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(PREF);
+ });
+
+ preparePendingTab(function (aTab) {
+ let win = gBrowser.replaceTabWithWindow(aTab);
+
+ whenDelayedStartupFinished(win, function () {
+ let [tab] = win.gBrowser.tabs;
+
+ whenLoaded(tab.linkedBrowser, function () {
+ is(tab.linkedBrowser.currentURI.spec, URL, "correct url should be loaded");
+ ok(!tab.hasAttribute("pending"), "tab should not be pending");
+
+ win.close();
+ finish();
+ });
+ });
+ });
+}
+
+function preparePendingTab(aCallback) {
+ let tab = gBrowser.addTab(URL);
+
+ whenLoaded(tab.linkedBrowser, function () {
+ let state = ss.getTabState(tab);
+ gBrowser.removeTab(tab);
+
+ tab = gBrowser.addTab("about:blank");
+ whenLoaded(tab.linkedBrowser, function () {
+ ss.setTabState(tab, state);
+ ok(tab.hasAttribute("pending"), "tab should be pending");
+ aCallback(tab);
+ });
+ });
+}
+
+function whenLoaded(aElement, aCallback) {
+ aElement.addEventListener("load", function onLoad() {
+ aElement.removeEventListener("load", onLoad, true);
+ executeSoon(aCallback);
+ }, true);
+}
diff --git a/browser/base/content/test/browser_bug818118.js b/browser/base/content/test/browser_bug818118.js
new file mode 100644
index 000000000..3b781333f
--- /dev/null
+++ b/browser/base/content/test/browser_bug818118.js
@@ -0,0 +1,48 @@
+var gHttpTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+var gTestBrowser = null;
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+function test() {
+ waitForExplicitFinish();
+ registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ var plugin = getTestPlugin();
+ plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ gTestBrowser.removeEventListener("load", pageLoad, true);
+ });
+
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ var plugin = getTestPlugin();
+ plugin.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+ gTestBrowser.addEventListener("load", pageLoad, true);
+ gTestBrowser.contentWindow.location = gHttpTestRoot + "plugin_both.html";
+}
+
+function pageLoad(aEvent) {
+ // Due to layout being async, "PluginBindAttached" may trigger later.
+ // This forces a layout flush, thus triggering it, and schedules the
+ // test so it is definitely executed afterwards.
+ gTestBrowser.contentDocument.getElementById('test').clientTop;
+ executeSoon(actualTest);
+}
+
+function actualTest() {
+ var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "should have a click-to-play notification");
+ var plugin = gTestBrowser.contentDocument.getElementById("test");
+ ok(plugin, "should have known plugin in page");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, "plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
+ ok(!objLoadingContent.activated, "plugin should not be activated");
+
+ var unknown = gTestBrowser.contentDocument.getElementById("unknown");
+ ok(unknown, "should have unknown plugin in page");
+
+ gBrowser.removeCurrentTab();
+ window.focus();
+ finish();
+}
diff --git a/browser/base/content/test/browser_bug820497.js b/browser/base/content/test/browser_bug820497.js
new file mode 100644
index 000000000..f6cc2e702
--- /dev/null
+++ b/browser/base/content/test/browser_bug820497.js
@@ -0,0 +1,61 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var gTestBrowser = null;
+var gNumPluginBindingsAttached = 0;
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+function test() {
+ waitForExplicitFinish();
+ registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ gTestBrowser.removeEventListener("PluginBindingAttached", pluginBindingAttached, true, true);
+ gBrowser.removeCurrentTab();
+ window.focus();
+ });
+
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+ getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+ gTestBrowser.addEventListener("PluginBindingAttached", pluginBindingAttached, true, true);
+ var gHttpTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+ gTestBrowser.contentWindow.location = gHttpTestRoot + "plugin_bug820497.html";
+}
+
+function pluginBindingAttached() {
+ gNumPluginBindingsAttached++;
+
+ if (gNumPluginBindingsAttached == 1) {
+ var doc = gTestBrowser.contentDocument;
+ var testplugin = doc.getElementById("test");
+ ok(testplugin, "should have test plugin");
+ var secondtestplugin = doc.getElementById("secondtest");
+ ok(!secondtestplugin, "should not yet have second test plugin");
+ var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "should have popup notification");
+ // We don't set up the action list until the notification is shown
+ notification.reshow();
+ is(notification.options.centerActions.length, 1, "should be 1 type of plugin in the popup notification");
+ XPCNativeWrapper.unwrap(gTestBrowser.contentWindow).addSecondPlugin();
+ } else if (gNumPluginBindingsAttached == 2) {
+ var doc = gTestBrowser.contentDocument;
+ var testplugin = doc.getElementById("test");
+ ok(testplugin, "should have test plugin");
+ var secondtestplugin = doc.getElementById("secondtest");
+ ok(secondtestplugin, "should have second test plugin");
+ var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "should have popup notification");
+ notification.reshow();
+ is(notification.options.centerActions.length, 2, "should be 2 types of plugin in the popup notification");
+ finish();
+ } else {
+ ok(false, "if we've gotten here, something is quite wrong");
+ }
+}
diff --git a/browser/base/content/test/browser_bug822367.js b/browser/base/content/test/browser_bug822367.js
new file mode 100644
index 000000000..5ce497919
--- /dev/null
+++ b/browser/base/content/test/browser_bug822367.js
@@ -0,0 +1,196 @@
+/*
+ * User Override Mixed Content Block - Tests for Bug 822367
+ */
+
+
+const PREF_DISPLAY = "security.mixed_content.block_display_content";
+const PREF_ACTIVE = "security.mixed_content.block_active_content";
+
+// We alternate for even and odd test cases to simulate different hosts
+const gHttpTestRoot = "https://example.com/browser/browser/base/content/test/";
+const gHttpTestRoot2 = "https://test1.example.com/browser/browser/base/content/test/";
+
+var origBlockDisplay;
+var origBlockActive;
+var gTestBrowser = null;
+
+registerCleanupFunction(function() {
+ // Set preferences back to their original values
+ Services.prefs.setBoolPref(PREF_DISPLAY, origBlockDisplay);
+ Services.prefs.setBoolPref(PREF_ACTIVE, origBlockActive);
+});
+
+function MixedTestsCompleted() {
+ gBrowser.removeCurrentTab();
+ window.focus();
+ finish();
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ origBlockDisplay = Services.prefs.getBoolPref(PREF_DISPLAY);
+ origBlockActive = Services.prefs.getBoolPref(PREF_ACTIVE);
+
+ Services.prefs.setBoolPref(PREF_DISPLAY, true);
+ Services.prefs.setBoolPref(PREF_ACTIVE, true);
+
+ var newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+ newTab.linkedBrowser.stop()
+
+ // Mixed Script Test
+ gTestBrowser.addEventListener("load", MixedTest1A, true);
+ var url = gHttpTestRoot + "file_bug822367_1.html";
+ gTestBrowser.contentWindow.location = url;
+}
+
+// Mixed Script Test
+function MixedTest1A() {
+ gTestBrowser.removeEventListener("load", MixedTest1A, true);
+ gTestBrowser.addEventListener("load", MixedTest1B, true);
+ var notification = PopupNotifications.getNotification("mixed-content-blocked", gTestBrowser);
+ ok(notification, "Mixed Content Doorhanger didn't appear");
+ notification.secondaryActions[0].callback();
+}
+function MixedTest1B() {
+ waitForCondition(function() content.document.getElementById('p1').innerHTML == "hello", MixedTest1C, "Waited too long for mixed script to run in Test 1");
+}
+function MixedTest1C() {
+ ok(content.document.getElementById('p1').innerHTML == "hello","Mixed script didn't load in Test 1");
+ gTestBrowser.removeEventListener("load", MixedTest1B, true);
+ MixedTest2();
+}
+
+//Mixed Display Test - Doorhanger should not appear
+function MixedTest2() {
+ gTestBrowser.addEventListener("load", MixedTest2A, true);
+ var url = gHttpTestRoot2 + "file_bug822367_2.html";
+ gTestBrowser.contentWindow.location = url;
+}
+
+function MixedTest2A() {
+ var notification = PopupNotifications.getNotification("mixed-content-blocked", gTestBrowser);
+ ok(!notification, "Mixed Content Doorhanger appears for mixed display content!");
+ MixedTest3();
+}
+
+// Mixed Script and Display Test - User Override should cause both the script and the image to load.
+function MixedTest3() {
+ gTestBrowser.removeEventListener("load", MixedTest2A, true);
+ gTestBrowser.addEventListener("load", MixedTest3A, true);
+ var url = gHttpTestRoot + "file_bug822367_3.html";
+ gTestBrowser.contentWindow.location = url;
+}
+function MixedTest3A() {
+ gTestBrowser.removeEventListener("load", MixedTest3A, true);
+ gTestBrowser.addEventListener("load", MixedTest3B, true);
+ var notification = PopupNotifications.getNotification("mixed-content-blocked", gTestBrowser);
+ ok(notification, "Mixed Content Doorhanger doesn't appear for test 3");
+ notification.secondaryActions[0].callback();
+}
+function MixedTest3B() {
+ waitForCondition(function() content.document.getElementById('p1').innerHTML == "hello", MixedTest3C, "Waited too long for mixed script to run in Test 3");
+}
+function MixedTest3C() {
+ waitForCondition(function() content.document.getElementById('p2').innerHTML == "bye", MixedTest3D, "Waited too long for mixed image to load in Test 3");
+}
+function MixedTest3D() {
+ ok(content.document.getElementById('p1').innerHTML == "hello","Mixed script didn't load in Test 3");
+ ok(content.document.getElementById('p2').innerHTML == "bye","Mixed image didn't load in Test 3");
+ MixedTest4();
+}
+
+// Location change - User override on one page doesn't propogate to another page after location change.
+function MixedTest4() {
+ gTestBrowser.removeEventListener("load", MixedTest3B, true);
+ gTestBrowser.addEventListener("load", MixedTest4A, true);
+ var url = gHttpTestRoot2 + "file_bug822367_4.html";
+ gTestBrowser.contentWindow.location = url;
+}
+function MixedTest4A() {
+ gTestBrowser.removeEventListener("load", MixedTest4A, true);
+ gTestBrowser.addEventListener("load", MixedTest4B, true);
+ var notification = PopupNotifications.getNotification("mixed-content-blocked", gTestBrowser);
+ ok(notification, "Mixed Content Doorhanger doesn't appear for Test 4");
+ notification.secondaryActions[0].callback();
+}
+function MixedTest4B() {
+ waitForCondition(function() content.document.location == gHttpTestRoot + "file_bug822367_4B.html", MixedTest4C, "Waited too long for mixed script to run in Test 4");
+}
+function MixedTest4C() {
+ ok(content.document.location == gHttpTestRoot + "file_bug822367_4B.html", "Location didn't change in test 4");
+ var notification = PopupNotifications.getNotification("mixed-content-blocked", gTestBrowser);
+ ok(notification, "Mixed Content Doorhanger doesn't appear after location change in Test 4");
+ waitForCondition(function() content.document.getElementById('p1').innerHTML == "", MixedTest4D, "Mixed script loaded in test 4 after location change!");
+}
+function MixedTest4D() {
+ ok(content.document.getElementById('p1').innerHTML == "","p1.innerHTML changed; mixed script loaded after location change in Test 4");
+ MixedTest5();
+}
+
+// Mixed script attempts to load in a document.open()
+function MixedTest5() {
+ gTestBrowser.removeEventListener("load", MixedTest4B, true);
+ gTestBrowser.addEventListener("load", MixedTest5A, true);
+ var url = gHttpTestRoot + "file_bug822367_5.html";
+ gTestBrowser.contentWindow.location = url;
+}
+function MixedTest5A() {
+ gTestBrowser.removeEventListener("load", MixedTest5A, true);
+ gTestBrowser.addEventListener("load", MixedTest5B, true);
+ var notification = PopupNotifications.getNotification("mixed-content-blocked", gTestBrowser);
+ ok(notification, "Mixed Content Doorhanger doesn't appear for Test 5");
+ notification.secondaryActions[0].callback();
+}
+function MixedTest5B() {
+ waitForCondition(function() content.document.getElementById('p1').innerHTML == "hello", MixedTest5C, "Waited too long for mixed script to run in Test 5");
+}
+function MixedTest5C() {
+ ok(content.document.getElementById('p1').innerHTML == "hello","Mixed script didn't load in Test 5");
+ MixedTest6();
+}
+
+// Mixed script attempts to load in a document.open() that is within an iframe.
+function MixedTest6() {
+ gTestBrowser.removeEventListener("load", MixedTest5B, true);
+ gTestBrowser.addEventListener("load", MixedTest6A, true);
+ var url = gHttpTestRoot2 + "file_bug822367_6.html";
+ gTestBrowser.contentWindow.location = url;
+}
+function MixedTest6A() {
+ gTestBrowser.removeEventListener("load", MixedTest6A, true);
+ waitForCondition(function() PopupNotifications.getNotification("mixed-content-blocked", gTestBrowser), MixedTest6B, "waited to long for doorhanger");
+}
+
+function MixedTest6B() {
+ var notification = PopupNotifications.getNotification("mixed-content-blocked", gTestBrowser);
+ ok(notification, "Mixed Content Doorhanger doesn't appear for Test 6");
+ gTestBrowser.addEventListener("load", MixedTest6C, true);
+ notification.secondaryActions[0].callback();
+}
+
+function MixedTest6C() {
+ gTestBrowser.removeEventListener("load", MixedTest6C, true);
+ waitForCondition(function() content.document.getElementById('f1').contentDocument.getElementById('p1').innerHTML == "hello", MixedTest6D, "Waited too long for mixed script to run in Test 6");
+}
+function MixedTest6D() {
+ ok(content.document.getElementById('f1').contentDocument.getElementById('p1').innerHTML == "hello","Mixed script didn't load in Test 6");
+ MixedTestsCompleted();
+}
+
+function waitForCondition(condition, nextTest, errorMsg) {
+ var tries = 0;
+ var interval = setInterval(function() {
+ if (tries >= 30) {
+ ok(false, errorMsg);
+ moveOn();
+ }
+ if (condition()) {
+ moveOn();
+ }
+ tries++;
+ }, 100);
+ var moveOn = function() { clearInterval(interval); nextTest(); };
+}
diff --git a/browser/base/content/test/browser_bug832435.js b/browser/base/content/test/browser_bug832435.js
new file mode 100644
index 000000000..6be2604cd
--- /dev/null
+++ b/browser/base/content/test/browser_bug832435.js
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+ ok(true, "Starting up");
+
+ gBrowser.selectedBrowser.focus();
+ gURLBar.addEventListener("focus", function onFocus() {
+ gURLBar.removeEventListener("focus", onFocus);
+ ok(true, "Invoked onfocus handler");
+ EventUtils.synthesizeKey("VK_RETURN", { shiftKey: true });
+
+ // javscript: URIs are evaluated async.
+ SimpleTest.executeSoon(function() {
+ ok(true, "Evaluated without crashing");
+ finish();
+ });
+ });
+ gURLBar.inputField.value = "javascript: var foo = '11111111'; ";
+ gURLBar.focus();
+}
diff --git a/browser/base/content/test/browser_bug839103.js b/browser/base/content/test/browser_bug839103.js
new file mode 100644
index 000000000..2b777c00d
--- /dev/null
+++ b/browser/base/content/test/browser_bug839103.js
@@ -0,0 +1,159 @@
+const gTestRoot = getRootDirectory(gTestPath);
+const gStyleSheet = "bug839103.css";
+
+var gTab = null;
+var needsInitialApplicableStateEvent = false;
+var needsInitialApplicableStateEventFor = null;
+
+function test() {
+ waitForExplicitFinish();
+ gBrowser.addEventListener("StyleSheetAdded", initialStylesheetAdded, true);
+ gTab = gBrowser.selectedTab = gBrowser.addTab(gTestRoot + "test_bug839103.html");
+ gTab.linkedBrowser.addEventListener("load", tabLoad, true);
+}
+
+function initialStylesheetAdded(evt) {
+ gBrowser.removeEventListener("StyleSheetAdded", initialStylesheetAdded, true);
+ ok(true, "received initial style sheet event");
+ is(evt.type, "StyleSheetAdded", "evt.type has expected value");
+ is(evt.target, gBrowser.contentDocument, "event targets correct document");
+ ok(evt.stylesheet, "evt.stylesheet is defined");
+ ok(evt.stylesheet.toString().contains("CSSStyleSheet"), "evt.stylesheet is a stylesheet");
+ ok(evt.documentSheet, "style sheet is a document sheet");
+}
+
+function tabLoad(evt) {
+ gTab.linkedBrowser.removeEventListener(evt.type, tabLoad, true);
+ executeSoon(continueTest);
+}
+
+var gLinkElement = null;
+
+function unexpectedContentEvent(evt) {
+ ok(false, "Received a " + evt.type + " event on content");
+}
+
+// We've seen the original stylesheet in the document.
+// Now add a stylesheet on the fly and make sure we see it.
+function continueTest() {
+ info("continuing test");
+
+ let doc = gBrowser.contentDocument;
+ doc.styleSheetChangeEventsEnabled = true;
+ doc.addEventListener("StyleSheetAdded", unexpectedContentEvent, false);
+ doc.addEventListener("StyleSheetRemoved", unexpectedContentEvent, false);
+ doc.addEventListener("StyleSheetApplicableStateChanged", unexpectedContentEvent, false);
+ doc.defaultView.addEventListener("StyleSheetAdded", unexpectedContentEvent, false);
+ doc.defaultView.addEventListener("StyleSheetRemoved", unexpectedContentEvent, false);
+ doc.defaultView.addEventListener("StyleSheetApplicableStateChanged", unexpectedContentEvent, false);
+ let link = doc.createElement('link');
+ link.setAttribute('rel', 'stylesheet');
+ link.setAttribute('type', 'text/css');
+ link.setAttribute('href', gTestRoot + gStyleSheet);
+ gLinkElement = link;
+
+ gBrowser.addEventListener("StyleSheetAdded", dynamicStylesheetAdded, true);
+ gBrowser.addEventListener("StyleSheetApplicableStateChanged", dynamicStylesheetApplicableStateChanged, true);
+ doc.body.appendChild(link);
+}
+
+function dynamicStylesheetAdded(evt) {
+ gBrowser.removeEventListener("StyleSheetAdded", dynamicStylesheetAdded, true);
+ ok(true, "received dynamic style sheet event");
+ is(evt.type, "StyleSheetAdded", "evt.type has expected value");
+ is(evt.target, gBrowser.contentDocument, "event targets correct document");
+ ok(evt.stylesheet, "evt.stylesheet is defined");
+ ok(evt.stylesheet.toString().contains("CSSStyleSheet"), "evt.stylesheet is a stylesheet");
+ ok(evt.documentSheet, "style sheet is a document sheet");
+}
+
+function dynamicStylesheetApplicableStateChanged(evt) {
+ gBrowser.removeEventListener("StyleSheetApplicableStateChanged", dynamicStylesheetApplicableStateChanged, true);
+ ok(true, "received dynamic style sheet applicable state change event");
+ is(evt.type, "StyleSheetApplicableStateChanged", "evt.type has expected value");
+ is(evt.target, gBrowser.contentDocument, "event targets correct document");
+ is(evt.stylesheet, gLinkElement.sheet, "evt.stylesheet has the right value");
+ is(evt.applicable, true, "evt.applicable has the right value");
+
+ gBrowser.addEventListener("StyleSheetApplicableStateChanged", dynamicStylesheetApplicableStateChangedToFalse, true);
+ gLinkElement.disabled = true;
+}
+
+function dynamicStylesheetApplicableStateChangedToFalse(evt) {
+ gBrowser.removeEventListener("StyleSheetApplicableStateChanged", dynamicStylesheetApplicableStateChangedToFalse, true);
+ is(evt.type, "StyleSheetApplicableStateChanged", "evt.type has expected value");
+ ok(true, "received dynamic style sheet applicable state change event after media=\"\" changed");
+ is(evt.target, gBrowser.contentDocument, "event targets correct document");
+ is(evt.stylesheet, gLinkElement.sheet, "evt.stylesheet has the right value");
+ is(evt.applicable, false, "evt.applicable has the right value");
+
+ gBrowser.addEventListener("StyleSheetRemoved", dynamicStylesheetRemoved, true);
+ gBrowser.contentDocument.body.removeChild(gLinkElement);
+}
+
+function dynamicStylesheetRemoved(evt) {
+ gBrowser.removeEventListener("StyleSheetRemoved", dynamicStylesheetRemoved, true);
+ ok(true, "received dynamic style sheet removal");
+ is(evt.type, "StyleSheetRemoved", "evt.type has expected value");
+ is(evt.target, gBrowser.contentDocument, "event targets correct document");
+ ok(evt.stylesheet, "evt.stylesheet is defined");
+ ok(evt.stylesheet.toString().contains("CSSStyleSheet"), "evt.stylesheet is a stylesheet");
+ ok(evt.stylesheet.href.contains(gStyleSheet), "evt.stylesheet is the removed stylesheet");
+
+ gBrowser.addEventListener("StyleRuleAdded", styleRuleAdded, true);
+ gBrowser.contentDocument.querySelector("style").sheet.insertRule("*{color:black}", 0);
+}
+
+function styleRuleAdded(evt) {
+ gBrowser.removeEventListener("StyleRuleAdded", styleRuleAdded, true);
+ ok(true, "received style rule added event");
+ is(evt.type, "StyleRuleAdded", "evt.type has expected value");
+ is(evt.target, gBrowser.contentDocument, "event targets correct document");
+ ok(evt.stylesheet, "evt.stylesheet is defined");
+ ok(evt.stylesheet.toString().contains("CSSStyleSheet"), "evt.stylesheet is a stylesheet");
+ ok(evt.rule, "evt.rule is defined");
+ is(evt.rule.cssText, "* { color: black; }", "evt.rule.cssText has expected value");
+
+ gBrowser.addEventListener("StyleRuleChanged", styleRuleChanged, true);
+ evt.rule.style.cssText = "color:green";
+}
+
+function styleRuleChanged(evt) {
+ gBrowser.removeEventListener("StyleRuleChanged", styleRuleChanged, true);
+ ok(true, "received style rule changed event");
+ is(evt.type, "StyleRuleChanged", "evt.type has expected value");
+ is(evt.target, gBrowser.contentDocument, "event targets correct document");
+ ok(evt.stylesheet, "evt.stylesheet is defined");
+ ok(evt.stylesheet.toString().contains("CSSStyleSheet"), "evt.stylesheet is a stylesheet");
+ ok(evt.rule, "evt.rule is defined");
+ is(evt.rule.cssText, "* { color: green; }", "evt.rule.cssText has expected value");
+
+ gBrowser.addEventListener("StyleRuleRemoved", styleRuleRemoved, true);
+ evt.stylesheet.deleteRule(0);
+}
+
+function styleRuleRemoved(evt) {
+ gBrowser.removeEventListener("StyleRuleRemoved", styleRuleRemoved, true);
+ ok(true, "received style rule removed event");
+ is(evt.type, "StyleRuleRemoved", "evt.type has expected value");
+ is(evt.target, gBrowser.contentDocument, "event targets correct document");
+ ok(evt.stylesheet, "evt.stylesheet is defined");
+ ok(evt.stylesheet.toString().contains("CSSStyleSheet"), "evt.stylesheet is a stylesheet");
+ ok(evt.rule, "evt.rule is defined");
+
+ executeSoon(concludeTest);
+}
+
+function concludeTest() {
+ let doc = gBrowser.contentDocument;
+ doc.removeEventListener("StyleSheetAdded", unexpectedContentEvent, false);
+ doc.removeEventListener("StyleSheetRemoved", unexpectedContentEvent, false);
+ doc.removeEventListener("StyleSheetApplicableStateChanged", unexpectedContentEvent, false);
+ doc.defaultView.removeEventListener("StyleSheetAdded", unexpectedContentEvent, false);
+ doc.defaultView.removeEventListener("StyleSheetRemoved", unexpectedContentEvent, false);
+ doc.defaultView.removeEventListener("StyleSheetApplicableStateChanged", unexpectedContentEvent, false);
+ gBrowser.removeCurrentTab();
+ gLinkElement = null;
+ gTab = null;
+ finish();
+}
diff --git a/browser/base/content/test/browser_bug880101.js b/browser/base/content/test/browser_bug880101.js
new file mode 100644
index 000000000..abe05b864
--- /dev/null
+++ b/browser/base/content/test/browser_bug880101.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const URL = "about:robots";
+
+function test() {
+ let win;
+
+ let listener = {
+ onLocationChange: (webProgress, request, uri, flags) => {
+ ok(webProgress.isTopLevel, "Received onLocationChange from top frame");
+ is(uri.spec, URL, "Received onLocationChange for correct URL");
+ finish();
+ }
+ };
+
+ waitForExplicitFinish();
+
+ // Remove the listener and window when we're done.
+ registerCleanupFunction(() => {
+ win.gBrowser.removeProgressListener(listener);
+ win.close();
+ });
+
+ // Wait for the newly opened window.
+ whenNewWindowOpened(w => win = w);
+
+ // Open a link in a new window.
+ openLinkIn(URL, "window", {});
+
+ // On the next tick, but before the window has finished loading, access the
+ // window's gBrowser property to force the tabbrowser constructor early.
+ (function tryAddProgressListener() {
+ executeSoon(() => {
+ try {
+ win.gBrowser.addProgressListener(listener);
+ } catch (e) {
+ // win.gBrowser wasn't ready, yet. Try again in a tick.
+ tryAddProgressListener();
+ }
+ });
+ })();
+}
+
+function whenNewWindowOpened(cb) {
+ Services.obs.addObserver(function obs(win) {
+ Services.obs.removeObserver(obs, "domwindowopened");
+ cb(win);
+ }, "domwindowopened", false);
+}
diff --git a/browser/base/content/test/browser_bug902156.js b/browser/base/content/test/browser_bug902156.js
new file mode 100644
index 000000000..d08f30b09
--- /dev/null
+++ b/browser/base/content/test/browser_bug902156.js
@@ -0,0 +1,213 @@
+/*
+ * Description of the Tests for
+ * - Bug 902156: Persist "disable protection" option for Mixed Content Blocker
+ *
+ * 1. Navigate to the same domain via document.location
+ * - Load a html page which has mixed content
+ * - Doorhanger to disable protection appears - we disable it
+ * - Load a new page from the same origin using document.location
+ * - Doorhanger should not appear anymore!
+ *
+ * 2. Navigate to the same domain via simulateclick for a link on the page
+ * - Load a html page which has mixed content
+ * - Doorhanger to disable protection appears - we disable it
+ * - Load a new page from the same origin simulating a click
+ * - Doorhanger should not appear anymore!
+ *
+ * 3. Navigate to a differnet domain and show the content is still blocked
+ * - Load a different html page which has mixed content
+ * - Doorhanger to disable protection should appear again because
+ * we navigated away from html page where we disabled the protection.
+ *
+ * Note, for all tests we set gHttpTestRoot to use 'https'.
+ */
+
+const PREF_ACTIVE = "security.mixed_content.block_active_content";
+
+// We alternate for even and odd test cases to simulate different hosts
+const gHttpTestRoot1 = "https://test1.example.com/browser/browser/base/content/test/";
+const gHttpTestRoot2 = "https://test2.example.com/browser/browser/base/content/test/";
+
+var origBlockActive;
+var gTestBrowser = null;
+
+registerCleanupFunction(function() {
+ // Set preferences back to their original values
+ Services.prefs.setBoolPref(PREF_ACTIVE, origBlockActive);
+});
+
+function cleanUpAfterTests() {
+ gBrowser.removeCurrentTab();
+ window.focus();
+ finish();
+}
+/*
+ * Whenever we disable the Mixed Content Blocker of the page
+ * we have to make sure that our condition is properly loaded.
+ */
+function waitForCondition(condition, nextTest, errorMsg) {
+ var tries = 0;
+ var interval = setInterval(function() {
+ if (tries >= 30) {
+ ok(false, errorMsg);
+ moveOn();
+ }
+ if (condition()) {
+ moveOn();
+ }
+ tries++;
+ }, 100);
+ var moveOn = function() {
+ clearInterval(interval); nextTest();
+ };
+}
+
+//------------------------ Test 1 ------------------------------
+
+function test1A() {
+ // Removing EventListener because we have to register a new
+ // one once the page is loaded with mixed content blocker disabled
+ gTestBrowser.removeEventListener("load", test1A, true);
+ gTestBrowser.addEventListener("load", test1B, true);
+
+ var notification = PopupNotifications.getNotification("mixed-content-blocked", gTestBrowser);
+ ok(notification, "OK: Mixed Content Doorhanger appeared in Test1A!");
+
+ // Disable Mixed Content Protection for the page
+ notification.secondaryActions[0].callback();
+}
+
+function test1B() {
+ var expected = "Mixed Content Blocker disabled";
+ waitForCondition(
+ function() content.document.getElementById('mctestdiv').innerHTML == expected,
+ test1C, "Error: Waited too long for mixed script to run in Test 1B");
+}
+
+function test1C() {
+ gTestBrowser.removeEventListener("load", test1B, true);
+ var actual = content.document.getElementById('mctestdiv').innerHTML;
+ is(actual, "Mixed Content Blocker disabled", "OK: Executed mixed script in Test 1C");
+
+ // The Script loaded after we disabled the page, now we are going to reload the
+ // page and see if our decision is persistent
+ gTestBrowser.addEventListener("load", test1D, true);
+
+ var url = gHttpTestRoot1 + "file_bug902156_2.html";
+ gTestBrowser.contentWindow.location = url;
+}
+
+function test1D() {
+ gTestBrowser.removeEventListener("load", test1D, true);
+
+ // The Doorhanger should not appear, because our decision of disabling the
+ // mixed content blocker is persistent.
+ var notification = PopupNotifications.getNotification("mixed-content-blocked", gTestBrowser);
+ ok(!notification, "OK: Mixed Content Doorhanger did not appear again in Test1D!");
+
+ var actual = content.document.getElementById('mctestdiv').innerHTML;
+ is(actual, "Mixed Content Blocker disabled", "OK: Executed mixed script in Test 1D");
+
+ // move on to Test 2
+ test2();
+}
+
+//------------------------ Test 2 ------------------------------
+
+function test2() {
+ gTestBrowser.addEventListener("load", test2A, true);
+ var url = gHttpTestRoot2 + "file_bug902156_2.html";
+ gTestBrowser.contentWindow.location = url;
+}
+
+function test2A() {
+ // Removing EventListener because we have to register a new
+ // one once the page is loaded with mixed content blocker disabled
+ gTestBrowser.removeEventListener("load", test2A, true);
+ gTestBrowser.addEventListener("load", test2B, true);
+
+ var notification = PopupNotifications.getNotification("mixed-content-blocked", gTestBrowser);
+ ok(notification, "OK: Mixed Content Doorhanger appeared in Test 2A!");
+
+ // Disable Mixed Content Protection for the page
+ notification.secondaryActions[0].callback();
+}
+
+function test2B() {
+ var expected = "Mixed Content Blocker disabled";
+ waitForCondition(
+ function() content.document.getElementById('mctestdiv').innerHTML == expected,
+ test2C, "Error: Waited too long for mixed script to run in Test 2B");
+}
+
+function test2C() {
+ gTestBrowser.removeEventListener("load", test2B, true);
+ var actual = content.document.getElementById('mctestdiv').innerHTML;
+ is(actual, "Mixed Content Blocker disabled", "OK: Executed mixed script in Test 2C");
+
+ // The Script loaded after we disabled the page, now we are going to reload the
+ // page and see if our decision is persistent
+ gTestBrowser.addEventListener("load", test2D, true);
+
+ // reload the page using the provided link in the html file
+ var mctestlink = content.document.getElementById("mctestlink");
+ mctestlink.click();
+}
+
+function test2D() {
+ gTestBrowser.removeEventListener("load", test2D, true);
+
+ // The Doorhanger should not appear, because our decision of disabling the
+ // mixed content blocker is persistent.
+ var notification = PopupNotifications.getNotification("mixed-content-blocked", gTestBrowser);
+ ok(!notification, "OK: Mixed Content Doorhanger did not appear again in Test2D!");
+
+ var actual = content.document.getElementById('mctestdiv').innerHTML;
+ is(actual, "Mixed Content Blocker disabled", "OK: Executed mixed script in Test 2D");
+
+ // move on to Test 3
+ test3();
+}
+
+//------------------------ Test 3 ------------------------------
+
+function test3() {
+ gTestBrowser.addEventListener("load", test3A, true);
+ var url = gHttpTestRoot1 + "file_bug902156_3.html";
+ gTestBrowser.contentWindow.location = url;
+}
+
+function test3A() {
+ // Removing EventListener because we have to register a new
+ // one once the page is loaded with mixed content blocker disabled
+ gTestBrowser.removeEventListener("load", test3A, true);
+
+ var notification = PopupNotifications.getNotification("mixed-content-blocked", gTestBrowser);
+ ok(notification, "OK: Mixed Content Doorhanger appeared in Test 3A!");
+
+ // We are done with tests, clean up
+ cleanUpAfterTests();
+}
+
+//------------------------------------------------------
+
+function test() {
+ // Performing async calls, e.g. 'onload', we have to wait till all of them finished
+ waitForExplicitFinish();
+
+ // Store original preferences so we can restore settings after testing
+ origBlockActive = Services.prefs.getBoolPref(PREF_ACTIVE);
+
+ Services.prefs.setBoolPref(PREF_ACTIVE, true);
+
+ // Not really sure what this is doing
+ var newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+ newTab.linkedBrowser.stop()
+
+ // Starting Test Number 1:
+ gTestBrowser.addEventListener("load", test1A, true);
+ var url = gHttpTestRoot1 + "file_bug902156_1.html";
+ gTestBrowser.contentWindow.location = url;
+}
diff --git a/browser/base/content/test/browser_canonizeURL.js b/browser/base/content/test/browser_canonizeURL.js
new file mode 100644
index 000000000..983d0941c
--- /dev/null
+++ b/browser/base/content/test/browser_canonizeURL.js
@@ -0,0 +1,54 @@
+function test() {
+ waitForExplicitFinish();
+ testNext();
+}
+
+var pairs = [
+ ["example", "http://www.example.net/"],
+ ["ex-ample", "http://www.ex-ample.net/"],
+ [" example ", "http://www.example.net/"],
+ [" example/foo ", "http://www.example.net/foo"],
+ [" example/foo bar ", "http://www.example.net/foo%20bar"],
+ ["example.net", "http://example.net/"],
+ ["http://example", "http://example/"],
+ ["example:8080", "http://example:8080/"],
+ ["ex-ample.foo", "http://ex-ample.foo/"],
+ ["example.foo/bar ", "http://example.foo/bar"],
+ ["1.1.1.1", "http://1.1.1.1/"],
+ ["ftp://example", "ftp://example/"],
+ ["ftp.example.bar", "ftp://ftp.example.bar/"],
+ ["ex ample", Services.search.defaultEngine.getSubmission("ex ample", null, "keyword").uri.spec],
+];
+
+function testNext() {
+ if (!pairs.length) {
+ finish();
+ return;
+ }
+
+ let [inputValue, expectedURL] = pairs.shift();
+
+ gBrowser.addProgressListener({
+ onStateChange: function onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_START &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
+ is(aRequest.originalURI.spec, expectedURL,
+ "entering '" + inputValue + "' loads expected URL");
+
+ gBrowser.removeProgressListener(this);
+ gBrowser.stop();
+
+ executeSoon(testNext);
+ }
+ }
+ });
+
+ gURLBar.addEventListener("focus", function onFocus() {
+ gURLBar.removeEventListener("focus", onFocus);
+ EventUtils.synthesizeKey("VK_RETURN", { shiftKey: true });
+ });
+
+ gBrowser.selectedBrowser.focus();
+ gURLBar.inputField.value = inputValue;
+ gURLBar.focus();
+}
diff --git a/browser/base/content/test/browser_clearplugindata.html b/browser/base/content/test/browser_clearplugindata.html
new file mode 100644
index 000000000..d5a6872c8
--- /dev/null
+++ b/browser/base/content/test/browser_clearplugindata.html
@@ -0,0 +1,32 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+ <head>
+ <title>Plugin Clear Site Data sanitize test</title>
+
+ <embed id="plugin1" type="application/x-test" width="200" height="200"></embed>
+
+ <script type="application/javascript">
+ function testSteps()
+ {
+ // Make sure clearing by timerange is supported.
+ var p = document.getElementById("plugin1");
+ p.setSitesWithDataCapabilities(true);
+
+ p.setSitesWithData(
+ "foo.com:0:5," +
+ "bar.com:0:100," +
+ "baz.com:1:5," +
+ "qux.com:1:100"
+ );
+
+ setTimeout(testFinishedCallback, 0);
+ }
+ </script>
+ </head>
+
+ <body onload="testSteps();"></body>
+
+</html>
diff --git a/browser/base/content/test/browser_clearplugindata.js b/browser/base/content/test/browser_clearplugindata.js
new file mode 100644
index 000000000..4b79bbbb1
--- /dev/null
+++ b/browser/base/content/test/browser_clearplugindata.js
@@ -0,0 +1,140 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test clearing plugin data using sanitize.js.
+const testURL1 = "http://mochi.test:8888/browser/browser/base/content/test/browser_clearplugindata.html";
+const testURL2 = "http://mochi.test:8888/browser/browser/base/content/test/browser_clearplugindata_noage.html";
+
+let tempScope = {};
+Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://browser/content/sanitize.js", tempScope);
+let Sanitizer = tempScope.Sanitizer;
+
+const pluginHostIface = Ci.nsIPluginHost;
+var pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+pluginHost.QueryInterface(pluginHostIface);
+
+var pluginTag;
+var s;
+
+function stored(needles) {
+ var something = pluginHost.siteHasData(this.pluginTag, null);
+ if (!needles)
+ return something;
+
+ if (!something)
+ return false;
+
+ for (var i = 0; i < needles.length; ++i) {
+ if (!pluginHost.siteHasData(this.pluginTag, needles[i]))
+ return false;
+ }
+ return true;
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ var tags = pluginHost.getPluginTags();
+
+ // Find the test plugin
+ for (var i = 0; i < tags.length; i++)
+ {
+ if (tags[i].name == "Test Plug-in")
+ {
+ pluginTag = tags[i];
+ }
+ }
+
+ s = new Sanitizer();
+ s.ignoreTimespan = false;
+ s.prefDomain = "privacy.cpd.";
+ var itemPrefs = gPrefService.getBranch(s.prefDomain);
+ itemPrefs.setBoolPref("history", false);
+ itemPrefs.setBoolPref("downloads", false);
+ itemPrefs.setBoolPref("cache", false);
+ itemPrefs.setBoolPref("cookies", true); // plugin data
+ itemPrefs.setBoolPref("formdata", false);
+ itemPrefs.setBoolPref("offlineApps", false);
+ itemPrefs.setBoolPref("passwords", false);
+ itemPrefs.setBoolPref("sessions", false);
+ itemPrefs.setBoolPref("siteSettings", false);
+
+ executeSoon(test_with_age);
+}
+
+function setFinishedCallback(callback)
+{
+ let testPage = gBrowser.selectedBrowser.contentWindow.wrappedJSObject;
+ testPage.testFinishedCallback = function() {
+ setTimeout(function() {
+ info("got finished callback");
+ callback();
+ }, 0);
+ }
+}
+
+function test_with_age()
+{
+ // Load page to set data for the plugin.
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", function () {
+ gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+ setFinishedCallback(function() {
+ ok(stored(["foo.com","bar.com","baz.com","qux.com"]),
+ "Data stored for sites");
+
+ // Clear 20 seconds ago
+ var now_uSec = Date.now() * 1000;
+ s.range = [now_uSec - 20*1000000, now_uSec];
+ s.sanitize();
+
+ ok(stored(["bar.com","qux.com"]), "Data stored for sites");
+ ok(!stored(["foo.com"]), "Data cleared for foo.com");
+ ok(!stored(["baz.com"]), "Data cleared for baz.com");
+
+ // Clear everything
+ s.range = null;
+ s.sanitize();
+
+ ok(!stored(null), "All data cleared");
+
+ gBrowser.removeCurrentTab();
+
+ executeSoon(test_without_age);
+ });
+ }, true);
+ content.location = testURL1;
+}
+
+function test_without_age()
+{
+ // Load page to set data for the plugin.
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", function () {
+ gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+ setFinishedCallback(function() {
+ ok(stored(["foo.com","bar.com","baz.com","qux.com"]),
+ "Data stored for sites");
+
+ // Attempt to clear 20 seconds ago. The plugin will throw
+ // NS_ERROR_PLUGIN_TIME_RANGE_NOT_SUPPORTED, which should result in us
+ // clearing all data regardless of age.
+ var now_uSec = Date.now() * 1000;
+ s.range = [now_uSec - 20*1000000, now_uSec];
+ s.sanitize();
+
+ ok(!stored(null), "All data cleared");
+
+ gBrowser.removeCurrentTab();
+
+ executeSoon(finish);
+ });
+ }, true);
+ content.location = testURL2;
+}
+
diff --git a/browser/base/content/test/browser_clearplugindata_noage.html b/browser/base/content/test/browser_clearplugindata_noage.html
new file mode 100644
index 000000000..75e1f2e1f
--- /dev/null
+++ b/browser/base/content/test/browser_clearplugindata_noage.html
@@ -0,0 +1,32 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+ <head>
+ <title>Plugin Clear Site Data sanitize test without age</title>
+
+ <embed id="plugin1" type="application/x-test" width="200" height="200"></embed>
+
+ <script type="application/javascript">
+ function testSteps()
+ {
+ // Make sure clearing by timerange is disabled.
+ var p = document.getElementById("plugin1");
+ p.setSitesWithDataCapabilities(false);
+
+ p.setSitesWithData(
+ "foo.com:0:5," +
+ "bar.com:0:100," +
+ "baz.com:1:5," +
+ "qux.com:1:100"
+ );
+
+ setTimeout(testFinishedCallback, 0);
+ }
+ </script>
+ </head>
+
+ <body onload="testSteps();"></body>
+
+</html>
diff --git a/browser/base/content/test/browser_contentAreaClick.js b/browser/base/content/test/browser_contentAreaClick.js
new file mode 100644
index 000000000..ba89240ea
--- /dev/null
+++ b/browser/base/content/test/browser_contentAreaClick.js
@@ -0,0 +1,307 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Test for bug 549340.
+ * Test for browser.js::contentAreaClick() util.
+ *
+ * The test opens a new browser window, then replaces browser.js methods invoked
+ * by contentAreaClick with a mock function that tracks which methods have been
+ * called.
+ * Each sub-test synthesizes a mouse click event on links injected in content,
+ * the event is collected by a click handler that ensures that contentAreaClick
+ * correctly prevent default events, and follows the correct code path.
+ */
+
+let gTests = [
+
+ {
+ desc: "Simple left click",
+ setup: function() {},
+ clean: function() {},
+ event: {},
+ targets: [ "commonlink", "mathxlink", "svgxlink", "maplink" ],
+ expectedInvokedMethods: [],
+ preventDefault: false,
+ },
+
+ {
+ desc: "Ctrl/Cmd left click",
+ setup: function() {},
+ clean: function() {},
+ event: { ctrlKey: true,
+ metaKey: true },
+ targets: [ "commonlink", "mathxlink", "svgxlink", "maplink" ],
+ expectedInvokedMethods: [ "urlSecurityCheck", "openLinkIn" ],
+ preventDefault: true,
+ },
+
+ // The next test was once handling feedService.forcePreview(). Now it should
+ // just be like Alt click.
+ {
+ desc: "Shift+Alt left click",
+ setup: function() {
+ gPrefService.setBoolPref("browser.altClickSave", true);
+ },
+ clean: function() {
+ gPrefService.clearUserPref("browser.altClickSave");
+ },
+ event: { shiftKey: true,
+ altKey: true },
+ targets: [ "commonlink", "maplink" ],
+ expectedInvokedMethods: [ "gatherTextUnder", "saveURL" ],
+ preventDefault: true,
+ },
+
+ {
+ desc: "Shift+Alt left click on XLinks",
+ setup: function() {
+ gPrefService.setBoolPref("browser.altClickSave", true);
+ },
+ clean: function() {
+ gPrefService.clearUserPref("browser.altClickSave");
+ },
+ event: { shiftKey: true,
+ altKey: true },
+ targets: [ "mathxlink", "svgxlink"],
+ expectedInvokedMethods: [ "saveURL" ],
+ preventDefault: true,
+ },
+
+ {
+ desc: "Shift click",
+ setup: function() {},
+ clean: function() {},
+ event: { shiftKey: true },
+ targets: [ "commonlink", "mathxlink", "svgxlink", "maplink" ],
+ expectedInvokedMethods: [ "urlSecurityCheck", "openLinkIn" ],
+ preventDefault: true,
+ },
+
+ {
+ desc: "Alt click",
+ setup: function() {
+ gPrefService.setBoolPref("browser.altClickSave", true);
+ },
+ clean: function() {
+ gPrefService.clearUserPref("browser.altClickSave");
+ },
+ event: { altKey: true },
+ targets: [ "commonlink", "maplink" ],
+ expectedInvokedMethods: [ "gatherTextUnder", "saveURL" ],
+ preventDefault: true,
+ },
+
+ {
+ desc: "Alt click on XLinks",
+ setup: function() {
+ gPrefService.setBoolPref("browser.altClickSave", true);
+ },
+ clean: function() {
+ gPrefService.clearUserPref("browser.altClickSave");
+ },
+ event: { altKey: true },
+ targets: [ "mathxlink", "svgxlink" ],
+ expectedInvokedMethods: [ "saveURL" ],
+ preventDefault: true,
+ },
+
+ {
+ desc: "Panel click",
+ setup: function() {},
+ clean: function() {},
+ event: {},
+ targets: [ "panellink" ],
+ expectedInvokedMethods: [ "urlSecurityCheck", "loadURI" ],
+ preventDefault: true,
+ },
+
+ {
+ desc: "Simple middle click opentab",
+ setup: function() {},
+ clean: function() {},
+ event: { button: 1 },
+ targets: [ "commonlink", "mathxlink", "svgxlink", "maplink" ],
+ expectedInvokedMethods: [ "urlSecurityCheck", "openLinkIn" ],
+ preventDefault: true,
+ },
+
+ {
+ desc: "Simple middle click openwin",
+ setup: function() {
+ gPrefService.setBoolPref("browser.tabs.opentabfor.middleclick", false);
+ },
+ clean: function() {
+ gPrefService.clearUserPref("browser.tabs.opentabfor.middleclick");
+ },
+ event: { button: 1 },
+ targets: [ "commonlink", "mathxlink", "svgxlink", "maplink" ],
+ expectedInvokedMethods: [ "urlSecurityCheck", "openLinkIn" ],
+ preventDefault: true,
+ },
+
+ {
+ desc: "Middle mouse paste",
+ setup: function() {
+ gPrefService.setBoolPref("middlemouse.contentLoadURL", true);
+ gPrefService.setBoolPref("general.autoScroll", false);
+ },
+ clean: function() {
+ gPrefService.clearUserPref("middlemouse.contentLoadURL");
+ gPrefService.clearUserPref("general.autoScroll");
+ },
+ event: { button: 1 },
+ targets: [ "emptylink" ],
+ expectedInvokedMethods: [ "middleMousePaste" ],
+ preventDefault: true,
+ },
+
+];
+
+// Array of method names that will be replaced in the new window.
+let gReplacedMethods = [
+ "middleMousePaste",
+ "urlSecurityCheck",
+ "loadURI",
+ "gatherTextUnder",
+ "saveURL",
+ "openLinkIn",
+ "getShortcutOrURI",
+];
+
+// Reference to the new window.
+let gTestWin = null;
+
+// List of methods invoked by a specific call to contentAreaClick.
+let gInvokedMethods = [];
+
+// The test currently running.
+let gCurrentTest = null;
+
+function test() {
+ waitForExplicitFinish();
+
+ gTestWin = openDialog(location, "", "chrome,all,dialog=no", "about:blank");
+ whenDelayedStartupFinished(gTestWin, function () {
+ info("Browser window opened");
+ waitForFocus(function() {
+ info("Browser window focused");
+ waitForFocus(function() {
+ info("Setting up browser...");
+ setupTestBrowserWindow();
+ info("Running tests...");
+ executeSoon(runNextTest);
+ }, gTestWin.content, true);
+ }, gTestWin);
+ });
+}
+
+// Click handler used to steal click events.
+let gClickHandler = {
+ handleEvent: function (event) {
+ let linkId = event.target.id || event.target.localName;
+ is(event.type, "click",
+ gCurrentTest.desc + ":Handler received a click event on " + linkId);
+
+ let isPanelClick = linkId == "panellink";
+ gTestWin.contentAreaClick(event, isPanelClick);
+ let prevent = event.defaultPrevented;
+ is(prevent, gCurrentTest.preventDefault,
+ gCurrentTest.desc + ": event.defaultPrevented is correct (" + prevent + ")")
+
+ // Check that all required methods have been called.
+ gCurrentTest.expectedInvokedMethods.forEach(function(aExpectedMethodName) {
+ isnot(gInvokedMethods.indexOf(aExpectedMethodName), -1,
+ gCurrentTest.desc + ":" + aExpectedMethodName + " was invoked");
+ });
+
+ if (gInvokedMethods.length != gCurrentTest.expectedInvokedMethods.length) {
+ ok(false, "Wrong number of invoked methods");
+ gInvokedMethods.forEach(function (method) info(method + " was invoked"));
+ }
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ executeSoon(runNextTest);
+ }
+}
+
+// Wraps around the methods' replacement mock function.
+function wrapperMethod(aInvokedMethods, aMethodName) {
+ return function () {
+ aInvokedMethods.push(aMethodName);
+ // At least getShortcutOrURI requires to return url that is the first param.
+ return arguments[0];
+ }
+}
+
+function setupTestBrowserWindow() {
+ // Steal click events and don't propagate them.
+ gTestWin.addEventListener("click", gClickHandler, true);
+
+ // Replace methods.
+ gReplacedMethods.forEach(function (aMethodName) {
+ gTestWin["old_" + aMethodName] = gTestWin[aMethodName];
+ gTestWin[aMethodName] = wrapperMethod(gInvokedMethods, aMethodName);
+ });
+
+ // Inject links in content.
+ let doc = gTestWin.content.document;
+ let mainDiv = doc.createElement("div");
+ mainDiv.innerHTML =
+ '<p><a id="commonlink" href="http://mochi.test/moz/">Common link</a></p>' +
+ '<p><a id="panellink" href="http://mochi.test/moz/">Panel link</a></p>' +
+ '<p><a id="emptylink">Empty link</a></p>' +
+ '<p><math id="mathxlink" xmlns="http://www.w3.org/1998/Math/MathML" xlink:type="simple" xlink:href="http://mochi.test/moz/"><mtext>MathML XLink</mtext></math></p>' +
+ '<p><svg id="svgxlink" xmlns="http://www.w3.org/2000/svg" width="100px" height="50px" version="1.1"><a xlink:type="simple" xlink:href="http://mochi.test/moz/"><text transform="translate(10, 25)">SVG XLink</text></a></svg></p>' +
+ '<p><map name="map" id="map"><area href="http://mochi.test/moz/" shape="rect" coords="0,0,128,128" /></map><img id="maplink" usemap="#map" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAIAAABMXPacAAAABGdBTUEAALGPC%2FxhBQAAAOtJREFUeF7t0IEAAAAAgKD9qRcphAoDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGDAgAEDBgwYMGBgwIAAAT0N51AAAAAASUVORK5CYII%3D"/></p>'
+ doc.body.appendChild(mainDiv);
+}
+
+function runNextTest() {
+ if (!gCurrentTest) {
+ gCurrentTest = gTests.shift();
+ gCurrentTest.setup();
+ }
+
+ if (gCurrentTest.targets.length == 0) {
+ info(gCurrentTest.desc + ": cleaning up...")
+ gCurrentTest.clean();
+
+ if (gTests.length > 0) {
+ gCurrentTest = gTests.shift();
+ gCurrentTest.setup();
+ }
+ else {
+ finishTest();
+ return;
+ }
+ }
+
+ // Move to next target.
+ gInvokedMethods.length = 0;
+ let target = gCurrentTest.targets.shift();
+
+ info(gCurrentTest.desc + ": testing " + target);
+
+ // Fire click event.
+ let targetElt = gTestWin.content.document.getElementById(target);
+ ok(targetElt, gCurrentTest.desc + ": target is valid (" + targetElt.id + ")");
+ EventUtils.synthesizeMouseAtCenter(targetElt, gCurrentTest.event, gTestWin.content);
+}
+
+function finishTest() {
+ info("Restoring browser...");
+ gTestWin.removeEventListener("click", gClickHandler, true);
+
+ // Restore original methods.
+ gReplacedMethods.forEach(function (aMethodName) {
+ gTestWin[aMethodName] = gTestWin["old_" + aMethodName];
+ delete gTestWin["old_" + aMethodName];
+ });
+
+ gTestWin.close();
+ finish();
+}
diff --git a/browser/base/content/test/browser_contextSearchTabPosition.js b/browser/base/content/test/browser_contextSearchTabPosition.js
new file mode 100644
index 000000000..38dbc2adf
--- /dev/null
+++ b/browser/base/content/test/browser_contextSearchTabPosition.js
@@ -0,0 +1,68 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ function tabAdded(event) {
+ let tab = event.target;
+ tabs.push(tab);
+ }
+
+ let tabs = [];
+
+ let container = gBrowser.tabContainer;
+ container.addEventListener("TabOpen", tabAdded, false);
+
+ gBrowser.addTab("about:blank");
+ BrowserSearch.loadSearchFromContext("mozilla");
+ BrowserSearch.loadSearchFromContext("firefox");
+
+ is(tabs[0], gBrowser.tabs[3], "blank tab has been pushed to the end");
+ is(tabs[1], gBrowser.tabs[1], "first search tab opens next to the current tab");
+ is(tabs[2], gBrowser.tabs[2], "second search tab opens next to the first search tab");
+
+ container.removeEventListener("TabOpen", tabAdded, false);
+ tabs.forEach(gBrowser.removeTab, gBrowser);
+
+ try {
+ let cm = Components.classes["@mozilla.org/categorymanager;1"]
+ .getService(Components.interfaces.nsICategoryManager);
+ cm.getCategoryEntry("healthreport-js-provider-default", "SearchesProvider");
+ } catch (ex) {
+ // Health Report disabled, or no SearchesProvider.
+ finish();
+ return;
+ }
+
+ let reporter = Components.classes["@mozilla.org/datareporting/service;1"]
+ .getService()
+ .wrappedJSObject
+ .healthReporter;
+
+ // reporter should always be available in automation.
+ ok(reporter, "Health Reporter available.");
+ reporter.onInit().then(function onInit() {
+ let provider = reporter.getProvider("org.mozilla.searches");
+ ok(provider, "Searches provider is available.");
+
+ let m = provider.getMeasurement("counts", 2);
+ m.getValues().then(function onValues(data) {
+ let now = new Date();
+ ok(data.days.hasDay(now), "Have data for today.");
+ let day = data.days.getDay(now);
+
+ // Will need to be changed if Google isn't the default search engine.
+ let field = "google.contextmenu";
+ ok(day.has(field), "Have search recorded for context menu.");
+
+ // If any other mochitests perform a context menu search, this will fail.
+ // The solution will be to look up count at test start and ensure it is
+ // incremented by two.
+ is(day.get(field), 2, "2 searches recorded in FHR.");
+ finish();
+ });
+ });
+}
+
diff --git a/browser/base/content/test/browser_ctrlTab.js b/browser/base/content/test/browser_ctrlTab.js
new file mode 100644
index 000000000..61937a5ff
--- /dev/null
+++ b/browser/base/content/test/browser_ctrlTab.js
@@ -0,0 +1,151 @@
+function test() {
+ gPrefService.setBoolPref("browser.ctrlTab.previews", true);
+
+ gBrowser.addTab();
+ gBrowser.addTab();
+ gBrowser.addTab();
+
+ checkTabs(4);
+
+ ctrlTabTest([2] , 1, 0);
+ ctrlTabTest([2, 3, 1], 2, 2);
+ ctrlTabTest([] , 5, 2);
+
+ {
+ let selectedIndex = gBrowser.tabContainer.selectedIndex;
+ pressCtrlTab();
+ pressCtrlTab(true);
+ releaseCtrl();
+ is(gBrowser.tabContainer.selectedIndex, selectedIndex,
+ "Ctrl+Tab -> Ctrl+Shift+Tab keeps the selected tab");
+ }
+
+ { // test for bug 445369
+ let tabs = gBrowser.tabs.length;
+ pressCtrlTab();
+ EventUtils.synthesizeKey("w", { ctrlKey: true });
+ is(gBrowser.tabs.length, tabs - 1, "Ctrl+Tab -> Ctrl+W removes one tab");
+ releaseCtrl();
+ }
+
+ { // test for bug 667314
+ let tabs = gBrowser.tabs.length;
+ pressCtrlTab();
+ pressCtrlTab(true);
+ EventUtils.synthesizeKey("w", { ctrlKey: true });
+ is(gBrowser.tabs.length, tabs - 1, "Ctrl+Tab -> Ctrl+W removes the selected tab");
+ releaseCtrl();
+ }
+
+ gBrowser.addTab();
+ checkTabs(3);
+ ctrlTabTest([2, 1, 0], 9, 1);
+
+ gBrowser.addTab();
+ checkTabs(4);
+
+ { // test for bug 445369
+ selectTabs([1, 2, 0]);
+
+ let selectedTab = gBrowser.selectedTab;
+ let tabToRemove = gBrowser.tabs[1];
+
+ pressCtrlTab();
+ pressCtrlTab();
+ EventUtils.synthesizeKey("w", { ctrlKey: true });
+ ok(!tabToRemove.parentNode,
+ "Ctrl+Tab*2 -> Ctrl+W removes the second most recently selected tab");
+
+ pressCtrlTab(true);
+ pressCtrlTab(true);
+ releaseCtrl();
+ ok(selectedTab.selected,
+ "Ctrl+Tab*2 -> Ctrl+W -> Ctrl+Shift+Tab*2 keeps the selected tab");
+ }
+ gBrowser.removeTab(gBrowser.tabContainer.lastChild);
+ checkTabs(2);
+
+ ctrlTabTest([1], 1, 0);
+
+ gBrowser.removeTab(gBrowser.tabContainer.lastChild);
+ checkTabs(1);
+
+ { // test for bug 445768
+ let focusedWindow = document.commandDispatcher.focusedWindow;
+ let eventConsumed = true;
+ let detectKeyEvent = function (event) {
+ eventConsumed = event.defaultPrevented;
+ };
+ document.addEventListener("keypress", detectKeyEvent, false);
+ pressCtrlTab();
+ document.removeEventListener("keypress", detectKeyEvent, false);
+ ok(eventConsumed, "Ctrl+Tab consumed by the tabbed browser if one tab is open");
+ is(focusedWindow, document.commandDispatcher.focusedWindow,
+ "Ctrl+Tab doesn't change focus if one tab is open");
+ }
+
+ // cleanup
+ if (gPrefService.prefHasUserValue("browser.ctrlTab.previews"))
+ gPrefService.clearUserPref("browser.ctrlTab.previews");
+
+ /* private utility functions */
+
+ function pressCtrlTab(aShiftKey)
+ EventUtils.synthesizeKey("VK_TAB", { ctrlKey: true, shiftKey: !!aShiftKey });
+
+ function releaseCtrl()
+ EventUtils.synthesizeKey("VK_CONTROL", { type: "keyup" });
+
+ function isOpen()
+ ctrlTab.isOpen;
+
+ function checkTabs(aTabs) {
+ var tabs = gBrowser.tabs.length;
+ if (tabs != aTabs) {
+ while (gBrowser.tabs.length > 1)
+ gBrowser.removeCurrentTab();
+ throw "expected " + aTabs + " open tabs, got " + tabs;
+ }
+ }
+
+ function selectTabs(tabs) {
+ tabs.forEach(function (index) {
+ gBrowser.selectedTab = gBrowser.tabs[index];
+ });
+ }
+
+ function ctrlTabTest(tabsToSelect, tabTimes, expectedIndex) {
+ selectTabs(tabsToSelect);
+
+ var indexStart = gBrowser.tabContainer.selectedIndex;
+ var tabCount = gBrowser.tabs.length;
+ var normalized = tabTimes % tabCount;
+ var where = normalized == 1 ? "back to the previously selected tab" :
+ normalized + " tabs back in most-recently-selected order";
+
+ for (let i = 0; i < tabTimes; i++) {
+ pressCtrlTab();
+
+ if (tabCount > 2)
+ is(gBrowser.tabContainer.selectedIndex, indexStart,
+ "Selected tab doesn't change while tabbing");
+ }
+
+ if (tabCount > 2) {
+ ok(isOpen(),
+ "With " + tabCount + " tabs open, Ctrl+Tab opens the preview panel");
+
+ releaseCtrl();
+
+ ok(!isOpen(),
+ "Releasing Ctrl closes the preview panel");
+ } else {
+ ok(!isOpen(),
+ "With " + tabCount + " tabs open, Ctrl+Tab doesn't open the preview panel");
+ }
+
+ is(gBrowser.tabContainer.selectedIndex, expectedIndex,
+ "With "+ tabCount +" tabs open and tab " + indexStart
+ + " selected, Ctrl+Tab*" + tabTimes + " goes " + where);
+ }
+}
diff --git a/browser/base/content/test/browser_customize.js b/browser/base/content/test/browser_customize.js
new file mode 100644
index 000000000..8cff36686
--- /dev/null
+++ b/browser/base/content/test/browser_customize.js
@@ -0,0 +1,24 @@
+function test() {
+ waitForExplicitFinish();
+
+ openToolbarCustomizationUI(customizationWindowLoaded);
+}
+
+function customizationWindowLoaded(win) {
+ let x = win.screenX;
+ let iconModeList = win.document.getElementById("modelist");
+
+ iconModeList.addEventListener("popupshown", function popupshown() {
+ iconModeList.removeEventListener("popupshown", popupshown, false);
+
+ executeSoon(function () {
+ is(win.screenX, x,
+ "toolbar customization window shouldn't move when the iconmode menulist is opened");
+ iconModeList.open = false;
+
+ closeToolbarCustomizationUI(finish);
+ });
+ }, false);
+
+ iconModeList.open = true;
+}
diff --git a/browser/base/content/test/browser_customize_popupNotification.js b/browser/base/content/test/browser_customize_popupNotification.js
new file mode 100644
index 000000000..138e0dc47
--- /dev/null
+++ b/browser/base/content/test/browser_customize_popupNotification.js
@@ -0,0 +1,27 @@
+/*
+Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/
+*/
+
+function test() {
+ waitForExplicitFinish();
+ let newWin = OpenBrowserWindow();
+ whenDelayedStartupFinished(newWin, function () {
+ // Remove the URL bar
+ newWin.gURLBar.parentNode.removeChild(newWin.gURLBar);
+
+ waitForFocus(function () {
+ let PN = newWin.PopupNotifications;
+ try {
+ let notification = PN.show(newWin.gBrowser.selectedBrowser, "some-notification", "Some message");
+ ok(notification, "showed the notification");
+ ok(PN.isPanelOpen, "panel is open");
+ is(PN.panel.anchorNode, newWin.gBrowser.selectedTab, "notification is correctly anchored to the tab");
+ } catch (ex) {
+ ok(false, "threw exception: " + ex);
+ }
+ newWin.close();
+ finish();
+ }, newWin);
+ });
+}
diff --git a/browser/base/content/test/browser_datareporting_notification.js b/browser/base/content/test/browser_datareporting_notification.js
new file mode 100644
index 000000000..b892bd009
--- /dev/null
+++ b/browser/base/content/test/browser_datareporting_notification.js
@@ -0,0 +1,198 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function sendNotifyRequest(name) {
+ let ns = {};
+ Components.utils.import("resource://gre/modules/services/datareporting/policy.jsm", ns);
+ Components.utils.import("resource://gre/modules/Preferences.jsm", ns);
+
+ let service = Components.classes["@mozilla.org/datareporting/service;1"]
+ .getService(Components.interfaces.nsISupports)
+ .wrappedJSObject;
+ ok(service.healthReporter, "Health Reporter instance is available.");
+
+ let policyPrefs = new ns.Preferences("testing." + name + ".");
+ ok(service._prefs, "Health Reporter prefs are available.");
+ let hrPrefs = service._prefs;
+
+ let policy = new ns.DataReportingPolicy(policyPrefs, hrPrefs, service);
+ policy.firstRunDate = new Date(Date.now() - 24 * 60 * 60 * 1000);
+
+ is(policy.notifyState, policy.STATE_NOTIFY_UNNOTIFIED, "Policy is in unnotified state.");
+
+ service.healthReporter.onInit().then(function onInit() {
+ is(policy.ensureNotifyResponse(new Date()), false, "User has not responded to policy.");
+ });
+
+ return policy;
+}
+
+/**
+ * Wait for a <notification> to be closed then call the specified callback.
+ */
+function waitForNotificationClose(notification, cb) {
+ let parent = notification.parentNode;
+
+ let observer = new MutationObserver(function onMutatations(mutations) {
+ for (let mutation of mutations) {
+ for (let i = 0; i < mutation.removedNodes.length; i++) {
+ let node = mutation.removedNodes.item(i);
+
+ if (node != notification) {
+ continue;
+ }
+
+ observer.disconnect();
+ cb();
+ }
+ }
+ });
+
+ observer.observe(parent, {childList: true});
+}
+
+let dumpAppender, rootLogger;
+
+function test() {
+ waitForExplicitFinish();
+
+ let ns = {};
+ Components.utils.import("resource://services-common/log4moz.js", ns);
+ rootLogger = ns.Log4Moz.repository.rootLogger;
+ dumpAppender = new ns.Log4Moz.DumpAppender();
+ dumpAppender.level = ns.Log4Moz.Level.All;
+ rootLogger.addAppender(dumpAppender);
+
+ let notification = document.getElementById("global-notificationbox");
+ let policy;
+
+ notification.addEventListener("AlertActive", function active() {
+ notification.removeEventListener("AlertActive", active, true);
+
+ executeSoon(function afterNotification() {
+ is(policy.notifyState, policy.STATE_NOTIFY_WAIT, "Policy is waiting for user response.");
+ ok(!policy.dataSubmissionPolicyAccepted, "Data submission policy not yet accepted.");
+
+ waitForNotificationClose(notification.currentNotification, function onClose() {
+ is(policy.notifyState, policy.STATE_NOTIFY_COMPLETE, "Closing info bar completes user notification.");
+ ok(policy.dataSubmissionPolicyAccepted, "Data submission policy accepted.");
+ is(policy.dataSubmissionPolicyResponseType, "accepted-info-bar-dismissed",
+ "Reason for acceptance was info bar dismissal.");
+ is(notification.allNotifications.length, 0, "No notifications remain.");
+ test_multiple_windows();
+ });
+ notification.currentNotification.close();
+ });
+ }, true);
+
+ policy = sendNotifyRequest("single_window_notified");
+}
+
+function test_multiple_windows() {
+ // Ensure we see the notification on all windows and that action on one window
+ // results in dismiss on every window.
+ let window2 = OpenBrowserWindow();
+ whenDelayedStartupFinished(window2, function onWindow() {
+ let notification1 = document.getElementById("global-notificationbox");
+ let notification2 = window2.document.getElementById("global-notificationbox");
+ ok(notification2, "2nd window has a global notification box.");
+
+ let policy;
+
+ let displayCount = 0;
+ let prefPaneClosed = false;
+ let childWindowClosed = false;
+
+ function onAlertDisplayed() {
+ displayCount++;
+
+ if (displayCount != 2) {
+ return;
+ }
+
+ ok(true, "Data reporting info bar displayed on all open windows.");
+
+ // We register two independent observers and we need both to clean up
+ // properly. This handles gating for test completion.
+ function maybeFinish() {
+ if (!prefPaneClosed) {
+ dump("Not finishing test yet because pref pane isn't closed.\n");
+ return;
+ }
+
+ if (!childWindowClosed) {
+ dump("Not finishing test yet because child window isn't closed.\n");
+ return;
+ }
+
+ dump("Finishing multiple window test.\n");
+ rootLogger.removeAppender(dumpAppender);
+ delete dumpAppender;
+ delete rootLogger;
+ finish();
+ }
+
+ let closeCount = 0;
+ function onAlertClose() {
+ closeCount++;
+
+ if (closeCount != 2) {
+ return;
+ }
+
+ ok(true, "Closing info bar on one window closed them on all.");
+
+ is(policy.notifyState, policy.STATE_NOTIFY_COMPLETE,
+ "Closing info bar with multiple windows completes notification.");
+ ok(policy.dataSubmissionPolicyAccepted, "Data submission policy accepted.");
+ is(policy.dataSubmissionPolicyResponseType, "accepted-info-bar-button-pressed",
+ "Policy records reason for acceptance was button press.");
+ is(notification1.allNotifications.length, 0, "No notifications remain on main window.");
+ is(notification2.allNotifications.length, 0, "No notifications remain on 2nd window.");
+
+ window2.close();
+ childWindowClosed = true;
+ maybeFinish();
+ }
+
+ waitForNotificationClose(notification1.currentNotification, onAlertClose);
+ waitForNotificationClose(notification2.currentNotification, onAlertClose);
+
+ // While we're here, we dual purpose this test to check that pressing the
+ // button does the right thing.
+ let buttons = notification2.currentNotification.getElementsByTagName("button");
+ is(buttons.length, 1, "There is 1 button in the data reporting notification.");
+ let button = buttons[0];
+
+ // Automatically close preferences window when it is opened as part of
+ // button press.
+ Services.obs.addObserver(function observer(prefWin, topic, data) {
+ Services.obs.removeObserver(observer, "advanced-pane-loaded");
+
+ ok(true, "Pref pane opened on info bar button press.");
+ executeSoon(function soon() {
+ dump("Closing pref pane.\n");
+ prefWin.close();
+ prefPaneClosed = true;
+ maybeFinish();
+ });
+ }, "advanced-pane-loaded", false);
+
+ button.click();
+ }
+
+ notification1.addEventListener("AlertActive", function active1() {
+ notification1.removeEventListener("AlertActive", active1, true);
+ executeSoon(onAlertDisplayed);
+ }, true);
+
+ notification2.addEventListener("AlertActive", function active2() {
+ notification2.removeEventListener("AlertActive", active2, true);
+ executeSoon(onAlertDisplayed);
+ }, true);
+
+ policy = sendNotifyRequest("multiple_window_behavior");
+ });
+}
+
diff --git a/browser/base/content/test/browser_disablechrome.js b/browser/base/content/test/browser_disablechrome.js
new file mode 100644
index 000000000..24b3f4811
--- /dev/null
+++ b/browser/base/content/test/browser_disablechrome.js
@@ -0,0 +1,216 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Tests that the disablechrome attribute gets propogated to the main UI
+
+const HTTPSRC = "http://example.com/browser/browser/base/content/test/";
+
+function is_element_hidden(aElement) {
+ var style = window.getComputedStyle(document.getElementById("nav-bar"), "");
+ if (style.visibility != "visible" || style.display == "none")
+ return true;
+
+ if (aElement.ownerDocument != aElement.parentNode)
+ return is_element_hidden(aElement.parentNode);
+
+ return false;
+}
+
+function is_chrome_hidden() {
+ is(document.documentElement.getAttribute("disablechrome"), "true", "Attribute should be set");
+ if (TabsOnTop.enabled)
+ ok(is_element_hidden(document.getElementById("nav-bar")), "Toolbar should be hidden");
+ else
+ ok(!is_element_hidden(document.getElementById("nav-bar")), "Toolbar should not be hidden");
+}
+
+function is_chrome_visible() {
+ isnot(document.getElementById("main-window").getAttribute("disablechrome"), "true", "Attribute should not be set");
+ ok(!is_element_hidden(document.getElementById("nav-bar")), "Toolbar should not be hidden");
+}
+
+function load_page(aURL, aCanHide, aCallback) {
+ gNewBrowser.addEventListener("pageshow", function() {
+ // Filter out about:blank loads
+ if (gNewBrowser.currentURI.spec != aURL)
+ return;
+
+ gNewBrowser.removeEventListener("pageshow", arguments.callee, false);
+
+ if (aCanHide)
+ is_chrome_hidden();
+ else
+ is_chrome_visible();
+
+ if (aURL == "about:addons") {
+ function check_after_init() {
+ if (aCanHide)
+ is_chrome_hidden();
+ else
+ is_chrome_visible();
+
+ aCallback();
+ }
+
+ if (gNewBrowser.contentWindow.gIsInitializing) {
+ gNewBrowser.contentDocument.addEventListener("Initialized", function() {
+ gNewBrowser.contentDocument.removeEventListener("Initialized", arguments.callee, false);
+
+ check_after_init();
+ }, false);
+ }
+ else {
+ check_after_init();
+ }
+ }
+ else {
+ executeSoon(aCallback);
+ }
+ }, false);
+ gNewBrowser.loadURI(aURL);
+}
+
+var gOldTab;
+var gNewTab;
+var gNewBrowser;
+
+function test() {
+ // Opening the add-ons manager and waiting for it to load the discovery pane
+ // takes more time in windows debug builds
+ requestLongerTimeout(2);
+
+ var gOldTabsOnTop = TabsOnTop.enabled;
+ registerCleanupFunction(function() {
+ TabsOnTop.enabled = gOldTabsOnTop;
+ });
+
+ waitForExplicitFinish();
+
+ gOldTab = gBrowser.selectedTab;
+ gNewTab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ gNewBrowser = gBrowser.selectedBrowser;
+
+ info("Tabs on top");
+ TabsOnTop.enabled = true;
+
+ run_http_test_1();
+}
+
+function end_test() {
+ gBrowser.removeTab(gNewTab);
+ finish();
+}
+
+function test_url(aURL, aCanHide, aNextTest) {
+ is_chrome_visible();
+ info("Page load");
+ load_page(aURL, aCanHide, function() {
+ info("Switch away");
+ gBrowser.selectedTab = gOldTab;
+ is_chrome_visible();
+
+ info("Switch back");
+ gBrowser.selectedTab = gNewTab;
+ if (aCanHide)
+ is_chrome_hidden();
+ else
+ is_chrome_visible();
+
+ gBrowser.removeTab(gNewTab);
+ gNewTab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ gNewBrowser = gBrowser.selectedBrowser;
+
+ gBrowser.selectedTab = gOldTab;
+
+ info("Background load");
+ load_page(aURL, false, function() {
+ info("Switch back");
+ gBrowser.selectedTab = gNewTab;
+ if (aCanHide)
+ is_chrome_hidden();
+ else
+ is_chrome_visible();
+
+ load_page("about:blank", false, aNextTest);
+ });
+ });
+}
+
+// Should never hide the chrome
+function run_http_test_1() {
+ info("HTTP tests");
+ test_url(HTTPSRC + "disablechrome.html", false, run_chrome_about_test);
+}
+
+// Should hide the chrome
+function run_chrome_about_test() {
+ info("Chrome about: tests");
+ test_url("about:addons", true, function() {
+ info("Tabs on bottom");
+ TabsOnTop.enabled = false;
+ run_http_test_2();
+ });
+}
+
+// Should never hide the chrome
+function run_http_test_2() {
+ info("HTTP tests");
+ test_url(HTTPSRC + "disablechrome.html", false, run_chrome_about_test_2);
+}
+
+// Should not hide the chrome
+function run_chrome_about_test_2() {
+ info("Chrome about: tests");
+ test_url("about:addons", true, run_http_test3);
+}
+
+function run_http_test3() {
+ info("HTTP tests");
+ test_url(HTTPSRC + "disablechrome.html", false, run_chrome_about_test_3);
+}
+
+// Should not hide the chrome
+function run_chrome_about_test_3() {
+ info("Chrome about: tests");
+ test_url("about:Addons", true, function(){
+ info("Tabs on top");
+ TabsOnTop.enabled = true;
+ run_http_test4();
+ });
+}
+
+function run_http_test4() {
+ info("HTTP tests");
+ test_url(HTTPSRC + "disablechrome.html", false, run_chrome_about_test_4);
+}
+
+function run_chrome_about_test_4() {
+ info("Chrome about: tests");
+ test_url("about:Addons", true, run_http_test5);
+ }
+
+function run_http_test5() {
+ info("HTTP tests");
+ test_url(HTTPSRC + "disablechrome.html", false, run_chrome_about_test_5);
+}
+
+// Should hide the chrome
+function run_chrome_about_test_5() {
+ info("Chrome about: tests");
+ test_url("about:preferences", true, function(){
+ info("Tabs on bottom");
+ TabsOnTop.enabled = false;
+ run_http_test6();
+ });
+}
+
+function run_http_test6() {
+ info("HTTP tests");
+ test_url(HTTPSRC + "disablechrome.html", false, run_chrome_about_test_6);
+}
+
+function run_chrome_about_test_6() {
+ info("Chrome about: tests");
+ test_url("about:preferences", true, end_test);
+} \ No newline at end of file
diff --git a/browser/base/content/test/browser_discovery.js b/browser/base/content/test/browser_discovery.js
new file mode 100644
index 000000000..61098fbef
--- /dev/null
+++ b/browser/base/content/test/browser_discovery.js
@@ -0,0 +1,159 @@
+var browser;
+
+function doc() browser.contentDocument;
+
+function setHandlerFunc(aResultFunc) {
+ gBrowser.addEventListener("DOMLinkAdded", function (event) {
+ gBrowser.removeEventListener("DOMLinkAdded", arguments.callee, false);
+ executeSoon(aResultFunc);
+ }, false);
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ browser = gBrowser.selectedBrowser;
+ browser.addEventListener("load", function (event) {
+ event.currentTarget.removeEventListener("load", arguments.callee, true);
+ iconDiscovery();
+ }, true);
+ var rootDir = getRootDirectory(gTestPath);
+ content.location = rootDir + "discovery.html";
+}
+
+var iconDiscoveryTests = [
+ { text: "rel icon discovered" },
+ { rel: "abcdefg icon qwerty", text: "rel may contain additional rels separated by spaces" },
+ { rel: "ICON", text: "rel is case insensitive" },
+ { rel: "shortcut-icon", pass: false, text: "rel shortcut-icon not discovered" },
+ { href: "moz.png", text: "relative href works" },
+ { href: "notthere.png", text: "404'd icon is removed properly" },
+ { href: "data:image/x-icon,%00", type: "image/x-icon", text: "data: URIs work" },
+ { type: "image/png; charset=utf-8", text: "type may have optional parameters (RFC2046)" }
+];
+
+function runIconDiscoveryTest() {
+ var test = iconDiscoveryTests[0];
+ var head = doc().getElementById("linkparent");
+ var hasSrc = gBrowser.getIcon() != null;
+ if (test.pass)
+ ok(hasSrc, test.text);
+ else
+ ok(!hasSrc, test.text);
+
+ head.removeChild(head.getElementsByTagName('link')[0]);
+ iconDiscoveryTests.shift();
+ iconDiscovery(); // Run the next test.
+}
+
+function iconDiscovery() {
+ if (iconDiscoveryTests.length) {
+ setHandlerFunc(runIconDiscoveryTest);
+ gBrowser.setIcon(gBrowser.selectedTab, null);
+
+ var test = iconDiscoveryTests[0];
+ var head = doc().getElementById("linkparent");
+ var link = doc().createElement("link");
+
+ var rootDir = getRootDirectory(gTestPath);
+ var rel = test.rel || "icon";
+ var href = test.href || rootDir + "moz.png";
+ var type = test.type || "image/png";
+ if (test.pass == undefined)
+ test.pass = true;
+
+ link.rel = rel;
+ link.href = href;
+ link.type = type;
+ head.appendChild(link);
+ } else {
+ searchDiscovery();
+ }
+}
+
+var searchDiscoveryTests = [
+ { text: "rel search discovered" },
+ { rel: "SEARCH", text: "rel is case insensitive" },
+ { rel: "-search-", pass: false, text: "rel -search- not discovered" },
+ { rel: "foo bar baz search quux", text: "rel may contain additional rels separated by spaces" },
+ { href: "https://not.mozilla.com", text: "HTTPS ok" },
+ { href: "ftp://not.mozilla.com", text: "FTP ok" },
+ { href: "data:text/foo,foo", pass: false, text: "data URI not permitted" },
+ { href: "javascript:alert(0)", pass: false, text: "JS URI not permitted" },
+ { type: "APPLICATION/OPENSEARCHDESCRIPTION+XML", text: "type is case insensitve" },
+ { type: " application/opensearchdescription+xml ", text: "type may contain extra whitespace" },
+ { type: "application/opensearchdescription+xml; charset=utf-8", text: "type may have optional parameters (RFC2046)" },
+ { type: "aapplication/opensearchdescription+xml", pass: false, text: "type should not be loosely matched" },
+ { rel: "search search search", count: 1, text: "only one engine should be added" }
+];
+
+function runSearchDiscoveryTest() {
+ var test = searchDiscoveryTests[0];
+ var title = test.title || searchDiscoveryTests.length;
+ if (browser.engines) {
+ var hasEngine = (test.count) ? (browser.engines[0].title == title &&
+ browser.engines.length == test.count) :
+ (browser.engines[0].title == title);
+ ok(hasEngine, test.text);
+ browser.engines = null;
+ }
+ else
+ ok(!test.pass, test.text);
+
+ searchDiscoveryTests.shift();
+ searchDiscovery(); // Run the next test.
+}
+
+// This handler is called twice, once for each added link element.
+// Only want to check once the second link element has been added.
+var ranOnce = false;
+function runMultipleEnginesTestAndFinalize() {
+ if (!ranOnce) {
+ ranOnce = true;
+ return;
+ }
+ ok(browser.engines, "has engines");
+ is(browser.engines.length, 1, "only one engine");
+ is(browser.engines[0].uri, "http://first.mozilla.com/search.xml", "first engine wins");
+
+ gBrowser.removeCurrentTab();
+ finish();
+}
+
+function searchDiscovery() {
+ var head = doc().getElementById("linkparent");
+
+ if (searchDiscoveryTests.length) {
+ setHandlerFunc(runSearchDiscoveryTest);
+ var test = searchDiscoveryTests[0];
+ var link = doc().createElement("link");
+
+ var rel = test.rel || "search";
+ var href = test.href || "http://so.not.here.mozilla.com/search.xml";
+ var type = test.type || "application/opensearchdescription+xml";
+ var title = test.title || searchDiscoveryTests.length;
+ if (test.pass == undefined)
+ test.pass = true;
+
+ link.rel = rel;
+ link.href = href;
+ link.type = type;
+ link.title = title;
+ head.appendChild(link);
+ } else {
+ setHandlerFunc(runMultipleEnginesTestAndFinalize);
+ setHandlerFunc(runMultipleEnginesTestAndFinalize);
+ // Test multiple engines with the same title
+ var link = doc().createElement("link");
+ link.rel = "search";
+ link.href = "http://first.mozilla.com/search.xml";
+ link.type = "application/opensearchdescription+xml";
+ link.title = "Test Engine";
+ var link2 = link.cloneNode(false);
+ link2.href = "http://second.mozilla.com/search.xml";
+
+ head.appendChild(link);
+ head.appendChild(link2);
+ }
+}
diff --git a/browser/base/content/test/browser_drag.js b/browser/base/content/test/browser_drag.js
new file mode 100644
index 000000000..6aa14bea0
--- /dev/null
+++ b/browser/base/content/test/browser_drag.js
@@ -0,0 +1,45 @@
+function test()
+{
+ waitForExplicitFinish();
+
+ let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+ let ChromeUtils = {};
+ scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js", ChromeUtils);
+
+ // ---- Test dragging the proxy icon ---
+ var value = content.location.href;
+ var urlString = value + "\n" + content.document.title;
+ var htmlString = "<a href=\"" + value + "\">" + value + "</a>";
+ var expected = [ [
+ { type : "text/x-moz-url",
+ data : urlString },
+ { type : "text/uri-list",
+ data : value },
+ { type : "text/plain",
+ data : value },
+ { type : "text/html",
+ data : htmlString }
+ ] ];
+ // set the valid attribute so dropping is allowed
+ var oldstate = gURLBar.getAttribute("pageproxystate");
+ gURLBar.setAttribute("pageproxystate", "valid");
+ var dt = EventUtils.synthesizeDragStart(document.getElementById("identity-box"), expected);
+ is(dt, null, "drag on proxy icon");
+ gURLBar.setAttribute("pageproxystate", oldstate);
+ // Now, the identity information panel is opened by the proxy icon click.
+ // We need to close it for next tests.
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, window);
+
+ // now test dragging onto a tab
+ var tab = gBrowser.addTab("about:blank", {skipAnimation: true});
+ var browser = gBrowser.getBrowserForTab(tab);
+
+ browser.addEventListener("load", function () {
+ is(browser.contentWindow.location, "http://mochi.test:8888/", "drop on tab");
+ gBrowser.removeTab(tab);
+ finish();
+ }, true);
+
+ ChromeUtils.synthesizeDrop(tab, tab, [[{type: "text/uri-list", data: "http://mochi.test:8888/"}]], "copy", window);
+}
diff --git a/browser/base/content/test/browser_duplicateIDs.js b/browser/base/content/test/browser_duplicateIDs.js
new file mode 100644
index 000000000..38fc17820
--- /dev/null
+++ b/browser/base/content/test/browser_duplicateIDs.js
@@ -0,0 +1,8 @@
+function test() {
+ var ids = {};
+ Array.forEach(document.querySelectorAll("[id]"), function (node) {
+ var id = node.id;
+ ok(!(id in ids), id + " should be unique");
+ ids[id] = null;
+ });
+}
diff --git a/browser/base/content/test/browser_findbarClose.js b/browser/base/content/test/browser_findbarClose.js
new file mode 100644
index 000000000..7a4ff5470
--- /dev/null
+++ b/browser/base/content/test/browser_findbarClose.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests find bar auto-close behavior
+
+let newTab, iframe;
+
+function test() {
+ waitForExplicitFinish();
+ newTab = gBrowser.addTab("about:blank");
+ newTab.linkedBrowser.addEventListener("DOMContentLoaded",
+ prepareTestFindBarStaysOpenOnSubdocumentLocationChange, false);
+ newTab.linkedBrowser.contentWindow.location = "http://example.com/browser/" +
+ "browser/base/content/test/test_bug628179.html";
+}
+
+function prepareTestFindBarStaysOpenOnSubdocumentLocationChange() {
+ newTab.linkedBrowser.removeEventListener("DOMContentLoaded",
+ prepareTestFindBarStaysOpenOnSubdocumentLocationChange, false);
+
+ gFindBar.open();
+
+ iframe = newTab.linkedBrowser.contentDocument.getElementById("iframe");
+ iframe.addEventListener("load",
+ testFindBarStaysOpenOnSubdocumentLocationChange, false);
+ iframe.src = "http://example.org/";
+}
+
+function testFindBarStaysOpenOnSubdocumentLocationChange() {
+ iframe.removeEventListener("load",
+ testFindBarStaysOpenOnSubdocumentLocationChange, false);
+
+ ok(!gFindBar.hidden, "the Find bar isn't hidden after the location of a " +
+ "subdocument changes");
+
+ gFindBar.close();
+ gBrowser.removeTab(newTab);
+ finish();
+}
+
diff --git a/browser/base/content/test/browser_fullscreen-window-open.js b/browser/base/content/test/browser_fullscreen-window-open.js
new file mode 100644
index 000000000..4504cb104
--- /dev/null
+++ b/browser/base/content/test/browser_fullscreen-window-open.js
@@ -0,0 +1,394 @@
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+
+const PREF_DISABLE_OPEN_NEW_WINDOW = "browser.link.open_newwindow.disabled_in_fullscreen";
+const isOSX = (Services.appinfo.OS === "Darwin");
+
+const TEST_FILE = "file_fullscreen-window-open.html";
+const gHttpTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/",
+ "http://127.0.0.1:8888/");
+
+function test () {
+ waitForExplicitFinish();
+
+ Services.prefs.setBoolPref(PREF_DISABLE_OPEN_NEW_WINDOW, true);
+
+ let newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+
+ let gTestBrowser = gBrowser.selectedBrowser;
+ gTestBrowser.addEventListener("load", function onLoad(){
+ gTestBrowser.removeEventListener("load", onLoad, true, true);
+
+ // Enter browser fullscreen mode.
+ BrowserFullScreen();
+
+ runNextTest();
+ }, true, true);
+ gTestBrowser.contentWindow.location.href = gHttpTestRoot + TEST_FILE;
+}
+
+registerCleanupFunction(function(){
+ // Exit browser fullscreen mode.
+ BrowserFullScreen();
+
+ gBrowser.removeCurrentTab();
+
+ Services.prefs.clearUserPref(PREF_DISABLE_OPEN_NEW_WINDOW);
+});
+
+let gTests = [
+ test_open,
+ test_open_with_size,
+ test_open_with_pos,
+ test_open_with_outerSize,
+ test_open_with_innerSize,
+ test_open_with_dialog,
+ test_open_when_open_new_window_by_pref,
+ test_open_with_pref_to_disable_in_fullscreen,
+ test_open_from_chrome,
+];
+
+function runNextTest () {
+ let test = gTests.shift();
+ if (test) {
+ executeSoon(test);
+ }
+ else {
+ finish();
+ }
+}
+
+
+// Test for window.open() with no feature.
+function test_open() {
+ waitForTabOpen({
+ message: {
+ title: "test_open",
+ param: "",
+ },
+ finalizeFn: function () {},
+ });
+}
+
+// Test for window.open() with width/height.
+function test_open_with_size() {
+ waitForTabOpen({
+ message: {
+ title: "test_open_with_size",
+ param: "width=400,height=400",
+ },
+ finalizeFn: function () {},
+ });
+}
+
+// Test for window.open() with top/left.
+function test_open_with_pos() {
+ waitForTabOpen({
+ message: {
+ title: "test_open_with_pos",
+ param: "top=200,left=200",
+ },
+ finalizeFn: function () {},
+ });
+}
+
+// Test for window.open() with outerWidth/Height.
+function test_open_with_outerSize() {
+ let [outerWidth, outerHeight] = [window.outerWidth, window.outerHeight];
+ waitForTabOpen({
+ message: {
+ title: "test_open_with_outerSize",
+ param: "outerWidth=200,outerHeight=200",
+ },
+ successFn: function () {
+ is(window.outerWidth, outerWidth, "Don't change window.outerWidth.");
+ is(window.outerHeight, outerHeight, "Don't change window.outerHeight.");
+ },
+ finalizeFn: function () {},
+ });
+}
+
+// Test for window.open() with innerWidth/Height.
+function test_open_with_innerSize() {
+ let [innerWidth, innerHeight] = [window.innerWidth, window.innerHeight];
+ waitForTabOpen({
+ message: {
+ title: "test_open_with_innerSize",
+ param: "innerWidth=200,innerHeight=200",
+ },
+ successFn: function () {
+ is(window.innerWidth, innerWidth, "Don't change window.innerWidth.");
+ is(window.innerHeight, innerHeight, "Don't change window.innerHeight.");
+ },
+ finalizeFn: function () {},
+ });
+}
+
+// Test for window.open() with dialog.
+function test_open_with_dialog() {
+ waitForTabOpen({
+ message: {
+ title: "test_open_with_dialog",
+ param: "dialog=yes",
+ },
+ finalizeFn: function () {},
+ });
+}
+
+// Test for window.open()
+// when "browser.link.open_newwindow" is nsIBrowserDOMWindow.OPEN_NEWWINDOW
+function test_open_when_open_new_window_by_pref() {
+ const PREF_NAME = "browser.link.open_newwindow";
+ Services.prefs.setIntPref(PREF_NAME, Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW);
+ is(Services.prefs.getIntPref(PREF_NAME), Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW,
+ PREF_NAME + " is nsIBrowserDOMWindow.OPEN_NEWWINDOW at this time");
+
+ waitForTabOpen({
+ message: {
+ title: "test_open_when_open_new_window_by_pref",
+ param: "width=400,height=400",
+ },
+ finalizeFn: function () {
+ Services.prefs.clearUserPref(PREF_NAME);
+ },
+ });
+}
+
+// Test for the pref, "browser.link.open_newwindow.disabled_in_fullscreen"
+function test_open_with_pref_to_disable_in_fullscreen() {
+ Services.prefs.setBoolPref(PREF_DISABLE_OPEN_NEW_WINDOW, false);
+
+ waitForWindowOpen({
+ message: {
+ title: "test_open_with_pref_disabled_in_fullscreen",
+ param: "width=400,height=400",
+ },
+ finalizeFn: function () {
+ Services.prefs.setBoolPref(PREF_DISABLE_OPEN_NEW_WINDOW, true);
+ },
+ });
+}
+
+
+// Test for window.open() called from chrome context.
+function test_open_from_chrome() {
+ waitForWindowOpenFromChrome({
+ message: {
+ title: "test_open_from_chrome",
+ param: "",
+ },
+ finalizeFn: function () {},
+ timeout: 10000,
+ });
+}
+
+function waitForTabOpen(aOptions) {
+ let start = Date.now();
+ let timeout = aOptions.timeout || 5000;
+ let message = aOptions.message;
+
+ if (!message.title) {
+ ok(false, "Can't get message.title.");
+ aOptions.finalizeFn();
+ runNextTest();
+ return;
+ }
+
+ info("Running test: " + message.title);
+
+ let onTabOpen = function onTabOpen(aEvent) {
+ gBrowser.tabContainer.removeEventListener("TabOpen", onTabOpen, true);
+
+ let tab = aEvent.target;
+ tab.linkedBrowser.addEventListener("load", function onLoad(ev){
+ let browser = ev.currentTarget;
+ browser.removeEventListener("load", onLoad, true, true);
+ clearTimeout(onTimeout);
+
+ is(browser.contentWindow.document.title, message.title,
+ "Opened Tab is expected: " + message.title);
+
+ if (aOptions.successFn) {
+ aOptions.successFn();
+ }
+
+ gBrowser.removeTab(tab);
+ finalize();
+ }, true, true);
+ }
+ gBrowser.tabContainer.addEventListener("TabOpen", onTabOpen, true);
+
+ let finalize = function () {
+ aOptions.finalizeFn();
+ info("Finished: " + message.title);
+ runNextTest();
+ };
+
+ let onTimeout = setTimeout(function(){
+ gBrowser.tabContainer.removeEventListener("TabOpen", onTabOpen, true);
+
+ ok(false, "Timeout: '"+message.title + "'.");
+ finalize();
+ }, timeout);
+
+
+ const URI = "data:text/html;charset=utf-8,<!DOCTYPE html><html><head><title>"+
+ message.title +
+ "<%2Ftitle><%2Fhead><body><%2Fbody><%2Fhtml>";
+
+ executeWindowOpenInContent({
+ uri: URI,
+ title: message.title,
+ option: message.param,
+ });
+}
+
+
+function waitForWindowOpen(aOptions) {
+ let start = Date.now();
+ let timeout = aOptions.timeout || 10000;
+ let message = aOptions.message;
+ let url = aOptions.url || getBrowserURL();
+
+ if (!message.title) {
+ ok(false, "Can't get message.title");
+ aOptions.finalizeFn();
+ runNextTest();
+ return;
+ }
+
+ info("Running test: " + message.title);
+
+ let onFinalize = function () {
+ aOptions.finalizeFn();
+
+ info("Finished: " + message.title);
+ runNextTest();
+ };
+
+ let onTimeout = setTimeout(function(){
+ Services.wm.removeListener(listener);
+ ok(false, "Fail: '"+message.title + "'.");
+
+ onFinalize();
+ }, timeout);
+
+ let listener = new WindowListener(message.title, url, {
+ onSuccess: aOptions.successFn,
+ onTimeout: onTimeout,
+ onFinalize: onFinalize,
+ });
+ Services.wm.addListener(listener);
+
+ const URI = aOptions.url || "about:blank";
+
+ executeWindowOpenInContent({
+ uri: URI,
+ title: message.title,
+ option: message.param,
+ });
+}
+
+function executeWindowOpenInContent(aParam) {
+ var testWindow = gBrowser.selectedBrowser.contentWindow;
+ var testElm = testWindow.document.getElementById("test");
+
+ testElm.setAttribute("data-test-param", JSON.stringify(aParam));
+ EventUtils.synthesizeMouseAtCenter(testElm, {}, testWindow);
+}
+
+function waitForWindowOpenFromChrome(aOptions) {
+ let start = Date.now();
+ let timeout = aOptions.timeout || 10000;
+ let message = aOptions.message;
+ let url = aOptions.url || getBrowserURL();
+
+ if (!message.title) {
+ ok(false, "Can't get message.title");
+ aOptions.finalizeFn();
+ runNextTest();
+ return;
+ }
+
+ info("Running test: " + message.title);
+
+ let onFinalize = function () {
+ aOptions.finalizeFn();
+
+ info("Finished: " + message.title);
+ runNextTest();
+ };
+
+ let onTimeout = setTimeout(function(){
+ Services.wm.removeListener(listener);
+ ok(false, "Fail: '"+message.title + "'.");
+
+ testWindow.close();
+ onFinalize();
+ }, timeout);
+
+ let listener = new WindowListener(message.title, url, {
+ onSuccess: aOptions.successFn,
+ onTimeout: onTimeout,
+ onFinalize: onFinalize,
+ });
+ Services.wm.addListener(listener);
+
+
+ const URI = aOptions.url || "about:blank";
+
+ let testWindow = window.open(URI, message.title, message.option);
+}
+
+function WindowListener(aTitle, aUrl, aCallBackObj) {
+ this.test_title = aTitle;
+ this.test_url = aUrl;
+ this.callback_onSuccess = aCallBackObj.onSuccess;
+ this.callBack_onTimeout = aCallBackObj.onTimeout;
+ this.callBack_onFinalize = aCallBackObj.onFinalize;
+}
+WindowListener.prototype = {
+
+ test_title: null,
+ test_url: null,
+ callback_onSuccess: null,
+ callBack_onTimeout: null,
+ callBack_onFinalize: null,
+
+ onOpenWindow: function(aXULWindow) {
+ Services.wm.removeListener(this);
+
+ let domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ domwindow.addEventListener("load", function onLoad(aEvent) {
+ is(domwindow.document.location.href, this.test_url,
+ "Opened Window is expected: "+ this.test_title);
+ if (this.callback_onSuccess) {
+ this.callback_onSuccess();
+ }
+
+ domwindow.removeEventListener("load", onLoad, true);
+ clearTimeout(this.callBack_onTimeout);
+
+ // wait for trasition to fullscreen on OSX Lion later
+ if (isOSX) {
+ setTimeout(function(){
+ domwindow.close();
+ executeSoon(this.callBack_onFinalize);
+ }.bind(this), 3000);
+ }
+ else {
+ domwindow.close();
+ executeSoon(this.callBack_onFinalize);
+ }
+ }.bind(this), true);
+ },
+ onCloseWindow: function(aXULWindow) {},
+ onWindowTitleChange: function(aXULWindow, aNewTitle) {},
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWindowMediatorListener,
+ Ci.nsISupports]),
+};
diff --git a/browser/base/content/test/browser_gestureSupport.js b/browser/base/content/test/browser_gestureSupport.js
new file mode 100644
index 000000000..d60ae58f4
--- /dev/null
+++ b/browser/base/content/test/browser_gestureSupport.js
@@ -0,0 +1,671 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Simple gestures tests
+//
+// These tests require the ability to disable the fact that the
+// Firefox chrome intentionally prevents "simple gesture" events from
+// reaching web content.
+
+let test_utils;
+let test_commandset;
+let test_prefBranch = "browser.gesture.";
+
+function test()
+{
+ waitForExplicitFinish();
+
+ // Disable the default gestures support during the test
+ gGestureSupport.init(false);
+
+ test_utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+ getInterface(Components.interfaces.nsIDOMWindowUtils);
+
+ // Run the tests of "simple gesture" events generally
+ test_EnsureConstantsAreDisjoint();
+ test_TestEventListeners();
+ test_TestEventCreation();
+
+ // Reenable the default gestures support. The remaining tests target
+ // the Firefox gesture functionality.
+ gGestureSupport.init(true);
+
+ // Test Firefox's gestures support.
+ test_commandset = document.getElementById("mainCommandSet");
+ test_swipeGestures();
+ test_latchedGesture("pinch", "out", "in", "MozMagnifyGesture");
+ test_thresholdGesture("pinch", "out", "in", "MozMagnifyGesture");
+ test_rotateGestures();
+}
+
+let test_eventCount = 0;
+let test_expectedType;
+let test_expectedDirection;
+let test_expectedDelta;
+let test_expectedModifiers;
+let test_expectedClickCount;
+let test_imageTab;
+
+function test_gestureListener(evt)
+{
+ is(evt.type, test_expectedType,
+ "evt.type (" + evt.type + ") does not match expected value");
+ is(evt.target, test_utils.elementFromPoint(20, 20, false, false),
+ "evt.target (" + evt.target + ") does not match expected value");
+ is(evt.clientX, 20,
+ "evt.clientX (" + evt.clientX + ") does not match expected value");
+ is(evt.clientY, 20,
+ "evt.clientY (" + evt.clientY + ") does not match expected value");
+ isnot(evt.screenX, 0,
+ "evt.screenX (" + evt.screenX + ") does not match expected value");
+ isnot(evt.screenY, 0,
+ "evt.screenY (" + evt.screenY + ") does not match expected value");
+
+ is(evt.direction, test_expectedDirection,
+ "evt.direction (" + evt.direction + ") does not match expected value");
+ is(evt.delta, test_expectedDelta,
+ "evt.delta (" + evt.delta + ") does not match expected value");
+
+ is(evt.shiftKey, (test_expectedModifiers & Components.interfaces.nsIDOMEvent.SHIFT_MASK) != 0,
+ "evt.shiftKey did not match expected value");
+ is(evt.ctrlKey, (test_expectedModifiers & Components.interfaces.nsIDOMEvent.CONTROL_MASK) != 0,
+ "evt.ctrlKey did not match expected value");
+ is(evt.altKey, (test_expectedModifiers & Components.interfaces.nsIDOMEvent.ALT_MASK) != 0,
+ "evt.altKey did not match expected value");
+ is(evt.metaKey, (test_expectedModifiers & Components.interfaces.nsIDOMEvent.META_MASK) != 0,
+ "evt.metaKey did not match expected value");
+
+ if (evt.type == "MozTapGesture") {
+ is(evt.clickCount, test_expectedClickCount, "evt.clickCount does not match");
+ }
+
+ test_eventCount++;
+}
+
+function test_helper1(type, direction, delta, modifiers)
+{
+ // Setup the expected values
+ test_expectedType = type;
+ test_expectedDirection = direction;
+ test_expectedDelta = delta;
+ test_expectedModifiers = modifiers;
+
+ let expectedEventCount = test_eventCount + 1;
+
+ document.addEventListener(type, test_gestureListener, true);
+ test_utils.sendSimpleGestureEvent(type, 20, 20, direction, delta, modifiers);
+ document.removeEventListener(type, test_gestureListener, true);
+
+ is(expectedEventCount, test_eventCount, "Event (" + type + ") was never received by event listener");
+}
+
+function test_clicks(type, clicks)
+{
+ // Setup the expected values
+ test_expectedType = type;
+ test_expectedDirection = 0;
+ test_expectedDelta = 0;
+ test_expectedModifiers = 0;
+ test_expectedClickCount = clicks;
+
+ let expectedEventCount = test_eventCount + 1;
+
+ document.addEventListener(type, test_gestureListener, true);
+ test_utils.sendSimpleGestureEvent(type, 20, 20, 0, 0, 0, clicks);
+ document.removeEventListener(type, test_gestureListener, true);
+
+ is(expectedEventCount, test_eventCount, "Event (" + type + ") was never received by event listener");
+}
+
+function test_TestEventListeners()
+{
+ let e = test_helper1; // easier to type this name
+
+ // Swipe gesture animation events
+ e("MozSwipeGestureStart", 0, -0.7, 0);
+ e("MozSwipeGestureUpdate", 0, -0.4, 0);
+ e("MozSwipeGestureEnd", 0, 0, 0);
+ e("MozSwipeGestureStart", 0, 0.6, 0);
+ e("MozSwipeGestureUpdate", 0, 0.3, 0);
+ e("MozSwipeGestureEnd", 0, 1, 0);
+
+ // Swipe gesture event
+ e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_LEFT, 0.0, 0);
+ e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_RIGHT, 0.0, 0);
+ e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_UP, 0.0, 0);
+ e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_DOWN, 0.0, 0);
+ e("MozSwipeGesture",
+ SimpleGestureEvent.DIRECTION_UP | SimpleGestureEvent.DIRECTION_LEFT, 0.0, 0);
+ e("MozSwipeGesture",
+ SimpleGestureEvent.DIRECTION_DOWN | SimpleGestureEvent.DIRECTION_RIGHT, 0.0, 0);
+ e("MozSwipeGesture",
+ SimpleGestureEvent.DIRECTION_UP | SimpleGestureEvent.DIRECTION_RIGHT, 0.0, 0);
+ e("MozSwipeGesture",
+ SimpleGestureEvent.DIRECTION_DOWN | SimpleGestureEvent.DIRECTION_LEFT, 0.0, 0);
+
+ // magnify gesture events
+ e("MozMagnifyGestureStart", 0, 50.0, 0);
+ e("MozMagnifyGestureUpdate", 0, -25.0, 0);
+ e("MozMagnifyGestureUpdate", 0, 5.0, 0);
+ e("MozMagnifyGesture", 0, 30.0, 0);
+
+ // rotate gesture events
+ e("MozRotateGestureStart", SimpleGestureEvent.ROTATION_CLOCKWISE, 33.0, 0);
+ e("MozRotateGestureUpdate", SimpleGestureEvent.ROTATION_COUNTERCLOCKWISE, -13.0, 0);
+ e("MozRotateGestureUpdate", SimpleGestureEvent.ROTATION_CLOCKWISE, 13.0, 0);
+ e("MozRotateGesture", SimpleGestureEvent.ROTATION_CLOCKWISE, 33.0, 0);
+
+ // Tap and presstap gesture events
+ test_clicks("MozTapGesture", 1);
+ test_clicks("MozTapGesture", 2);
+ test_clicks("MozTapGesture", 3);
+ test_clicks("MozPressTapGesture", 1);
+
+ // simple delivery test for edgeui gestures
+ e("MozEdgeUIStarted", 0, 0, 0);
+ e("MozEdgeUICanceled", 0, 0, 0);
+ e("MozEdgeUICompleted", 0, 0, 0);
+
+ // event.shiftKey
+ let modifier = Components.interfaces.nsIDOMEvent.SHIFT_MASK;
+ e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_RIGHT, 0, modifier);
+
+ // event.metaKey
+ modifier = Components.interfaces.nsIDOMEvent.META_MASK;
+ e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_RIGHT, 0, modifier);
+
+ // event.altKey
+ modifier = Components.interfaces.nsIDOMEvent.ALT_MASK;
+ e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_RIGHT, 0, modifier);
+
+ // event.ctrlKey
+ modifier = Components.interfaces.nsIDOMEvent.CONTROL_MASK;
+ e("MozSwipeGesture", SimpleGestureEvent.DIRECTION_RIGHT, 0, modifier);
+}
+
+function test_eventDispatchListener(evt)
+{
+ test_eventCount++;
+ evt.stopPropagation();
+}
+
+function test_helper2(type, direction, delta, altKey, ctrlKey, shiftKey, metaKey)
+{
+ let event = null;
+ let successful;
+
+ try {
+ event = document.createEvent("SimpleGestureEvent");
+ successful = true;
+ }
+ catch (ex) {
+ successful = false;
+ }
+ ok(successful, "Unable to create SimpleGestureEvent");
+
+ try {
+ event.initSimpleGestureEvent(type, true, true, window, 1,
+ 10, 10, 10, 10,
+ ctrlKey, altKey, shiftKey, metaKey,
+ 1, window,
+ 0, direction, delta, 0);
+ successful = true;
+ }
+ catch (ex) {
+ successful = false;
+ }
+ ok(successful, "event.initSimpleGestureEvent should not fail");
+
+ // Make sure the event fields match the expected values
+ is(event.type, type, "Mismatch on evt.type");
+ is(event.direction, direction, "Mismatch on evt.direction");
+ is(event.delta, delta, "Mismatch on evt.delta");
+ is(event.altKey, altKey, "Mismatch on evt.altKey");
+ is(event.ctrlKey, ctrlKey, "Mismatch on evt.ctrlKey");
+ is(event.shiftKey, shiftKey, "Mismatch on evt.shiftKey");
+ is(event.metaKey, metaKey, "Mismatch on evt.metaKey");
+ is(event.view, window, "Mismatch on evt.view");
+ is(event.detail, 1, "Mismatch on evt.detail");
+ is(event.clientX, 10, "Mismatch on evt.clientX");
+ is(event.clientY, 10, "Mismatch on evt.clientY");
+ is(event.screenX, 10, "Mismatch on evt.screenX");
+ is(event.screenY, 10, "Mismatch on evt.screenY");
+ is(event.button, 1, "Mismatch on evt.button");
+ is(event.relatedTarget, window, "Mismatch on evt.relatedTarget");
+
+ // Test event dispatch
+ let expectedEventCount = test_eventCount + 1;
+ document.addEventListener(type, test_eventDispatchListener, true);
+ document.dispatchEvent(event);
+ document.removeEventListener(type, test_eventDispatchListener, true);
+ is(expectedEventCount, test_eventCount, "Dispatched event was never received by listener");
+}
+
+function test_TestEventCreation()
+{
+ // Event creation
+ test_helper2("MozMagnifyGesture", SimpleGestureEvent.DIRECTION_RIGHT, 20.0,
+ true, false, true, false);
+ test_helper2("MozMagnifyGesture", SimpleGestureEvent.DIRECTION_LEFT, -20.0,
+ false, true, false, true);
+}
+
+function test_EnsureConstantsAreDisjoint()
+{
+ let up = SimpleGestureEvent.DIRECTION_UP;
+ let down = SimpleGestureEvent.DIRECTION_DOWN;
+ let left = SimpleGestureEvent.DIRECTION_LEFT;
+ let right = SimpleGestureEvent.DIRECTION_RIGHT;
+
+ let clockwise = SimpleGestureEvent.ROTATION_CLOCKWISE;
+ let cclockwise = SimpleGestureEvent.ROTATION_COUNTERCLOCKWISE;
+
+ ok(up ^ down, "DIRECTION_UP and DIRECTION_DOWN are not bitwise disjoint");
+ ok(up ^ left, "DIRECTION_UP and DIRECTION_LEFT are not bitwise disjoint");
+ ok(up ^ right, "DIRECTION_UP and DIRECTION_RIGHT are not bitwise disjoint");
+ ok(down ^ left, "DIRECTION_DOWN and DIRECTION_LEFT are not bitwise disjoint");
+ ok(down ^ right, "DIRECTION_DOWN and DIRECTION_RIGHT are not bitwise disjoint");
+ ok(left ^ right, "DIRECTION_LEFT and DIRECTION_RIGHT are not bitwise disjoint");
+ ok(clockwise ^ cclockwise, "ROTATION_CLOCKWISE and ROTATION_COUNTERCLOCKWISE are not bitwise disjoint");
+}
+
+// Helper for test of latched event processing. Emits the actual
+// gesture events to test whether the commands associated with the
+// gesture will only trigger once for each direction of movement.
+function test_emitLatchedEvents(eventPrefix, initialDelta, cmd)
+{
+ let cumulativeDelta = 0;
+ let isIncreasing = initialDelta > 0;
+
+ let expect = {};
+ // Reset the call counters and initialize expected values
+ for (let dir in cmd)
+ cmd[dir].callCount = expect[dir] = 0;
+
+ let check = function(aDir, aMsg) ok(cmd[aDir].callCount == expect[aDir], aMsg);
+ let checkBoth = function(aNum, aInc, aDec) {
+ let prefix = "Step " + aNum + ": ";
+ check("inc", prefix + aInc);
+ check("dec", prefix + aDec);
+ };
+
+ // Send the "Start" event.
+ test_utils.sendSimpleGestureEvent(eventPrefix + "Start", 0, 0, 0, initialDelta, 0);
+ cumulativeDelta += initialDelta;
+ if (isIncreasing) {
+ expect.inc++;
+ checkBoth(1, "Increasing command was not triggered", "Decreasing command was triggered");
+ } else {
+ expect.dec++;
+ checkBoth(1, "Increasing command was triggered", "Decreasing command was not triggered");
+ }
+
+ // Send random values in the same direction and ensure neither
+ // command triggers.
+ for (let i = 0; i < 5; i++) {
+ let delta = Math.random() * (isIncreasing ? 100 : -100);
+ test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0, delta, 0);
+ cumulativeDelta += delta;
+ checkBoth(2, "Increasing command was triggered", "Decreasing command was triggered");
+ }
+
+ // Now go back in the opposite direction.
+ test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0,
+ - initialDelta, 0);
+ cumulativeDelta += - initialDelta;
+ if (isIncreasing) {
+ expect.dec++;
+ checkBoth(3, "Increasing command was triggered", "Decreasing command was not triggered");
+ } else {
+ expect.inc++;
+ checkBoth(3, "Increasing command was not triggered", "Decreasing command was triggered");
+ }
+
+ // Send random values in the opposite direction and ensure neither
+ // command triggers.
+ for (let i = 0; i < 5; i++) {
+ let delta = Math.random() * (isIncreasing ? -100 : 100);
+ test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0, delta, 0);
+ cumulativeDelta += delta;
+ checkBoth(4, "Increasing command was triggered", "Decreasing command was triggered");
+ }
+
+ // Go back to the original direction. The original command should trigger.
+ test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0,
+ initialDelta, 0);
+ cumulativeDelta += initialDelta;
+ if (isIncreasing) {
+ expect.inc++;
+ checkBoth(5, "Increasing command was not triggered", "Decreasing command was triggered");
+ } else {
+ expect.dec++;
+ checkBoth(5, "Increasing command was triggered", "Decreasing command was not triggered");
+ }
+
+ // Send the wrap-up event. No commands should be triggered.
+ test_utils.sendSimpleGestureEvent(eventPrefix, 0, 0, 0, cumulativeDelta, 0);
+ checkBoth(6, "Increasing command was triggered", "Decreasing command was triggered");
+}
+
+function test_addCommand(prefName, id)
+{
+ let cmd = test_commandset.appendChild(document.createElement("command"));
+ cmd.setAttribute("id", id);
+ cmd.setAttribute("oncommand", "this.callCount++;");
+
+ cmd.origPrefName = prefName;
+ cmd.origPrefValue = gPrefService.getCharPref(prefName);
+ gPrefService.setCharPref(prefName, id);
+
+ return cmd;
+}
+
+function test_removeCommand(cmd)
+{
+ gPrefService.setCharPref(cmd.origPrefName, cmd.origPrefValue);
+ test_commandset.removeChild(cmd);
+}
+
+// Test whether latched events are only called once per direction of motion.
+function test_latchedGesture(gesture, inc, dec, eventPrefix)
+{
+ let branch = test_prefBranch + gesture + ".";
+
+ // Put the gesture into latched mode.
+ let oldLatchedValue = gPrefService.getBoolPref(branch + "latched");
+ gPrefService.setBoolPref(branch + "latched", true);
+
+ // Install the test commands for increasing and decreasing motion.
+ let cmd = {
+ inc: test_addCommand(branch + inc, "test:incMotion"),
+ dec: test_addCommand(branch + dec, "test:decMotion"),
+ };
+
+ // Test the gestures in each direction.
+ test_emitLatchedEvents(eventPrefix, 500, cmd);
+ test_emitLatchedEvents(eventPrefix, -500, cmd);
+
+ // Restore the gesture to its original configuration.
+ gPrefService.setBoolPref(branch + "latched", oldLatchedValue);
+ for (let dir in cmd)
+ test_removeCommand(cmd[dir]);
+}
+
+// Test whether non-latched events are triggered upon sufficient motion.
+function test_thresholdGesture(gesture, inc, dec, eventPrefix)
+{
+ let branch = test_prefBranch + gesture + ".";
+
+ // Disable latched mode for this gesture.
+ let oldLatchedValue = gPrefService.getBoolPref(branch + "latched");
+ gPrefService.setBoolPref(branch + "latched", false);
+
+ // Set the triggering threshold value to 50.
+ let oldThresholdValue = gPrefService.getIntPref(branch + "threshold");
+ gPrefService.setIntPref(branch + "threshold", 50);
+
+ // Install the test commands for increasing and decreasing motion.
+ let cmdInc = test_addCommand(branch + inc, "test:incMotion");
+ let cmdDec = test_addCommand(branch + dec, "test:decMotion");
+
+ // Send the start event but stop short of triggering threshold.
+ cmdInc.callCount = cmdDec.callCount = 0;
+ test_utils.sendSimpleGestureEvent(eventPrefix + "Start", 0, 0, 0, 49.5, 0);
+ ok(cmdInc.callCount == 0, "Increasing command was triggered");
+ ok(cmdDec.callCount == 0, "Decreasing command was triggered");
+
+ // Now trigger the threshold.
+ cmdInc.callCount = cmdDec.callCount = 0;
+ test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0, 1, 0);
+ ok(cmdInc.callCount == 1, "Increasing command was not triggered");
+ ok(cmdDec.callCount == 0, "Decreasing command was triggered");
+
+ // The tracking counter should go to zero. Go back the other way and
+ // stop short of triggering the threshold.
+ cmdInc.callCount = cmdDec.callCount = 0;
+ test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0, -49.5, 0);
+ ok(cmdInc.callCount == 0, "Increasing command was triggered");
+ ok(cmdDec.callCount == 0, "Decreasing command was triggered");
+
+ // Now cross the threshold and trigger the decreasing command.
+ cmdInc.callCount = cmdDec.callCount = 0;
+ test_utils.sendSimpleGestureEvent(eventPrefix + "Update", 0, 0, 0, -1.5, 0);
+ ok(cmdInc.callCount == 0, "Increasing command was triggered");
+ ok(cmdDec.callCount == 1, "Decreasing command was not triggered");
+
+ // Send the wrap-up event. No commands should trigger.
+ cmdInc.callCount = cmdDec.callCount = 0;
+ test_utils.sendSimpleGestureEvent(eventPrefix, 0, 0, 0, -0.5, 0);
+ ok(cmdInc.callCount == 0, "Increasing command was triggered");
+ ok(cmdDec.callCount == 0, "Decreasing command was triggered");
+
+ // Restore the gesture to its original configuration.
+ gPrefService.setBoolPref(branch + "latched", oldLatchedValue);
+ gPrefService.setIntPref(branch + "threshold", oldThresholdValue);
+ test_removeCommand(cmdInc);
+ test_removeCommand(cmdDec);
+}
+
+function test_swipeGestures()
+{
+ // easier to type names for the direction constants
+ let up = SimpleGestureEvent.DIRECTION_UP;
+ let down = SimpleGestureEvent.DIRECTION_DOWN;
+ let left = SimpleGestureEvent.DIRECTION_LEFT;
+ let right = SimpleGestureEvent.DIRECTION_RIGHT;
+
+ let branch = test_prefBranch + "swipe.";
+
+ // Install the test commands for the swipe gestures.
+ let cmdUp = test_addCommand(branch + "up", "test:swipeUp");
+ let cmdDown = test_addCommand(branch + "down", "test:swipeDown");
+ let cmdLeft = test_addCommand(branch + "left", "test:swipeLeft");
+ let cmdRight = test_addCommand(branch + "right", "test:swipeRight");
+
+ function resetCounts() {
+ cmdUp.callCount = 0;
+ cmdDown.callCount = 0;
+ cmdLeft.callCount = 0;
+ cmdRight.callCount = 0;
+ }
+
+ // UP
+ resetCounts();
+ test_utils.sendSimpleGestureEvent("MozSwipeGesture", 0, 0, up, 0, 0);
+ ok(cmdUp.callCount == 1, "Step 1: Up command was not triggered");
+ ok(cmdDown.callCount == 0, "Step 1: Down command was triggered");
+ ok(cmdLeft.callCount == 0, "Step 1: Left command was triggered");
+ ok(cmdRight.callCount == 0, "Step 1: Right command was triggered");
+
+ // DOWN
+ resetCounts();
+ test_utils.sendSimpleGestureEvent("MozSwipeGesture", 0, 0, down, 0, 0);
+ ok(cmdUp.callCount == 0, "Step 2: Up command was triggered");
+ ok(cmdDown.callCount == 1, "Step 2: Down command was not triggered");
+ ok(cmdLeft.callCount == 0, "Step 2: Left command was triggered");
+ ok(cmdRight.callCount == 0, "Step 2: Right command was triggered");
+
+ // LEFT
+ resetCounts();
+ test_utils.sendSimpleGestureEvent("MozSwipeGesture", 0, 0, left, 0, 0);
+ ok(cmdUp.callCount == 0, "Step 3: Up command was triggered");
+ ok(cmdDown.callCount == 0, "Step 3: Down command was triggered");
+ ok(cmdLeft.callCount == 1, "Step 3: Left command was not triggered");
+ ok(cmdRight.callCount == 0, "Step 3: Right command was triggered");
+
+ // RIGHT
+ resetCounts();
+ test_utils.sendSimpleGestureEvent("MozSwipeGesture", 0, 0, right, 0, 0);
+ ok(cmdUp.callCount == 0, "Step 4: Up command was triggered");
+ ok(cmdDown.callCount == 0, "Step 4: Down command was triggered");
+ ok(cmdLeft.callCount == 0, "Step 4: Left command was triggered");
+ ok(cmdRight.callCount == 1, "Step 4: Right command was not triggered");
+
+ // Make sure combinations do not trigger events.
+ let combos = [ up | left, up | right, down | left, down | right];
+ for (let i = 0; i < combos.length; i++) {
+ resetCounts();
+ test_utils.sendSimpleGestureEvent("MozSwipeGesture", 0, 0, combos[i], 0, 0);
+ ok(cmdUp.callCount == 0, "Step 5-"+i+": Up command was triggered");
+ ok(cmdDown.callCount == 0, "Step 5-"+i+": Down command was triggered");
+ ok(cmdLeft.callCount == 0, "Step 5-"+i+": Left command was triggered");
+ ok(cmdRight.callCount == 0, "Step 5-"+i+": Right command was triggered");
+ }
+
+ // Remove the test commands.
+ test_removeCommand(cmdUp);
+ test_removeCommand(cmdDown);
+ test_removeCommand(cmdLeft);
+ test_removeCommand(cmdRight);
+}
+
+
+function test_rotateHelperGetImageRotation(aImageElement)
+{
+ // Get the true image rotation from the transform matrix, bounded
+ // to 0 <= result < 360
+ let transformValue = content.window.getComputedStyle(aImageElement, null)
+ .transform;
+ if (transformValue == "none")
+ return 0;
+
+ transformValue = transformValue.split("(")[1]
+ .split(")")[0]
+ .split(",");
+ var rotation = Math.round(Math.atan2(transformValue[1], transformValue[0]) *
+ (180 / Math.PI));
+ return (rotation < 0 ? rotation + 360 : rotation);
+}
+
+function test_rotateHelperOneGesture(aImageElement, aCurrentRotation,
+ aDirection, aAmount, aStop)
+{
+ if (aAmount <= 0 || aAmount > 90) // Bound to 0 < aAmount <= 90
+ return;
+
+ // easier to type names for the direction constants
+ let clockwise = SimpleGestureEvent.ROTATION_CLOCKWISE;
+ let cclockwise = SimpleGestureEvent.ROTATION_COUNTERCLOCKWISE;
+
+ let delta = aAmount * (aDirection == clockwise ? 1 : -1);
+
+ // Kill transition time on image so test isn't wrong and doesn't take 10 seconds
+ aImageElement.style.transitionDuration = "0s";
+
+ // Start the gesture, perform an update, and force flush
+ test_utils.sendSimpleGestureEvent("MozRotateGestureStart", 0, 0, aDirection, .001, 0);
+ test_utils.sendSimpleGestureEvent("MozRotateGestureUpdate", 0, 0, aDirection, delta, 0);
+ aImageElement.clientTop;
+
+ // If stop, check intermediate
+ if (aStop) {
+ // Send near-zero-delta to stop, and force flush
+ test_utils.sendSimpleGestureEvent("MozRotateGestureUpdate", 0, 0, aDirection, .001, 0);
+ aImageElement.clientTop;
+
+ let stopExpectedRotation = (aCurrentRotation + delta) % 360;
+ if (stopExpectedRotation < 0)
+ stopExpectedRotation += 360;
+
+ is(stopExpectedRotation, test_rotateHelperGetImageRotation(aImageElement),
+ "Image rotation at gesture stop/hold: expected=" + stopExpectedRotation +
+ ", observed=" + test_rotateHelperGetImageRotation(aImageElement) +
+ ", init=" + aCurrentRotation +
+ ", amt=" + aAmount +
+ ", dir=" + (aDirection == clockwise ? "cl" : "ccl"));
+ }
+ // End it and force flush
+ test_utils.sendSimpleGestureEvent("MozRotateGesture", 0, 0, aDirection, 0, 0);
+ aImageElement.clientTop;
+
+ let finalExpectedRotation;
+
+ if (aAmount < 45 && aStop) {
+ // Rotate a bit, then stop. Expect no change at end of gesture.
+ finalExpectedRotation = aCurrentRotation;
+ }
+ else {
+ // Either not stopping (expect 90 degree change in aDirection), OR
+ // stopping but after 45, (expect 90 degree change in aDirection)
+ finalExpectedRotation = (aCurrentRotation +
+ (aDirection == clockwise ? 1 : -1) * 90) % 360;
+ if (finalExpectedRotation < 0)
+ finalExpectedRotation += 360;
+ }
+
+ is(finalExpectedRotation, test_rotateHelperGetImageRotation(aImageElement),
+ "Image rotation gesture end: expected=" + finalExpectedRotation +
+ ", observed=" + test_rotateHelperGetImageRotation(aImageElement) +
+ ", init=" + aCurrentRotation +
+ ", amt=" + aAmount +
+ ", dir=" + (aDirection == clockwise ? "cl" : "ccl"));
+}
+
+function test_rotateGesturesOnTab()
+{
+ gBrowser.selectedBrowser.removeEventListener("load", test_rotateGesturesOnTab, true);
+
+ if (!(content.document instanceof ImageDocument)) {
+ ok(false, "Image document failed to open for rotation testing");
+ gBrowser.removeTab(test_imageTab);
+ finish();
+ return;
+ }
+
+ // easier to type names for the direction constants
+ let cl = SimpleGestureEvent.ROTATION_CLOCKWISE;
+ let ccl = SimpleGestureEvent.ROTATION_COUNTERCLOCKWISE;
+
+ let imgElem = content.document.body &&
+ content.document.body.firstElementChild;
+
+ if (!imgElem) {
+ ok(false, "Could not get image element on ImageDocument for rotation!");
+ gBrowser.removeTab(test_imageTab);
+ finish();
+ return;
+ }
+
+ // Quick function to normalize rotation to 0 <= r < 360
+ var normRot = function(rotation) {
+ rotation = rotation % 360;
+ if (rotation < 0)
+ rotation += 360;
+ return rotation;
+ }
+
+ for (var initRot = 0; initRot < 360; initRot += 90) {
+ // Test each case: at each 90 degree snap; cl/ccl;
+ // amount more or less than 45; stop and hold or don't (32 total tests)
+ // The amount added to the initRot is where it is expected to be
+ test_rotateHelperOneGesture(imgElem, normRot(initRot + 0), cl, 35, true );
+ test_rotateHelperOneGesture(imgElem, normRot(initRot + 0), cl, 35, false);
+ test_rotateHelperOneGesture(imgElem, normRot(initRot + 90), cl, 55, true );
+ test_rotateHelperOneGesture(imgElem, normRot(initRot + 180), cl, 55, false);
+ test_rotateHelperOneGesture(imgElem, normRot(initRot + 270), ccl, 35, true );
+ test_rotateHelperOneGesture(imgElem, normRot(initRot + 270), ccl, 35, false);
+ test_rotateHelperOneGesture(imgElem, normRot(initRot + 180), ccl, 55, true );
+ test_rotateHelperOneGesture(imgElem, normRot(initRot + 90), ccl, 55, false);
+
+ // Manually rotate it 90 degrees clockwise to prepare for next iteration,
+ // and force flush
+ test_utils.sendSimpleGestureEvent("MozRotateGestureStart", 0, 0, cl, .001, 0);
+ test_utils.sendSimpleGestureEvent("MozRotateGestureUpdate", 0, 0, cl, 90, 0);
+ test_utils.sendSimpleGestureEvent("MozRotateGestureUpdate", 0, 0, cl, .001, 0);
+ test_utils.sendSimpleGestureEvent("MozRotateGesture", 0, 0, cl, 0, 0);
+ imgElem.clientTop;
+ }
+
+ gBrowser.removeTab(test_imageTab);
+ test_imageTab = null;
+ finish();
+}
+
+function test_rotateGestures()
+{
+ test_imageTab = gBrowser.addTab("chrome://branding/content/about-logo.png");
+ gBrowser.selectedTab = test_imageTab;
+
+ gBrowser.selectedBrowser.addEventListener("load", test_rotateGesturesOnTab, true);
+}
diff --git a/browser/base/content/test/browser_getshortcutoruri.js b/browser/base/content/test/browser_getshortcutoruri.js
new file mode 100644
index 000000000..39a24f605
--- /dev/null
+++ b/browser/base/content/test/browser_getshortcutoruri.js
@@ -0,0 +1,146 @@
+function getPostDataString(aIS) {
+ if (!aIS)
+ return null;
+
+ var sis = Cc["@mozilla.org/scriptableinputstream;1"].
+ createInstance(Ci.nsIScriptableInputStream);
+ sis.init(aIS);
+ var dataLines = sis.read(aIS.available()).split("\n");
+
+ // only want the last line
+ return dataLines[dataLines.length-1];
+}
+
+function keywordResult(aURL, aPostData, aIsUnsafe) {
+ this.url = aURL;
+ this.postData = aPostData;
+ this.isUnsafe = aIsUnsafe;
+}
+
+function keyWordData() {}
+keyWordData.prototype = {
+ init: function(aKeyWord, aURL, aPostData, aSearchWord) {
+ this.keyword = aKeyWord;
+ this.uri = makeURI(aURL);
+ this.postData = aPostData;
+ this.searchWord = aSearchWord;
+
+ this.method = (this.postData ? "POST" : "GET");
+ }
+}
+
+function bmKeywordData(aKeyWord, aURL, aPostData, aSearchWord) {
+ this.init(aKeyWord, aURL, aPostData, aSearchWord);
+}
+bmKeywordData.prototype = new keyWordData();
+
+function searchKeywordData(aKeyWord, aURL, aPostData, aSearchWord) {
+ this.init(aKeyWord, aURL, aPostData, aSearchWord);
+}
+searchKeywordData.prototype = new keyWordData();
+
+var testData = [
+ [new bmKeywordData("bmget", "http://bmget/search=%s", null, "foo"),
+ new keywordResult("http://bmget/search=foo", null)],
+
+ [new bmKeywordData("bmpost", "http://bmpost/", "search=%s", "foo2"),
+ new keywordResult("http://bmpost/", "search=foo2")],
+
+ [new bmKeywordData("bmpostget", "http://bmpostget/search1=%s", "search2=%s", "foo3"),
+ new keywordResult("http://bmpostget/search1=foo3", "search2=foo3")],
+
+ [new bmKeywordData("bmget-nosearch", "http://bmget-nosearch/", null, ""),
+ new keywordResult("http://bmget-nosearch/", null)],
+
+ [new searchKeywordData("searchget", "http://searchget/?search={searchTerms}", null, "foo4"),
+ new keywordResult("http://searchget/?search=foo4", null, true)],
+
+ [new searchKeywordData("searchpost", "http://searchpost/", "search={searchTerms}", "foo5"),
+ new keywordResult("http://searchpost/", "search=foo5", true)],
+
+ [new searchKeywordData("searchpostget", "http://searchpostget/?search1={searchTerms}", "search2={searchTerms}", "foo6"),
+ new keywordResult("http://searchpostget/?search1=foo6", "search2=foo6", true)],
+
+ // Bookmark keywords that don't take parameters should not be activated if a
+ // parameter is passed (bug 420328).
+ [new bmKeywordData("bmget-noparam", "http://bmget-noparam/", null, "foo7"),
+ new keywordResult(null, null, true)],
+ [new bmKeywordData("bmpost-noparam", "http://bmpost-noparam/", "not_a=param", "foo8"),
+ new keywordResult(null, null, true)],
+
+ // Test escaping (%s = escaped, %S = raw)
+ // UTF-8 default
+ [new bmKeywordData("bmget-escaping", "http://bmget/?esc=%s&raw=%S", null, "foé"),
+ new keywordResult("http://bmget/?esc=fo%C3%A9&raw=foé", null)],
+ // Explicitly-defined ISO-8859-1
+ [new bmKeywordData("bmget-escaping2", "http://bmget/?esc=%s&raw=%S&mozcharset=ISO-8859-1", null, "foé"),
+ new keywordResult("http://bmget/?esc=fo%E9&raw=foé", null)],
+
+ // Bug 359809: Test escaping +, /, and @
+ // UTF-8 default
+ [new bmKeywordData("bmget-escaping", "http://bmget/?esc=%s&raw=%S", null, "+/@"),
+ new keywordResult("http://bmget/?esc=%2B%2F%40&raw=+/@", null)],
+ // Explicitly-defined ISO-8859-1
+ [new bmKeywordData("bmget-escaping2", "http://bmget/?esc=%s&raw=%S&mozcharset=ISO-8859-1", null, "+/@"),
+ new keywordResult("http://bmget/?esc=%2B%2F%40&raw=+/@", null)],
+
+ // Test using a non-bmKeywordData object, to test the behavior of
+ // getShortcutOrURI for non-keywords (setupKeywords only adds keywords for
+ // bmKeywordData objects)
+ [{keyword: "http://gavinsharp.com"},
+ new keywordResult(null, null, true)]
+];
+
+function test() {
+ setupKeywords();
+
+ for each (var item in testData) {
+ var [data, result] = item;
+
+ var postData = {};
+ var query = data.keyword;
+ if (data.searchWord)
+ query += " " + data.searchWord;
+ var mayInheritPrincipal = {};
+ var url = getShortcutOrURI(query, postData, mayInheritPrincipal);
+
+ // null result.url means we should expect the same query we sent in
+ var expected = result.url || query;
+ is(url, expected, "got correct URL for " + data.keyword);
+ is(getPostDataString(postData.value), result.postData, "got correct postData for " + data.keyword);
+ is(mayInheritPrincipal.value, !result.isUnsafe, "got correct mayInheritPrincipal for " + data.keyword);
+ }
+
+ cleanupKeywords();
+}
+
+var gBMFolder = null;
+var gAddedEngines = [];
+function setupKeywords() {
+ gBMFolder = Application.bookmarks.menu.addFolder("keyword-test");
+ for each (var item in testData) {
+ var data = item[0];
+ if (data instanceof bmKeywordData) {
+ var bm = gBMFolder.addBookmark(data.keyword, data.uri);
+ bm.keyword = data.keyword;
+ if (data.postData)
+ bm.annotations.set("bookmarkProperties/POSTData", data.postData, Ci.nsIAnnotationService.EXPIRE_SESSION);
+ }
+
+ if (data instanceof searchKeywordData) {
+ Services.search.addEngineWithDetails(data.keyword, "", data.keyword, "", data.method, data.uri.spec);
+ var addedEngine = Services.search.getEngineByName(data.keyword);
+ if (data.postData) {
+ var [paramName, paramValue] = data.postData.split("=");
+ addedEngine.addParam(paramName, paramValue, null);
+ }
+
+ gAddedEngines.push(addedEngine);
+ }
+ }
+}
+
+function cleanupKeywords() {
+ gBMFolder.remove();
+ gAddedEngines.map(Services.search.removeEngine);
+}
diff --git a/browser/base/content/test/browser_hide_removing.js b/browser/base/content/test/browser_hide_removing.js
new file mode 100644
index 000000000..d29826556
--- /dev/null
+++ b/browser/base/content/test/browser_hide_removing.js
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Bug 587922: tabs don't get removed if they're hidden
+
+function test() {
+ waitForExplicitFinish();
+
+ // Add a tab that will get removed and hidden
+ let testTab = gBrowser.addTab("about:blank", {skipAnimation: true});
+ is(gBrowser.visibleTabs.length, 2, "just added a tab, so 2 tabs");
+ gBrowser.selectedTab = testTab;
+
+ let numVisBeforeHide, numVisAfterHide;
+ gBrowser.tabContainer.addEventListener("TabSelect", function() {
+ gBrowser.tabContainer.removeEventListener("TabSelect", arguments.callee, false);
+
+ // While the next tab is being selected, hide the removing tab
+ numVisBeforeHide = gBrowser.visibleTabs.length;
+ gBrowser.hideTab(testTab);
+ numVisAfterHide = gBrowser.visibleTabs.length;
+ }, false);
+ gBrowser.removeTab(testTab, {animate: true});
+
+ // Make sure the tab gets removed at the end of the animation by polling
+ (function checkRemoved() setTimeout(function() {
+ if (gBrowser.tabs.length != 1)
+ return checkRemoved();
+
+ is(numVisBeforeHide, 1, "animated remove has in 1 tab left");
+ is(numVisAfterHide, 1, "hiding a removing tab is also has 1 tab");
+ finish();
+ }, 50))();
+}
diff --git a/browser/base/content/test/browser_homeDrop.js b/browser/base/content/test/browser_homeDrop.js
new file mode 100644
index 000000000..e9c977827
--- /dev/null
+++ b/browser/base/content/test/browser_homeDrop.js
@@ -0,0 +1,91 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+ waitForExplicitFinish();
+
+ let str = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(Ci.nsISupportsString);
+ str.data = "about:mozilla";
+ Services.prefs.setComplexValue("browser.startup.homepage",
+ Ci.nsISupportsString, str);
+ registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("browser.startup.homepage");
+ });
+
+ // Open a new tab, since starting a drag from the home button activates it and
+ // we don't want to interfere with future tests by loading the home page.
+ let newTab = gBrowser.selectedTab = gBrowser.addTab();
+ registerCleanupFunction(function () {
+ gBrowser.removeTab(newTab);
+ });
+
+ let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+ let ChromeUtils = {};
+ scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js", ChromeUtils);
+
+ let homeButton = document.getElementById("home-button");
+ ok(homeButton, "home button present");
+
+ let dialogListener = new WindowListener("chrome://global/content/commonDialog.xul", function (domwindow) {
+ ok(true, "dialog appeared in response to home button drop");
+ domwindow.document.documentElement.cancelDialog();
+ Services.wm.removeListener(dialogListener);
+
+ // Now trigger the invalid URI test
+ executeSoon(function () {
+ let consoleListener = {
+ observe: function (m) {
+ if (m.message.contains("NS_ERROR_DOM_BAD_URI")) {
+ ok(true, "drop was blocked");
+ executeSoon(finish);
+ }
+ }
+ }
+ Services.console.registerListener(consoleListener);
+ registerCleanupFunction(function () {
+ Services.console.unregisterListener(consoleListener);
+ });
+
+ executeSoon(function () {
+ info("Attempting second drop, of a javascript: URI");
+ // The drop handler throws an exception when dragging URIs that inherit
+ // principal, e.g. javascript:
+ expectUncaughtException();
+ ChromeUtils.synthesizeDrop(homeButton, homeButton, [[{type: "text/plain", data: "javascript:8888"}]], "copy", window);
+ });
+ })
+ });
+
+ Services.wm.addListener(dialogListener);
+
+ ChromeUtils.synthesizeDrop(homeButton, homeButton, [[{type: "text/plain", data: "http://mochi.test:8888/"}]], "copy", window);
+}
+
+function WindowListener(aURL, aCallback) {
+ this.callback = aCallback;
+ this.url = aURL;
+}
+WindowListener.prototype = {
+ onOpenWindow: function(aXULWindow) {
+ var domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ var self = this;
+ domwindow.addEventListener("load", function() {
+ domwindow.removeEventListener("load", arguments.callee, false);
+
+ ok(true, "domwindow.document.location.href: " + domwindow.document.location.href);
+ if (domwindow.document.location.href != self.url)
+ return;
+
+ // Allow other window load listeners to execute before passing to callback
+ executeSoon(function() {
+ self.callback(domwindow);
+ });
+ }, false);
+ },
+ onCloseWindow: function(aXULWindow) {},
+ onWindowTitleChange: function(aXULWindow, aNewTitle) {}
+}
+
diff --git a/browser/base/content/test/browser_identity_UI.js b/browser/base/content/test/browser_identity_UI.js
new file mode 100644
index 000000000..9f07265c9
--- /dev/null
+++ b/browser/base/content/test/browser_identity_UI.js
@@ -0,0 +1,119 @@
+/* Tests for correct behaviour of getEffectiveHost on identity handler */
+function test() {
+ waitForExplicitFinish();
+
+ ok(gIdentityHandler, "gIdentityHandler should exist");
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", checkResult, true);
+
+ nextTest();
+}
+
+// Greek IDN for 'example.test'.
+var idnDomain = "\u03C0\u03B1\u03C1\u03AC\u03B4\u03B5\u03B9\u03B3\u03BC\u03B1.\u03B4\u03BF\u03BA\u03B9\u03BC\u03AE";
+var tests = [
+ {
+ name: "normal domain",
+ location: "http://test1.example.org/",
+ host: "test1.example.org",
+ effectiveHost: "example.org"
+ },
+ {
+ name: "view-source",
+ location: "view-source:http://example.com/",
+ // TODO: these should not be blank, bug 646690
+ host: "",
+ effectiveHost: ""
+ },
+ {
+ name: "normal HTTPS",
+ location: "https://example.com/",
+ host: "example.com",
+ effectiveHost: "example.com",
+ isHTTPS: true
+ },
+ {
+ name: "IDN subdomain",
+ location: "http://sub1." + idnDomain + "/",
+ host: "sub1." + idnDomain,
+ effectiveHost: idnDomain
+ },
+ {
+ name: "subdomain with port",
+ location: "http://sub1.test1.example.org:8000/",
+ host: "sub1.test1.example.org:8000",
+ effectiveHost: "example.org"
+ },
+ {
+ name: "subdomain HTTPS",
+ location: "https://test1.example.com",
+ host: "test1.example.com",
+ effectiveHost: "example.com",
+ isHTTPS: true
+ },
+ {
+ name: "view-source HTTPS",
+ location: "view-source:https://example.com/",
+ // TODO: these should not be blank, bug 646690
+ host: "",
+ effectiveHost: "",
+ isHTTPS: true
+ },
+ {
+ name: "IP address",
+ location: "http://127.0.0.1:8888/",
+ host: "127.0.0.1:8888",
+ effectiveHost: "127.0.0.1"
+ },
+]
+
+let gCurrentTest, gCurrentTestIndex = -1, gTestDesc;
+// Go through the tests in both directions, to add additional coverage for
+// transitions between different states.
+let gForward = true;
+let gCheckETLD = false;
+function nextTest() {
+ if (!gCheckETLD) {
+ if (gForward)
+ gCurrentTestIndex++;
+ else
+ gCurrentTestIndex--;
+
+ if (gCurrentTestIndex == tests.length) {
+ // Went too far, reverse
+ gCurrentTestIndex--;
+ gForward = false;
+ }
+
+ if (gCurrentTestIndex == -1) {
+ gBrowser.selectedBrowser.removeEventListener("load", checkResult, true);
+ gBrowser.removeCurrentTab();
+ finish();
+ return;
+ }
+
+ gCurrentTest = tests[gCurrentTestIndex];
+ gTestDesc = "#" + gCurrentTestIndex + " (" + gCurrentTest.name + ")";
+ if (!gForward)
+ gTestDesc += " (second time)";
+ if (gCurrentTest.isHTTPS) {
+ gCheckETLD = true;
+ }
+ content.location = gCurrentTest.location;
+ } else {
+ gCheckETLD = false;
+ gTestDesc = "#" + gCurrentTestIndex + " (" + gCurrentTest.name + " without eTLD in identity icon label)";
+ if (!gForward)
+ gTestDesc += " (second time)";
+ content.location.reload(true);
+ }
+}
+
+function checkResult() {
+ // Sanity check other values, and the value of gIdentityHandler.getEffectiveHost()
+ is(gIdentityHandler._lastLocation.host, gCurrentTest.host, "host matches for test " + gTestDesc);
+ is(gIdentityHandler.getEffectiveHost(), gCurrentTest.effectiveHost, "effectiveHost matches for test " + gTestDesc);
+
+ executeSoon(nextTest);
+}
diff --git a/browser/base/content/test/browser_keywordBookmarklets.js b/browser/base/content/test/browser_keywordBookmarklets.js
new file mode 100644
index 000000000..8b075d74c
--- /dev/null
+++ b/browser/base/content/test/browser_keywordBookmarklets.js
@@ -0,0 +1,38 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+ waitForExplicitFinish();
+
+ let bmFolder = Application.bookmarks.menu.addFolder("keyword-test");
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+
+ registerCleanupFunction (function () {
+ bmFolder.remove();
+ gBrowser.removeTab(tab);
+ });
+
+ let bm = bmFolder.addBookmark("bookmarklet", makeURI("javascript:1;"));
+ bm.keyword = "bm";
+
+ addPageShowListener(function () {
+ let originalPrincipal = gBrowser.contentPrincipal;
+
+ // Enter bookmarklet keyword in the URL bar
+ gURLBar.value = "bm";
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_RETURN", {});
+
+ addPageShowListener(function () {
+ ok(gBrowser.contentPrincipal.equals(originalPrincipal), "javascript bookmarklet should inherit principal");
+ finish();
+ });
+ });
+}
+
+function addPageShowListener(func) {
+ gBrowser.selectedBrowser.addEventListener("pageshow", function loadListener() {
+ gBrowser.selectedBrowser.removeEventListener("pageshow", loadListener, false);
+ func();
+ });
+}
diff --git a/browser/base/content/test/browser_keywordSearch.js b/browser/base/content/test/browser_keywordSearch.js
new file mode 100644
index 000000000..73efe580e
--- /dev/null
+++ b/browser/base/content/test/browser_keywordSearch.js
@@ -0,0 +1,86 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ **/
+
+var gTests = [
+ {
+ name: "normal search (search service)",
+ testText: "test search",
+ searchURL: Services.search.defaultEngine.getSubmission("test search", null, "keyword").uri.spec
+ },
+ {
+ name: "?-prefixed search (search service)",
+ testText: "? foo ",
+ searchURL: Services.search.defaultEngine.getSubmission("foo", null, "keyword").uri.spec
+ }
+];
+
+function test() {
+ waitForExplicitFinish();
+
+ let windowObserver = {
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic == "domwindowopened") {
+ ok(false, "Alert window opened");
+ let win = aSubject.QueryInterface(Ci.nsIDOMEventTarget);
+ win.addEventListener("load", function() {
+ win.removeEventListener("load", arguments.callee, false);
+ win.close();
+ }, false);
+ executeSoon(finish);
+ }
+ }
+ };
+
+ Services.ww.registerNotification(windowObserver);
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+
+ let listener = {
+ onStateChange: function onLocationChange(webProgress, req, flags, status) {
+ // Only care about document starts
+ let docStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT |
+ Ci.nsIWebProgressListener.STATE_START;
+ if (!(flags & docStart))
+ return;
+
+ info("received document start");
+
+ ok(req instanceof Ci.nsIChannel, "req is a channel");
+ is(req.originalURI.spec, gCurrTest.searchURL, "search URL was loaded");
+ info("Actual URI: " + req.URI.spec);
+
+ executeSoon(nextTest);
+ }
+ }
+ gBrowser.addProgressListener(listener);
+
+ registerCleanupFunction(function () {
+ Services.ww.unregisterNotification(windowObserver);
+
+ gBrowser.removeProgressListener(listener);
+ gBrowser.removeTab(tab);
+ });
+
+ nextTest();
+}
+
+var gCurrTest;
+function nextTest() {
+ if (gTests.length) {
+ gCurrTest = gTests.shift();
+ doTest();
+ } else {
+ finish();
+ }
+}
+
+function doTest() {
+ info("Running test: " + gCurrTest.name);
+
+ // Simulate a user entering search terms
+ gURLBar.value = gCurrTest.testText;
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_RETURN", {});
+}
diff --git a/browser/base/content/test/browser_keywordSearch_postData.js b/browser/base/content/test/browser_keywordSearch_postData.js
new file mode 100644
index 000000000..48db709c1
--- /dev/null
+++ b/browser/base/content/test/browser_keywordSearch_postData.js
@@ -0,0 +1,94 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ **/
+
+var gTests = [
+ {
+ name: "normal search (search service)",
+ testText: "test search",
+ expectText: "test+search"
+ },
+ {
+ name: "?-prefixed search (search service)",
+ testText: "? foo ",
+ expectText: "foo"
+ }
+];
+
+function test() {
+ waitForExplicitFinish();
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+
+ let searchObserver = function search_observer(aSubject, aTopic, aData) {
+ let engine = aSubject.QueryInterface(Ci.nsISearchEngine);
+ info("Observer: " + aData + " for " + engine.name);
+
+ if (aData != "engine-added")
+ return;
+
+ if (engine.name != "POST Search")
+ return;
+
+ Services.search.defaultEngine = engine;
+
+ registerCleanupFunction(function () {
+ Services.search.removeEngine(engine);
+ });
+
+ // ready to execute the tests!
+ executeSoon(nextTest);
+ };
+
+ Services.obs.addObserver(searchObserver, "browser-search-engine-modified", false);
+
+ registerCleanupFunction(function () {
+ gBrowser.removeTab(tab);
+
+ Services.obs.removeObserver(searchObserver, "browser-search-engine-modified");
+ });
+
+ Services.search.addEngine("http://test:80/browser/browser/base/content/test/POSTSearchEngine.xml",
+ Ci.nsISearchEngine.DATA_XML, null, false);
+}
+
+var gCurrTest;
+function nextTest() {
+ if (gTests.length) {
+ gCurrTest = gTests.shift();
+ doTest();
+ } else {
+ finish();
+ }
+}
+
+function doTest() {
+ info("Running test: " + gCurrTest.name);
+
+ waitForLoad(function () {
+ let loadedText = gBrowser.contentDocument.body.textContent;
+ ok(loadedText, "search page loaded");
+ let needle = "searchterms=" + gCurrTest.expectText;
+ is(loadedText, needle, "The query POST data should be returned in the response");
+ nextTest();
+ });
+
+ // Simulate a user entering search terms
+ gURLBar.value = gCurrTest.testText;
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_RETURN", {});
+}
+
+
+function waitForLoad(cb) {
+ let browser = gBrowser.selectedBrowser;
+ browser.addEventListener("load", function listener() {
+ if (browser.currentURI.spec == "about:blank")
+ return;
+ info("Page loaded: " + browser.currentURI.spec);
+ browser.removeEventListener("load", listener, true);
+
+ cb();
+ }, true);
+}
diff --git a/browser/base/content/test/browser_lastAccessedTab.js b/browser/base/content/test/browser_lastAccessedTab.js
new file mode 100644
index 000000000..d0ad8b4cd
--- /dev/null
+++ b/browser/base/content/test/browser_lastAccessedTab.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test for bug 739866.
+ *
+ * 1. Adds a new tab (but doesn't select it)
+ * 2. Checks if timestamp on the new tab is 0
+ * 3. Selects the new tab, checks that the timestamp is updated (>0)
+ * 4. Selects the original tab & checks if new tab's timestamp has remained changed
+ */
+
+function test() {
+ let originalTab = gBrowser.selectedTab;
+ let newTab = gBrowser.addTab("about:blank", {skipAnimation: true});
+ is(newTab.lastAccessed, 0, "Timestamp on the new tab is 0.");
+ gBrowser.selectedTab = newTab;
+ let newTabAccessedDate = newTab.lastAccessed;
+ ok(newTabAccessedDate > 0, "Timestamp on the selected tab is more than 0.");
+ // Date.now is not guaranteed to be monotonic, so include one second of fudge.
+ let now = Date.now() + 1000;
+ ok(newTabAccessedDate <= now, "Timestamp less than or equal current Date: " + newTabAccessedDate + " <= " + now);
+ gBrowser.selectedTab = originalTab;
+ is(newTab.lastAccessed, newTabAccessedDate, "New tab's timestamp remains the same.");
+ gBrowser.removeTab(newTab);
+}
diff --git a/browser/base/content/test/browser_locationBarCommand.js b/browser/base/content/test/browser_locationBarCommand.js
new file mode 100644
index 000000000..fe70300f9
--- /dev/null
+++ b/browser/base/content/test/browser_locationBarCommand.js
@@ -0,0 +1,213 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_VALUE = "example.com";
+const START_VALUE = "example.org";
+
+let gFocusManager = Cc["@mozilla.org/focus-manager;1"].
+ getService(Ci.nsIFocusManager);
+
+function test() {
+ waitForExplicitFinish();
+
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref("browser.altClickSave");
+ });
+ Services.prefs.setBoolPref("browser.altClickSave", true);
+
+ runAltLeftClickTest();
+}
+
+// Monkey patch saveURL to avoid dealing with file save code paths
+var oldSaveURL = saveURL;
+saveURL = function() {
+ ok(true, "SaveURL was called");
+ is(gURLBar.value, "", "Urlbar reverted to original value");
+ saveURL = oldSaveURL;
+ runShiftLeftClickTest();
+}
+function runAltLeftClickTest() {
+ info("Running test: Alt left click");
+ triggerCommand(true, { altKey: true });
+}
+
+function runShiftLeftClickTest() {
+ let listener = new BrowserWindowListener(getBrowserURL(), function(aWindow) {
+ Services.wm.removeListener(listener);
+ addPageShowListener(aWindow.gBrowser.selectedBrowser, function() {
+ executeSoon(function () {
+ info("URL should be loaded in a new window");
+ is(gURLBar.value, "", "Urlbar reverted to original value");
+ is(gFocusManager.focusedElement, null, "There should be no focused element");
+ is(gFocusManager.focusedWindow, aWindow.gBrowser.contentWindow, "Content window should be focused");
+ is(aWindow.gURLBar.value, TEST_VALUE, "New URL is loaded in new window");
+
+ aWindow.close();
+
+ // Continue testing when the original window has focus again.
+ whenWindowActivated(window, runNextTest);
+ });
+ }, "http://example.com/");
+ });
+ Services.wm.addListener(listener);
+
+ info("Running test: Shift left click");
+ triggerCommand(true, { shiftKey: true });
+}
+
+function runNextTest() {
+ let test = gTests.shift();
+ if (!test) {
+ finish();
+ return;
+ }
+
+ info("Running test: " + test.desc);
+ // Tab will be blank if test.startValue is null
+ let tab = gBrowser.selectedTab = gBrowser.addTab(test.startValue);
+ addPageShowListener(gBrowser.selectedBrowser, function() {
+ triggerCommand(test.click, test.event);
+ test.check(tab);
+
+ // Clean up
+ while (gBrowser.tabs.length > 1)
+ gBrowser.removeTab(gBrowser.selectedTab)
+ runNextTest();
+ });
+}
+
+let gTests = [
+ { desc: "Right click on go button",
+ click: true,
+ event: { button: 2 },
+ check: function(aTab) {
+ // Right click should do nothing (context menu will be shown)
+ is(gURLBar.value, TEST_VALUE, "Urlbar still has the value we entered");
+ }
+ },
+
+ { desc: "Left click on go button",
+ click: true,
+ event: {},
+ check: checkCurrent
+ },
+
+ { desc: "Ctrl/Cmd left click on go button",
+ click: true,
+ event: { accelKey: true },
+ check: checkNewTab
+ },
+
+ { desc: "Shift+Ctrl/Cmd left click on go button",
+ click: true,
+ event: { accelKey: true, shiftKey: true },
+ check: function(aTab) {
+ info("URL should be loaded in a new background tab");
+ is(gURLBar.value, "", "Urlbar reverted to original value");
+ ok(!gURLBar.focused, "Urlbar is no longer focused after urlbar command");
+ is(gBrowser.selectedTab, aTab, "Focus did not change to the new tab");
+
+ // Select the new background tab
+ gBrowser.selectedTab = gBrowser.selectedTab.nextSibling;
+ is(gURLBar.value, TEST_VALUE, "New URL is loaded in new tab");
+ }
+ },
+
+ { desc: "Simple return keypress",
+ event: {},
+ check: checkCurrent
+ },
+
+ { desc: "Alt+Return keypress in a blank tab",
+ event: { altKey: true },
+ check: checkCurrent
+ },
+
+ { desc: "Alt+Return keypress in a dirty tab",
+ event: { altKey: true },
+ check: checkNewTab,
+ startValue: START_VALUE
+ },
+
+ { desc: "Ctrl/Cmd+Return keypress",
+ event: { accelKey: true },
+ check: checkCurrent
+ }
+]
+
+let gGoButton = document.getElementById("urlbar-go-button");
+function triggerCommand(aClick, aEvent) {
+ gURLBar.value = TEST_VALUE;
+ gURLBar.focus();
+
+ if (aClick) {
+ is(gURLBar.getAttribute("pageproxystate"), "invalid",
+ "page proxy state must be invalid for go button to be visible");
+ EventUtils.synthesizeMouseAtCenter(gGoButton, aEvent);
+ }
+ else
+ EventUtils.synthesizeKey("VK_RETURN", aEvent);
+}
+
+/* Checks that the URL was loaded in the current tab */
+function checkCurrent(aTab) {
+ info("URL should be loaded in the current tab");
+ is(gURLBar.value, TEST_VALUE, "Urlbar still has the value we entered");
+ is(gFocusManager.focusedElement, null, "There should be no focused element");
+ is(gFocusManager.focusedWindow, gBrowser.contentWindow, "Content window should be focused");
+ is(gBrowser.selectedTab, aTab, "New URL was loaded in the current tab");
+}
+
+/* Checks that the URL was loaded in a new focused tab */
+function checkNewTab(aTab) {
+ info("URL should be loaded in a new focused tab");
+ is(gURLBar.value, TEST_VALUE, "Urlbar still has the value we entered");
+ is(gFocusManager.focusedElement, null, "There should be no focused element");
+ is(gFocusManager.focusedWindow, gBrowser.contentWindow, "Content window should be focused");
+ isnot(gBrowser.selectedTab, aTab, "New URL was loaded in a new tab");
+}
+
+function addPageShowListener(browser, cb, expectedURL) {
+ browser.addEventListener("pageshow", function pageShowListener() {
+ info("pageshow: " + browser.currentURI.spec);
+ if (expectedURL && browser.currentURI.spec != expectedURL)
+ return; // ignore pageshows for non-expected URLs
+ browser.removeEventListener("pageshow", pageShowListener, false);
+ cb();
+ });
+}
+
+function whenWindowActivated(win, cb) {
+ if (Services.focus.activeWindow == win) {
+ executeSoon(cb);
+ return;
+ }
+
+ win.addEventListener("activate", function onActivate() {
+ win.removeEventListener("activate", onActivate);
+ executeSoon(cb);
+ });
+}
+
+function BrowserWindowListener(aURL, aCallback) {
+ this.callback = aCallback;
+ this.url = aURL;
+}
+BrowserWindowListener.prototype = {
+ onOpenWindow: function(aXULWindow) {
+ let cb = () => this.callback(domwindow);
+ let domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+
+ let numWait = 2;
+ function maybeRunCallback() {
+ if (--numWait == 0)
+ cb();
+ }
+
+ whenWindowActivated(domwindow, maybeRunCallback);
+ whenDelayedStartupFinished(domwindow, maybeRunCallback);
+ },
+ onCloseWindow: function(aXULWindow) {},
+ onWindowTitleChange: function(aXULWindow, aNewTitle) {}
+}
diff --git a/browser/base/content/test/browser_locationBarExternalLoad.js b/browser/base/content/test/browser_locationBarExternalLoad.js
new file mode 100644
index 000000000..2bc88a989
--- /dev/null
+++ b/browser/base/content/test/browser_locationBarExternalLoad.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+ waitForExplicitFinish();
+
+ nextTest();
+}
+
+let urls = [
+ "javascript:'foopy';",
+ "data:text/html,<body>hi"
+];
+
+function urlEnter(url) {
+ gURLBar.value = url;
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_RETURN", {});
+}
+
+function urlClick(url) {
+ gURLBar.value = url;
+ gURLBar.focus();
+ let goButton = document.getElementById("urlbar-go-button");
+ EventUtils.synthesizeMouseAtCenter(goButton, {});
+}
+
+function nextTest() {
+ let url = urls.shift();
+ if (url) {
+ testURL(url, urlEnter, function () {
+ testURL(url, urlClick, nextTest);
+ });
+ }
+ else
+ finish();
+}
+
+function testURL(url, loadFunc, endFunc) {
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+ registerCleanupFunction(function () {
+ gBrowser.removeTab(tab);
+ });
+ addPageShowListener(function () {
+ let pagePrincipal = gBrowser.contentPrincipal;
+ loadFunc(url);
+
+ addPageShowListener(function () {
+ let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
+ is(fm.focusedElement, null, "should be no focused element");
+ is(fm.focusedWindow, gBrowser.contentWindow, "content window should be focused");
+
+ ok(!gBrowser.contentPrincipal.equals(pagePrincipal),
+ "load of " + url + " by " + loadFunc.name + " should produce a page with a different principal");
+ endFunc();
+ });
+ });
+}
+
+function addPageShowListener(func) {
+ gBrowser.selectedBrowser.addEventListener("pageshow", function loadListener() {
+ gBrowser.selectedBrowser.removeEventListener("pageshow", loadListener, false);
+ func();
+ });
+}
diff --git a/browser/base/content/test/browser_middleMouse_inherit.js b/browser/base/content/test/browser_middleMouse_inherit.js
new file mode 100644
index 000000000..891ea2ed0
--- /dev/null
+++ b/browser/base/content/test/browser_middleMouse_inherit.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const middleMousePastePref = "middlemouse.contentLoadURL";
+const autoScrollPref = "general.autoScroll";
+function test() {
+ waitForExplicitFinish();
+
+ Services.prefs.setBoolPref(middleMousePastePref, true);
+ Services.prefs.setBoolPref(autoScrollPref, false);
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(middleMousePastePref);
+ Services.prefs.clearUserPref(autoScrollPref);
+ gBrowser.removeTab(tab);
+ });
+
+ addPageShowListener(function () {
+ let pagePrincipal = gBrowser.contentPrincipal;
+
+ // copy javascript URI to the clipboard
+ let url = "javascript:1+1";
+ waitForClipboard(url,
+ function() {
+ Components.classes["@mozilla.org/widget/clipboardhelper;1"]
+ .getService(Components.interfaces.nsIClipboardHelper)
+ .copyString(url, document);
+ },
+ function () {
+ // Middle click on the content area
+ info("Middle clicking");
+ EventUtils.sendMouseEvent({type: "click", button: 1}, gBrowser);
+ },
+ function() {
+ ok(false, "Failed to copy URL to the clipboard");
+ finish();
+ }
+ );
+
+ addPageShowListener(function () {
+ is(gBrowser.currentURI.spec, url, "url loaded by middle click");
+ ok(!gBrowser.contentPrincipal.equals(pagePrincipal),
+ "middle click load of " + url + " should produce a page with a different principal");
+ finish();
+ });
+ });
+}
+
+function addPageShowListener(func) {
+ gBrowser.selectedBrowser.addEventListener("pageshow", function loadListener() {
+ gBrowser.selectedBrowser.removeEventListener("pageshow", loadListener, false);
+ func();
+ });
+}
diff --git a/browser/base/content/test/browser_minimize.js b/browser/base/content/test/browser_minimize.js
new file mode 100644
index 000000000..459f84b44
--- /dev/null
+++ b/browser/base/content/test/browser_minimize.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+function waitForActive() {
+ if (!gBrowser.docShell.isActive) {
+ executeSoon(waitForActive);
+ return;
+ }
+ is(gBrowser.docShell.isActive, true, "Docshell should be active again");
+ finish();
+}
+
+function waitForInactive() {
+ if (gBrowser.docShell.isActive) {
+ executeSoon(waitForInactive);
+ return;
+ }
+ is(gBrowser.docShell.isActive, false, "Docshell should be inactive");
+ window.restore();
+ waitForActive();
+}
+
+function test() {
+ registerCleanupFunction(function() {
+ window.restore();
+ });
+
+ waitForExplicitFinish();
+ is(gBrowser.docShell.isActive, true, "Docshell should be active");
+ window.minimize();
+ // XXX On Linux minimize/restore seem to be very very async, but
+ // our window.windowState changes sync.... so we can't rely on the
+ // latter correctly reflecting the state of the former. In
+ // particular, a restore() call before minimizing is done will not
+ // actually restore the window, but change the window state. As a
+ // result, just poll waiting for our expected isActive values.
+ waitForInactive();
+}
diff --git a/browser/base/content/test/browser_offlineQuotaNotification.js b/browser/base/content/test/browser_offlineQuotaNotification.js
new file mode 100644
index 000000000..a8aba6b97
--- /dev/null
+++ b/browser/base/content/test/browser_offlineQuotaNotification.js
@@ -0,0 +1,74 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Test offline quota warnings - must be run as a mochitest-browser test or
+// else the test runner gets in the way of notifications due to bug 857897.
+
+const URL = "http://mochi.test:8888/browser/browser/base/content/test/offlineQuotaNotification.html";
+
+registerCleanupFunction(function() {
+ // Clean up after ourself
+ let uri = Services.io.newURI(URL, null, null);
+ var principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(uri);
+ Services.perms.removeFromPrincipal(principal, "offline-app");
+ Services.prefs.clearUserPref("offline-apps.quota.warn");
+});
+
+// Check that the "preferences" UI is opened and showing which websites have
+// offline storage permissions - currently this is the "network" tab in the
+// "advanced" pane.
+function checkPreferences(prefsWin) {
+ // We expect a 'paneload' event for the 'advanced' pane, then
+ // a 'select' event on the 'network' tab inside that pane.
+ prefsWin.addEventListener("paneload", function paneload(evt) {
+ prefsWin.removeEventListener("paneload", paneload);
+ is(evt.target.id, "paneAdvanced", "advanced pane loaded");
+ let tabPanels = evt.target.getElementsByTagName("tabpanels")[0];
+ tabPanels.addEventListener("select", function tabselect() {
+ tabPanels.removeEventListener("select", tabselect);
+ is(tabPanels.selectedPanel.id, "networkPanel", "networkPanel is selected");
+ // all good, we are done.
+ prefsWin.close();
+ finish();
+ });
+ });
+}
+
+function test() {
+ waitForExplicitFinish();
+ gBrowser.selectedBrowser.addEventListener("load", function onload() {
+ gBrowser.selectedBrowser.removeEventListener("load", onload, true);
+ gBrowser.selectedBrowser.contentWindow.applicationCache.oncached = function() {
+ executeSoon(function() {
+ // We got cached - now we should have provoked the quota warning.
+ let notification = PopupNotifications.getNotification('offline-app-usage');
+ ok(notification, "have offline-app-usage notification");
+ // select the default action - this should cause the preferences
+ // window to open - which we track either via a window watcher (for
+ // the window-based prefs) or via an "Initialized" event (for
+ // in-content prefs.)
+ if (Services.prefs.getBoolPref("browser.preferences.inContent")) {
+ // Bug 881576 - ensure this works with inContent prefs.
+ todo(false, "Bug 881576 - this test needs to be updated for inContent prefs");
+ } else {
+ Services.ww.registerNotification(function wwobserver(aSubject, aTopic, aData) {
+ if (aTopic != "domwindowopened")
+ return;
+ Services.ww.unregisterNotification(wwobserver);
+ checkPreferences(aSubject);
+ });
+ PopupNotifications.panel.firstElementChild.button.click();
+ }
+ });
+ };
+ Services.prefs.setIntPref("offline-apps.quota.warn", 1);
+
+ // Click the notification panel's "Allow" button. This should kick
+ // off updates which will call our oncached handler above.
+ PopupNotifications.panel.firstElementChild.button.click();
+ }, true);
+
+ gBrowser.contentWindow.location = URL;
+}
diff --git a/browser/base/content/test/browser_overflowScroll.js b/browser/base/content/test/browser_overflowScroll.js
new file mode 100644
index 000000000..8c0eac709
--- /dev/null
+++ b/browser/base/content/test/browser_overflowScroll.js
@@ -0,0 +1,87 @@
+var tabstrip = gBrowser.tabContainer.mTabstrip;
+var scrollbox = tabstrip._scrollbox;
+var originalSmoothScroll = tabstrip.smoothScroll;
+var tabs = gBrowser.tabs;
+
+function rect(ele) ele.getBoundingClientRect();
+function width(ele) rect(ele).width;
+function left(ele) rect(ele).left;
+function right(ele) rect(ele).right;
+function isLeft(ele, msg) is(left(ele), left(scrollbox), msg);
+function isRight(ele, msg) is(right(ele), right(scrollbox), msg);
+function elementFromPoint(x) tabstrip._elementFromPoint(x);
+function nextLeftElement() elementFromPoint(left(scrollbox) - 1);
+function nextRightElement() elementFromPoint(right(scrollbox) + 1);
+function firstScrollable() tabs[gBrowser._numPinnedTabs];
+
+function test() {
+ requestLongerTimeout(2);
+ waitForExplicitFinish();
+
+ // If the previous (or more) test finished with cleaning up the tabs,
+ // there may be some pending animations. That can cause a failure of
+ // this tests, so, we should test this in another stack.
+ setTimeout(doTest, 0);
+}
+
+function doTest() {
+ tabstrip.smoothScroll = false;
+
+ var tabMinWidth = parseInt(getComputedStyle(gBrowser.selectedTab, null).minWidth);
+ var tabCountForOverflow = Math.ceil(width(tabstrip) / tabMinWidth * 3);
+ while (tabs.length < tabCountForOverflow)
+ gBrowser.addTab("about:blank", {skipAnimation: true});
+ gBrowser.pinTab(tabs[0]);
+
+ tabstrip.addEventListener("overflow", runOverflowTests, false);
+}
+
+function runOverflowTests(aEvent) {
+ if (aEvent.detail != 1)
+ return;
+
+ tabstrip.removeEventListener("overflow", runOverflowTests, false);
+
+ var upButton = tabstrip._scrollButtonUp;
+ var downButton = tabstrip._scrollButtonDown;
+ var element;
+
+ gBrowser.selectedTab = firstScrollable();
+ ok(left(scrollbox) <= left(firstScrollable()), "Selecting the first tab scrolls it into view " +
+ "(" + left(scrollbox) + " <= " + left(firstScrollable()) + ")");
+
+ element = nextRightElement();
+ EventUtils.synthesizeMouseAtCenter(downButton, {});
+ isRight(element, "Scrolled one tab to the right with a single click");
+
+ gBrowser.selectedTab = tabs[tabs.length - 1];
+ ok(right(gBrowser.selectedTab) <= right(scrollbox), "Selecting the last tab scrolls it into view " +
+ "(" + right(gBrowser.selectedTab) + " <= " + right(scrollbox) + ")");
+
+ element = nextLeftElement();
+ EventUtils.synthesizeMouse(upButton, 1, 1, {});
+ isLeft(element, "Scrolled one tab to the left with a single click");
+
+ element = elementFromPoint(left(scrollbox) - width(scrollbox));
+ EventUtils.synthesizeMouse(upButton, 1, 1, {clickCount: 2});
+ isLeft(element, "Scrolled one page of tabs with a double click");
+
+ EventUtils.synthesizeMouse(upButton, 1, 1, {clickCount: 3});
+ var firstScrollableLeft = left(firstScrollable());
+ ok(left(scrollbox) <= firstScrollableLeft, "Scrolled to the start with a triple click " +
+ "(" + left(scrollbox) + " <= " + firstScrollableLeft + ")");
+
+ for (var i = 2; i; i--)
+ EventUtils.synthesizeWheel(scrollbox, 1, 1, { deltaX: -1.0, deltaMode: WheelEvent.DOM_DELTA_LINE });
+ is(left(firstScrollable()), firstScrollableLeft, "Remained at the start with the mouse wheel");
+
+ element = nextRightElement();
+ EventUtils.synthesizeWheel(scrollbox, 1, 1, { deltaX: 1.0, deltaMode: WheelEvent.DOM_DELTA_LINE});
+ isRight(element, "Scrolled one tab to the right with the mouse wheel");
+
+ while (tabs.length > 1)
+ gBrowser.removeTab(tabs[0]);
+
+ tabstrip.smoothScroll = originalSmoothScroll;
+ finish();
+}
diff --git a/browser/base/content/test/browser_pageInfo.js b/browser/base/content/test/browser_pageInfo.js
new file mode 100644
index 000000000..c0159380c
--- /dev/null
+++ b/browser/base/content/test/browser_pageInfo.js
@@ -0,0 +1,38 @@
+function test() {
+ waitForExplicitFinish();
+
+ var pageInfo;
+
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", function loadListener() {
+ gBrowser.selectedBrowser.removeEventListener("load", loadListener, true);
+
+ Services.obs.addObserver(observer, "page-info-dialog-loaded", false);
+ pageInfo = BrowserPageInfo();
+ }, true);
+ content.location =
+ "https://example.com/browser/browser/base/content/test/feed_tab.html";
+
+ function observer(win, topic, data) {
+ Services.obs.removeObserver(observer, "page-info-dialog-loaded");
+ handlePageInfo();
+ }
+
+ function handlePageInfo() {
+ ok(pageInfo.document.getElementById("feedTab"), "Feed tab");
+ let feedListbox = pageInfo.document.getElementById("feedListbox");
+ ok(feedListbox, "Feed list");
+
+ var feedRowsNum = feedListbox.getRowCount();
+ is(feedRowsNum, 3, "Number of feeds listed");
+
+ for (var i = 0; i < feedRowsNum; i++) {
+ let feedItem = feedListbox.getItemAtIndex(i);
+ is(feedItem.getAttribute("name"), i + 1, "Feed name");
+ }
+
+ pageInfo.close();
+ gBrowser.removeCurrentTab();
+ finish();
+ }
+}
diff --git a/browser/base/content/test/browser_pageInfo_plugins.js b/browser/base/content/test/browser_pageInfo_plugins.js
new file mode 100644
index 000000000..58fd82586
--- /dev/null
+++ b/browser/base/content/test/browser_pageInfo_plugins.js
@@ -0,0 +1,187 @@
+let gHttpTestRoot = getRootDirectory(gTestPath).replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+let gPageInfo = null;
+let gNextTest = null;
+let gTestBrowser = null;
+let gPluginHost = Components.classes["@mozilla.org/plugin/host;1"]
+ .getService(Components.interfaces.nsIPluginHost);
+let gPermissionManager = Components.classes["@mozilla.org/permissionmanager;1"]
+ .getService(Components.interfaces.nsIPermissionManager);
+let gTestPermissionString = gPluginHost.getPermissionStringForType("application/x-test");
+let gSecondTestPermissionString = gPluginHost.getPermissionStringForType("application/x-second-test");
+
+function doOnPageLoad(url, continuation) {
+ gNextTest = continuation;
+ gTestBrowser.addEventListener("load", pageLoad, true);
+ gTestBrowser.contentWindow.location = url;
+}
+
+function pageLoad() {
+ gTestBrowser.removeEventListener("load", pageLoad);
+ // The plugin events are async dispatched and can come after the load event
+ // This just allows the events to fire before we then go on to test the states
+ executeSoon(gNextTest);
+}
+
+function doOnOpenPageInfo(continuation) {
+ Services.obs.addObserver(pageInfoObserve, "page-info-dialog-loaded", false);
+ gNextTest = continuation;
+ // An explanation: it looks like the test harness complains about leaked
+ // windows if we don't keep a reference to every window we've opened.
+ // So, don't reuse pointers to opened Page Info windows - simply append
+ // to this list.
+ gPageInfo = BrowserPageInfo(null, "permTab");
+}
+
+function pageInfoObserve(win, topic, data) {
+ Services.obs.removeObserver(pageInfoObserve, "page-info-dialog-loaded");
+ executeSoon(gNextTest);
+}
+
+function finishTest() {
+ gPermissionManager.remove("127.0.0.1:8888", gTestPermissionString);
+ gPermissionManager.remove("127.0.0.1:8888", gSecondTestPermissionString);
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ gBrowser.removeCurrentTab();
+ finish();
+}
+
+function test() {
+ waitForExplicitFinish();
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+ getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ gBrowser.selectedTab = gBrowser.addTab();
+ gTestBrowser = gBrowser.selectedBrowser;
+ gPermissionManager.remove("127.0.0.1:8888", gTestPermissionString);
+ gPermissionManager.remove("127.0.0.1:8888", gSecondTestPermissionString);
+ doOnPageLoad(gHttpTestRoot + "plugin_two_types.html", testPart1a);
+}
+
+// The first test plugin is CtP and the second test plugin is enabled.
+function testPart1a() {
+ let test = gTestBrowser.contentDocument.getElementById("test");
+ let objLoadingContent = test.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!objLoadingContent.activated, "part 1a: Test plugin should not be activated");
+ let secondtest = gTestBrowser.contentDocument.getElementById("secondtestA");
+ let objLoadingContent = secondtest.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.activated, "part 1a: Second Test plugin should be activated");
+
+ doOnOpenPageInfo(testPart1b);
+}
+
+function testPart1b() {
+ let testRadioGroup = gPageInfo.document.getElementById(gTestPermissionString + "RadioGroup");
+ let testRadioDefault = gPageInfo.document.getElementById(gTestPermissionString + "#0");
+
+ var qString = "#" + gTestPermissionString.replace(':', '\\:') + "\\#0";
+ is(testRadioGroup.selectedItem, testRadioDefault, "part 1b: Test radio group should be set to 'Default'");
+ let testRadioAllow = gPageInfo.document.getElementById(gTestPermissionString + "#1");
+ testRadioGroup.selectedItem = testRadioAllow;
+ testRadioAllow.doCommand();
+
+ let secondtestRadioGroup = gPageInfo.document.getElementById(gSecondTestPermissionString + "RadioGroup");
+ let secondtestRadioDefault = gPageInfo.document.getElementById(gSecondTestPermissionString + "#0");
+ is(secondtestRadioGroup.selectedItem, secondtestRadioDefault, "part 1b: Second Test radio group should be set to 'Default'");
+ let secondtestRadioAsk = gPageInfo.document.getElementById(gSecondTestPermissionString + "#3");
+ secondtestRadioGroup.selectedItem = secondtestRadioAsk;
+ secondtestRadioAsk.doCommand();
+
+ doOnPageLoad(gHttpTestRoot + "plugin_two_types.html", testPart2);
+}
+
+// Now, the Test plugin should be allowed, and the Test2 plugin should be CtP
+function testPart2() {
+ let test = gTestBrowser.contentDocument.getElementById("test").
+ QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(test.activated, "part 2: Test plugin should be activated");
+
+ let secondtest = gTestBrowser.contentDocument.getElementById("secondtestA").
+ QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!secondtest.activated, "part 2: Second Test plugin should not be activated");
+ is(secondtest.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY,
+ "part 2: Second test plugin should be click-to-play.");
+
+ let testRadioGroup = gPageInfo.document.getElementById(gTestPermissionString + "RadioGroup");
+ let testRadioAllow = gPageInfo.document.getElementById(gTestPermissionString + "#1");
+ is(testRadioGroup.selectedItem, testRadioAllow, "part 2: Test radio group should be set to 'Allow'");
+ let testRadioBlock = gPageInfo.document.getElementById(gTestPermissionString + "#2");
+ testRadioGroup.selectedItem = testRadioBlock;
+ testRadioBlock.doCommand();
+
+ let secondtestRadioGroup = gPageInfo.document.getElementById(gSecondTestPermissionString + "RadioGroup");
+ let secondtestRadioAsk = gPageInfo.document.getElementById(gSecondTestPermissionString + "#3");
+ is(secondtestRadioGroup.selectedItem, secondtestRadioAsk, "part 2: Second Test radio group should be set to 'Always Ask'");
+ let secondtestRadioBlock = gPageInfo.document.getElementById(gSecondTestPermissionString + "#2");
+ secondtestRadioGroup.selectedItem = secondtestRadioBlock;
+ secondtestRadioBlock.doCommand();
+
+ doOnPageLoad(gHttpTestRoot + "plugin_two_types.html", testPart3);
+}
+
+// Now, all the things should be blocked
+function testPart3() {
+ let test = gTestBrowser.contentDocument.getElementById("test").
+ QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!test.activated, "part 3: Test plugin should not be activated");
+ is(test.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_DISABLED,
+ "part 3: Test plugin should be marked as PLUGIN_DISABLED");
+
+ let secondtest = gTestBrowser.contentDocument.getElementById("secondtestA").
+ QueryInterface(Ci.nsIObjectLoadingContent);
+
+ ok(!secondtest.activated, "part 3: Second Test plugin should not be activated");
+ is(secondtest.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_DISABLED,
+ "part 3: Second test plugin should be marked as PLUGIN_DISABLED");
+
+ // reset permissions
+ gPermissionManager.remove("127.0.0.1:8888", gTestPermissionString);
+ gPermissionManager.remove("127.0.0.1:8888", gSecondTestPermissionString);
+ // check that changing the permissions affects the radio state in the
+ // open Page Info window
+ let testRadioGroup = gPageInfo.document.getElementById(gTestPermissionString + "RadioGroup");
+ let testRadioDefault = gPageInfo.document.getElementById(gTestPermissionString + "#0");
+ is(testRadioGroup.selectedItem, testRadioDefault, "part 3: Test radio group should be set to 'Default'");
+ let secondtestRadioGroup = gPageInfo.document.getElementById(gSecondTestPermissionString + "RadioGroup");
+ let secondtestRadioDefault = gPageInfo.document.getElementById(gSecondTestPermissionString + "#0");
+ is(secondtestRadioGroup.selectedItem, secondtestRadioDefault, "part 3: Second Test radio group should be set to 'Default'");
+
+ doOnPageLoad(gHttpTestRoot + "plugin_two_types.html", testPart4a);
+}
+
+// Now test that setting permission directly (as from the popup notification)
+// immediately influences Page Info.
+function testPart4a() {
+ // simulate "allow" from the doorhanger
+ gPermissionManager.add(gTestBrowser.currentURI, gTestPermissionString, Ci.nsIPermissionManager.ALLOW_ACTION);
+ gPermissionManager.add(gTestBrowser.currentURI, gSecondTestPermissionString, Ci.nsIPermissionManager.ALLOW_ACTION);
+
+ // check (again) that changing the permissions affects the radio state in the
+ // open Page Info window
+ let testRadioGroup = gPageInfo.document.getElementById(gTestPermissionString + "RadioGroup");
+ let testRadioAllow = gPageInfo.document.getElementById(gTestPermissionString + "#1");
+ is(testRadioGroup.selectedItem, testRadioAllow, "part 4a: Test radio group should be set to 'Allow'");
+ let secondtestRadioGroup = gPageInfo.document.getElementById(gSecondTestPermissionString + "RadioGroup");
+ let secondtestRadioAllow = gPageInfo.document.getElementById(gSecondTestPermissionString + "#1");
+ is(secondtestRadioGroup.selectedItem, secondtestRadioAllow, "part 4a: Second Test radio group should be set to 'Always Allow'");
+
+ // now close Page Info and see that it opens with the right settings
+ gPageInfo.close();
+ doOnOpenPageInfo(testPart4b);
+}
+
+// check that "always allow" resulted in the radio buttons getting set to allow
+function testPart4b() {
+ let testRadioGroup = gPageInfo.document.getElementById(gTestPermissionString + "RadioGroup");
+ let testRadioAllow = gPageInfo.document.getElementById(gTestPermissionString + "#1");
+ is(testRadioGroup.selectedItem, testRadioAllow, "part 4b: Test radio group should be set to 'Allow'");
+
+ let secondtestRadioGroup = gPageInfo.document.getElementById(gSecondTestPermissionString + "RadioGroup");
+ let secondtestRadioAllow = gPageInfo.document.getElementById(gSecondTestPermissionString + "#1");
+ is(secondtestRadioGroup.selectedItem, secondtestRadioAllow, "part 4b: Second Test radio group should be set to 'Allow'");
+
+ Services.prefs.setBoolPref("plugins.click_to_play", false);
+ gPageInfo.close();
+ finishTest();
+}
diff --git a/browser/base/content/test/browser_page_style_menu.js b/browser/base/content/test/browser_page_style_menu.js
new file mode 100644
index 000000000..99b1bcfc2
--- /dev/null
+++ b/browser/base/content/test/browser_page_style_menu.js
@@ -0,0 +1,67 @@
+function test() {
+ waitForExplicitFinish();
+
+ var tab = gBrowser.addTab();
+ gBrowser.selectedTab = tab;
+ tab.linkedBrowser.addEventListener("load", function () {
+ tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+ checkPageStyleMenu();
+ }, true);
+ let rootDir = getRootDirectory(gTestPath);
+ content.location = rootDir + "page_style_sample.html";
+}
+
+function checkPageStyleMenu() {
+ var menupopup = document.getElementById("pageStyleMenu")
+ .getElementsByTagName("menupopup")[0];
+ gPageStyleMenu.fillPopup(menupopup);
+
+ var items = [];
+ var current = menupopup.getElementsByTagName("menuseparator")[0];
+ while (current.nextSibling) {
+ current = current.nextSibling;
+ items.push(current);
+ }
+
+ var validLinks = 0;
+ Array.forEach(content.document.getElementsByTagName("link"), function (link) {
+ var title = link.getAttribute("title");
+ var rel = link.getAttribute("rel");
+ var media = link.getAttribute("media");
+ var idstring = "link " + (title ? title : "without title and") +
+ " with rel=\"" + rel + "\"" +
+ (media ? " and media=\"" + media + "\"" : "");
+
+ var item = items.filter(function (item) item.getAttribute("label") == title);
+ var found = item.length == 1;
+ var checked = found && (item[0].getAttribute("checked") == "true");
+
+ switch (link.getAttribute("data-state")) {
+ case "0":
+ ok(!found, idstring + " does not show up in page style menu");
+ break;
+ case "0-todo":
+ validLinks++;
+ todo(!found, idstring + " should not show up in page style menu");
+ ok(!checked, idstring + " is not selected");
+ break;
+ case "1":
+ validLinks++;
+ ok(found, idstring + " shows up in page style menu");
+ ok(!checked, idstring + " is not selected");
+ break;
+ case "2":
+ validLinks++;
+ ok(found, idstring + " shows up in page style menu");
+ ok(checked, idstring + " is selected");
+ break;
+ default:
+ throw "data-state attribute is missing or has invalid value";
+ }
+ });
+
+ is(validLinks, items.length, "all valid links found");
+
+ gBrowser.removeCurrentTab();
+ finish();
+}
diff --git a/browser/base/content/test/browser_pinnedTabs.js b/browser/base/content/test/browser_pinnedTabs.js
new file mode 100644
index 000000000..5dbe941a2
--- /dev/null
+++ b/browser/base/content/test/browser_pinnedTabs.js
@@ -0,0 +1,73 @@
+var tabs;
+
+function index(tab) Array.indexOf(gBrowser.tabs, tab);
+
+function indexTest(tab, expectedIndex, msg) {
+ var diag = "tab " + tab + " should be at index " + expectedIndex;
+ if (msg)
+ msg = msg + " (" + diag + ")";
+ else
+ msg = diag;
+ is(index(tabs[tab]), expectedIndex, msg);
+}
+
+function PinUnpinHandler(tab, eventName) {
+ this.eventCount = 0;
+ var self = this;
+ tab.addEventListener(eventName, function() {
+ tab.removeEventListener(eventName, arguments.callee, true);
+
+ self.eventCount++;
+ }, true);
+ gBrowser.tabContainer.addEventListener(eventName, function(e) {
+ gBrowser.tabContainer.removeEventListener(eventName, arguments.callee, true);
+
+ if (e.originalTarget == tab) {
+ self.eventCount++;
+ }
+ }, true);
+}
+
+function test() {
+ tabs = [gBrowser.selectedTab, gBrowser.addTab(), gBrowser.addTab(), gBrowser.addTab()];
+ indexTest(0, 0);
+ indexTest(1, 1);
+ indexTest(2, 2);
+ indexTest(3, 3);
+
+ var eh = new PinUnpinHandler(tabs[3], "TabPinned");
+ gBrowser.pinTab(tabs[3]);
+ is(eh.eventCount, 2, "TabPinned event should be fired");
+ indexTest(0, 1);
+ indexTest(1, 2);
+ indexTest(2, 3);
+ indexTest(3, 0);
+
+ eh = new PinUnpinHandler(tabs[1], "TabPinned");
+ gBrowser.pinTab(tabs[1]);
+ is(eh.eventCount, 2, "TabPinned event should be fired");
+ indexTest(0, 2);
+ indexTest(1, 1);
+ indexTest(2, 3);
+ indexTest(3, 0);
+
+ gBrowser.moveTabTo(tabs[3], 3);
+ indexTest(3, 1, "shouldn't be able to mix a pinned tab into normal tabs");
+
+ gBrowser.moveTabTo(tabs[2], 0);
+ indexTest(2, 2, "shouldn't be able to mix a normal tab into pinned tabs");
+
+ eh = new PinUnpinHandler(tabs[1], "TabUnpinned");
+ gBrowser.unpinTab(tabs[1]);
+ is(eh.eventCount, 2, "TabUnpinned event should be fired");
+ indexTest(1, 1, "unpinning a tab should move a tab to the start of normal tabs");
+
+ eh = new PinUnpinHandler(tabs[3], "TabUnpinned");
+ gBrowser.unpinTab(tabs[3]);
+ is(eh.eventCount, 2, "TabUnpinned event should be fired");
+ indexTest(3, 0, "unpinning a tab should move a tab to the start of normal tabs");
+
+ gBrowser.removeTab(tabs[1]);
+ gBrowser.removeTab(tabs[2]);
+ gBrowser.removeTab(tabs[3]);
+}
diff --git a/browser/base/content/test/browser_plainTextLinks.js b/browser/base/content/test/browser_plainTextLinks.js
new file mode 100644
index 000000000..4c7c8ee98
--- /dev/null
+++ b/browser/base/content/test/browser_plainTextLinks.js
@@ -0,0 +1,115 @@
+let doc, range, selection;
+function setSelection(el1, el2, index1, index2) {
+ while (el1.nodeType != Node.TEXT_NODE)
+ el1 = el1.firstChild;
+ while (el2.nodeType != Node.TEXT_NODE)
+ el2 = el2.firstChild;
+
+ selection.removeAllRanges();
+ range.setStart(el1, index1);
+ range.setEnd(el2, index2);
+ selection.addRange(range);
+}
+
+function initContextMenu(aNode) {
+ document.popupNode = aNode;
+ let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
+ let contextMenu = new nsContextMenu(contentAreaContextMenu);
+ return contextMenu;
+}
+
+function testExpected(expected, msg, aNode) {
+ let popupNode = aNode || doc.getElementsByTagName("DIV")[0];
+ initContextMenu(popupNode);
+ let linkMenuItem = document.getElementById("context-openlinkincurrent");
+ is(linkMenuItem.hidden, expected, msg);
+}
+
+function testLinkExpected(expected, msg, aNode) {
+ let popupNode = aNode || doc.getElementsByTagName("DIV")[0];
+ let contextMenu = initContextMenu(popupNode);
+ is(contextMenu.linkURL, expected, msg);
+}
+
+function runSelectionTests() {
+ let mainDiv = doc.createElement("div");
+ let div = doc.createElement("div");
+ let div2 = doc.createElement("div");
+ let span1 = doc.createElement("span");
+ let span2 = doc.createElement("span");
+ let span3 = doc.createElement("span");
+ let span4 = doc.createElement("span");
+ let p1 = doc.createElement("p");
+ let p2 = doc.createElement("p");
+ span1.textContent = "http://index.";
+ span2.textContent = "example.com example.com";
+ span3.textContent = " - Test";
+ span4.innerHTML = "<a href='http://www.example.com'>http://www.example.com/example</a>";
+ p1.textContent = "mailto:test.com ftp.example.com";
+ p2.textContent = "example.com -";
+ div.appendChild(span1);
+ div.appendChild(span2);
+ div.appendChild(span3);
+ div.appendChild(span4);
+ div.appendChild(p1);
+ div.appendChild(p2);
+ let p3 = doc.createElement("p");
+ p3.textContent = "main.example.com";
+ div2.appendChild(p3);
+ mainDiv.appendChild(div);
+ mainDiv.appendChild(div2);
+ doc.body.appendChild(mainDiv);
+ setSelection(span1.firstChild, span2.firstChild, 0, 11);
+ testExpected(false, "The link context menu should show for http://www.example.com");
+ setSelection(span1.firstChild, span2.firstChild, 7, 11);
+ testExpected(false, "The link context menu should show for www.example.com");
+ setSelection(span1.firstChild, span2.firstChild, 8, 11);
+ testExpected(true, "The link context menu should not show for ww.example.com");
+ setSelection(span2.firstChild, span2.firstChild, 0, 11);
+ testExpected(false, "The link context menu should show for example.com");
+ testLinkExpected("http://example.com/", "url for example.com selection should not prepend www");
+ setSelection(span2.firstChild, span2.firstChild, 11, 23);
+ testExpected(false, "The link context menu should show for example.com");
+ setSelection(span2.firstChild, span2.firstChild, 0, 10);
+ testExpected(true, "Link options should not show for selection that's not at a word boundary");
+ setSelection(span2.firstChild, span3.firstChild, 12, 7);
+ testExpected(true, "Link options should not show for selection that has whitespace");
+ setSelection(span2.firstChild, span2.firstChild, 12, 19);
+ testExpected(true, "Link options should not show unless a url is selected");
+ setSelection(p1.firstChild, p1.firstChild, 0, 15);
+ testExpected(true, "Link options should not show for mailto: links");
+ setSelection(p1.firstChild, p1.firstChild, 16, 31);
+ testExpected(false, "Link options should show for ftp.example.com");
+ testLinkExpected("ftp://ftp.example.com/", "ftp.example.com should be preceeded with ftp://");
+ setSelection(p2.firstChild, p2.firstChild, 0, 14);
+ testExpected(false, "Link options should show for www.example.com ");
+ selection.selectAllChildren(div2);
+ testExpected(false, "Link options should show for triple-click selections");
+ selection.selectAllChildren(span4);
+ testLinkExpected("http://www.example.com/", "Linkified text should open the correct link", span4.firstChild);
+
+ mainDiv.innerHTML = "(open-suse.ru)";
+ setSelection(mainDiv, mainDiv, 1, 13);
+ testExpected(false, "Link options should show for open-suse.ru");
+ testLinkExpected("http://open-suse.ru/", "Linkified text should open the correct link");
+ setSelection(mainDiv, mainDiv, 1, 14);
+ testExpected(true, "Link options should not show for 'open-suse.ru)'");
+
+ gBrowser.removeCurrentTab();
+ finish();
+}
+
+function test() {
+ waitForExplicitFinish();
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", function() {
+ gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+ doc = content.document;
+ range = doc.createRange();
+ selection = content.getSelection();
+ waitForFocus(runSelectionTests, content);
+ }, true);
+
+ content.location =
+ "data:text/html;charset=UTF-8,Test For Non-Hyperlinked url selection";
+}
diff --git a/browser/base/content/test/browser_pluginCrashCommentAndURL.js b/browser/base/content/test/browser_pluginCrashCommentAndURL.js
new file mode 100644
index 000000000..1243daaca
--- /dev/null
+++ b/browser/base/content/test/browser_pluginCrashCommentAndURL.js
@@ -0,0 +1,154 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const CRASH_URL = "http://example.com/browser/browser/base/content/test/pluginCrashCommentAndURL.html";
+
+const SERVER_URL = "http://example.com/browser/toolkit/crashreporter/test/browser/crashreport.sjs";
+
+function test() {
+ // Crashing the plugin takes up a lot of time, so extend the test timeout.
+ requestLongerTimeout(runs.length);
+ waitForExplicitFinish();
+
+ // The test harness sets MOZ_CRASHREPORTER_NO_REPORT, which disables plugin
+ // crash reports. This test needs them enabled. The test also needs a mock
+ // report server, and fortunately one is already set up by toolkit/
+ // crashreporter/test/Makefile.in. Assign its URL to MOZ_CRASHREPORTER_URL,
+ // which CrashSubmit.jsm uses as a server override.
+ let env = Cc["@mozilla.org/process/environment;1"].
+ getService(Components.interfaces.nsIEnvironment);
+ let noReport = env.get("MOZ_CRASHREPORTER_NO_REPORT");
+ let serverURL = env.get("MOZ_CRASHREPORTER_URL");
+ env.set("MOZ_CRASHREPORTER_NO_REPORT", "");
+ env.set("MOZ_CRASHREPORTER_URL", SERVER_URL);
+
+ let tab = gBrowser.loadOneTab("about:blank", { inBackground: false });
+ let browser = gBrowser.getBrowserForTab(tab);
+ browser.addEventListener("PluginCrashed", onCrash, false);
+ Services.obs.addObserver(onSubmitStatus, "crash-report-status", false);
+
+ registerCleanupFunction(function cleanUp() {
+ env.set("MOZ_CRASHREPORTER_NO_REPORT", noReport);
+ env.set("MOZ_CRASHREPORTER_URL", serverURL);
+ gBrowser.selectedBrowser.removeEventListener("PluginCrashed", onCrash,
+ false);
+ Services.obs.removeObserver(onSubmitStatus, "crash-report-status");
+ gBrowser.removeCurrentTab();
+ });
+
+ doNextRun();
+}
+
+let runs = [
+ {
+ shouldSubmissionUIBeVisible: true,
+ comment: "",
+ urlOptIn: false,
+ },
+ {
+ shouldSubmissionUIBeVisible: true,
+ comment: "a test comment",
+ urlOptIn: true,
+ },
+ {
+ width: 300,
+ height: 300,
+ shouldSubmissionUIBeVisible: false,
+ },
+];
+
+let currentRun = null;
+
+function doNextRun() {
+ try {
+ if (!runs.length) {
+ finish();
+ return;
+ }
+ currentRun = runs.shift();
+ let args = ["width", "height"].reduce(function (memo, arg) {
+ if (arg in currentRun)
+ memo[arg] = currentRun[arg];
+ return memo;
+ }, {});
+ gBrowser.loadURI(CRASH_URL + "?" +
+ encodeURIComponent(JSON.stringify(args)));
+ // And now wait for the crash.
+ }
+ catch (err) {
+ failWithException(err);
+ finish();
+ }
+}
+
+function onCrash() {
+ try {
+ let plugin = gBrowser.contentDocument.getElementById("plugin");
+ let elt = gPluginHandler.getPluginUI.bind(gPluginHandler, plugin);
+ let style =
+ gBrowser.contentWindow.getComputedStyle(elt("msg msgPleaseSubmit"));
+ is(style.display,
+ currentRun.shouldSubmissionUIBeVisible ? "block" : "none",
+ "Submission UI visibility should be correct");
+ if (!currentRun.shouldSubmissionUIBeVisible) {
+ // Done with this run.
+ doNextRun();
+ return;
+ }
+ elt("submitComment").value = currentRun.comment;
+ elt("submitURLOptIn").checked = currentRun.urlOptIn;
+ elt("submitButton").click();
+ // And now wait for the submission status notification.
+ }
+ catch (err) {
+ failWithException(err);
+ doNextRun();
+ }
+}
+
+function onSubmitStatus(subj, topic, data) {
+ try {
+ // Wait for success or failed, doesn't matter which.
+ if (data != "success" && data != "failed")
+ return;
+
+ let extra = getPropertyBagValue(subj.QueryInterface(Ci.nsIPropertyBag),
+ "extra");
+ ok(extra instanceof Ci.nsIPropertyBag, "Extra data should be property bag");
+
+ let val = getPropertyBagValue(extra, "PluginUserComment");
+ if (currentRun.comment)
+ is(val, currentRun.comment,
+ "Comment in extra data should match comment in textbox");
+ else
+ ok(val === undefined,
+ "Comment should be absent from extra data when textbox is empty");
+
+ val = getPropertyBagValue(extra, "PluginContentURL");
+ if (currentRun.urlOptIn)
+ is(val, gBrowser.currentURI.spec,
+ "URL in extra data should match browser URL when opt-in checked");
+ else
+ ok(val === undefined,
+ "URL should be absent from extra data when opt-in not checked");
+ }
+ catch (err) {
+ failWithException(err);
+ }
+ doNextRun();
+}
+
+function getPropertyBagValue(bag, key) {
+ try {
+ var val = bag.getProperty(key);
+ }
+ catch (e if e.result == Cr.NS_ERROR_FAILURE) {}
+ return val;
+}
+
+function failWithException(err) {
+ ok(false, "Uncaught exception: " + err + "\n" + err.stack);
+}
diff --git a/browser/base/content/test/browser_pluginnotification.js b/browser/base/content/test/browser_pluginnotification.js
new file mode 100644
index 000000000..1b4fc86cc
--- /dev/null
+++ b/browser/base/content/test/browser_pluginnotification.js
@@ -0,0 +1,841 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir;
+const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+
+var gTestBrowser = null;
+var gNextTest = null;
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+// This listens for the next opened tab and checks it is of the right url.
+// opencallback is called when the new tab is fully loaded
+// closecallback is called when the tab is closed
+function TabOpenListener(url, opencallback, closecallback) {
+ this.url = url;
+ this.opencallback = opencallback;
+ this.closecallback = closecallback;
+
+ gBrowser.tabContainer.addEventListener("TabOpen", this, false);
+}
+
+TabOpenListener.prototype = {
+ url: null,
+ opencallback: null,
+ closecallback: null,
+ tab: null,
+ browser: null,
+
+ handleEvent: function(event) {
+ if (event.type == "TabOpen") {
+ gBrowser.tabContainer.removeEventListener("TabOpen", this, false);
+ this.tab = event.originalTarget;
+ this.browser = this.tab.linkedBrowser;
+ gBrowser.addEventListener("pageshow", this, false);
+ } else if (event.type == "pageshow") {
+ if (event.target.location.href != this.url)
+ return;
+ gBrowser.removeEventListener("pageshow", this, false);
+ this.tab.addEventListener("TabClose", this, false);
+ var url = this.browser.contentDocument.location.href;
+ is(url, this.url, "Should have opened the correct tab");
+ this.opencallback(this.tab, this.browser.contentWindow);
+ } else if (event.type == "TabClose") {
+ if (event.originalTarget != this.tab)
+ return;
+ this.tab.removeEventListener("TabClose", this, false);
+ this.opencallback = null;
+ this.tab = null;
+ this.browser = null;
+ // Let the window close complete
+ executeSoon(this.closecallback);
+ this.closecallback = null;
+ }
+ }
+};
+
+function test() {
+ waitForExplicitFinish();
+ requestLongerTimeout(2);
+ registerCleanupFunction(function() {
+ clearAllPluginPermissions();
+ Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+ getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ });
+ Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+ var plugin = getTestPlugin();
+ plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+
+ var newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+ gTestBrowser.addEventListener("load", pageLoad, true);
+ prepareTest(runAfterPluginBindingAttached(test1), gTestRoot + "plugin_unknown.html");
+}
+
+function finishTest() {
+ clearAllPluginPermissions();
+ gTestBrowser.removeEventListener("load", pageLoad, true);
+ gBrowser.removeCurrentTab();
+ window.focus();
+ finish();
+}
+
+function pageLoad() {
+ // The plugin events are async dispatched and can come after the load event
+ // This just allows the events to fire before we then go on to test the states
+ executeSoon(gNextTest);
+}
+
+function prepareTest(nextTest, url) {
+ gNextTest = nextTest;
+ gTestBrowser.contentWindow.location = url;
+}
+
+// Due to layout being async, "PluginBindAttached" may trigger later.
+// This wraps a function to force a layout flush, thus triggering it,
+// and schedules the function execution so they're definitely executed
+// afterwards.
+function runAfterPluginBindingAttached(func) {
+ return function() {
+ let doc = gTestBrowser.contentDocument;
+ let elems = doc.getElementsByTagName('embed');
+ if (elems.length < 1) {
+ elems = doc.getElementsByTagName('object');
+ }
+ elems[0].clientTop;
+ executeSoon(func);
+ };
+}
+
+// Tests a page with an unknown plugin in it.
+function test1() {
+ ok(PopupNotifications.getNotification("plugins-not-found", gTestBrowser), "Test 1, Should have displayed the missing plugin notification");
+ ok(gTestBrowser.missingPlugins, "Test 1, Should be a missing plugin list");
+ ok(gTestBrowser.missingPlugins.has("application/x-unknown"), "Test 1, Should know about application/x-unknown");
+ ok(!gTestBrowser.missingPlugins.has("application/x-test"), "Test 1, Should not know about application/x-test");
+
+ var pluginNode = gTestBrowser.contentDocument.getElementById("unknown");
+ ok(pluginNode, "Test 1, Found plugin in page");
+ var objLoadingContent = pluginNode.QueryInterface(Ci.nsIObjectLoadingContent);
+ is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED, "Test 1, plugin fallback type should be PLUGIN_UNSUPPORTED");
+
+ var plugin = getTestPlugin();
+ ok(plugin, "Should have a test plugin");
+ plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ prepareTest(runAfterPluginBindingAttached(test2), gTestRoot + "plugin_test.html");
+}
+
+// Tests a page with a working plugin in it.
+function test2() {
+ ok(!PopupNotifications.getNotification("plugins-not-found", gTestBrowser), "Test 2, Should not have displayed the missing plugin notification");
+ ok(!gTestBrowser.missingPlugins, "Test 2, Should not be a missing plugin list");
+
+ var plugin = getTestPlugin();
+ ok(plugin, "Should have a test plugin");
+ plugin.enabledState = Ci.nsIPluginTag.STATE_DISABLED;
+ prepareTest(runAfterPluginBindingAttached(test3), gTestRoot + "plugin_test.html");
+}
+
+// Tests a page with a disabled plugin in it.
+function test3() {
+ ok(!PopupNotifications.getNotification("plugins-not-found", gTestBrowser), "Test 3, Should not have displayed the missing plugin notification");
+ ok(!gTestBrowser.missingPlugins, "Test 3, Should not be a missing plugin list");
+
+ new TabOpenListener("about:addons", test4, prepareTest5);
+
+ var pluginNode = gTestBrowser.contentDocument.getElementById("test");
+ ok(pluginNode, "Test 3, Found plugin in page");
+ var objLoadingContent = pluginNode.QueryInterface(Ci.nsIObjectLoadingContent);
+ is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_DISABLED, "Test 3, plugin fallback type should be PLUGIN_DISABLED");
+ var manageLink = gTestBrowser.contentDocument.getAnonymousElementByAttribute(pluginNode, "anonid", "managePluginsLink");
+ ok(manageLink, "Test 3, found 'manage' link in plugin-problem binding");
+
+ EventUtils.synthesizeMouseAtCenter(manageLink, {}, gTestBrowser.contentWindow);
+}
+
+function test4(tab, win) {
+ is(win.wrappedJSObject.gViewController.currentViewId, "addons://list/plugin", "Test 4, Should have displayed the plugins pane");
+ gBrowser.removeTab(tab);
+}
+
+function prepareTest5() {
+ info("prepareTest5");
+ var plugin = getTestPlugin();
+ plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ setAndUpdateBlocklist(gHttpTestRoot + "blockPluginHard.xml",
+ function() {
+ info("prepareTest5 callback");
+ prepareTest(runAfterPluginBindingAttached(test5), gTestRoot + "plugin_test.html");
+ });
+}
+
+// Tests a page with a blocked plugin in it.
+function test5() {
+ info("test5");
+ ok(!PopupNotifications.getNotification("plugins-not-found", gTestBrowser), "Test 5, Should not have displayed the missing plugin notification");
+ let notification = PopupNotifications.getNotification("click-to-play-plugins");
+ ok(notification, "Test 5: There should be a plugin notification for blocked plugins");
+ ok(notification.dismissed, "Test 5: The plugin notification should be dismissed by default");
+
+ notification.reshow();
+ is(notification.options.centerActions.length, 1, "Test 5: Only the blocked plugin should be present in the notification");
+ ok(PopupNotifications.panel.firstChild._buttonContainer.hidden, "Part 5: The blocked plugins notification should not have any buttons visible.");
+
+ ok(!gTestBrowser.missingPlugins, "Test 5, Should not be a missing plugin list");
+ var pluginNode = gTestBrowser.contentDocument.getElementById("test");
+ ok(pluginNode, "Test 5, Found plugin in page");
+ var objLoadingContent = pluginNode.QueryInterface(Ci.nsIObjectLoadingContent);
+ is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED, "Test 5, plugin fallback type should be PLUGIN_BLOCKLISTED");
+
+ prepareTest(runAfterPluginBindingAttached(test6), gTestRoot + "plugin_both.html");
+}
+
+// Tests a page with a blocked and unknown plugin in it.
+function test6() {
+ ok(PopupNotifications.getNotification("plugins-not-found", gTestBrowser), "Test 6, Should have displayed the missing plugin notification");
+ ok(gTestBrowser.missingPlugins, "Test 6, Should be a missing plugin list");
+ ok(gTestBrowser.missingPlugins.has("application/x-unknown"), "Test 6, Should know about application/x-unknown");
+ ok(!gTestBrowser.missingPlugins.has("application/x-test"), "Test 6, application/x-test should not be a missing plugin");
+
+ prepareTest(runAfterPluginBindingAttached(test7), gTestRoot + "plugin_both2.html");
+}
+
+// Tests a page with a blocked and unknown plugin in it (alternate order to above).
+function test7() {
+ ok(PopupNotifications.getNotification("plugins-not-found", gTestBrowser), "Test 7, Should have displayed the missing plugin notification");
+ ok(gTestBrowser.missingPlugins, "Test 7, Should be a missing plugin list");
+ ok(gTestBrowser.missingPlugins.has("application/x-unknown"), "Test 7, Should know about application/x-unknown");
+ ok(!gTestBrowser.missingPlugins.has("application/x-test"), "Test 7, application/x-test should not be a missing plugin");
+
+ var plugin = getTestPlugin();
+ plugin.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+ getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+
+ setAndUpdateBlocklist(gHttpTestRoot + "blockNoPlugins.xml", function() {
+ prepareTest(runAfterPluginBindingAttached(test8), gTestRoot + "plugin_test.html");
+ });
+}
+
+// Tests a page with a working plugin that is click-to-play
+function test8() {
+ ok(!PopupNotifications.getNotification("plugins-not-found", gTestBrowser), "Test 8, Should not have displayed the missing plugin notification");
+ ok(!gTestBrowser.missingPlugins, "Test 8, Should not be a missing plugin list");
+ ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser), "Test 8, Should have a click-to-play notification");
+
+ var pluginNode = gTestBrowser.contentDocument.getElementById("test");
+ ok(pluginNode, "Test 8, Found plugin in page");
+ var objLoadingContent = pluginNode.QueryInterface(Ci.nsIObjectLoadingContent);
+ is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, "Test 8, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
+
+ prepareTest(runAfterPluginBindingAttached(test11a), gTestRoot + "plugin_test3.html");
+}
+
+// Tests 9 & 10 removed
+
+// Tests that the going back will reshow the notification for click-to-play plugins (part 1/4)
+function test11a() {
+ var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "Test 11a, Should have a click-to-play notification");
+
+ prepareTest(test11b, "about:blank");
+}
+
+// Tests that the going back will reshow the notification for click-to-play plugins (part 2/4)
+function test11b() {
+ var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(!popupNotification, "Test 11b, Should not have a click-to-play notification");
+
+ Services.obs.addObserver(test11c, "PopupNotifications-updateNotShowing", false);
+ gTestBrowser.contentWindow.history.back();
+}
+
+// Tests that the going back will reshow the notification for click-to-play plugins (part 3/4)
+function test11c() {
+ Services.obs.removeObserver(test11c, "PopupNotifications-updateNotShowing");
+ var condition = function() PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ waitForCondition(condition, test11d, "Test 11c, waited too long for click-to-play-plugin notification");
+}
+
+// Tests that the going back will reshow the notification for click-to-play plugins (part 4/4)
+function test11d() {
+ var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "Test 11d, Should have a click-to-play notification");
+
+ prepareTest(runAfterPluginBindingAttached(test12a), gHttpTestRoot + "plugin_clickToPlayAllow.html");
+}
+
+// Tests that the "Allow Always" permission works for click-to-play plugins
+function test12a() {
+ var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "Test 12a, Should have a click-to-play notification");
+ var plugin = gTestBrowser.contentDocument.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!objLoadingContent.activated, "Test 12a, Plugin should not be activated");
+
+ // Simulate clicking the "Allow Always" button.
+ popupNotification.reshow();
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ var condition = function() objLoadingContent.activated;
+ waitForCondition(condition, test12b, "Test 12a, Waited too long for plugin to activate");
+}
+
+function test12b() {
+ var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "Test 12d, Should have a click-to-play notification");
+ prepareTest(runAfterPluginBindingAttached(test12c), gHttpTestRoot + "plugin_two_types.html");
+}
+
+// Test that the "Always" permission, when set for just the Test plugin,
+// does not also allow the Second Test plugin.
+function test12c() {
+ var popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "Test 12d, Should have a click-to-play notification");
+ var test = gTestBrowser.contentDocument.getElementById("test");
+ var secondtestA = gTestBrowser.contentDocument.getElementById("secondtestA");
+ var secondtestB = gTestBrowser.contentDocument.getElementById("secondtestB");
+ ok(test.activated, "Test 12d, Test plugin should be activated");
+ ok(!secondtestA.activated, "Test 12d, Second Test plugin (A) should not be activated");
+ ok(!secondtestB.activated, "Test 12d, Second Test plugin (B) should not be activated");
+
+ clearAllPluginPermissions();
+ var plugin = getTestPlugin();
+ plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ prepareTest(test14, gTestRoot + "plugin_test2.html");
+}
+
+// Test 13 removed
+
+// Tests that the plugin's "activated" property is true for working plugins with click-to-play disabled.
+function test14() {
+ var plugin = gTestBrowser.contentDocument.getElementById("test1");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.activated, "Test 14, Plugin should be activated");
+
+ var plugin = getTestPlugin();
+ plugin.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+ getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+ prepareTest(runAfterPluginBindingAttached(test15), gTestRoot + "plugin_alternate_content.html");
+}
+
+// Tests that the overlay is shown instead of alternate content when
+// plugins are click to play
+function test15() {
+ var plugin = gTestBrowser.contentDocument.getElementById("test");
+ var doc = gTestBrowser.contentDocument;
+ var mainBox = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox");
+ ok(mainBox, "Test 15, Plugin with id=" + plugin.id + " overlay should exist");
+
+ prepareTest(runAfterPluginBindingAttached(test17), gTestRoot + "plugin_bug749455.html");
+}
+
+// Test 16 removed
+
+// Tests that mContentType is used for click-to-play plugins, and not the
+// inspected type.
+function test17() {
+ var clickToPlayNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(clickToPlayNotification, "Test 17, Should have a click-to-play notification");
+ var missingNotification = PopupNotifications.getNotification("missing-plugins", gTestBrowser);
+ ok(!missingNotification, "Test 17, Should not have a missing plugin notification");
+
+ setAndUpdateBlocklist(gHttpTestRoot + "blockPluginVulnerableUpdatable.xml",
+ function() {
+ prepareTest(runAfterPluginBindingAttached(test18a), gHttpTestRoot + "plugin_test.html");
+ });
+}
+
+// Tests a vulnerable, updatable plugin
+function test18a() {
+ var clickToPlayNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(clickToPlayNotification, "Test 18a, Should have a click-to-play notification");
+ var doc = gTestBrowser.contentDocument;
+ var plugin = doc.getElementById("test");
+ ok(plugin, "Test 18a, Found plugin in page");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE, "Test 18a, plugin fallback type should be PLUGIN_VULNERABLE_UPDATABLE");
+ ok(!objLoadingContent.activated, "Test 18a, Plugin should not be activated");
+ var overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox");
+ ok(overlay.style.visibility != "hidden", "Test 18a, Plugin overlay should exist, not be hidden");
+ var updateLink = doc.getAnonymousElementByAttribute(plugin, "anonid", "checkForUpdatesLink");
+ ok(updateLink.style.visibility != "hidden", "Test 18a, Plugin should have an update link");
+
+ var tabOpenListener = new TabOpenListener(Services.urlFormatter.formatURLPref("plugins.update.url"), false, false);
+ tabOpenListener.handleEvent = function(event) {
+ if (event.type == "TabOpen") {
+ gBrowser.tabContainer.removeEventListener("TabOpen", this, false);
+ this.tab = event.originalTarget;
+ ok(event.target.label == this.url, "Test 18a, Update link should open up the plugin check page");
+ gBrowser.removeTab(this.tab);
+ test18b();
+ }
+ };
+ EventUtils.synthesizeMouseAtCenter(updateLink, {}, gTestBrowser.contentWindow);
+}
+
+function test18b() {
+ // clicking the update link should not activate the plugin
+ var doc = gTestBrowser.contentDocument;
+ var plugin = doc.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!objLoadingContent.activated, "Test 18b, Plugin should not be activated");
+ var overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox");
+ ok(overlay.style.visibility != "hidden", "Test 18b, Plugin overlay should exist, not be hidden");
+
+ setAndUpdateBlocklist(gHttpTestRoot + "blockPluginVulnerableNoUpdate.xml",
+ function() {
+ prepareTest(runAfterPluginBindingAttached(test18c), gHttpTestRoot + "plugin_test.html");
+ });
+}
+
+// Tests a vulnerable plugin with no update
+function test18c() {
+ var clickToPlayNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(clickToPlayNotification, "Test 18c, Should have a click-to-play notification");
+ var doc = gTestBrowser.contentDocument;
+ var plugin = doc.getElementById("test");
+ ok(plugin, "Test 18c, Found plugin in page");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE, "Test 18c, plugin fallback type should be PLUGIN_VULNERABLE_NO_UPDATE");
+ ok(!objLoadingContent.activated, "Test 18c, Plugin should not be activated");
+ var overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox");
+ ok(overlay.style.visibility != "hidden", "Test 18c, Plugin overlay should exist, not be hidden");
+ var updateLink = doc.getAnonymousElementByAttribute(plugin, "anonid", "checkForUpdatesLink");
+ ok(updateLink.style.display != "block", "Test 18c, Plugin should not have an update link");
+
+ // check that click "Always allow" works with blocklisted plugins
+ clickToPlayNotification.reshow();
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ var condition = function() objLoadingContent.activated;
+ waitForCondition(condition, test18d, "Test 18d, Waited too long for plugin to activate");
+}
+
+// continue testing "Always allow"
+function test18d() {
+ var plugin = gTestBrowser.contentDocument.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.activated, "Test 18d, Plugin should be activated");
+
+ prepareTest(test18e, gHttpTestRoot + "plugin_test.html");
+}
+
+// continue testing "Always allow"
+function test18e() {
+ var plugin = gTestBrowser.contentDocument.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.activated, "Test 18e, Plugin should be activated");
+
+ clearAllPluginPermissions();
+ prepareTest(runAfterPluginBindingAttached(test18f), gHttpTestRoot + "plugin_test.html");
+}
+
+// clicking the in-content overlay of a vulnerable plugin should bring
+// up the notification and not directly activate the plugin
+function test18f() {
+ var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 18f, Should have a click-to-play notification");
+ ok(notification.dismissed, "Test 18f, notification should start dismissed");
+ var plugin = gTestBrowser.contentDocument.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!objLoadingContent.activated, "Test 18f, Plugin should not be activated");
+
+ // XXXBAD: this code doesn't do what you think it does! it is actually
+ // observing the "removed" event of the old notification, since we create
+ // a *new* one when the plugin is clicked.
+ notification.options.eventCallback = function() { executeSoon(test18g); };
+ EventUtils.synthesizeMouseAtCenter(plugin, {}, gTestBrowser.contentWindow);
+}
+
+function test18g() {
+ var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 18g, Should have a click-to-play notification");
+ ok(!notification.dismissed, "Test 18g, notification should be open");
+ notification.options.eventCallback = null;
+ var plugin = gTestBrowser.contentDocument.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!objLoadingContent.activated, "Test 18g, Plugin should not be activated");
+
+ setAndUpdateBlocklist(gHttpTestRoot + "blockNoPlugins.xml",
+ function() {
+ resetBlocklist();
+ prepareTest(runAfterPluginBindingAttached(test19a), gTestRoot + "plugin_test.html");
+ });
+}
+
+// Tests that clicking the icon of the overlay activates the doorhanger
+function test19a() {
+ var doc = gTestBrowser.contentDocument;
+ var plugin = doc.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!objLoadingContent.activated, "Test 19a, Plugin should not be activated");
+ ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed, "Test 19a, Doorhanger should start out dismissed");
+
+ var icon = doc.getAnonymousElementByAttribute(plugin, "class", "icon");
+ EventUtils.synthesizeMouseAtCenter(icon, {}, gTestBrowser.contentWindow);
+ let condition = function() !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed;
+ waitForCondition(condition, test19b, "Test 19a, Waited too long for doorhanger to activate");
+}
+
+function test19b() {
+ prepareTest(runAfterPluginBindingAttached(test19c), gTestRoot + "plugin_test.html");
+}
+
+// Tests that clicking the text of the overlay activates the plugin
+function test19c() {
+ var doc = gTestBrowser.contentDocument;
+ var plugin = doc.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!objLoadingContent.activated, "Test 19c, Plugin should not be activated");
+
+ ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed, "Test 19c, Doorhanger should start out dismissed");
+
+ var text = doc.getAnonymousElementByAttribute(plugin, "class", "msg msgClickToPlay");
+ EventUtils.synthesizeMouseAtCenter(text, {}, gTestBrowser.contentWindow);
+ let condition = function() !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed;
+ waitForCondition(condition, test19d, "Test 19c, Waited too long for doorhanger to activate");
+}
+
+function test19d() {
+ prepareTest(runAfterPluginBindingAttached(test19e), gTestRoot + "plugin_test.html");
+}
+
+// Tests that clicking the box of the overlay activates the doorhanger
+// (just to be thorough)
+function test19e() {
+ var doc = gTestBrowser.contentDocument;
+ var plugin = doc.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!objLoadingContent.activated, "Test 19e, Plugin should not be activated");
+
+ ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed, "Test 19e, Doorhanger should start out dismissed");
+
+ EventUtils.synthesizeMouse(plugin, 50, 50, {}, gTestBrowser.contentWindow);
+ let condition = function() !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed;
+ waitForCondition(condition, test19f, "Test 19e, Waited too long for plugin to activate");
+}
+
+function test19f() {
+ prepareTest(test20a, gTestRoot + "plugin_hidden_to_visible.html");
+}
+
+// Tests that a plugin in a div that goes from style="display: none" to
+// "display: block" can be clicked to activate.
+function test20a() {
+ var clickToPlayNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(!clickToPlayNotification, "Test 20a, Should not have a click-to-play notification");
+ var doc = gTestBrowser.contentDocument;
+ var plugin = doc.getElementById("plugin");
+ var mainBox = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox");
+ ok(mainBox, "Test 20a, plugin overlay should not be null");
+ var pluginRect = mainBox.getBoundingClientRect();
+ ok(pluginRect.width == 0, "Test 20a, plugin should have an overlay with 0px width");
+ ok(pluginRect.height == 0, "Test 20a, plugin should have an overlay with 0px height");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!objLoadingContent.activated, "Test 20a, plugin should not be activated");
+ var div = doc.getElementById("container");
+ ok(div.style.display == "none", "Test 20a, container div should be display: none");
+
+ div.style.display = "block";
+ var condition = function() {
+ var pluginRect = mainBox.getBoundingClientRect();
+ return (pluginRect.width == 200);
+ }
+ waitForCondition(condition, test20b, "Test 20a, Waited too long for plugin to become visible");
+}
+
+function test20b() {
+ var clickToPlayNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(clickToPlayNotification, "Test 20b, Should now have a click-to-play notification");
+ var doc = gTestBrowser.contentDocument;
+ var plugin = doc.getElementById("plugin");
+ var pluginRect = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox").getBoundingClientRect();
+ ok(pluginRect.width == 200, "Test 20b, plugin should have an overlay with 200px width");
+ ok(pluginRect.height == 200, "Test 20b, plugin should have an overlay with 200px height");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!objLoadingContent.activated, "Test 20b, plugin should not be activated");
+
+ ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed, "Test 20b, Doorhanger should start out dismissed");
+
+ EventUtils.synthesizeMouseAtCenter(plugin, {}, gTestBrowser.contentWindow);
+ let condition = function() !PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser).dismissed;
+ waitForCondition(condition, test20c, "Test 20b, Waited too long for plugin to activate");
+}
+
+function test20c() {
+ PopupNotifications.panel.firstChild._primaryButton.click();
+ var doc = gTestBrowser.contentDocument;
+ var plugin = doc.getElementById("plugin");
+ let condition = function() plugin.activated;
+ waitForCondition(condition, test20d, "Test 20c", "Waiting for plugin to activate");
+}
+
+function test20d() {
+ var doc = gTestBrowser.contentDocument;
+ var plugin = doc.getElementById("plugin");
+ var pluginRect = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox").getBoundingClientRect();
+ ok(pluginRect.width == 0, "Test 20d, plugin should have click-to-play overlay with zero width");
+ ok(pluginRect.height == 0, "Test 20d, plugin should have click-to-play overlay with zero height");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.activated, "Test 20d, plugin should be activated");
+
+ clearAllPluginPermissions();
+
+ prepareTest(runAfterPluginBindingAttached(test21a), gTestRoot + "plugin_two_types.html");
+}
+
+// Test having multiple different types of plugin on one page
+function test21a() {
+ var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 21a, Should have a click-to-play notification");
+
+ var doc = gTestBrowser.contentDocument;
+ var ids = ["test", "secondtestA", "secondtestB"];
+ for (var id of ids) {
+ var plugin = doc.getElementById(id);
+ var rect = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox").getBoundingClientRect();
+ ok(rect.width == 200, "Test 21a, Plugin with id=" + plugin.id + " overlay rect should have 200px width before being clicked");
+ ok(rect.height == 200, "Test 21a, Plugin with id=" + plugin.id + " overlay rect should have 200px height before being clicked");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!objLoadingContent.activated, "Test 21a, Plugin with id=" + plugin.id + " should not be activated");
+ }
+
+ // we have to actually show the panel to get the bindings to instantiate
+ notification.reshow();
+ is(notification.options.centerActions.length, 2, "Test 21a, Should have two types of plugin in the notification");
+
+ var centerAction = null;
+ for (var action of notification.options.centerActions) {
+ if (action.pluginName == "Test") {
+ centerAction = action;
+ break;
+ }
+ }
+ ok(centerAction, "Test 21b, found center action for the Test plugin");
+
+ var centerItem = null;
+ for (var item of PopupNotifications.panel.firstChild.childNodes) {
+ is(item.value, "block", "Test 21b, all plugins should start out blocked");
+ if (item.action == centerAction) {
+ centerItem = item;
+ break;
+ }
+ }
+ ok(centerItem, "Test 21b, found center item for the Test plugin");
+
+ // "click" the button to activate the Test plugin
+ centerItem.value = "allownow";
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ var doc = gTestBrowser.contentDocument;
+ var plugin = doc.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ var condition = function() objLoadingContent.activated;
+ waitForCondition(condition, test21c, "Test 21b, Waited too long for plugin to activate");
+}
+
+function test21c() {
+ var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 21c, Should have a click-to-play notification");
+
+ notification.reshow();
+ ok(notification.options.centerActions.length == 2, "Test 21c, Should have one type of plugin in the notification");
+
+ var doc = gTestBrowser.contentDocument;
+ var plugin = doc.getElementById("test");
+ var rect = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox").getBoundingClientRect();
+ ok(rect.width == 0, "Test 21c, Plugin with id=" + plugin.id + " overlay rect should have 0px width after being clicked");
+ ok(rect.height == 0, "Test 21c, Plugin with id=" + plugin.id + " overlay rect should have 0px height after being clicked");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.activated, "Test 21c, Plugin with id=" + plugin.id + " should be activated");
+
+ var ids = ["secondtestA", "secondtestB"];
+ for (var id of ids) {
+ var plugin = doc.getElementById(id);
+ var rect = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox").getBoundingClientRect();
+ ok(rect.width == 200, "Test 21c, Plugin with id=" + plugin.id + " overlay rect should have 200px width before being clicked");
+ ok(rect.height == 200, "Test 21c, Plugin with id=" + plugin.id + " overlay rect should have 200px height before being clicked");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(!objLoadingContent.activated, "Test 21c, Plugin with id=" + plugin.id + " should not be activated");
+ }
+
+ var centerAction = null;
+ for (var action of notification.options.centerActions) {
+ if (action.pluginName == "Second Test") {
+ centerAction = action;
+ break;
+ }
+ }
+ ok(centerAction, "Test 21d, found center action for the Second Test plugin");
+
+ var centerItem = null;
+ for (var item of PopupNotifications.panel.firstChild.childNodes) {
+ if (item.action == centerAction) {
+ is(item.value, "block", "Test 21d, test plugin 2 should start blocked");
+ centerItem = item;
+ break;
+ }
+ else {
+ is(item.value, "allownow", "Test 21d, test plugin should be enabled");
+ }
+ }
+ ok(centerItem, "Test 21d, found center item for the Second Test plugin");
+
+ // "click" the button to activate the Second Test plugins
+ centerItem.value = "allownow";
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ var doc = gTestBrowser.contentDocument;
+ var plugin = doc.getElementById("secondtestA");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ var condition = function() objLoadingContent.activated;
+ waitForCondition(condition, test21e, "Test 21d, Waited too long for plugin to activate");
+}
+
+function test21e() {
+ var doc = gTestBrowser.contentDocument;
+ var ids = ["test", "secondtestA", "secondtestB"];
+ for (var id of ids) {
+ var plugin = doc.getElementById(id);
+ var rect = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox").getBoundingClientRect();
+ ok(rect.width == 0, "Test 21e, Plugin with id=" + plugin.id + " overlay rect should have 0px width after being clicked");
+ ok(rect.height == 0, "Test 21e, Plugin with id=" + plugin.id + " overlay rect should have 0px height after being clicked");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.activated, "Test 21e, Plugin with id=" + plugin.id + " should be activated");
+ }
+
+ getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+ getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+
+ clearAllPluginPermissions();
+
+ prepareTest(runAfterPluginBindingAttached(test22), gTestRoot + "plugin_test.html");
+}
+
+// Tests that a click-to-play plugin retains its activated state upon reloading
+function test22() {
+ ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser), "Test 22, Should have a click-to-play notification");
+
+ // Plugin should start as CTP
+ var pluginNode = gTestBrowser.contentDocument.getElementById("test");
+ ok(pluginNode, "Test 22, Found plugin in page");
+ var objLoadingContent = pluginNode.QueryInterface(Ci.nsIObjectLoadingContent);
+ is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, "Test 22, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
+
+ // Activate
+ objLoadingContent.playPlugin();
+ is(objLoadingContent.displayedType, Ci.nsIObjectLoadingContent.TYPE_PLUGIN, "Test 22, plugin should have started");
+ ok(pluginNode.activated, "Test 22, plugin should be activated");
+
+ // Reload plugin
+ var oldVal = pluginNode.getObjectValue();
+ pluginNode.src = pluginNode.src;
+ is(objLoadingContent.displayedType, Ci.nsIObjectLoadingContent.TYPE_PLUGIN, "Test 22, Plugin should have retained activated state");
+ ok(pluginNode.activated, "Test 22, plugin should have remained activated");
+ // Sanity, ensure that we actually reloaded the instance, since this behavior might change in the future.
+ var pluginsDiffer;
+ try {
+ pluginNode.checkObjectValue(oldVal);
+ } catch (e) {
+ pluginsDiffer = true;
+ }
+ ok(pluginsDiffer, "Test 22, plugin should have reloaded");
+
+ prepareTest(runAfterPluginBindingAttached(test23), gTestRoot + "plugin_test.html");
+}
+
+// Tests that a click-to-play plugin resets its activated state when changing types
+function test23() {
+ ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser), "Test 23, Should have a click-to-play notification");
+
+ // Plugin should start as CTP
+ var pluginNode = gTestBrowser.contentDocument.getElementById("test");
+ ok(pluginNode, "Test 23, Found plugin in page");
+ var objLoadingContent = pluginNode.QueryInterface(Ci.nsIObjectLoadingContent);
+ is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, "Test 23, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
+
+ // Activate
+ objLoadingContent.playPlugin();
+ is(objLoadingContent.displayedType, Ci.nsIObjectLoadingContent.TYPE_PLUGIN, "Test 23, plugin should have started");
+ ok(pluginNode.activated, "Test 23, plugin should be activated");
+
+ // Reload plugin (this may need RunSoon() in the future when plugins change state asynchronously)
+ pluginNode.type = null;
+ // We currently don't properly change state just on type change,
+ // so rebind the plugin to tree. bug 767631
+ pluginNode.parentNode.appendChild(pluginNode);
+ is(objLoadingContent.displayedType, Ci.nsIObjectLoadingContent.TYPE_NULL, "Test 23, plugin should be unloaded");
+ pluginNode.type = "application/x-test";
+ pluginNode.parentNode.appendChild(pluginNode);
+ is(objLoadingContent.displayedType, Ci.nsIObjectLoadingContent.TYPE_NULL, "Test 23, Plugin should not have activated");
+ is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, "Test 23, Plugin should be click-to-play");
+ ok(!pluginNode.activated, "Test 23, plugin node should not be activated");
+
+ prepareTest(runAfterPluginBindingAttached(test24a), gHttpTestRoot + "plugin_test.html");
+}
+
+// Test that "always allow"-ing a plugin will not allow it when it becomes
+// blocklisted.
+function test24a() {
+ var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 24a, Should have a click-to-play notification");
+ var plugin = gTestBrowser.contentDocument.getElementById("test");
+ ok(plugin, "Test 24a, Found plugin in page");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, "Test 24a, Plugin should be click-to-play");
+ ok(!objLoadingContent.activated, "Test 24a, plugin should not be activated");
+
+ // simulate "always allow"
+ notification.reshow();
+ PopupNotifications.panel.firstChild._primaryButton.click();
+ prepareTest(test24b, gHttpTestRoot + "plugin_test.html");
+}
+
+// did the "always allow" work as intended?
+function test24b() {
+ var plugin = gTestBrowser.contentDocument.getElementById("test");
+ ok(plugin, "Test 24b, Found plugin in page");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.activated, "Test 24b, plugin should be activated");
+ setAndUpdateBlocklist(gHttpTestRoot + "blockPluginVulnerableUpdatable.xml",
+ function() {
+ prepareTest(runAfterPluginBindingAttached(test24c), gHttpTestRoot + "plugin_test.html");
+ });
+}
+
+// the plugin is now blocklisted, so it should not automatically load
+function test24c() {
+ var notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(notification, "Test 24c, Should have a click-to-play notification");
+ var plugin = gTestBrowser.contentDocument.getElementById("test");
+ ok(plugin, "Test 24c, Found plugin in page");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_UPDATABLE, "Test 24c, Plugin should be vulnerable/updatable");
+ ok(!objLoadingContent.activated, "Test 24c, plugin should not be activated");
+
+ // simulate "always allow"
+ notification.reshow();
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ prepareTest(test24d, gHttpTestRoot + "plugin_test.html");
+}
+
+// We should still be able to always allow a plugin after we've seen that it's
+// blocklisted.
+function test24d() {
+ var plugin = gTestBrowser.contentDocument.getElementById("test");
+ ok(plugin, "Test 24d, Found plugin in page");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.activated, "Test 24d, plugin should be activated");
+
+ // this resets the vulnerable plugin permission
+ setAndUpdateBlocklist(gHttpTestRoot + "blockNoPlugins.xml",
+ function() {
+ clearAllPluginPermissions();
+ resetBlocklist();
+ finishTest();
+ });
+}
diff --git a/browser/base/content/test/browser_pluginplaypreview.js b/browser/base/content/test/browser_pluginplaypreview.js
new file mode 100644
index 000000000..d1d8a53fb
--- /dev/null
+++ b/browser/base/content/test/browser_pluginplaypreview.js
@@ -0,0 +1,317 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir;
+
+var gTestBrowser = null;
+var gNextTest = null;
+var gNextTestSkip = 0;
+var gPlayPreviewPluginActualEvents = 0;
+var gPlayPreviewPluginExpectedEvents = 1;
+
+var gPlayPreviewRegistration = null;
+
+function registerPlayPreview(mimeType, targetUrl) {
+
+ function StreamConverterFactory() {}
+ StreamConverterFactory.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory]),
+ _targetConstructor: null,
+
+ register: function register(targetConstructor) {
+ this._targetConstructor = targetConstructor;
+ var proto = targetConstructor.prototype;
+ var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+ registrar.registerFactory(proto.classID, proto.classDescription,
+ proto.contractID, this);
+ },
+
+ unregister: function unregister() {
+ var proto = this._targetConstructor.prototype;
+ var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+ registrar.unregisterFactory(proto.classID, this);
+ this._targetConstructor = null;
+ },
+
+ // nsIFactory
+ createInstance: function createInstance(aOuter, iid) {
+ if (aOuter !== null)
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ return (new (this._targetConstructor)).QueryInterface(iid);
+ },
+
+ // nsIFactory
+ lockFactory: function lockFactory(lock) {
+ // No longer used as of gecko 1.7.
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ }
+ };
+
+ function OverlayStreamConverter() {}
+ OverlayStreamConverter.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsISupports,
+ Ci.nsIStreamConverter,
+ Ci.nsIStreamListener,
+ Ci.nsIRequestObserver
+ ]),
+
+ classID: Components.ID('{4c6030f7-e20a-264f-0f9b-ada3a9e97384}'),
+ classDescription: 'overlay-test-data Component',
+ contractID: '@mozilla.org/streamconv;1?from=application/x-moz-playpreview&to=*/*',
+
+ // nsIStreamConverter::convert
+ convert: function(aFromStream, aFromType, aToType, aCtxt) {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ // nsIStreamConverter::asyncConvertData
+ asyncConvertData: function(aFromType, aToType, aListener, aCtxt) {
+ var isValidRequest = false;
+ try {
+ var request = aCtxt;
+ request.QueryInterface(Ci.nsIChannel);
+ var spec = request.URI.spec;
+ var expectedSpec = 'data:application/x-moz-playpreview;,' + mimeType;
+ isValidRequest = (spec == expectedSpec);
+ } catch (e) { }
+ if (!isValidRequest)
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+
+ // Store the listener passed to us
+ this.listener = aListener;
+ },
+
+ // nsIStreamListener::onDataAvailable
+ onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
+ // Do nothing since all the data loading is handled by the viewer.
+ ok(false, "onDataAvailable should not be called");
+ },
+
+ // nsIRequestObserver::onStartRequest
+ onStartRequest: function(aRequest, aContext) {
+
+ // Setup the request so we can use it below.
+ aRequest.QueryInterface(Ci.nsIChannel);
+ // Cancel the request so the viewer can handle it.
+ aRequest.cancel(Cr.NS_BINDING_ABORTED);
+
+ // Create a new channel that is viewer loaded as a resource.
+ var ioService = Services.io;
+ var channel = ioService.newChannel(targetUrl, null, null);
+ channel.asyncOpen(this.listener, aContext);
+ },
+
+ // nsIRequestObserver::onStopRequest
+ onStopRequest: function(aRequest, aContext, aStatusCode) {
+ // Do nothing.
+ }
+ };
+
+ var ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+ ph.registerPlayPreviewMimeType(mimeType, true); // ignoring CTP rules
+
+ var factory = new StreamConverterFactory();
+ factory.register(OverlayStreamConverter);
+
+ return (gPlayPreviewRegistration = {
+ unregister: function() {
+ ph.unregisterPlayPreviewMimeType(mimeType);
+ factory.unregister();
+ gPlayPreviewRegistration = null;
+ }
+ });
+}
+
+function unregisterPlayPreview() {
+ gPlayPreviewRegistration.unregister();
+}
+
+Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+
+function test() {
+ waitForExplicitFinish();
+ registerCleanupFunction(function() {
+ if (gPlayPreviewRegistration)
+ gPlayPreviewRegistration.unregister();
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ var plugin = getTestPlugin();
+ plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ });
+
+ var newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+ gTestBrowser.addEventListener("load", pageLoad, true);
+ gTestBrowser.addEventListener("PluginBindingAttached", handleBindingAttached, true, true);
+
+ registerPlayPreview('application/x-test', 'about:');
+ prepareTest(test1a, gTestRoot + "plugin_test.html", 1);
+}
+
+function finishTest() {
+ gTestBrowser.removeEventListener("load", pageLoad, true);
+ gTestBrowser.removeEventListener("PluginBindingAttached", handleBindingAttached, true, true);
+ gBrowser.removeCurrentTab();
+ window.focus();
+ finish();
+}
+
+function handleBindingAttached(evt) {
+ if (evt.target instanceof Ci.nsIObjectLoadingContent &&
+ evt.target.pluginFallbackType == Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW)
+ gPlayPreviewPluginActualEvents++;
+}
+
+function pageLoad() {
+ // The plugin events are async dispatched and can come after the load event
+ // This just allows the events to fire before we then go on to test the states
+
+ // iframe might triggers load event as well, making sure we skip some to let
+ // all iframes on the page be loaded as well
+ if (gNextTestSkip) {
+ gNextTestSkip--;
+ return;
+ }
+ executeSoon(gNextTest);
+}
+
+function prepareTest(nextTest, url, skip) {
+ gNextTest = nextTest;
+ gNextTestSkip = skip;
+ gTestBrowser.contentWindow.location = url;
+}
+
+// Tests a page with normal play preview registration (1/2)
+function test1a() {
+ var notificationBox = gBrowser.getNotificationBox(gTestBrowser);
+ ok(!notificationBox.getNotificationWithValue("missing-plugins"), "Test 1a, Should not have displayed the missing plugin notification");
+ ok(!notificationBox.getNotificationWithValue("blocked-plugins"), "Test 1a, Should not have displayed the blocked plugin notification");
+
+ var pluginInfo = getTestPlugin();
+ ok(pluginInfo, "Should have a test plugin");
+
+ var doc = gTestBrowser.contentDocument;
+ var plugin = doc.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW, "Test 1a, plugin fallback type should be PLUGIN_PLAY_PREVIEW");
+ ok(!objLoadingContent.activated, "Test 1a, Plugin should not be activated");
+
+ var overlay = doc.getAnonymousElementByAttribute(plugin, "class", "previewPluginContent");
+ ok(overlay, "Test 1a, the overlay div is expected");
+
+ var iframe = overlay.getElementsByClassName("previewPluginContentFrame")[0];
+ ok(iframe && iframe.localName == "iframe", "Test 1a, the overlay iframe is expected");
+ var iframeHref = iframe.contentWindow.location.href;
+ ok(iframeHref == "about:", "Test 1a, the overlay about: content is expected");
+
+ var rect = iframe.getBoundingClientRect();
+ ok(rect.width == 200, "Test 1a, Plugin with id=" + plugin.id + " overlay rect should have 200px width before being replaced by actual plugin");
+ ok(rect.height == 200, "Test 1a, Plugin with id=" + plugin.id + " overlay rect should have 200px height before being replaced by actual plugin");
+
+ var e = overlay.ownerDocument.createEvent("CustomEvent");
+ e.initCustomEvent("MozPlayPlugin", true, true, null);
+ overlay.dispatchEvent(e);
+ var condition = function() objLoadingContent.activated;
+ waitForCondition(condition, test1b, "Test 1a, Waited too long for plugin to stop play preview");
+}
+
+// Tests that activating via MozPlayPlugin through the notification works (part 2/2)
+function test1b() {
+ var plugin = gTestBrowser.contentDocument.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.activated, "Test 1b, Plugin should be activated");
+
+ is(gPlayPreviewPluginActualEvents, gPlayPreviewPluginExpectedEvents,
+ "There should be exactly one PluginPlayPreview event");
+
+ unregisterPlayPreview();
+
+ prepareTest(test2, gTestRoot + "plugin_test.html");
+}
+
+// Tests a page with a working plugin in it -- the mime type was just unregistered.
+function test2() {
+ var plugin = gTestBrowser.contentDocument.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.activated, "Test 2, Plugin should be activated");
+
+ registerPlayPreview('application/x-unknown', 'about:');
+
+ prepareTest(test3, gTestRoot + "plugin_test.html");
+}
+
+// Tests a page with a working plugin in it -- diffent play preview type is reserved.
+function test3() {
+ var plugin = gTestBrowser.contentDocument.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.activated, "Test 3, Plugin should be activated");
+
+ unregisterPlayPreview();
+
+ registerPlayPreview('application/x-test', 'about:');
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ var plugin = getTestPlugin();
+ plugin.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+ prepareTest(test4a, gTestRoot + "plugin_test.html", 1);
+}
+
+// Test a fallback to the click-to-play
+function test4a() {
+ var doc = gTestBrowser.contentDocument;
+ var plugin = doc.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW, "Test 4a, plugin fallback type should be PLUGIN_PLAY_PREVIEW");
+ ok(!objLoadingContent.activated, "Test 4a, Plugin should not be activated");
+
+ var overlay = doc.getAnonymousElementByAttribute(plugin, "class", "previewPluginContent");
+ ok(overlay, "Test 4a, the overlay div is expected");
+
+ var e = overlay.ownerDocument.createEvent("CustomEvent");
+ e.initCustomEvent("MozPlayPlugin", true, true, true);
+ overlay.dispatchEvent(e);
+ var condition = function() objLoadingContent.pluginFallbackType == Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY;
+ waitForCondition(condition, test4b, "Test 4a, Waited too long for plugin to stop play preview");
+}
+
+function test4b() {
+ var doc = gTestBrowser.contentDocument;
+ var plugin = doc.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.pluginFallbackType != Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW, "Test 4b, plugin fallback type should not be PLUGIN_PLAY_PREVIEW");
+ ok(!objLoadingContent.activated, "Test 4b, Plugin should not be activated");
+
+ prepareTest(test5a, gTestRoot + "plugin_test.html", 1);
+}
+
+// Test a bypass of the click-to-play
+function test5a() {
+ var doc = gTestBrowser.contentDocument;
+ var plugin = doc.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW, "Test 5a, plugin fallback type should be PLUGIN_PLAY_PREVIEW");
+ ok(!objLoadingContent.activated, "Test 5a, Plugin should not be activated");
+
+ var overlay = doc.getAnonymousElementByAttribute(plugin, "class", "previewPluginContent");
+ ok(overlay, "Test 5a, the overlay div is expected");
+
+ var e = overlay.ownerDocument.createEvent("CustomEvent");
+ e.initCustomEvent("MozPlayPlugin", true, true, false);
+ overlay.dispatchEvent(e);
+ var condition = function() objLoadingContent.activated;
+ waitForCondition(condition, test5b, "Test 5a, Waited too long for plugin to stop play preview");
+}
+
+function test5b() {
+ var doc = gTestBrowser.contentDocument;
+ var plugin = doc.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.activated, "Test 5b, Plugin should be activated");
+
+ finishTest();
+}
+
diff --git a/browser/base/content/test/browser_pluginplaypreview2.js b/browser/base/content/test/browser_pluginplaypreview2.js
new file mode 100644
index 000000000..972e62a8d
--- /dev/null
+++ b/browser/base/content/test/browser_pluginplaypreview2.js
@@ -0,0 +1,179 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir;
+
+var gTestBrowser = null;
+var gNextTest = null;
+var gNextTestSkip = 0;
+var gPlayPreviewPluginActualEvents = 0;
+var gPlayPreviewPluginExpectedEvents = 1;
+
+var gPlayPreviewRegistration = null;
+
+function registerPlayPreview(mimeType, targetUrl) {
+ var ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+ ph.registerPlayPreviewMimeType(mimeType, false, targetUrl);
+
+ return (gPlayPreviewRegistration = {
+ unregister: function() {
+ ph.unregisterPlayPreviewMimeType(mimeType);
+ gPlayPreviewRegistration = null;
+ }
+ });
+}
+
+function unregisterPlayPreview() {
+ gPlayPreviewRegistration.unregister();
+}
+
+Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+
+function test() {
+ waitForExplicitFinish();
+ registerCleanupFunction(function() {
+ if (gPlayPreviewRegistration)
+ gPlayPreviewRegistration.unregister();
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ var plugin = getTestPlugin();
+ plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ });
+
+ var newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+ gTestBrowser.addEventListener("load", pageLoad, true);
+ gTestBrowser.addEventListener("PluginBindingAttached", handleBindingAttached, true, true);
+
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ var plugin = getTestPlugin();
+ plugin.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+
+ registerPlayPreview('application/x-test', 'about:');
+ prepareTest(test1a, gTestRoot + "plugin_test.html", 1);
+}
+
+function finishTest() {
+ gTestBrowser.removeEventListener("load", pageLoad, true);
+ gTestBrowser.removeEventListener("PluginBindingAttached", handleBindingAttached, true, true);
+ gBrowser.removeCurrentTab();
+ window.focus();
+ finish();
+}
+
+function handleBindingAttached(evt) {
+ if (evt.target instanceof Ci.nsIObjectLoadingContent &&
+ evt.target.pluginFallbackType == Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW)
+ gPlayPreviewPluginActualEvents++;
+}
+
+function pageLoad() {
+ // The plugin events are async dispatched and can come after the load event
+ // This just allows the events to fire before we then go on to test the states
+
+ // iframe might triggers load event as well, making sure we skip some to let
+ // all iframes on the page be loaded as well
+ if (gNextTestSkip) {
+ gNextTestSkip--;
+ return;
+ }
+ executeSoon(gNextTest);
+}
+
+function prepareTest(nextTest, url, skip) {
+ gNextTest = nextTest;
+ gNextTestSkip = skip;
+ gTestBrowser.contentWindow.location = url;
+}
+
+// Tests a page with normal play preview registration (1/2)
+function test1a() {
+ var notificationBox = gBrowser.getNotificationBox(gTestBrowser);
+ ok(!notificationBox.getNotificationWithValue("missing-plugins"), "Test 1a, Should not have displayed the missing plugin notification");
+ ok(!notificationBox.getNotificationWithValue("blocked-plugins"), "Test 1a, Should not have displayed the blocked plugin notification");
+
+ var pluginInfo = getTestPlugin();
+ ok(pluginInfo, "Should have a test plugin");
+
+ var doc = gTestBrowser.contentDocument;
+ var plugin = doc.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW, "Test 1a, plugin fallback type should be PLUGIN_PLAY_PREVIEW");
+ ok(!objLoadingContent.activated, "Test 1a, Plugin should not be activated");
+
+ var overlay = doc.getAnonymousElementByAttribute(plugin, "class", "previewPluginContent");
+ ok(overlay, "Test 1a, the overlay div is expected");
+
+ var iframe = overlay.getElementsByClassName("previewPluginContentFrame")[0];
+ ok(iframe && iframe.localName == "iframe", "Test 1a, the overlay iframe is expected");
+ var iframeHref = iframe.contentWindow.location.href;
+ ok(iframeHref == "about:", "Test 1a, the overlay about: content is expected");
+
+ var rect = iframe.getBoundingClientRect();
+ ok(rect.width == 200, "Test 1a, Plugin with id=" + plugin.id + " overlay rect should have 200px width before being replaced by actual plugin");
+ ok(rect.height == 200, "Test 1a, Plugin with id=" + plugin.id + " overlay rect should have 200px height before being replaced by actual plugin");
+
+ var e = overlay.ownerDocument.createEvent("CustomEvent");
+ e.initCustomEvent("MozPlayPlugin", true, true, null);
+ overlay.dispatchEvent(e);
+ var condition = function() objLoadingContent.activated;
+ waitForCondition(condition, test1b, "Test 1a, Waited too long for plugin to stop play preview");
+}
+
+// Tests that activating via MozPlayPlugin through the notification works (part 2/2)
+function test1b() {
+ var plugin = gTestBrowser.contentDocument.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.activated, "Test 1b, Plugin should be activated");
+
+ is(gPlayPreviewPluginActualEvents, gPlayPreviewPluginExpectedEvents,
+ "There should be exactly one PluginPlayPreview event");
+
+ unregisterPlayPreview();
+
+ prepareTest(test2, gTestRoot + "plugin_test.html");
+}
+
+// Tests a page with a working plugin in it -- the mime type was just unregistered.
+function test2() {
+ var doc = gTestBrowser.contentDocument;
+ var plugin = doc.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.pluginFallbackType != Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW, "Test 2, plugin fallback type should not be PLUGIN_PLAY_PREVIEW");
+ ok(!objLoadingContent.activated, "Test 2, Plugin should not be activated");
+
+ registerPlayPreview('application/x-unknown', 'about:');
+
+ prepareTest(test3, gTestRoot + "plugin_test.html");
+}
+
+// Tests a page with a working plugin in it -- diffent play preview type is reserved.
+function test3() {
+ var doc = gTestBrowser.contentDocument;
+ var plugin = doc.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.pluginFallbackType != Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW, "Test 3, plugin fallback type should not be PLUGIN_PLAY_PREVIEW");
+ ok(!objLoadingContent.activated, "Test 3, Plugin should not be activated");
+
+ unregisterPlayPreview();
+
+ registerPlayPreview('application/x-test', 'about:');
+ Services.prefs.setBoolPref("plugins.click_to_play", false);
+ var plugin = getTestPlugin();
+ plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ prepareTest(test4, gTestRoot + "plugin_test.html");
+}
+
+// Tests a page with a working plugin in it -- click-to-play is off
+function test4() {
+ var plugin = gTestBrowser.contentDocument.getElementById("test");
+ var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+ ok(objLoadingContent.activated, "Test 4, Plugin should be activated");
+
+ finishTest();
+}
+
diff --git a/browser/base/content/test/browser_plugins_added_dynamically.js b/browser/base/content/test/browser_plugins_added_dynamically.js
new file mode 100644
index 000000000..457a72dcc
--- /dev/null
+++ b/browser/base/content/test/browser_plugins_added_dynamically.js
@@ -0,0 +1,179 @@
+const gTestRoot = "http://mochi.test:8888/browser/browser/base/content/test/";
+
+let gTestBrowser = null;
+let gNextTest = null;
+let gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+// Let's do the XPCNativeWrapper dance!
+function addPlugin(browser, type) {
+ let contentWindow = XPCNativeWrapper.unwrap(browser.contentWindow);
+ contentWindow.addPlugin(type);
+}
+
+function test() {
+ waitForExplicitFinish();
+ registerCleanupFunction(function() {
+ clearAllPluginPermissions();
+ Services.prefs.clearUserPref("plugins.click_to_play");
+ getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+ });
+ Services.prefs.setBoolPref("plugins.click_to_play", true);
+ getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+ getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+
+ let newTab = gBrowser.addTab();
+ gBrowser.selectedTab = newTab;
+ gTestBrowser = gBrowser.selectedBrowser;
+ gTestBrowser.addEventListener("load", pageLoad, true);
+ prepareTest(testActivateAddSameTypePart1, gTestRoot + "plugin_add_dynamically.html");
+}
+
+function finishTest() {
+ gTestBrowser.removeEventListener("load", pageLoad, true);
+ gBrowser.removeCurrentTab();
+ window.focus();
+ finish();
+}
+
+function pageLoad() {
+ // The plugin events are async dispatched and can come after the load event
+ // This just allows the events to fire before we then go on to test the states
+ executeSoon(gNextTest);
+}
+
+function prepareTest(nextTest, url) {
+ gNextTest = nextTest;
+ gTestBrowser.contentWindow.location = url;
+}
+
+// "Activate" of a given type -> plugins of that type dynamically added should
+// automatically play.
+function testActivateAddSameTypePart1() {
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(!popupNotification, "testActivateAddSameTypePart1: should not have a click-to-play notification");
+ addPlugin(gTestBrowser);
+ let condition = function() PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ waitForCondition(condition, testActivateAddSameTypePart2, "testActivateAddSameTypePart1: waited too long for click-to-play-plugin notification");
+}
+
+function testActivateAddSameTypePart2() {
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "testActivateAddSameTypePart2: should have a click-to-play notification");
+
+ popupNotification.reshow();
+ testActivateAddSameTypePart3();
+}
+
+function testActivateAddSameTypePart3() {
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ let centerAction = null;
+ for (let action of popupNotification.options.centerActions) {
+ if (action.pluginName == "Test") {
+ centerAction = action;
+ break;
+ }
+ }
+ ok(centerAction, "testActivateAddSameTypePart3: found center action for the Test plugin");
+
+ let centerItem = null;
+ for (let item of PopupNotifications.panel.firstChild.childNodes) {
+ if (item.action && item.action == centerAction) {
+ centerItem = item;
+ break;
+ }
+ }
+ ok(centerItem, "testActivateAddSameTypePart3: found center item for the Test plugin");
+
+ let plugin = gTestBrowser.contentDocument.getElementsByTagName("embed")[0];
+ ok(!plugin.activated, "testActivateAddSameTypePart3: plugin should not be activated");
+
+ // Change the state and click the ok button to activate the Test plugin
+ centerItem.value = "allownow";
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ let condition = function() plugin.activated;
+ waitForCondition(condition, testActivateAddSameTypePart4, "testActivateAddSameTypePart3: waited too long for plugin to activate");
+}
+
+function testActivateAddSameTypePart4() {
+ let plugin = gTestBrowser.contentDocument.getElementsByTagName("embed")[0];
+ ok(plugin.activated, "testActivateAddSameTypePart4: plugin should be activated");
+
+ addPlugin(gTestBrowser);
+ let condition = function() {
+ let embeds = gTestBrowser.contentDocument.getElementsByTagName("embed");
+ let allActivated = true;
+ for (let embed of embeds) {
+ if (!embed.activated)
+ allActivated = false;
+ }
+ return allActivated && embeds.length == 2;
+ };
+ waitForCondition(condition, testActivateAddSameTypePart5, "testActivateAddSameTypePart4: waited too long for second plugin to activate"); }
+
+function testActivateAddSameTypePart5() {
+ let embeds = gTestBrowser.contentDocument.getElementsByTagName("embed");
+ for (let embed of embeds) {
+ ok(embed.activated, "testActivateAddSameTypePart5: all plugins should be activated");
+ }
+ clearAllPluginPermissions();
+ prepareTest(testActivateAddDifferentTypePart1, gTestRoot + "plugin_add_dynamically.html");
+}
+
+// "Activate" of a given type -> plugins of other types dynamically added
+// should not automatically play.
+function testActivateAddDifferentTypePart1() {
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(!popupNotification, "testActivateAddDifferentTypePart1: should not have a click-to-play notification");
+ addPlugin(gTestBrowser);
+ let condition = function() PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ waitForCondition(condition, testActivateAddDifferentTypePart2, "testActivateAddDifferentTypePart1: waited too long for click-to-play-plugin notification");
+}
+
+function testActivateAddDifferentTypePart2() {
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ ok(popupNotification, "testActivateAddDifferentTypePart2: should have a click-to-play notification");
+
+ // we have to actually show the panel to get the bindings to instantiate
+ popupNotification.reshow();
+ testActivateAddDifferentTypePart3();
+}
+
+function testActivateAddDifferentTypePart3() {
+ let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ is(popupNotification.options.centerActions.length, 1, "Should be one plugin action");
+
+ let plugin = gTestBrowser.contentDocument.getElementsByTagName("embed")[0];
+ ok(!plugin.activated, "testActivateAddDifferentTypePart3: plugin should not be activated");
+
+ // "click" the button to activate the Test plugin
+ PopupNotifications.panel.firstChild._primaryButton.click();
+
+ let condition = function() plugin.activated;
+ waitForCondition(condition, testActivateAddDifferentTypePart4, "testActivateAddDifferentTypePart3: waited too long for plugin to activate");
+}
+
+function testActivateAddDifferentTypePart4() {
+ let plugin = gTestBrowser.contentDocument.getElementsByTagName("embed")[0];
+ ok(plugin.activated, "testActivateAddDifferentTypePart4: plugin should be activated");
+
+ addPlugin(gTestBrowser);
+ let condition = function() PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+ waitForCondition(condition, testActivateAddDifferentTypePart5, "testActivateAddDifferentTypePart5: waited too long for popup notification");
+}
+
+function testActivateAddDifferentTypePart5() {
+ ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser), "testActivateAddDifferentTypePart5: should have popup notification");
+ let embeds = gTestBrowser.contentDocument.getElementsByTagName("embed");
+ for (let embed of embeds) {
+ if (embed.type == "application/x-test")
+ ok(embed.activated, "testActivateAddDifferentTypePart5: Test plugin should be activated");
+ else if (embed.type == "application/x-second-test")
+ ok(!embed.activated, "testActivateAddDifferentTypePart5: Second Test plugin should not be activated");
+ }
+
+ finishTest();
+}
diff --git a/browser/base/content/test/browser_popupNotification.js b/browser/base/content/test/browser_popupNotification.js
new file mode 100644
index 000000000..1146b5d7c
--- /dev/null
+++ b/browser/base/content/test/browser_popupNotification.js
@@ -0,0 +1,991 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ ok(PopupNotifications, "PopupNotifications object exists");
+ ok(PopupNotifications.panel, "PopupNotifications panel exists");
+
+ registerCleanupFunction(cleanUp);
+
+ runNextTest();
+}
+
+function cleanUp() {
+ for (var topic in gActiveObservers)
+ Services.obs.removeObserver(gActiveObservers[topic], topic);
+ for (var eventName in gActiveListeners)
+ PopupNotifications.panel.removeEventListener(eventName, gActiveListeners[eventName], false);
+ PopupNotifications.buttonDelay = PREF_SECURITY_DELAY_INITIAL;
+}
+
+const PREF_SECURITY_DELAY_INITIAL = Services.prefs.getIntPref("security.notification_enable_delay");
+
+var gActiveListeners = {};
+var gActiveObservers = {};
+var gShownState = {};
+
+function goNext() {
+ if (++gTestIndex == tests.length)
+ executeSoon(finish);
+ else
+ executeSoon(runNextTest);
+}
+
+function runNextTest() {
+ let nextTest = tests[gTestIndex];
+
+ function addObserver(topic) {
+ function observer() {
+ Services.obs.removeObserver(observer, "PopupNotifications-" + topic);
+ delete gActiveObservers["PopupNotifications-" + topic];
+
+ info("[Test #" + gTestIndex + "] observer for " + topic + " called");
+ nextTest[topic]();
+ goNext();
+ }
+ Services.obs.addObserver(observer, "PopupNotifications-" + topic, false);
+ gActiveObservers["PopupNotifications-" + topic] = observer;
+ }
+
+ if (nextTest.backgroundShow) {
+ addObserver("backgroundShow");
+ } else if (nextTest.updateNotShowing) {
+ addObserver("updateNotShowing");
+ } else if (nextTest.onShown) {
+ doOnPopupEvent("popupshowing", function () {
+ info("[Test #" + gTestIndex + "] popup showing");
+ });
+ doOnPopupEvent("popupshown", function () {
+ gShownState[gTestIndex] = true;
+ info("[Test #" + gTestIndex + "] popup shown");
+ nextTest.onShown(this);
+ });
+
+ // We allow multiple onHidden functions to be defined in an array. They're
+ // called in the order they appear.
+ let onHiddenArray = nextTest.onHidden instanceof Array ?
+ nextTest.onHidden :
+ [nextTest.onHidden];
+ doOnPopupEvent("popuphidden", function () {
+ if (!gShownState[gTestIndex]) {
+ // This is expected to happen for test 9, so let's not treat it as a failure.
+ info("Popup from test " + gTestIndex + " was hidden before its popupshown fired");
+ }
+
+ let onHidden = onHiddenArray.shift();
+ info("[Test #" + gTestIndex + "] popup hidden (" + onHiddenArray.length + " hides remaining)");
+ executeSoon(function () {
+ onHidden.call(nextTest, this);
+ if (!onHiddenArray.length)
+ goNext();
+ }.bind(this));
+ }, onHiddenArray.length);
+ info("[Test #" + gTestIndex + "] added listeners; panel state: " + PopupNotifications.isPanelOpen);
+ }
+
+ info("[Test #" + gTestIndex + "] running test");
+ nextTest.run();
+}
+
+function doOnPopupEvent(eventName, callback, numExpected) {
+ gActiveListeners[eventName] = function (event) {
+ if (event.target != PopupNotifications.panel)
+ return;
+ if (typeof(numExpected) === "number")
+ numExpected--;
+ if (!numExpected) {
+ PopupNotifications.panel.removeEventListener(eventName, gActiveListeners[eventName], false);
+ delete gActiveListeners[eventName];
+ }
+
+ callback.call(PopupNotifications.panel);
+ }
+ PopupNotifications.panel.addEventListener(eventName, gActiveListeners[eventName], false);
+}
+
+var gTestIndex = 0;
+var gNewTab;
+
+function basicNotification() {
+ var self = this;
+ this.browser = gBrowser.selectedBrowser;
+ this.id = "test-notification-" + gTestIndex;
+ this.message = "This is popup notification " + this.id + " from test " + gTestIndex;
+ this.anchorID = null;
+ this.mainAction = {
+ label: "Main Action",
+ accessKey: "M",
+ callback: function () {
+ self.mainActionClicked = true;
+ }
+ };
+ this.secondaryActions = [
+ {
+ label: "Secondary Action",
+ accessKey: "S",
+ callback: function () {
+ self.secondaryActionClicked = true;
+ }
+ }
+ ];
+ this.options = {
+ eventCallback: function (eventName) {
+ switch (eventName) {
+ case "dismissed":
+ self.dismissalCallbackTriggered = true;
+ break;
+ case "showing":
+ self.showingCallbackTriggered = true;
+ break;
+ case "shown":
+ self.shownCallbackTriggered = true;
+ break;
+ case "removed":
+ self.removedCallbackTriggered = true;
+ break;
+ }
+ }
+ };
+ this.addOptions = function(options) {
+ for (let [name, value] in Iterator(options))
+ self.options[name] = value;
+ }
+}
+
+var wrongBrowserNotificationObject = new basicNotification();
+var wrongBrowserNotification;
+
+var tests = [
+ { // Test #0
+ run: function () {
+ this.notifyObj = new basicNotification();
+ showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ triggerMainCommand(popup);
+ },
+ onHidden: function (popup) {
+ ok(this.notifyObj.mainActionClicked, "mainAction was clicked");
+ ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered");
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ { // Test #1
+ run: function () {
+ this.notifyObj = new basicNotification();
+ showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ triggerSecondaryCommand(popup, 0);
+ },
+ onHidden: function (popup) {
+ ok(this.notifyObj.secondaryActionClicked, "secondaryAction was clicked");
+ ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered");
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ { // Test #2
+ run: function () {
+ this.notifyObj = new basicNotification();
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ dismissNotification(popup);
+ },
+ onHidden: function (popup) {
+ ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
+ this.notification.remove();
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ // test opening a notification for a background browser
+ { // Test #3
+ run: function () {
+ gNewTab = gBrowser.addTab("about:blank");
+ isnot(gBrowser.selectedTab, gNewTab, "new tab isn't selected");
+ wrongBrowserNotificationObject.browser = gBrowser.getBrowserForTab(gNewTab);
+ wrongBrowserNotification = showNotification(wrongBrowserNotificationObject);
+ },
+ backgroundShow: function () {
+ is(PopupNotifications.isPanelOpen, false, "panel isn't open");
+ ok(!wrongBrowserNotificationObject.mainActionClicked, "main action wasn't clicked");
+ ok(!wrongBrowserNotificationObject.secondaryActionClicked, "secondary action wasn't clicked");
+ ok(!wrongBrowserNotificationObject.dismissalCallbackTriggered, "dismissal callback wasn't called");
+ }
+ },
+ // now select that browser and test to see that the notification appeared
+ { // Test #4
+ run: function () {
+ this.oldSelectedTab = gBrowser.selectedTab;
+ gBrowser.selectedTab = gNewTab;
+ },
+ onShown: function (popup) {
+ checkPopup(popup, wrongBrowserNotificationObject);
+ is(PopupNotifications.isPanelOpen, true, "isPanelOpen getter doesn't lie");
+
+ // switch back to the old browser
+ gBrowser.selectedTab = this.oldSelectedTab;
+ },
+ onHidden: function (popup) {
+ // actually remove the notification to prevent it from reappearing
+ ok(wrongBrowserNotificationObject.dismissalCallbackTriggered, "dismissal callback triggered due to tab switch");
+ wrongBrowserNotification.remove();
+ ok(wrongBrowserNotificationObject.removedCallbackTriggered, "removed callback triggered");
+ wrongBrowserNotification = null;
+ }
+ },
+ // test that the removed notification isn't shown on browser re-select
+ { // Test #5
+ run: function () {
+ gBrowser.selectedTab = gNewTab;
+ },
+ updateNotShowing: function () {
+ is(PopupNotifications.isPanelOpen, false, "panel isn't open");
+ gBrowser.removeTab(gNewTab);
+ }
+ },
+ // Test that two notifications with the same ID result in a single displayed
+ // notification.
+ { // Test #6
+ run: function () {
+ this.notifyObj = new basicNotification();
+ // Show the same notification twice
+ this.notification1 = showNotification(this.notifyObj);
+ this.notification2 = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ this.notification2.remove();
+ },
+ onHidden: function (popup) {
+ ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered");
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ // Test that two notifications with different IDs are displayed
+ { // Test #7
+ run: function () {
+ this.testNotif1 = new basicNotification();
+ this.testNotif1.message += " 1";
+ showNotification(this.testNotif1);
+ this.testNotif2 = new basicNotification();
+ this.testNotif2.message += " 2";
+ this.testNotif2.id += "-2";
+ showNotification(this.testNotif2);
+ },
+ onShown: function (popup) {
+ is(popup.childNodes.length, 2, "two notifications are shown");
+ // Trigger the main command for the first notification, and the secondary
+ // for the second. Need to do mainCommand first since the secondaryCommand
+ // triggering is async.
+ triggerMainCommand(popup);
+ is(popup.childNodes.length, 1, "only one notification left");
+ triggerSecondaryCommand(popup, 0);
+ },
+ onHidden: function (popup) {
+ ok(this.testNotif1.mainActionClicked, "main action #1 was clicked");
+ ok(!this.testNotif1.secondaryActionClicked, "secondary action #1 wasn't clicked");
+ ok(!this.testNotif1.dismissalCallbackTriggered, "dismissal callback #1 wasn't called");
+
+ ok(!this.testNotif2.mainActionClicked, "main action #2 wasn't clicked");
+ ok(this.testNotif2.secondaryActionClicked, "secondary action #2 was clicked");
+ ok(!this.testNotif2.dismissalCallbackTriggered, "dismissal callback #2 wasn't called");
+ }
+ },
+ // Test notification without mainAction
+ { // Test #8
+ run: function () {
+ this.notifyObj = new basicNotification();
+ this.notifyObj.mainAction = null;
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ dismissNotification(popup);
+ },
+ onHidden: function (popup) {
+ this.notification.remove();
+ }
+ },
+ // Test two notifications with different anchors
+ { // Test #9
+ run: function () {
+ this.notifyObj = new basicNotification();
+ this.firstNotification = showNotification(this.notifyObj);
+ this.notifyObj2 = new basicNotification();
+ this.notifyObj2.id += "-2";
+ this.notifyObj2.anchorID = "addons-notification-icon";
+ // Second showNotification() overrides the first
+ this.secondNotification = showNotification(this.notifyObj2);
+ },
+ onShown: function (popup) {
+ // This also checks that only one element is shown.
+ checkPopup(popup, this.notifyObj2);
+ is(document.getElementById("geo-notification-icon").boxObject.width, 0,
+ "geo anchor shouldn't be visible");
+ dismissNotification(popup);
+ },
+ onHidden: [
+ // The second showing triggers a popuphidden event that we should ignore.
+ function (popup) {},
+ function (popup) {
+ // Remove the notifications
+ this.firstNotification.remove();
+ this.secondNotification.remove();
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ ok(this.notifyObj2.removedCallbackTriggered, "removed callback triggered");
+ }
+ ]
+ },
+ // Test optional params
+ { // Test #10
+ run: function () {
+ this.notifyObj = new basicNotification();
+ this.notifyObj.secondaryActions = undefined;
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ dismissNotification(popup);
+ },
+ onHidden: function (popup) {
+ ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
+ this.notification.remove();
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ // Test that icons appear
+ { // Test #11
+ run: function () {
+ this.notifyObj = new basicNotification();
+ this.notifyObj.id = "geolocation";
+ this.notifyObj.anchorID = "geo-notification-icon";
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ isnot(document.getElementById("geo-notification-icon").boxObject.width, 0,
+ "geo anchor should be visible");
+ dismissNotification(popup);
+ },
+ onHidden: function (popup) {
+ let icon = document.getElementById("geo-notification-icon");
+ isnot(icon.boxObject.width, 0,
+ "geo anchor should be visible after dismissal");
+ this.notification.remove();
+ is(icon.boxObject.width, 0,
+ "geo anchor should not be visible after removal");
+ }
+ },
+ // Test that persistence allows the notification to persist across reloads
+ { // Test #12
+ run: function () {
+ this.oldSelectedTab = gBrowser.selectedTab;
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+
+ let self = this;
+ loadURI("http://example.com/", function() {
+ self.notifyObj = new basicNotification();
+ self.notifyObj.addOptions({
+ persistence: 2
+ });
+ self.notification = showNotification(self.notifyObj);
+ });
+ },
+ onShown: function (popup) {
+ this.complete = false;
+
+ let self = this;
+ loadURI("http://example.org/", function() {
+ loadURI("http://example.com/", function() {
+
+ // Next load will remove the notification
+ self.complete = true;
+
+ loadURI("http://example.org/");
+ });
+ });
+ },
+ onHidden: function (popup) {
+ ok(this.complete, "Should only have hidden the notification after 3 page loads");
+ ok(this.notifyObj.removedCallbackTriggered, "removal callback triggered");
+ gBrowser.removeTab(gBrowser.selectedTab);
+ gBrowser.selectedTab = this.oldSelectedTab;
+ }
+ },
+ // Test that a timeout allows the notification to persist across reloads
+ { // Test #13
+ run: function () {
+ this.oldSelectedTab = gBrowser.selectedTab;
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+
+ let self = this;
+ loadURI("http://example.com/", function() {
+ self.notifyObj = new basicNotification();
+ // Set a timeout of 10 minutes that should never be hit
+ self.notifyObj.addOptions({
+ timeout: Date.now() + 600000
+ });
+ self.notification = showNotification(self.notifyObj);
+ });
+ },
+ onShown: function (popup) {
+ this.complete = false;
+
+ let self = this;
+ loadURI("http://example.org/", function() {
+ loadURI("http://example.com/", function() {
+
+ // Next load will hide the notification
+ self.notification.options.timeout = Date.now() - 1;
+ self.complete = true;
+
+ loadURI("http://example.org/");
+ });
+ });
+ },
+ onHidden: function (popup) {
+ ok(this.complete, "Should only have hidden the notification after the timeout was passed");
+ this.notification.remove();
+ gBrowser.removeTab(gBrowser.selectedTab);
+ gBrowser.selectedTab = this.oldSelectedTab;
+ }
+ },
+ // Test that setting persistWhileVisible allows a visible notification to
+ // persist across location changes
+ { // Test #14
+ run: function () {
+ this.oldSelectedTab = gBrowser.selectedTab;
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+
+ let self = this;
+ loadURI("http://example.com/", function() {
+ self.notifyObj = new basicNotification();
+ self.notifyObj.addOptions({
+ persistWhileVisible: true
+ });
+ self.notification = showNotification(self.notifyObj);
+ });
+ },
+ onShown: function (popup) {
+ this.complete = false;
+
+ let self = this;
+ loadURI("http://example.org/", function() {
+ loadURI("http://example.com/", function() {
+
+ // Notification should persist across location changes
+ self.complete = true;
+ dismissNotification(popup);
+ });
+ });
+ },
+ onHidden: function (popup) {
+ ok(this.complete, "Should only have hidden the notification after it was dismissed");
+ this.notification.remove();
+ gBrowser.removeTab(gBrowser.selectedTab);
+ gBrowser.selectedTab = this.oldSelectedTab;
+ }
+ },
+ // Test that nested icon nodes correctly activate popups
+ { // Test #15
+ run: function() {
+ // Add a temporary box as the anchor with a button
+ this.box = document.createElement("box");
+ PopupNotifications.iconBox.appendChild(this.box);
+
+ let button = document.createElement("button");
+ button.setAttribute("label", "Please click me!");
+ this.box.appendChild(button);
+
+ // The notification should open up on the box
+ this.notifyObj = new basicNotification();
+ this.notifyObj.anchorID = this.box.id = "nested-box";
+ this.notifyObj.addOptions({dismissed: true});
+ this.notification = showNotification(this.notifyObj);
+
+ EventUtils.synthesizeMouse(button, 1, 1, {});
+ },
+ onShown: function(popup) {
+ checkPopup(popup, this.notifyObj);
+ dismissNotification(popup);
+ },
+ onHidden: function(popup) {
+ this.notification.remove();
+ this.box.parentNode.removeChild(this.box);
+ }
+ },
+ // Test that popupnotifications without popups have anchor icons shown
+ { // Test #16
+ run: function() {
+ let notifyObj = new basicNotification();
+ notifyObj.anchorID = "geo-notification-icon";
+ notifyObj.addOptions({neverShow: true});
+ showNotification(notifyObj);
+ },
+ updateNotShowing: function() {
+ isnot(document.getElementById("geo-notification-icon").boxObject.width, 0,
+ "geo anchor should be visible");
+ }
+ },
+ // Test notification "Not Now" menu item
+ { // Test #17
+ run: function () {
+ this.notifyObj = new basicNotification();
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ triggerSecondaryCommand(popup, 1);
+ },
+ onHidden: function (popup) {
+ ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
+ this.notification.remove();
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ // Test notification close button
+ { // Test #18
+ run: function () {
+ this.notifyObj = new basicNotification();
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ let notification = popup.childNodes[0];
+ EventUtils.synthesizeMouseAtCenter(notification.closebutton, {});
+ },
+ onHidden: function (popup) {
+ ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
+ this.notification.remove();
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ // Test notification when chrome is hidden
+ { // Test #19
+ run: function () {
+ window.locationbar.visible = false;
+ this.notifyObj = new basicNotification();
+ this.notification = showNotification(this.notifyObj);
+ window.locationbar.visible = true;
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ is(popup.anchorNode.className, "tabbrowser-tab", "notification anchored to tab");
+ dismissNotification(popup);
+ },
+ onHidden: function (popup) {
+ ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
+ this.notification.remove();
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ // Test notification is removed when dismissed if removeOnDismissal is true
+ { // Test #20
+ run: function () {
+ this.notifyObj = new basicNotification();
+ this.notifyObj.addOptions({
+ removeOnDismissal: true
+ });
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ dismissNotification(popup);
+ },
+ onHidden: function (popup) {
+ ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered");
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ // Test multiple notification icons are shown
+ { // Test #21
+ run: function () {
+ this.notifyObj1 = new basicNotification();
+ this.notifyObj1.id += "_1";
+ this.notifyObj1.anchorID = "default-notification-icon";
+ this.notification1 = showNotification(this.notifyObj1);
+
+ this.notifyObj2 = new basicNotification();
+ this.notifyObj2.id += "_2";
+ this.notifyObj2.anchorID = "geo-notification-icon";
+ this.notification2 = showNotification(this.notifyObj2);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj2);
+
+ // check notifyObj1 anchor icon is showing
+ isnot(document.getElementById("default-notification-icon").boxObject.width, 0,
+ "default anchor should be visible");
+ // check notifyObj2 anchor icon is showing
+ isnot(document.getElementById("geo-notification-icon").boxObject.width, 0,
+ "geo anchor should be visible");
+
+ dismissNotification(popup);
+ },
+ onHidden: [
+ function (popup) {
+ },
+ function (popup) {
+ this.notification1.remove();
+ ok(this.notifyObj1.removedCallbackTriggered, "removed callback triggered");
+
+ this.notification2.remove();
+ ok(this.notifyObj2.removedCallbackTriggered, "removed callback triggered");
+ }
+ ]
+ },
+ // Test that multiple notification icons are removed when switching tabs
+ { // Test #22
+ run: function () {
+ // show the notification on old tab.
+ this.notifyObjOld = new basicNotification();
+ this.notifyObjOld.anchorID = "default-notification-icon";
+ this.notificationOld = showNotification(this.notifyObjOld);
+
+ // switch tab
+ this.oldSelectedTab = gBrowser.selectedTab;
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+
+ // show the notification on new tab.
+ this.notifyObjNew = new basicNotification();
+ this.notifyObjNew.anchorID = "geo-notification-icon";
+ this.notificationNew = showNotification(this.notifyObjNew);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObjNew);
+
+ // check notifyObjOld anchor icon is removed
+ is(document.getElementById("default-notification-icon").boxObject.width, 0,
+ "default anchor shouldn't be visible");
+ // check notifyObjNew anchor icon is showing
+ isnot(document.getElementById("geo-notification-icon").boxObject.width, 0,
+ "geo anchor should be visible");
+
+ dismissNotification(popup);
+ },
+ onHidden: [
+ function (popup) {
+ },
+ function (popup) {
+ this.notificationNew.remove();
+ gBrowser.removeTab(gBrowser.selectedTab);
+
+ gBrowser.selectedTab = this.oldSelectedTab;
+ this.notificationOld.remove();
+ }
+ ]
+ },
+ { // Test #23 - test security delay - too early
+ run: function () {
+ // Set the security delay to 100s
+ PopupNotifications.buttonDelay = 100000;
+
+ this.notifyObj = new basicNotification();
+ showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+ triggerMainCommand(popup);
+
+ // Wait to see if the main command worked
+ executeSoon(function delayedDismissal() {
+ dismissNotification(popup);
+ });
+
+ },
+ onHidden: function (popup) {
+ ok(!this.notifyObj.mainActionClicked, "mainAction was not clicked because it was too soon");
+ ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback was triggered");
+ }
+ },
+ { // Test #24 - test security delay - after delay
+ run: function () {
+ // Set the security delay to 10ms
+ PopupNotifications.buttonDelay = 10;
+
+ this.notifyObj = new basicNotification();
+ showNotification(this.notifyObj);
+ },
+ onShown: function (popup) {
+ checkPopup(popup, this.notifyObj);
+
+ // Wait until after the delay to trigger the main action
+ setTimeout(function delayedDismissal() {
+ triggerMainCommand(popup);
+ }, 500);
+
+ },
+ onHidden: function (popup) {
+ ok(this.notifyObj.mainActionClicked, "mainAction was clicked after the delay");
+ ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback was not triggered");
+ PopupNotifications.buttonDelay = PREF_SECURITY_DELAY_INITIAL;
+ }
+ },
+ { // Test #25 - reload removes notification
+ run: function () {
+ loadURI("http://example.com/", function() {
+ let notifyObj = new basicNotification();
+ notifyObj.options.eventCallback = function (eventName) {
+ if (eventName == "removed") {
+ ok(true, "Notification removed in background tab after reloading");
+ executeSoon(function () {
+ goNext();
+ });
+ }
+ };
+ showNotification(notifyObj);
+ executeSoon(function () {
+ gBrowser.selectedBrowser.reload();
+ });
+ });
+ }
+ },
+ { // Test #26 - location change in background tab removes notification
+ run: function () {
+ let oldSelectedTab = gBrowser.selectedTab;
+ let newTab = gBrowser.addTab("about:blank");
+ gBrowser.selectedTab = newTab;
+
+ loadURI("http://example.com/", function() {
+ gBrowser.selectedTab = oldSelectedTab;
+ let browser = gBrowser.getBrowserForTab(newTab);
+
+ let notifyObj = new basicNotification();
+ notifyObj.browser = browser;
+ notifyObj.options.eventCallback = function (eventName) {
+ if (eventName == "removed") {
+ ok(true, "Notification removed in background tab after reloading");
+ executeSoon(function () {
+ gBrowser.removeTab(newTab);
+ goNext();
+ });
+ }
+ };
+ showNotification(notifyObj);
+ executeSoon(function () {
+ browser.reload();
+ });
+ });
+ }
+ },
+ { // Test #27 - Popup notification anchor shouldn't disappear when a notification with the same ID is re-added in a background tab
+ run: function () {
+ loadURI("http://example.com/", function () {
+ let originalTab = gBrowser.selectedTab;
+ let bgTab = gBrowser.addTab("about:blank");
+ gBrowser.selectedTab = bgTab;
+ loadURI("http://example.com/", function () {
+ let anchor = document.createElement("box");
+ anchor.id = "test26-anchor";
+ anchor.className = "notification-anchor-icon";
+ PopupNotifications.iconBox.appendChild(anchor);
+
+ gBrowser.selectedTab = originalTab;
+
+ let fgNotifyObj = new basicNotification();
+ fgNotifyObj.anchorID = anchor.id;
+ fgNotifyObj.options.dismissed = true;
+ let fgNotification = showNotification(fgNotifyObj);
+
+ let bgNotifyObj = new basicNotification();
+ bgNotifyObj.anchorID = anchor.id;
+ bgNotifyObj.browser = gBrowser.getBrowserForTab(bgTab);
+ // show the notification in the background tab ...
+ let bgNotification = showNotification(bgNotifyObj);
+ // ... and re-show it
+ bgNotification = showNotification(bgNotifyObj);
+
+ ok(fgNotification.id, "notification has id");
+ is(fgNotification.id, bgNotification.id, "notification ids are the same");
+ is(anchor.getAttribute("showing"), "true", "anchor still showing");
+
+ fgNotification.remove();
+ gBrowser.removeTab(bgTab);
+ goNext();
+ });
+ });
+ }
+ },
+ { // Test #28 - location change in embedded frame removes notification
+ run: function () {
+ loadURI("data:text/html,<iframe id='iframe' src='http://example.com/'>", function () {
+ let notifyObj = new basicNotification();
+ notifyObj.options.eventCallback = function (eventName) {
+ if (eventName == "removed") {
+ ok(true, "Notification removed in background tab after reloading");
+ executeSoon(goNext);
+ }
+ };
+ showNotification(notifyObj);
+ executeSoon(function () {
+ content.document.getElementById("iframe")
+ .setAttribute("src", "http://example.org/");
+ });
+ });
+ }
+ },
+ { // Test #29 - Existing popup notification shouldn't disappear when adding a dismissed notification
+ run: function () {
+ this.notifyObj1 = new basicNotification();
+ this.notifyObj1.id += "_1";
+ this.notifyObj1.anchorID = "default-notification-icon";
+ this.notification1 = showNotification(this.notifyObj1);
+ },
+ onShown: function (popup) {
+ // Now show a dismissed notification, and check that it doesn't clobber
+ // the showing one.
+ this.notifyObj2 = new basicNotification();
+ this.notifyObj2.id += "_2";
+ this.notifyObj2.anchorID = "geo-notification-icon";
+ this.notifyObj2.options.dismissed = true;
+ this.notification2 = showNotification(this.notifyObj2);
+
+ checkPopup(popup, this.notifyObj1);
+
+ // check that both anchor icons are showing
+ is(document.getElementById("default-notification-icon").getAttribute("showing"), "true",
+ "notification1 anchor should be visible");
+ is(document.getElementById("geo-notification-icon").getAttribute("showing"), "true",
+ "notification2 anchor should be visible");
+
+ dismissNotification(popup);
+ },
+ onHidden: function(popup) {
+ this.notification1.remove();
+ this.notification2.remove();
+ }
+ },
+ { // Test #30 - Showing should be able to modify the popup data
+ run: function() {
+ this.notifyObj = new basicNotification();
+ var normalCallback = this.notifyObj.options.eventCallback;
+ this.notifyObj.options.eventCallback = function (eventName) {
+ if (eventName == "showing") {
+ this.mainAction.label = "Alternate Label";
+ }
+ normalCallback.call(this, eventName);
+ };
+ showNotification(this.notifyObj);
+ },
+ onShown: function(popup) {
+ // checkPopup checks for the matching label. Note that this assumes that
+ // this.notifyObj.mainAction is the same as notification.mainAction,
+ // which could be a problem if we ever decided to deep-copy.
+ checkPopup(popup, this.notifyObj);
+ triggerMainCommand(popup);
+ },
+ onHidden: function() { }
+ }
+];
+
+function showNotification(notifyObj) {
+ return PopupNotifications.show(notifyObj.browser,
+ notifyObj.id,
+ notifyObj.message,
+ notifyObj.anchorID,
+ notifyObj.mainAction,
+ notifyObj.secondaryActions,
+ notifyObj.options);
+}
+
+function checkPopup(popup, notificationObj) {
+ info("[Test #" + gTestIndex + "] checking popup");
+
+ ok(notificationObj.showingCallbackTriggered, "showing callback was triggered");
+ ok(notificationObj.shownCallbackTriggered, "shown callback was triggered");
+
+ let notifications = popup.childNodes;
+ is(notifications.length, 1, "one notification displayed");
+ let notification = notifications[0];
+ if (!notification)
+ return;
+ let icon = document.getAnonymousElementByAttribute(notification, "class", "popup-notification-icon");
+ if (notificationObj.id == "geolocation") {
+ isnot(icon.boxObject.width, 0, "icon for geo displayed");
+ is(popup.anchorNode.className, "notification-anchor-icon", "notification anchored to icon");
+ }
+ is(notification.getAttribute("label"), notificationObj.message, "message matches");
+ is(notification.id, notificationObj.id + "-notification", "id matches");
+ if (notificationObj.mainAction) {
+ is(notification.getAttribute("buttonlabel"), notificationObj.mainAction.label, "main action label matches");
+ is(notification.getAttribute("buttonaccesskey"), notificationObj.mainAction.accessKey, "main action accesskey matches");
+ }
+ let actualSecondaryActions = Array.filter(notification.childNodes,
+ function (child) child.nodeName == "menuitem");
+ let secondaryActions = notificationObj.secondaryActions || [];
+ let actualSecondaryActionsCount = actualSecondaryActions.length;
+ if (secondaryActions.length) {
+ is(notification.lastChild.tagName, "menuseparator", "menuseparator exists");
+ }
+ is(actualSecondaryActionsCount, secondaryActions.length, actualSecondaryActions.length + " secondary actions");
+ secondaryActions.forEach(function (a, i) {
+ is(actualSecondaryActions[i].getAttribute("label"), a.label, "label for secondary action " + i + " matches");
+ is(actualSecondaryActions[i].getAttribute("accesskey"), a.accessKey, "accessKey for secondary action " + i + " matches");
+ });
+}
+
+function triggerMainCommand(popup) {
+ info("[Test #" + gTestIndex + "] triggering main command");
+ let notifications = popup.childNodes;
+ ok(notifications.length > 0, "at least one notification displayed");
+ let notification = notifications[0];
+
+ // 20, 10 so that the inner button is hit
+ EventUtils.synthesizeMouse(notification.button, 20, 10, {});
+}
+
+function triggerSecondaryCommand(popup, index) {
+ info("[Test #" + gTestIndex + "] triggering secondary command");
+ let notifications = popup.childNodes;
+ ok(notifications.length > 0, "at least one notification displayed");
+ let notification = notifications[0];
+
+ // Cancel the arrow panel slide-in transition (bug 767133) such that
+ // it won't interfere with us interacting with the dropdown.
+ document.getAnonymousNodes(popup)[0].style.transition = "none";
+
+ notification.button.focus();
+
+ popup.addEventListener("popupshown", function () {
+ popup.removeEventListener("popupshown", arguments.callee, false);
+
+ // Press down until the desired command is selected
+ for (let i = 0; i <= index; i++)
+ EventUtils.synthesizeKey("VK_DOWN", {});
+
+ // Activate
+ EventUtils.synthesizeKey("VK_ENTER", {});
+ }, false);
+
+ // One down event to open the popup
+ EventUtils.synthesizeKey("VK_DOWN", { altKey: !navigator.platform.contains("Mac") });
+}
+
+function loadURI(uri, callback) {
+ if (callback) {
+ gBrowser.addEventListener("load", function() {
+ // Ignore the about:blank load
+ if (gBrowser.currentURI.spec == "about:blank")
+ return;
+
+ gBrowser.removeEventListener("load", arguments.callee, true);
+
+ callback();
+ }, true);
+ }
+ gBrowser.loadURI(uri);
+}
+
+function dismissNotification(popup) {
+ info("[Test #" + gTestIndex + "] dismissing notification");
+ executeSoon(function () {
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ });
+}
diff --git a/browser/base/content/test/browser_popupUI.js b/browser/base/content/test/browser_popupUI.js
new file mode 100644
index 000000000..fd722886d
--- /dev/null
+++ b/browser/base/content/test/browser_popupUI.js
@@ -0,0 +1,57 @@
+function test() {
+ waitForExplicitFinish();
+ gPrefService.setBoolPref("dom.disable_open_during_load", false);
+
+ var browser = gBrowser.selectedBrowser;
+ browser.addEventListener("load", function () {
+ browser.removeEventListener("load", arguments.callee, true);
+
+ if (gPrefService.prefHasUserValue("dom.disable_open_during_load"))
+ gPrefService.clearUserPref("dom.disable_open_during_load");
+
+ findPopup();
+ }, true);
+
+ content.location =
+ "data:text/html,<html><script>popup=open('about:blank','','width=300,height=200')</script>";
+}
+
+function findPopup() {
+ var enumerator = Services.wm.getEnumerator("navigator:browser");
+
+ while (enumerator.hasMoreElements()) {
+ let win = enumerator.getNext();
+ if (win.content.wrappedJSObject == content.wrappedJSObject.popup) {
+ testPopupUI(win);
+ return;
+ }
+ }
+
+ throw "couldn't find the popup";
+}
+
+function testPopupUI(win) {
+ var doc = win.document;
+
+ ok(win.gURLBar, "location bar exists in the popup");
+ isnot(win.gURLBar.clientWidth, 0, "location bar is visible in the popup");
+ ok(win.gURLBar.readOnly, "location bar is read-only in the popup");
+ isnot(doc.getElementById("Browser:OpenLocation").getAttribute("disabled"), "true",
+ "'open location' command is not disabled in the popup");
+
+ let historyButton = doc.getAnonymousElementByAttribute(win.gURLBar, "anonid",
+ "historydropmarker");
+ is(historyButton.clientWidth, 0, "history dropdown button is hidden in the popup");
+
+ EventUtils.synthesizeKey("t", { accelKey: true }, win);
+ is(win.gBrowser.browsers.length, 1, "Accel+T doesn't open a new tab in the popup");
+
+ EventUtils.synthesizeKey("w", { accelKey: true }, win);
+ ok(win.closed, "Accel+W closes the popup");
+
+ if (!win.closed)
+ win.close();
+ gBrowser.addTab();
+ gBrowser.removeCurrentTab();
+ finish();
+}
diff --git a/browser/base/content/test/browser_private_browsing_window.js b/browser/base/content/test/browser_private_browsing_window.js
new file mode 100644
index 000000000..607a34060
--- /dev/null
+++ b/browser/base/content/test/browser_private_browsing_window.js
@@ -0,0 +1,65 @@
+// Make sure that we can open private browsing windows
+
+function test() {
+ waitForExplicitFinish();
+ var nonPrivateWin = OpenBrowserWindow();
+ ok(!PrivateBrowsingUtils.isWindowPrivate(nonPrivateWin), "OpenBrowserWindow() should open a normal window");
+ nonPrivateWin.close();
+
+ var privateWin = OpenBrowserWindow({private: true});
+ ok(PrivateBrowsingUtils.isWindowPrivate(privateWin), "OpenBrowserWindow({private: true}) should open a private window");
+
+ nonPrivateWin = OpenBrowserWindow({private: false});
+ ok(!PrivateBrowsingUtils.isWindowPrivate(nonPrivateWin), "OpenBrowserWindow({private: false}) should open a normal window");
+ nonPrivateWin.close();
+
+ whenDelayedStartupFinished(privateWin, function() {
+ nonPrivateWin = privateWin.OpenBrowserWindow({private: false});
+ ok(!PrivateBrowsingUtils.isWindowPrivate(nonPrivateWin), "privateWin.OpenBrowserWindow({private: false}) should open a normal window");
+
+ nonPrivateWin.close();
+
+ [
+ { normal: "menu_newNavigator", private: "menu_newPrivateWindow", accesskey: true },
+ { normal: "appmenu_newNavigator", private: "appmenu_newPrivateWindow", accesskey: false },
+ ].forEach(function(menu) {
+ let newWindow = privateWin.document.getElementById(menu.normal);
+ let newPrivateWindow = privateWin.document.getElementById(menu.private);
+ if (newWindow && newPrivateWindow) {
+ ok(!newPrivateWindow.hidden, "New Private Window menu item should be hidden");
+ isnot(newWindow.label, newPrivateWindow.label, "New Window's label shouldn't be overwritten");
+ if (menu.accesskey) {
+ isnot(newWindow.accessKey, newPrivateWindow.accessKey, "New Window's accessKey shouldn't be overwritten");
+ }
+ isnot(newWindow.command, newPrivateWindow.command, "New Window's command shouldn't be overwritten");
+ }
+ });
+
+ privateWin.close();
+
+ Services.prefs.setBoolPref("browser.privatebrowsing.autostart", true);
+ privateWin = OpenBrowserWindow({private: true});
+ whenDelayedStartupFinished(privateWin, function() {
+ [
+ { normal: "menu_newNavigator", private: "menu_newPrivateWindow", accessKey: true },
+ { normal: "appmenu_newNavigator", private: "appmenu_newPrivateWindow", accessKey: false },
+ ].forEach(function(menu) {
+ let newWindow = privateWin.document.getElementById(menu.normal);
+ let newPrivateWindow = privateWin.document.getElementById(menu.private);
+ if (newWindow && newPrivateWindow) {
+ ok(newPrivateWindow.hidden, "New Private Window menu item should be hidden");
+ is(newWindow.label, newPrivateWindow.label, "New Window's label should be overwritten");
+ if (menu.accesskey) {
+ is(newWindow.accessKey, newPrivateWindow.accessKey, "New Window's accessKey should be overwritten");
+ }
+ is(newWindow.command, newPrivateWindow.command, "New Window's command should be overwritten");
+ }
+ });
+
+ privateWin.close();
+ Services.prefs.clearUserPref("browser.privatebrowsing.autostart");
+ finish();
+ });
+ });
+}
+
diff --git a/browser/base/content/test/browser_private_no_prompt.js b/browser/base/content/test/browser_private_no_prompt.js
new file mode 100644
index 000000000..17ab1b437
--- /dev/null
+++ b/browser/base/content/test/browser_private_no_prompt.js
@@ -0,0 +1,13 @@
+function test() {
+ waitForExplicitFinish();
+ var privateWin = OpenBrowserWindow({private: true});
+ privateWin.addEventListener("load", function onload() {
+ privateWin.removeEventListener("load", onload, false);
+ ok(true, "Load listener called");
+
+ privateWin.BrowserOpenTab();
+ privateWin.BrowserTryToCloseWindow();
+ ok(true, "didn't prompt");
+ finish();
+ }, false);
+} \ No newline at end of file
diff --git a/browser/base/content/test/browser_relatedTabs.js b/browser/base/content/test/browser_relatedTabs.js
new file mode 100644
index 000000000..f59e0bbbb
--- /dev/null
+++ b/browser/base/content/test/browser_relatedTabs.js
@@ -0,0 +1,49 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ is(gBrowser.tabs.length, 1, "one tab is open initially");
+
+ // Add several new tabs in sequence, interrupted by selecting a
+ // different tab, moving a tab around and closing a tab,
+ // returning a list of opened tabs for verifying the expected order.
+ // The new tab behaviour is documented in bug 465673
+ let tabs = [];
+ function addTab(aURL, aReferrer) {
+ tabs.push(gBrowser.addTab(aURL, {referrerURI: aReferrer}));
+ }
+
+ addTab("http://mochi.test:8888/#0");
+ gBrowser.selectedTab = tabs[0];
+ addTab("http://mochi.test:8888/#1");
+ addTab("http://mochi.test:8888/#2", gBrowser.currentURI);
+ addTab("http://mochi.test:8888/#3", gBrowser.currentURI);
+ gBrowser.selectedTab = tabs[tabs.length - 1];
+ gBrowser.selectedTab = tabs[0];
+ addTab("http://mochi.test:8888/#4", gBrowser.currentURI);
+ gBrowser.selectedTab = tabs[3];
+ addTab("http://mochi.test:8888/#5", gBrowser.currentURI);
+ gBrowser.removeTab(tabs.pop());
+ addTab("about:blank", gBrowser.currentURI);
+ gBrowser.moveTabTo(gBrowser.selectedTab, 1);
+ addTab("http://mochi.test:8888/#6", gBrowser.currentURI);
+ addTab();
+ addTab("http://mochi.test:8888/#7");
+
+ function testPosition(tabNum, expectedPosition, msg) {
+ is(Array.indexOf(gBrowser.tabs, tabs[tabNum]), expectedPosition, msg);
+ }
+
+ testPosition(0, 3, "tab without referrer was opened to the far right");
+ testPosition(1, 7, "tab without referrer was opened to the far right");
+ testPosition(2, 5, "tab with referrer opened immediately to the right");
+ testPosition(3, 1, "next tab with referrer opened further to the right");
+ testPosition(4, 4, "tab selection changed, tab opens immediately to the right");
+ testPosition(5, 6, "blank tab with referrer opens to the right of 3rd original tab where removed tab was");
+ testPosition(6, 2, "tab has moved, new tab opens immediately to the right");
+ testPosition(7, 8, "blank tab without referrer opens at the end");
+ testPosition(8, 9, "tab without referrer opens at the end");
+
+ tabs.forEach(gBrowser.removeTab, gBrowser);
+}
diff --git a/browser/base/content/test/browser_removeTabsToTheEnd.js b/browser/base/content/test/browser_removeTabsToTheEnd.js
new file mode 100644
index 000000000..856f25aac
--- /dev/null
+++ b/browser/base/content/test/browser_removeTabsToTheEnd.js
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ // Add two new tabs after the original tab. Pin the first one.
+ let originalTab = gBrowser.selectedTab;
+ let newTab1 = gBrowser.addTab();
+ let newTab2 = gBrowser.addTab();
+ gBrowser.pinTab(newTab1);
+
+ // Check that there is only one closable tab from originalTab to the end
+ is(gBrowser.getTabsToTheEndFrom(originalTab).length, 1,
+ "One unpinned tab to the right");
+
+ // Remove tabs to the end
+ gBrowser.removeTabsToTheEndFrom(originalTab);
+ is(gBrowser.tabs.length, 2, "Length is 2");
+ is(gBrowser.tabs[1], originalTab, "Starting tab is not removed");
+ is(gBrowser.tabs[0], newTab1, "Pinned tab is not removed");
+
+ // Remove pinned tab
+ gBrowser.removeTab(newTab1);
+}
diff --git a/browser/base/content/test/browser_sanitize-download-history.js b/browser/base/content/test/browser_sanitize-download-history.js
new file mode 100644
index 000000000..186b02167
--- /dev/null
+++ b/browser/base/content/test/browser_sanitize-download-history.js
@@ -0,0 +1,142 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+function test()
+{
+ //////////////////////////////////////////////////////////////////////////////
+ //// Tests (defined locally for scope's sake)
+
+ function test_checkedAndDisabledAtStart(aWin)
+ {
+ let doc = aWin.document;
+ let downloads = doc.getElementById("downloads-checkbox");
+ let history = doc.getElementById("history-checkbox");
+
+ ok(history.checked, "history checkbox is checked");
+ ok(downloads.disabled, "downloads checkbox is disabled");
+ ok(downloads.checked, "downloads checkbox is checked");
+ }
+
+ function test_checkedAndDisabledOnHistoryToggle(aWin)
+ {
+ let doc = aWin.document;
+ let downloads = doc.getElementById("downloads-checkbox");
+ let history = doc.getElementById("history-checkbox");
+
+ EventUtils.synthesizeMouse(history, 0, 0, {}, aWin);
+ ok(!history.checked, "history checkbox is not checked");
+ ok(downloads.disabled, "downloads checkbox is disabled");
+ ok(downloads.checked, "downloads checkbox is checked");
+ }
+
+ function test_checkedAfterAddingDownload(aWin)
+ {
+ let doc = aWin.document;
+ let downloads = doc.getElementById("downloads-checkbox");
+ let history = doc.getElementById("history-checkbox");
+
+ // Add download to DB
+ let file = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).get("TmpD", Ci.nsIFile);
+ file.append("sanitize-dm-test.file");
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0666);
+ let testPath = Services.io.newFileURI(file).spec;
+ let data = {
+ name: "381603.patch",
+ source: "https://bugzilla.mozilla.org/attachment.cgi?id=266520",
+ target: testPath,
+ startTime: 1180493839859230,
+ endTime: 1180493839859239,
+ state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
+ currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0,
+ guid: "a1bcD23eF4g5"
+ };
+ let db = Cc["@mozilla.org/download-manager;1"].
+ getService(Ci.nsIDownloadManager).DBConnection;
+ let stmt = db.createStatement(
+ "INSERT INTO moz_downloads (name, source, target, startTime, endTime, " +
+ "state, currBytes, maxBytes, preferredAction, autoResume, guid) " +
+ "VALUES (:name, :source, :target, :startTime, :endTime, :state, " +
+ ":currBytes, :maxBytes, :preferredAction, :autoResume, :guid)");
+ try {
+ for (let prop in data)
+ stmt.params[prop] = data[prop];
+ stmt.execute();
+ }
+ finally {
+ stmt.finalize();
+ }
+
+ // Toggle history to get everything to update
+ EventUtils.synthesizeMouse(history, 0, 0, {}, aWin);
+ EventUtils.synthesizeMouse(history, 0, 0, {}, aWin);
+
+ ok(!history.checked, "history checkbox is not checked");
+ ok(!downloads.disabled, "downloads checkbox is not disabled");
+ ok(downloads.checked, "downloads checkbox is checked");
+ }
+
+ function test_checkedAndDisabledWithHistoryChecked(aWin)
+ {
+ let doc = aWin.document;
+ let downloads = doc.getElementById("downloads-checkbox");
+ let history = doc.getElementById("history-checkbox");
+
+ EventUtils.synthesizeMouse(history, 0, 0, {}, aWin);
+ ok(history.checked, "history checkbox is checked");
+ ok(downloads.disabled, "downloads checkbox is disabled");
+ ok(downloads.checked, "downloads checkbox is checked");
+ }
+
+ let tests = [
+ test_checkedAndDisabledAtStart,
+ test_checkedAndDisabledOnHistoryToggle,
+ test_checkedAfterAddingDownload,
+ test_checkedAndDisabledWithHistoryChecked,
+ ];
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Run the tests
+
+ let dm = Cc["@mozilla.org/download-manager;1"].
+ getService(Ci.nsIDownloadManager);
+ let db = dm.DBConnection;
+
+ // Empty any old downloads
+ db.executeSimpleSQL("DELETE FROM moz_downloads");
+
+ // Close the UI if necessary
+ let win = Services.ww.getWindowByName("Sanitize", null);
+ if (win && (win instanceof Ci.nsIDOMWindow))
+ win.close();
+
+ // Start the test when the sanitize window loads
+ Services.ww.registerNotification(function (aSubject, aTopic, aData) {
+ Services.ww.unregisterNotification(arguments.callee);
+ aSubject.QueryInterface(Ci.nsIDOMEventTarget)
+ .addEventListener("DOMContentLoaded", doTest, false);
+ });
+
+ // Let the methods that run onload finish before we test
+ let doTest = function() setTimeout(function() {
+ let win = Services.ww.getWindowByName("Sanitize", null)
+ .QueryInterface(Ci.nsIDOMWindow);
+
+ for (let i = 0; i < tests.length; i++)
+ tests[i](win);
+
+ win.close();
+ finish();
+ }, 0);
+
+ // Show the UI
+ Services.ww.openWindow(window,
+ "chrome://browser/content/sanitize.xul",
+ "Sanitize",
+ "chrome,titlebar,centerscreen",
+ null);
+
+ waitForExplicitFinish();
+}
diff --git a/browser/base/content/test/browser_sanitize-passwordDisabledHosts.js b/browser/base/content/test/browser_sanitize-passwordDisabledHosts.js
new file mode 100644
index 000000000..06cf2467e
--- /dev/null
+++ b/browser/base/content/test/browser_sanitize-passwordDisabledHosts.js
@@ -0,0 +1,41 @@
+// Bug 474792 - Clear "Never remember passwords for this site" when
+// clearing site-specific settings in Clear Recent History dialog
+
+let tempScope = {};
+Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://browser/content/sanitize.js", tempScope);
+let Sanitizer = tempScope.Sanitizer;
+
+function test() {
+
+ var pwmgr = Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
+
+ // Add a disabled host
+ pwmgr.setLoginSavingEnabled("http://example.com", false);
+
+ // Sanity check
+ is(pwmgr.getLoginSavingEnabled("http://example.com"), false,
+ "example.com should be disabled for password saving since we haven't cleared that yet.");
+
+ // Set up the sanitizer to just clear siteSettings
+ let s = new Sanitizer();
+ s.ignoreTimespan = false;
+ s.prefDomain = "privacy.cpd.";
+ var itemPrefs = gPrefService.getBranch(s.prefDomain);
+ itemPrefs.setBoolPref("history", false);
+ itemPrefs.setBoolPref("downloads", false);
+ itemPrefs.setBoolPref("cache", false);
+ itemPrefs.setBoolPref("cookies", false);
+ itemPrefs.setBoolPref("formdata", false);
+ itemPrefs.setBoolPref("offlineApps", false);
+ itemPrefs.setBoolPref("passwords", false);
+ itemPrefs.setBoolPref("sessions", false);
+ itemPrefs.setBoolPref("siteSettings", true);
+
+ // Clear it
+ s.sanitize();
+
+ // Make sure it's gone
+ is(pwmgr.getLoginSavingEnabled("http://example.com"), true,
+ "example.com should be enabled for password saving again now that we've cleared.");
+}
diff --git a/browser/base/content/test/browser_sanitize-sitepermissions.js b/browser/base/content/test/browser_sanitize-sitepermissions.js
new file mode 100644
index 000000000..13c0e2068
--- /dev/null
+++ b/browser/base/content/test/browser_sanitize-sitepermissions.js
@@ -0,0 +1,37 @@
+// Bug 380852 - Delete permission manager entries in Clear Recent History
+
+let tempScope = {};
+Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://browser/content/sanitize.js", tempScope);
+let Sanitizer = tempScope.Sanitizer;
+
+function test() {
+
+ // Add a permission entry
+ var pm = Services.perms;
+ pm.add(makeURI("http://example.com"), "testing", pm.ALLOW_ACTION);
+
+ // Sanity check
+ ok(pm.enumerator.hasMoreElements(), "Permission manager should have elements, since we just added one");
+
+ // Set up the sanitizer to just clear siteSettings
+ let s = new Sanitizer();
+ s.ignoreTimespan = false;
+ s.prefDomain = "privacy.cpd.";
+ var itemPrefs = gPrefService.getBranch(s.prefDomain);
+ itemPrefs.setBoolPref("history", false);
+ itemPrefs.setBoolPref("downloads", false);
+ itemPrefs.setBoolPref("cache", false);
+ itemPrefs.setBoolPref("cookies", false);
+ itemPrefs.setBoolPref("formdata", false);
+ itemPrefs.setBoolPref("offlineApps", false);
+ itemPrefs.setBoolPref("passwords", false);
+ itemPrefs.setBoolPref("sessions", false);
+ itemPrefs.setBoolPref("siteSettings", true);
+
+ // Clear it
+ s.sanitize();
+
+ // Make sure it's gone
+ ok(!pm.enumerator.hasMoreElements(), "Permission manager shouldn't have entries after Sanitizing");
+}
diff --git a/browser/base/content/test/browser_sanitize-timespans.js b/browser/base/content/test/browser_sanitize-timespans.js
new file mode 100644
index 000000000..6c6fb059b
--- /dev/null
+++ b/browser/base/content/test/browser_sanitize-timespans.js
@@ -0,0 +1,810 @@
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+// Bug 453440 - Test the timespan-based logic of the sanitizer code
+var now_uSec = Date.now() * 1000;
+
+const dm = Cc["@mozilla.org/download-manager;1"].getService(Ci.nsIDownloadManager);
+
+const kUsecPerMin = 60 * 1000000;
+
+let tempScope = {};
+Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://browser/content/sanitize.js", tempScope);
+let Sanitizer = tempScope.Sanitizer;
+
+let FormHistory = (Components.utils.import("resource://gre/modules/FormHistory.jsm", {})).FormHistory;
+
+function promiseFormHistoryRemoved() {
+ let deferred = Promise.defer();
+ Services.obs.addObserver(function onfh() {
+ Services.obs.removeObserver(onfh, "satchel-storage-changed", false);
+ deferred.resolve();
+ }, "satchel-storage-changed", false);
+ return deferred.promise;
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ Task.spawn(function() {
+ setupDownloads();
+ yield setupFormHistory();
+ yield setupHistory();
+ yield onHistoryReady();
+ }).then(finish);
+}
+
+function countEntries(name, message, check) {
+ let deferred = Promise.defer();
+
+ var obj = {};
+ if (name !== null)
+ obj.fieldname = name;
+
+ let count;
+ FormHistory.count(obj, { handleResult: function (result) count = result,
+ handleError: function (error) {
+ do_throw("Error occurred searching form history: " + error);
+ deferred.reject(error)
+ },
+ handleCompletion: function (reason) {
+ if (!reason) {
+ check(count, message);
+ deferred.resolve();
+ }
+ },
+ });
+
+ return deferred.promise;
+}
+
+function onHistoryReady() {
+ var hoursSinceMidnight = new Date().getHours();
+ var minutesSinceMidnight = hoursSinceMidnight * 60 + new Date().getMinutes();
+
+ // Should test cookies here, but nsICookieManager/nsICookieService
+ // doesn't let us fake creation times. bug 463127
+
+ let s = new Sanitizer();
+ s.ignoreTimespan = false;
+ s.prefDomain = "privacy.cpd.";
+ var itemPrefs = gPrefService.getBranch(s.prefDomain);
+ itemPrefs.setBoolPref("history", true);
+ itemPrefs.setBoolPref("downloads", true);
+ itemPrefs.setBoolPref("cache", false);
+ itemPrefs.setBoolPref("cookies", false);
+ itemPrefs.setBoolPref("formdata", true);
+ itemPrefs.setBoolPref("offlineApps", false);
+ itemPrefs.setBoolPref("passwords", false);
+ itemPrefs.setBoolPref("sessions", false);
+ itemPrefs.setBoolPref("siteSettings", false);
+
+ // Clear 10 minutes ago
+ s.range = [now_uSec - 10*60*1000000, now_uSec];
+ s.sanitize();
+ s.range = null;
+
+ yield promiseFormHistoryRemoved();
+
+ ok(!(yield promiseIsURIVisited(makeURI("http://10minutes.com"))),
+ "Pretend visit to 10minutes.com should now be deleted");
+ ok((yield promiseIsURIVisited(makeURI("http://1hour.com"))),
+ "Pretend visit to 1hour.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://1hour10minutes.com"))),
+ "Pretend visit to 1hour10minutes.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://2hour.com"))),
+ "Pretend visit to 2hour.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))),
+ "Pretend visit to 2hour10minutes.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+ "Pretend visit to 4hour.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+ "Pretend visit to 4hour10minutes.com should should still exist");
+ if (minutesSinceMidnight > 10) {
+ ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+ "Pretend visit to today.com should still exist");
+ }
+ ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+ "Pretend visit to before-today.com should still exist");
+
+ let checkZero = function(num, message) { is(num, 0, message); }
+ let checkOne = function(num, message) { is(num, 1, message); }
+
+ yield countEntries("10minutes", "10minutes form entry should be deleted", checkZero);
+ yield countEntries("1hour", "1hour form entry should still exist", checkOne);
+ yield countEntries("1hour10minutes", "1hour10minutes form entry should still exist", checkOne);
+ yield countEntries("2hour", "2hour form entry should still exist", checkOne);
+ yield countEntries("2hour10minutes", "2hour10minutes form entry should still exist", checkOne);
+ yield countEntries("4hour", "4hour form entry should still exist", checkOne);
+ yield countEntries("4hour10minutes", "4hour10minutes form entry should still exist", checkOne);
+ if (minutesSinceMidnight > 10)
+ yield countEntries("today", "today form entry should still exist", checkOne);
+ yield countEntries("b4today", "b4today form entry should still exist", checkOne);
+
+ ok(!downloadExists(5555555), "10 minute download should now be deleted");
+ ok(downloadExists(5555551), "<1 hour download should still be present");
+ ok(downloadExists(5555556), "1 hour 10 minute download should still be present");
+ ok(downloadExists(5555550), "Year old download should still be present");
+ ok(downloadExists(5555552), "<2 hour old download should still be present");
+ ok(downloadExists(5555557), "2 hour 10 minute download should still be present");
+ ok(downloadExists(5555553), "<4 hour old download should still be present");
+ ok(downloadExists(5555558), "4 hour 10 minute download should still be present");
+
+ if (minutesSinceMidnight > 10)
+ ok(downloadExists(5555554), "'Today' download should still be present");
+
+ // Clear 1 hour
+ Sanitizer.prefs.setIntPref("timeSpan", 1);
+ s.sanitize();
+
+ yield promiseFormHistoryRemoved();
+
+ ok(!(yield promiseIsURIVisited(makeURI("http://1hour.com"))),
+ "Pretend visit to 1hour.com should now be deleted");
+ ok((yield promiseIsURIVisited(makeURI("http://1hour10minutes.com"))),
+ "Pretend visit to 1hour10minutes.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://2hour.com"))),
+ "Pretend visit to 2hour.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))),
+ "Pretend visit to 2hour10minutes.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+ "Pretend visit to 4hour.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+ "Pretend visit to 4hour10minutes.com should should still exist");
+ if (hoursSinceMidnight > 1) {
+ ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+ "Pretend visit to today.com should still exist");
+ }
+ ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+ "Pretend visit to before-today.com should still exist");
+
+ yield countEntries("1hour", "1hour form entry should be deleted", checkZero);
+ yield countEntries("1hour10minutes", "1hour10minutes form entry should still exist", checkOne);
+ yield countEntries("2hour", "2hour form entry should still exist", checkOne);
+ yield countEntries("2hour10minutes", "2hour10minutes form entry should still exist", checkOne);
+ yield countEntries("4hour", "4hour form entry should still exist", checkOne);
+ yield countEntries("4hour10minutes", "4hour10minutes form entry should still exist", checkOne);
+ if (hoursSinceMidnight > 1)
+ yield countEntries("today", "today form entry should still exist", checkOne);
+ yield countEntries("b4today", "b4today form entry should still exist", checkOne);
+
+ ok(!downloadExists(5555551), "<1 hour download should now be deleted");
+ ok(downloadExists(5555556), "1 hour 10 minute download should still be present");
+ ok(downloadExists(5555550), "Year old download should still be present");
+ ok(downloadExists(5555552), "<2 hour old download should still be present");
+ ok(downloadExists(5555557), "2 hour 10 minute download should still be present");
+ ok(downloadExists(5555553), "<4 hour old download should still be present");
+ ok(downloadExists(5555558), "4 hour 10 minute download should still be present");
+
+ if (hoursSinceMidnight > 1)
+ ok(downloadExists(5555554), "'Today' download should still be present");
+
+ // Clear 1 hour 10 minutes
+ s.range = [now_uSec - 70*60*1000000, now_uSec];
+ s.sanitize();
+ s.range = null;
+
+ yield promiseFormHistoryRemoved();
+
+ ok(!(yield promiseIsURIVisited(makeURI("http://1hour10minutes.com"))),
+ "Pretend visit to 1hour10minutes.com should now be deleted");
+ ok((yield promiseIsURIVisited(makeURI("http://2hour.com"))),
+ "Pretend visit to 2hour.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))),
+ "Pretend visit to 2hour10minutes.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+ "Pretend visit to 4hour.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+ "Pretend visit to 4hour10minutes.com should should still exist");
+ if (minutesSinceMidnight > 70) {
+ ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+ "Pretend visit to today.com should still exist");
+ }
+ ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+ "Pretend visit to before-today.com should still exist");
+
+ yield countEntries("1hour10minutes", "1hour10minutes form entry should be deleted", checkZero);
+ yield countEntries("2hour", "2hour form entry should still exist", checkOne);
+ yield countEntries("2hour10minutes", "2hour10minutes form entry should still exist", checkOne);
+ yield countEntries("4hour", "4hour form entry should still exist", checkOne);
+ yield countEntries("4hour10minutes", "4hour10minutes form entry should still exist", checkOne);
+ if (minutesSinceMidnight > 70)
+ yield countEntries("today", "today form entry should still exist", checkOne);
+ yield countEntries("b4today", "b4today form entry should still exist", checkOne);
+
+ ok(!downloadExists(5555556), "1 hour 10 minute old download should now be deleted");
+ ok(downloadExists(5555550), "Year old download should still be present");
+ ok(downloadExists(5555552), "<2 hour old download should still be present");
+ ok(downloadExists(5555557), "2 hour 10 minute download should still be present");
+ ok(downloadExists(5555553), "<4 hour old download should still be present");
+ ok(downloadExists(5555558), "4 hour 10 minute download should still be present");
+ if (minutesSinceMidnight > 70)
+ ok(downloadExists(5555554), "'Today' download should still be present");
+
+ // Clear 2 hours
+ Sanitizer.prefs.setIntPref("timeSpan", 2);
+ s.sanitize();
+
+ yield promiseFormHistoryRemoved();
+
+ ok(!(yield promiseIsURIVisited(makeURI("http://2hour.com"))),
+ "Pretend visit to 2hour.com should now be deleted");
+ ok((yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))),
+ "Pretend visit to 2hour10minutes.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+ "Pretend visit to 4hour.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+ "Pretend visit to 4hour10minutes.com should should still exist");
+ if (hoursSinceMidnight > 2) {
+ ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+ "Pretend visit to today.com should still exist");
+ }
+ ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+ "Pretend visit to before-today.com should still exist");
+
+ yield countEntries("2hour", "2hour form entry should be deleted", checkZero);
+ yield countEntries("2hour10minutes", "2hour10minutes form entry should still exist", checkOne);
+ yield countEntries("4hour", "4hour form entry should still exist", checkOne);
+ yield countEntries("4hour10minutes", "4hour10minutes form entry should still exist", checkOne);
+ if (hoursSinceMidnight > 2)
+ yield countEntries("today", "today form entry should still exist", checkOne);
+ yield countEntries("b4today", "b4today form entry should still exist", checkOne);
+
+ ok(!downloadExists(5555552), "<2 hour old download should now be deleted");
+ ok(downloadExists(5555550), "Year old download should still be present");
+ ok(downloadExists(5555557), "2 hour 10 minute download should still be present");
+ ok(downloadExists(5555553), "<4 hour old download should still be present");
+ ok(downloadExists(5555558), "4 hour 10 minute download should still be present");
+ if (hoursSinceMidnight > 2)
+ ok(downloadExists(5555554), "'Today' download should still be present");
+
+ // Clear 2 hours 10 minutes
+ s.range = [now_uSec - 130*60*1000000, now_uSec];
+ s.sanitize();
+ s.range = null;
+
+ yield promiseFormHistoryRemoved();
+
+ ok(!(yield promiseIsURIVisited(makeURI("http://2hour10minutes.com"))),
+ "Pretend visit to 2hour10minutes.com should now be deleted");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+ "Pretend visit to 4hour.com should should still exist");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+ "Pretend visit to 4hour10minutes.com should should still exist");
+ if (minutesSinceMidnight > 130) {
+ ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+ "Pretend visit to today.com should still exist");
+ }
+ ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+ "Pretend visit to before-today.com should still exist");
+
+ yield countEntries("2hour10minutes", "2hour10minutes form entry should be deleted", checkZero);
+ yield countEntries("4hour", "4hour form entry should still exist", checkOne);
+ yield countEntries("4hour10minutes", "4hour10minutes form entry should still exist", checkOne);
+ if (minutesSinceMidnight > 130)
+ yield countEntries("today", "today form entry should still exist", checkOne);
+ yield countEntries("b4today", "b4today form entry should still exist", checkOne);
+
+ ok(!downloadExists(5555557), "2 hour 10 minute old download should now be deleted");
+ ok(downloadExists(5555553), "<4 hour old download should still be present");
+ ok(downloadExists(5555558), "4 hour 10 minute download should still be present");
+ ok(downloadExists(5555550), "Year old download should still be present");
+ if (minutesSinceMidnight > 130)
+ ok(downloadExists(5555554), "'Today' download should still be present");
+
+ // Clear 4 hours
+ Sanitizer.prefs.setIntPref("timeSpan", 3);
+ s.sanitize();
+
+ yield promiseFormHistoryRemoved();
+
+ ok(!(yield promiseIsURIVisited(makeURI("http://4hour.com"))),
+ "Pretend visit to 4hour.com should now be deleted");
+ ok((yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+ "Pretend visit to 4hour10minutes.com should should still exist");
+ if (hoursSinceMidnight > 4) {
+ ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+ "Pretend visit to today.com should still exist");
+ }
+ ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+ "Pretend visit to before-today.com should still exist");
+
+ yield countEntries("4hour", "4hour form entry should be deleted", checkZero);
+ yield countEntries("4hour10minutes", "4hour10minutes form entry should still exist", checkOne);
+ if (hoursSinceMidnight > 4)
+ yield countEntries("today", "today form entry should still exist", checkOne);
+ yield countEntries("b4today", "b4today form entry should still exist", checkOne);
+
+ ok(!downloadExists(5555553), "<4 hour old download should now be deleted");
+ ok(downloadExists(5555558), "4 hour 10 minute download should still be present");
+ ok(downloadExists(5555550), "Year old download should still be present");
+ if (hoursSinceMidnight > 4)
+ ok(downloadExists(5555554), "'Today' download should still be present");
+
+ // Clear 4 hours 10 minutes
+ s.range = [now_uSec - 250*60*1000000, now_uSec];
+ s.sanitize();
+ s.range = null;
+
+ yield promiseFormHistoryRemoved();
+
+ ok(!(yield promiseIsURIVisited(makeURI("http://4hour10minutes.com"))),
+ "Pretend visit to 4hour10minutes.com should now be deleted");
+ if (minutesSinceMidnight > 250) {
+ ok((yield promiseIsURIVisited(makeURI("http://today.com"))),
+ "Pretend visit to today.com should still exist");
+ }
+ ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+ "Pretend visit to before-today.com should still exist");
+
+ yield countEntries("4hour10minutes", "4hour10minutes form entry should be deleted", checkZero);
+ if (minutesSinceMidnight > 250)
+ yield countEntries("today", "today form entry should still exist", checkOne);
+ yield countEntries("b4today", "b4today form entry should still exist", checkOne);
+
+ ok(!downloadExists(5555558), "4 hour 10 minute download should now be deleted");
+ ok(downloadExists(5555550), "Year old download should still be present");
+ if (minutesSinceMidnight > 250)
+ ok(downloadExists(5555554), "'Today' download should still be present");
+
+ // Clear Today
+ Sanitizer.prefs.setIntPref("timeSpan", 4);
+ s.sanitize();
+
+ yield promiseFormHistoryRemoved();
+
+ // Be careful. If we add our objectss just before midnight, and sanitize
+ // runs immediately after, they won't be expired. This is expected, but
+ // we should not test in that case. We cannot just test for opposite
+ // condition because we could cross midnight just one moment after we
+ // cache our time, then we would have an even worse random failure.
+ var today = isToday(new Date(now_uSec/1000));
+ if (today) {
+ ok(!(yield promiseIsURIVisited(makeURI("http://today.com"))),
+ "Pretend visit to today.com should now be deleted");
+
+ yield countEntries("today", "today form entry should be deleted", checkZero);
+ ok(!downloadExists(5555554), "'Today' download should now be deleted");
+ }
+
+ ok((yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+ "Pretend visit to before-today.com should still exist");
+ yield countEntries("b4today", "b4today form entry should still exist", checkOne);
+ ok(downloadExists(5555550), "Year old download should still be present");
+
+ // Choose everything
+ Sanitizer.prefs.setIntPref("timeSpan", 0);
+ s.sanitize();
+
+ yield promiseFormHistoryRemoved();
+
+ ok(!(yield promiseIsURIVisited(makeURI("http://before-today.com"))),
+ "Pretend visit to before-today.com should now be deleted");
+
+ yield countEntries("b4today", "b4today form entry should be deleted", checkZero);
+
+ ok(!downloadExists(5555550), "Year old download should now be deleted");
+}
+
+function setupHistory() {
+ let deferred = Promise.defer();
+
+ let places = [];
+
+ function addPlace(aURI, aTitle, aVisitDate) {
+ places.push({
+ uri: aURI,
+ title: aTitle,
+ visits: [{
+ visitDate: aVisitDate,
+ transitionType: Ci.nsINavHistoryService.TRANSITION_LINK
+ }]
+ });
+ }
+
+ addPlace(makeURI("http://10minutes.com/"), "10 minutes ago", now_uSec - 10 * kUsecPerMin);
+ addPlace(makeURI("http://1hour.com/"), "Less than 1 hour ago", now_uSec - 45 * kUsecPerMin);
+ addPlace(makeURI("http://1hour10minutes.com/"), "1 hour 10 minutes ago", now_uSec - 70 * kUsecPerMin);
+ addPlace(makeURI("http://2hour.com/"), "Less than 2 hours ago", now_uSec - 90 * kUsecPerMin);
+ addPlace(makeURI("http://2hour10minutes.com/"), "2 hours 10 minutes ago", now_uSec - 130 * kUsecPerMin);
+ addPlace(makeURI("http://4hour.com/"), "Less than 4 hours ago", now_uSec - 180 * kUsecPerMin);
+ addPlace(makeURI("http://4hour10minutes.com/"), "4 hours 10 minutesago", now_uSec - 250 * kUsecPerMin);
+
+ let today = new Date();
+ today.setHours(0);
+ today.setMinutes(0);
+ today.setSeconds(1);
+ addPlace(makeURI("http://today.com/"), "Today", today.getTime() * 1000);
+
+ let lastYear = new Date();
+ lastYear.setFullYear(lastYear.getFullYear() - 1);
+ addPlace(makeURI("http://before-today.com/"), "Before Today", lastYear.getTime() * 1000);
+
+ PlacesUtils.asyncHistory.updatePlaces(places, {
+ handleError: function () ok(false, "Unexpected error in adding visit."),
+ handleResult: function () { },
+ handleCompletion: function () deferred.resolve()
+ });
+
+ return deferred.promise;
+}
+
+function setupFormHistory() {
+
+ function searchEntries(terms, params) {
+ let deferred = Promise.defer();
+
+ let results = [];
+ FormHistory.search(terms, params, { handleResult: function (result) results.push(result),
+ handleError: function (error) {
+ do_throw("Error occurred searching form history: " + error);
+ deferred.reject(error);
+ },
+ handleCompletion: function (reason) { deferred.resolve(results); }
+ });
+ return deferred.promise;
+ }
+
+ function update(changes)
+ {
+ let deferred = Promise.defer();
+ FormHistory.update(changes, { handleError: function (error) {
+ do_throw("Error occurred searching form history: " + error);
+ deferred.reject(error);
+ },
+ handleCompletion: function (reason) { deferred.resolve(); }
+ });
+ return deferred.promise;
+ }
+
+ // Make sure we've got a clean DB to start with, then add the entries we'll be testing.
+ yield update(
+ [{
+ op: "remove"
+ },
+ {
+ op : "add",
+ fieldname : "10minutes",
+ value : "10m"
+ }, {
+ op : "add",
+ fieldname : "1hour",
+ value : "1h"
+ }, {
+ op : "add",
+ fieldname : "1hour10minutes",
+ value : "1h10m"
+ }, {
+ op : "add",
+ fieldname : "2hour",
+ value : "2h"
+ }, {
+ op : "add",
+ fieldname : "2hour10minutes",
+ value : "2h10m"
+ }, {
+ op : "add",
+ fieldname : "4hour",
+ value : "4h"
+ }, {
+ op : "add",
+ fieldname : "4hour10minutes",
+ value : "4h10m"
+ }, {
+ op : "add",
+ fieldname : "today",
+ value : "1d"
+ }, {
+ op : "add",
+ fieldname : "b4today",
+ value : "1y"
+ }]);
+
+ // Artifically age the entries to the proper vintage.
+ let timestamp = now_uSec - 10 * kUsecPerMin;
+ let results = yield searchEntries(["guid"], { fieldname: "10minutes" });
+ yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid });
+
+ timestamp = now_uSec - 45 * kUsecPerMin;
+ results = yield searchEntries(["guid"], { fieldname: "1hour" });
+ yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid });
+
+ timestamp = now_uSec - 70 * kUsecPerMin;
+ results = yield searchEntries(["guid"], { fieldname: "1hour10minutes" });
+ yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid });
+
+ timestamp = now_uSec - 90 * kUsecPerMin;
+ results = yield searchEntries(["guid"], { fieldname: "2hour" });
+ yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid });
+
+ timestamp = now_uSec - 130 * kUsecPerMin;
+ results = yield searchEntries(["guid"], { fieldname: "2hour10minutes" });
+ yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid });
+
+ timestamp = now_uSec - 180 * kUsecPerMin;
+ results = yield searchEntries(["guid"], { fieldname: "4hour" });
+ yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid });
+
+ timestamp = now_uSec - 250 * kUsecPerMin;
+ results = yield searchEntries(["guid"], { fieldname: "4hour10minutes" });
+ yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid });
+
+ let today = new Date();
+ today.setHours(0);
+ today.setMinutes(0);
+ today.setSeconds(1);
+ timestamp = today.getTime() * 1000;
+ results = yield searchEntries(["guid"], { fieldname: "today" });
+ yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid });
+
+ let lastYear = new Date();
+ lastYear.setFullYear(lastYear.getFullYear() - 1);
+ timestamp = lastYear.getTime() * 1000;
+ results = yield searchEntries(["guid"], { fieldname: "b4today" });
+ yield update({ op: "update", firstUsed: timestamp, guid: results[0].guid });
+
+ var checks = 0;
+ let checkOne = function(num, message) { is(num, 1, message); checks++; }
+
+ // Sanity check.
+ yield countEntries("10minutes", "Checking for 10minutes form history entry creation", checkOne);
+ yield countEntries("1hour", "Checking for 1hour form history entry creation", checkOne);
+ yield countEntries("1hour10minutes", "Checking for 1hour10minutes form history entry creation", checkOne);
+ yield countEntries("2hour", "Checking for 2hour form history entry creation", checkOne);
+ yield countEntries("2hour10minutes", "Checking for 2hour10minutes form history entry creation", checkOne);
+ yield countEntries("4hour", "Checking for 4hour form history entry creation", checkOne);
+ yield countEntries("4hour10minutes", "Checking for 4hour10minutes form history entry creation", checkOne);
+ yield countEntries("today", "Checking for today form history entry creation", checkOne);
+ yield countEntries("b4today", "Checking for b4today form history entry creation", checkOne);
+ is(checks, 9, "9 checks made");
+}
+
+function setupDownloads() {
+
+ // Add 10-minutes download to DB
+ let data = {
+ id: "5555555",
+ name: "fakefile-10-minutes",
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
+ target: "fakefile-10-minutes",
+ startTime: now_uSec - 10 * kUsecPerMin, // 10 minutes ago, in uSec
+ endTime: now_uSec - 11 * kUsecPerMin, // 1 minute later
+ state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
+ currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0,
+ guid: "a1bcD23eF4g5"
+ };
+
+ let db = dm.DBConnection;
+ let stmt = db.createStatement(
+ "INSERT INTO moz_downloads (id, name, source, target, startTime, endTime, " +
+ "state, currBytes, maxBytes, preferredAction, autoResume, guid) " +
+ "VALUES (:id, :name, :source, :target, :startTime, :endTime, :state, " +
+ ":currBytes, :maxBytes, :preferredAction, :autoResume, :guid)");
+ try {
+ for (let prop in data)
+ stmt.params[prop] = data[prop];
+ stmt.execute();
+ }
+ finally {
+ stmt.reset();
+ }
+
+ // Add within-1-hour download to DB
+ data = {
+ id: "5555551",
+ name: "fakefile-1-hour",
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
+ target: "fakefile-1-hour",
+ startTime: now_uSec - 45 * kUsecPerMin, // 45 minutes ago, in uSec
+ endTime: now_uSec - 44 * kUsecPerMin, // 1 minute later
+ state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
+ currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0,
+ guid: "1bcD23eF4g5a"
+ };
+
+ try {
+ for (let prop in data)
+ stmt.params[prop] = data[prop];
+ stmt.execute();
+ }
+ finally {
+ stmt.reset();
+ }
+
+ // Add 1-hour-10-minutes download to DB
+ data = {
+ id: "5555556",
+ name: "fakefile-1-hour-10-minutes",
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
+ target: "fakefile-1-hour-10-minutes",
+ startTime: now_uSec - 70 * kUsecPerMin, // 70 minutes ago, in uSec
+ endTime: now_uSec - 71 * kUsecPerMin, // 1 minute later
+ state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
+ currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0,
+ guid: "a1cbD23e4Fg5"
+ };
+
+ try {
+ for (let prop in data)
+ stmt.params[prop] = data[prop];
+ stmt.execute();
+ }
+ finally {
+ stmt.reset();
+ }
+
+ // Add within-2-hour download
+ data = {
+ id: "5555552",
+ name: "fakefile-2-hour",
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
+ target: "fakefile-2-hour",
+ startTime: now_uSec - 90 * kUsecPerMin, // 90 minutes ago, in uSec
+ endTime: now_uSec - 89 * kUsecPerMin, // 1 minute later
+ state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
+ currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0,
+ guid: "b1aDc23eFg54"
+ };
+
+ try {
+ for (let prop in data)
+ stmt.params[prop] = data[prop];
+ stmt.execute();
+ }
+ finally {
+ stmt.reset();
+ }
+
+ // Add 2-hour-10-minutes download
+ data = {
+ id: "5555557",
+ name: "fakefile-2-hour-10-minutes",
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
+ target: "fakefile-2-hour-10-minutes",
+ startTime: now_uSec - 130 * kUsecPerMin, // 130 minutes ago, in uSec
+ endTime: now_uSec - 131 * kUsecPerMin, // 1 minute later
+ state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
+ currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0,
+ guid: "z1bcD23eF4g5"
+ };
+
+ try {
+ for (let prop in data)
+ stmt.params[prop] = data[prop];
+ stmt.execute();
+ }
+ finally {
+ stmt.reset();
+ }
+
+ // Add within-4-hour download
+ data = {
+ id: "5555553",
+ name: "fakefile-4-hour",
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
+ target: "fakefile-4-hour",
+ startTime: now_uSec - 180 * kUsecPerMin, // 180 minutes ago, in uSec
+ endTime: now_uSec - 179 * kUsecPerMin, // 1 minute later
+ state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
+ currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0,
+ guid: "zzzcD23eF4g5"
+ };
+
+ try {
+ for (let prop in data)
+ stmt.params[prop] = data[prop];
+ stmt.execute();
+ }
+ finally {
+ stmt.reset();
+ }
+
+ // Add 4-hour-10-minutes download
+ data = {
+ id: "5555558",
+ name: "fakefile-4-hour-10-minutes",
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
+ target: "fakefile-4-hour-10-minutes",
+ startTime: now_uSec - 250 * kUsecPerMin, // 250 minutes ago, in uSec
+ endTime: now_uSec - 251 * kUsecPerMin, // 1 minute later
+ state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
+ currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0,
+ guid: "z1bzz23eF4gz"
+ };
+
+ try {
+ for (let prop in data)
+ stmt.params[prop] = data[prop];
+ stmt.execute();
+ }
+ finally {
+ stmt.reset();
+ }
+
+ // Add "today" download
+ let today = new Date();
+ today.setHours(0);
+ today.setMinutes(0);
+ today.setSeconds(1);
+
+ data = {
+ id: "5555554",
+ name: "fakefile-today",
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
+ target: "fakefile-today",
+ startTime: today.getTime() * 1000, // 12:00:30am this morning, in uSec
+ endTime: (today.getTime() + 1000) * 1000, // 1 second later
+ state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
+ currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0,
+ guid: "ffffD23eF4g5"
+ };
+
+ try {
+ for (let prop in data)
+ stmt.params[prop] = data[prop];
+ stmt.execute();
+ }
+ finally {
+ stmt.reset();
+ }
+
+ // Add "before today" download
+ let lastYear = new Date();
+ lastYear.setFullYear(lastYear.getFullYear() - 1);
+ data = {
+ id: "5555550",
+ name: "fakefile-old",
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=453440",
+ target: "fakefile-old",
+ startTime: lastYear.getTime() * 1000, // 1 year ago, in uSec
+ endTime: (lastYear.getTime() + 1000) * 1000, // 1 second later
+ state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
+ currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0,
+ guid: "ggggg23eF4g5"
+ };
+
+ try {
+ for (let prop in data)
+ stmt.params[prop] = data[prop];
+ stmt.execute();
+ }
+ finally {
+ stmt.finalize();
+ }
+
+ // Confirm everything worked
+ ok(downloadExists(5555550), "Pretend download for everything case should exist");
+ ok(downloadExists(5555555), "Pretend download for 10-minutes case should exist");
+ ok(downloadExists(5555551), "Pretend download for 1-hour case should exist");
+ ok(downloadExists(5555556), "Pretend download for 1-hour-10-minutes case should exist");
+ ok(downloadExists(5555552), "Pretend download for 2-hour case should exist");
+ ok(downloadExists(5555557), "Pretend download for 2-hour-10-minutes case should exist");
+ ok(downloadExists(5555553), "Pretend download for 4-hour case should exist");
+ ok(downloadExists(5555558), "Pretend download for 4-hour-10-minutes case should exist");
+ ok(downloadExists(5555554), "Pretend download for Today case should exist");
+}
+
+/**
+ * Checks to see if the downloads with the specified id exists.
+ *
+ * @param aID
+ * The ids of the downloads to check.
+ */
+function downloadExists(aID)
+{
+ let db = dm.DBConnection;
+ let stmt = db.createStatement(
+ "SELECT * " +
+ "FROM moz_downloads " +
+ "WHERE id = :id"
+ );
+ stmt.params.id = aID;
+ var rows = stmt.executeStep();
+ stmt.finalize();
+ return rows;
+}
+
+function isToday(aDate) {
+ return aDate.getDate() == new Date().getDate();
+}
diff --git a/browser/base/content/test/browser_sanitizeDialog.js b/browser/base/content/test/browser_sanitizeDialog.js
new file mode 100644
index 000000000..02f32e3bf
--- /dev/null
+++ b/browser/base/content/test/browser_sanitizeDialog.js
@@ -0,0 +1,1099 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Tests the sanitize dialog (a.k.a. the clear recent history dialog).
+ * See bug 480169.
+ *
+ * The purpose of this test is not to fully flex the sanitize timespan code;
+ * browser/base/content/test/browser_sanitize-timespans.js does that. This
+ * test checks the UI of the dialog and makes sure it's correctly connected to
+ * the sanitize timespan code.
+ *
+ * Some of this code, especially the history creation parts, was taken from
+ * browser/base/content/test/browser_sanitize-timespans.js.
+ */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FormHistory",
+ "resource://gre/modules/FormHistory.jsm");
+
+let tempScope = {};
+Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://browser/content/sanitize.js", tempScope);
+let Sanitizer = tempScope.Sanitizer;
+
+const dm = Cc["@mozilla.org/download-manager;1"].
+ getService(Ci.nsIDownloadManager);
+
+const kUsecPerMin = 60 * 1000000;
+
+let formEntries;
+
+// Add tests here. Each is a function that's called by doNextTest().
+var gAllTests = [
+
+ /**
+ * Initializes the dialog to its default state.
+ */
+ function () {
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ // Select "Last Hour"
+ this.selectDuration(Sanitizer.TIMESPAN_HOUR);
+ // Hide details
+ if (!this.getItemList().collapsed)
+ this.toggleDetails();
+ this.acceptDialog();
+ };
+ wh.open();
+ },
+
+ /**
+ * Cancels the dialog, makes sure history not cleared.
+ */
+ function () {
+ // Add history (within the past hour)
+ let uris = [];
+ let places = [];
+ let pURI;
+ for (let i = 0; i < 30; i++) {
+ pURI = makeURI("http://" + i + "-minutes-ago.com/");
+ places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
+ uris.push(pURI);
+ }
+
+ addVisits(places, function() {
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ this.selectDuration(Sanitizer.TIMESPAN_HOUR);
+ this.checkPrefCheckbox("history", false);
+ this.checkDetails(false);
+
+ // Show details
+ this.toggleDetails();
+ this.checkDetails(true);
+
+ // Hide details
+ this.toggleDetails();
+ this.checkDetails(false);
+ this.cancelDialog();
+ };
+ wh.onunload = function () {
+ yield promiseHistoryClearedState(uris, false);
+ yield blankSlate();
+ yield promiseHistoryClearedState(uris, true);
+ };
+ wh.open();
+ });
+ },
+
+ /**
+ * Ensures that the combined history-downloads checkbox clears both history
+ * visits and downloads when checked; the dialog respects simple timespan.
+ */
+ function () {
+ // Add history (within the past hour).
+ let uris = [];
+ let places = [];
+ let pURI;
+ for (let i = 0; i < 30; i++) {
+ pURI = makeURI("http://" + i + "-minutes-ago.com/");
+ places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
+ uris.push(pURI);
+ }
+ // Add history (over an hour ago).
+ let olderURIs = [];
+ for (let i = 0; i < 5; i++) {
+ pURI = makeURI("http://" + (61 + i) + "-minutes-ago.com/");
+ places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(61 + i)});
+ olderURIs.push(pURI);
+ }
+
+ addVisits(places, function() {
+ // Add downloads (within the past hour).
+ let downloadIDs = [];
+ for (let i = 0; i < 5; i++) {
+ downloadIDs.push(addDownloadWithMinutesAgo(i));
+ }
+ // Add downloads (over an hour ago).
+ let olderDownloadIDs = [];
+ for (let i = 0; i < 5; i++) {
+ olderDownloadIDs.push(addDownloadWithMinutesAgo(61 + i));
+ }
+ let totalHistoryVisits = uris.length + olderURIs.length;
+
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ this.selectDuration(Sanitizer.TIMESPAN_HOUR);
+ this.checkPrefCheckbox("history", true);
+ this.acceptDialog();
+
+ intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_HOUR,
+ "timeSpan pref should be hour after accepting dialog with " +
+ "hour selected");
+ boolPrefIs("cpd.history", true,
+ "history pref should be true after accepting dialog with " +
+ "history checkbox checked");
+ boolPrefIs("cpd.downloads", true,
+ "downloads pref should be true after accepting dialog with " +
+ "history checkbox checked");
+ };
+ wh.onunload = function () {
+ // History visits and downloads within one hour should be cleared.
+ yield promiseHistoryClearedState(uris, true);
+ ensureDownloadsClearedState(downloadIDs, true);
+
+ // Visits and downloads > 1 hour should still exist.
+ yield promiseHistoryClearedState(olderURIs, false);
+ ensureDownloadsClearedState(olderDownloadIDs, false);
+
+ // OK, done, cleanup after ourselves.
+ yield blankSlate();
+ yield promiseHistoryClearedState(olderURIs, true);
+ ensureDownloadsClearedState(olderDownloadIDs, true);
+ };
+ wh.open();
+ });
+ },
+
+ /**
+ * Add form history entries for the next test.
+ */
+ function () {
+ formEntries = [];
+
+ let iter = function() {
+ for (let i = 0; i < 5; i++) {
+ formEntries.push(addFormEntryWithMinutesAgo(iter, i));
+ yield;
+ }
+ doNextTest();
+ }();
+
+ iter.next();
+ },
+
+ /**
+ * Ensures that the combined history-downloads checkbox removes neither
+ * history visits nor downloads when not checked.
+ */
+ function () {
+ // Add history, downloads, form entries (within the past hour).
+ let uris = [];
+ let places = [];
+ let pURI;
+ for (let i = 0; i < 5; i++) {
+ pURI = makeURI("http://" + i + "-minutes-ago.com/");
+ places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
+ uris.push(pURI);
+ }
+
+ addVisits(places, function() {
+ let downloadIDs = [];
+ for (let i = 0; i < 5; i++) {
+ downloadIDs.push(addDownloadWithMinutesAgo(i));
+ }
+
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ is(this.isWarningPanelVisible(), false,
+ "Warning panel should be hidden after previously accepting dialog " +
+ "with a predefined timespan");
+ this.selectDuration(Sanitizer.TIMESPAN_HOUR);
+
+ // Remove only form entries, leave history (including downloads).
+ this.checkPrefCheckbox("history", false);
+ this.checkPrefCheckbox("formdata", true);
+ this.acceptDialog();
+
+ intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_HOUR,
+ "timeSpan pref should be hour after accepting dialog with " +
+ "hour selected");
+ boolPrefIs("cpd.history", false,
+ "history pref should be false after accepting dialog with " +
+ "history checkbox unchecked");
+ boolPrefIs("cpd.downloads", false,
+ "downloads pref should be false after accepting dialog with " +
+ "history checkbox unchecked");
+ };
+ wh.onunload = function () {
+ // Of the three only form entries should be cleared.
+ yield promiseHistoryClearedState(uris, false);
+ ensureDownloadsClearedState(downloadIDs, false);
+
+ formEntries.forEach(function (entry) {
+ let exists = yield formNameExists(entry);
+ is(exists, false, "form entry " + entry + " should no longer exist");
+ });
+
+ // OK, done, cleanup after ourselves.
+ yield blankSlate();
+ yield promiseHistoryClearedState(uris, true);
+ ensureDownloadsClearedState(downloadIDs, true);
+ };
+ wh.open();
+ });
+ },
+
+ /**
+ * Ensures that the "Everything" duration option works.
+ */
+ function () {
+ // Add history.
+ let uris = [];
+ let places = [];
+ let pURI;
+ // within past hour, within past two hours, within past four hours and
+ // outside past four hours
+ [10, 70, 130, 250].forEach(function(aValue) {
+ pURI = makeURI("http://" + aValue + "-minutes-ago.com/");
+ places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(aValue)});
+ uris.push(pURI);
+ });
+ addVisits(places, function() {
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ is(this.isWarningPanelVisible(), false,
+ "Warning panel should be hidden after previously accepting dialog " +
+ "with a predefined timespan");
+ this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
+ this.checkPrefCheckbox("history", true);
+ this.checkDetails(true);
+
+ // Hide details
+ this.toggleDetails();
+ this.checkDetails(false);
+
+ // Show details
+ this.toggleDetails();
+ this.checkDetails(true);
+
+ this.acceptDialog();
+
+ intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_EVERYTHING,
+ "timeSpan pref should be everything after accepting dialog " +
+ "with everything selected");
+ };
+ wh.onunload = function () {
+ yield promiseHistoryClearedState(uris, true);
+ };
+ wh.open();
+ });
+ },
+
+ /**
+ * Ensures that the "Everything" warning is visible on dialog open after
+ * the previous test.
+ */
+ function () {
+ // Add history.
+ let uris = [];
+ let places = [];
+ let pURI;
+ // within past hour, within past two hours, within past four hours and
+ // outside past four hours
+ [10, 70, 130, 250].forEach(function(aValue) {
+ pURI = makeURI("http://" + aValue + "-minutes-ago.com/");
+ places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(aValue)});
+ uris.push(pURI);
+ });
+ addVisits(places, function() {
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ is(this.isWarningPanelVisible(), true,
+ "Warning panel should be visible after previously accepting dialog " +
+ "with clearing everything");
+ this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
+ this.checkPrefCheckbox("history", true);
+ this.acceptDialog();
+
+ intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_EVERYTHING,
+ "timeSpan pref should be everything after accepting dialog " +
+ "with everything selected");
+ };
+ wh.onunload = function () {
+ yield promiseHistoryClearedState(uris, true);
+ };
+ wh.open();
+ });
+ },
+
+ /**
+ * Add form history entry for the next test.
+ */
+ function () {
+ let iter = function() {
+ formEntries = [ addFormEntryWithMinutesAgo(iter, 10) ];
+ yield;
+ doNextTest();
+ }();
+
+ iter.next();
+ },
+
+ /**
+ * The next three tests checks that when a certain history item cannot be
+ * cleared then the checkbox should be both disabled and unchecked.
+ * In addition, we ensure that this behavior does not modify the preferences.
+ */
+ function () {
+ // Add history.
+ let pURI = makeURI("http://" + 10 + "-minutes-ago.com/");
+ addVisits({uri: pURI, visitDate: visitTimeForMinutesAgo(10)}, function() {
+ let uris = [ pURI ];
+
+ let wh = new WindowHelper();
+ wh.onload = function() {
+ // Check that the relevant checkboxes are enabled
+ var cb = this.win.document.querySelectorAll(
+ "#itemList > [preference='privacy.cpd.formdata']");
+ ok(cb.length == 1 && !cb[0].disabled, "There is formdata, checkbox to " +
+ "clear formdata should be enabled.");
+
+ var cb = this.win.document.querySelectorAll(
+ "#itemList > [preference='privacy.cpd.history']");
+ ok(cb.length == 1 && !cb[0].disabled, "There is history, checkbox to " +
+ "clear history should be enabled.");
+
+ this.checkAllCheckboxes();
+ this.acceptDialog();
+ };
+ wh.onunload = function () {
+ yield promiseHistoryClearedState(uris, true);
+
+ let exists = yield formNameExists(formEntries[0]);
+ is(exists, false, "form entry " + formEntries[0] + " should no longer exist");
+ };
+ wh.open();
+ });
+ },
+ function () {
+ let wh = new WindowHelper();
+ wh.onload = function() {
+ boolPrefIs("cpd.history", true,
+ "history pref should be true after accepting dialog with " +
+ "history checkbox checked");
+ boolPrefIs("cpd.formdata", true,
+ "formdata pref should be true after accepting dialog with " +
+ "formdata checkbox checked");
+
+
+ // Even though the formdata pref is true, because there is no history
+ // left to clear, the checkbox will be disabled.
+ var cb = this.win.document.querySelectorAll(
+ "#itemList > [preference='privacy.cpd.formdata']");
+ ok(cb.length == 1 && cb[0].disabled && !cb[0].checked,
+ "There is no formdata history, checkbox should be disabled and be " +
+ "cleared to reduce user confusion (bug 497664).");
+
+ var cb = this.win.document.querySelectorAll(
+ "#itemList > [preference='privacy.cpd.history']");
+ ok(cb.length == 1 && !cb[0].disabled && cb[0].checked,
+ "There is no history, but history checkbox should always be enabled " +
+ "and will be checked from previous preference.");
+
+ this.acceptDialog();
+ }
+ wh.open();
+ },
+
+ /**
+ * Add form history entry for the next test.
+ */
+ function () {
+ let iter = function() {
+ formEntries = [ addFormEntryWithMinutesAgo(iter, 10) ];
+ yield;
+ doNextTest();
+ }();
+
+ iter.next();
+ },
+
+ function () {
+ let wh = new WindowHelper();
+ wh.onload = function() {
+ boolPrefIs("cpd.formdata", true,
+ "formdata pref should persist previous value after accepting " +
+ "dialog where you could not clear formdata.");
+
+ var cb = this.win.document.querySelectorAll(
+ "#itemList > [preference='privacy.cpd.formdata']");
+ ok(cb.length == 1 && !cb[0].disabled && cb[0].checked,
+ "There exists formEntries so the checkbox should be in sync with " +
+ "the pref.");
+
+ this.acceptDialog();
+ };
+ wh.onunload = function () {
+ let exists = yield formNameExists(formEntries[0]);
+ is(exists, false, "form entry " + formEntries[0] + " should no longer exist");
+ };
+ wh.open();
+ },
+
+
+ /**
+ * These next six tests together ensure that toggling details persists
+ * across dialog openings.
+ */
+ function () {
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ // Check all items and select "Everything"
+ this.checkAllCheckboxes();
+ this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
+
+ // Hide details
+ this.toggleDetails();
+ this.checkDetails(false);
+ this.acceptDialog();
+ };
+ wh.open();
+ },
+ function () {
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ // Details should remain closed because all items are checked.
+ this.checkDetails(false);
+
+ // Uncheck history.
+ this.checkPrefCheckbox("history", false);
+ this.acceptDialog();
+ };
+ wh.open();
+ },
+ function () {
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ // Details should be open because not all items are checked.
+ this.checkDetails(true);
+
+ // Modify the Site Preferences item state (bug 527820)
+ this.checkAllCheckboxes();
+ this.checkPrefCheckbox("siteSettings", false);
+ this.acceptDialog();
+ };
+ wh.open();
+ },
+ function () {
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ // Details should be open because not all items are checked.
+ this.checkDetails(true);
+
+ // Hide details
+ this.toggleDetails();
+ this.checkDetails(false);
+ this.cancelDialog();
+ };
+ wh.open();
+ },
+ function () {
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ // Details should be open because not all items are checked.
+ this.checkDetails(true);
+
+ // Select another duration
+ this.selectDuration(Sanitizer.TIMESPAN_HOUR);
+ // Hide details
+ this.toggleDetails();
+ this.checkDetails(false);
+ this.acceptDialog();
+ };
+ wh.open();
+ },
+ function () {
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ // Details should not be open because "Last Hour" is selected
+ this.checkDetails(false);
+
+ this.cancelDialog();
+ };
+ wh.open();
+ },
+ function () {
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ // Details should have remained closed
+ this.checkDetails(false);
+
+ // Show details
+ this.toggleDetails();
+ this.checkDetails(true);
+ this.cancelDialog();
+ };
+ wh.open();
+ },
+ function () {
+ // Test for offline cache deletion
+
+ // Prepare stuff, we will work with www.example.com
+ var URL = "http://www.example.com";
+
+ var ios = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+ var URI = ios.newURI(URL, null, null);
+
+ var sm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ var principal = sm.getNoAppCodebasePrincipal(URI);
+
+ // Give www.example.com privileges to store offline data
+ var pm = Cc["@mozilla.org/permissionmanager;1"]
+ .getService(Ci.nsIPermissionManager);
+ pm.addFromPrincipal(principal, "offline-app", Ci.nsIPermissionManager.ALLOW_ACTION);
+ pm.addFromPrincipal(principal, "offline-app", Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN);
+
+ // Store something to the offline cache
+ const nsICache = Components.interfaces.nsICache;
+ var cs = Components.classes["@mozilla.org/network/cache-service;1"]
+ .getService(Components.interfaces.nsICacheService);
+ var session = cs.createSession(URL + "/manifest", nsICache.STORE_OFFLINE, nsICache.STREAM_BASED);
+
+ // Open the dialog
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
+ // Show details
+ this.toggleDetails();
+ // Clear only offlineApps
+ this.uncheckAllCheckboxes();
+ this.checkPrefCheckbox("offlineApps", true);
+ this.acceptDialog();
+
+ // Check if the cache has been deleted
+ var size = -1;
+ var visitor = {
+ visitDevice: function (deviceID, deviceInfo)
+ {
+ if (deviceID == "offline")
+ size = deviceInfo.totalSize;
+
+ // Do not enumerate entries
+ return false;
+ },
+
+ visitEntry: function (deviceID, entryInfo)
+ {
+ // Do not enumerate entries.
+ return false;
+ }
+ };
+ cs.visitEntries(visitor);
+ is(size, 0, "offline application cache entries evicted");
+ };
+
+ var cacheListener = {
+ onCacheEntryAvailable: function (entry, access, status) {
+ is(status, Cr.NS_OK);
+ var stream = entry.openOutputStream(0);
+ var content = "content";
+ stream.write(content, content.length);
+ stream.close();
+ entry.close();
+ wh.open();
+ }
+ };
+
+ session.asyncOpenCacheEntry(URL, nsICache.ACCESS_READ_WRITE, cacheListener);
+ },
+ function () {
+ // Test for offline apps permission deletion
+
+ // Prepare stuff, we will work with www.example.com
+ var URL = "http://www.example.com";
+
+ var ios = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+ var URI = ios.newURI(URL, null, null);
+
+ var sm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(Ci.nsIScriptSecurityManager);
+ var principal = sm.getNoAppCodebasePrincipal(URI);
+
+ // Open the dialog
+ let wh = new WindowHelper();
+ wh.onload = function () {
+ this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
+ // Show details
+ this.toggleDetails();
+ // Clear only offlineApps
+ this.uncheckAllCheckboxes();
+ this.checkPrefCheckbox("siteSettings", true);
+ this.acceptDialog();
+
+ // Check all has been deleted (privileges, data, cache)
+ var pm = Cc["@mozilla.org/permissionmanager;1"]
+ .getService(Ci.nsIPermissionManager);
+ is(pm.testPermissionFromPrincipal(principal, "offline-app"), 0, "offline-app permissions removed");
+ };
+ wh.open();
+ }
+];
+
+// Used as the download database ID for a new download. Incremented for each
+// new download. See addDownloadWithMinutesAgo().
+var gDownloadId = 5555551;
+
+// Index in gAllTests of the test currently being run. Incremented for each
+// test run. See doNextTest().
+var gCurrTest = 0;
+
+var now_uSec = Date.now() * 1000;
+
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * This wraps the dialog and provides some convenience methods for interacting
+ * with it.
+ *
+ * @param aWin
+ * The dialog's nsIDOMWindow
+ */
+function WindowHelper(aWin) {
+ this.win = aWin;
+}
+
+WindowHelper.prototype = {
+ /**
+ * "Presses" the dialog's OK button.
+ */
+ acceptDialog: function () {
+ is(this.win.document.documentElement.getButton("accept").disabled, false,
+ "Dialog's OK button should not be disabled");
+ this.win.document.documentElement.acceptDialog();
+ },
+
+ /**
+ * "Presses" the dialog's Cancel button.
+ */
+ cancelDialog: function () {
+ this.win.document.documentElement.cancelDialog();
+ },
+
+ /**
+ * Ensures that the details progressive disclosure button and the item list
+ * hidden by it match up. Also makes sure the height of the dialog is
+ * sufficient for the item list and warning panel.
+ *
+ * @param aShouldBeShown
+ * True if you expect the details to be shown and false if hidden
+ */
+ checkDetails: function (aShouldBeShown) {
+ let button = this.getDetailsButton();
+ let list = this.getItemList();
+ let hidden = list.hidden || list.collapsed;
+ is(hidden, !aShouldBeShown,
+ "Details should be " + (aShouldBeShown ? "shown" : "hidden") +
+ " but were actually " + (hidden ? "hidden" : "shown"));
+ let dir = hidden ? "down" : "up";
+ is(button.className, "expander-" + dir,
+ "Details button should be " + dir + " because item list is " +
+ (hidden ? "" : "not ") + "hidden");
+ let height = 0;
+ if (!hidden) {
+ ok(list.boxObject.height > 30, "listbox has sufficient size")
+ height += list.boxObject.height;
+ }
+ if (this.isWarningPanelVisible())
+ height += this.getWarningPanel().boxObject.height;
+ ok(height < this.win.innerHeight,
+ "Window should be tall enough to fit warning panel and item list");
+ },
+
+ /**
+ * (Un)checks a history scope checkbox (browser & download history,
+ * form history, etc.).
+ *
+ * @param aPrefName
+ * The final portion of the checkbox's privacy.cpd.* preference name
+ * @param aCheckState
+ * True if the checkbox should be checked, false otherwise
+ */
+ checkPrefCheckbox: function (aPrefName, aCheckState) {
+ var pref = "privacy.cpd." + aPrefName;
+ var cb = this.win.document.querySelectorAll(
+ "#itemList > [preference='" + pref + "']");
+ is(cb.length, 1, "found checkbox for " + pref + " preference");
+ if (cb[0].checked != aCheckState)
+ cb[0].click();
+ },
+
+ /**
+ * Makes sure all the checkboxes are checked.
+ */
+ _checkAllCheckboxesCustom: function (check) {
+ var cb = this.win.document.querySelectorAll("#itemList > [preference]");
+ ok(cb.length > 1, "found checkboxes for preferences");
+ for (var i = 0; i < cb.length; ++i) {
+ var pref = this.win.document.getElementById(cb[i].getAttribute("preference"));
+ if (!!pref.value ^ check)
+ cb[i].click();
+ }
+ },
+
+ checkAllCheckboxes: function () {
+ this._checkAllCheckboxesCustom(true);
+ },
+
+ uncheckAllCheckboxes: function () {
+ this._checkAllCheckboxesCustom(false);
+ },
+
+ /**
+ * @return The details progressive disclosure button
+ */
+ getDetailsButton: function () {
+ return this.win.document.getElementById("detailsExpander");
+ },
+
+ /**
+ * @return The dialog's duration dropdown
+ */
+ getDurationDropdown: function () {
+ return this.win.document.getElementById("sanitizeDurationChoice");
+ },
+
+ /**
+ * @return The item list hidden by the details progressive disclosure button
+ */
+ getItemList: function () {
+ return this.win.document.getElementById("itemList");
+ },
+
+ /**
+ * @return The clear-everything warning box
+ */
+ getWarningPanel: function () {
+ return this.win.document.getElementById("sanitizeEverythingWarningBox");
+ },
+
+ /**
+ * @return True if the "Everything" warning panel is visible (as opposed to
+ * the tree)
+ */
+ isWarningPanelVisible: function () {
+ return !this.getWarningPanel().hidden;
+ },
+
+ /**
+ * Opens the clear recent history dialog. Before calling this, set
+ * this.onload to a function to execute onload. It should close the dialog
+ * when done so that the tests may continue. Set this.onunload to a function
+ * to execute onunload. this.onunload is optional. If it returns true, the
+ * caller is expected to call waitForAsyncUpdates at some point; if false is
+ * returned, waitForAsyncUpdates is called automatically.
+ */
+ open: function () {
+ let wh = this;
+
+ function windowObserver(aSubject, aTopic, aData) {
+ if (aTopic != "domwindowopened")
+ return;
+
+ Services.ww.unregisterNotification(windowObserver);
+
+ var loaded = false;
+ let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
+
+ win.addEventListener("load", function onload(event) {
+ win.removeEventListener("load", onload, false);
+
+ if (win.name !== "SanitizeDialog")
+ return;
+
+ wh.win = win;
+ loaded = true;
+
+ executeSoon(function () {
+ // Some exceptions that reach here don't reach the test harness, but
+ // ok()/is() do...
+ try {
+ wh.onload();
+ }
+ catch (exc) {
+ win.close();
+ ok(false, "Unexpected exception: " + exc + "\n" + exc.stack);
+ finish();
+ }
+ });
+ }, false);
+
+ win.addEventListener("unload", function onunload(event) {
+ if (win.name !== "SanitizeDialog") {
+ win.removeEventListener("unload", onunload, false);
+ return;
+ }
+
+ // Why is unload fired before load?
+ if (!loaded)
+ return;
+
+ win.removeEventListener("unload", onunload, false);
+ wh.win = win;
+
+ executeSoon(function () {
+ // Some exceptions that reach here don't reach the test harness, but
+ // ok()/is() do...
+ try {
+ if (wh.onunload) {
+ Task.spawn(wh.onunload).then(function() {
+ waitForAsyncUpdates(doNextTest);
+ });
+ } else {
+ waitForAsyncUpdates(doNextTest);
+ }
+ }
+ catch (exc) {
+ win.close();
+ ok(false, "Unexpected exception: " + exc + "\n" + exc.stack);
+ finish();
+ }
+ });
+ }, false);
+ }
+ Services.ww.registerNotification(windowObserver);
+ Services.ww.openWindow(null,
+ "chrome://browser/content/sanitize.xul",
+ "SanitizeDialog",
+ "chrome,titlebar,dialog,centerscreen,modal",
+ null);
+ },
+
+ /**
+ * Selects a duration in the duration dropdown.
+ *
+ * @param aDurVal
+ * One of the Sanitizer.TIMESPAN_* values
+ */
+ selectDuration: function (aDurVal) {
+ this.getDurationDropdown().value = aDurVal;
+ if (aDurVal === Sanitizer.TIMESPAN_EVERYTHING) {
+ is(this.isWarningPanelVisible(), true,
+ "Warning panel should be visible for TIMESPAN_EVERYTHING");
+ }
+ else {
+ is(this.isWarningPanelVisible(), false,
+ "Warning panel should not be visible for non-TIMESPAN_EVERYTHING");
+ }
+ },
+
+ /**
+ * Toggles the details progressive disclosure button.
+ */
+ toggleDetails: function () {
+ this.getDetailsButton().click();
+ }
+};
+
+/**
+ * Adds a download to history.
+ *
+ * @param aMinutesAgo
+ * The download will be downloaded this many minutes ago
+ */
+function addDownloadWithMinutesAgo(aMinutesAgo) {
+ let name = "fakefile-" + aMinutesAgo + "-minutes-ago";
+ let data = {
+ id: gDownloadId,
+ name: name,
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
+ target: name,
+ startTime: now_uSec - (aMinutesAgo * kUsecPerMin),
+ endTime: now_uSec - ((aMinutesAgo + 1) * kUsecPerMin),
+ state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
+ currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0
+ };
+
+ let db = dm.DBConnection;
+ let stmt = db.createStatement(
+ "INSERT INTO moz_downloads (id, name, source, target, startTime, endTime, " +
+ "state, currBytes, maxBytes, preferredAction, autoResume) " +
+ "VALUES (:id, :name, :source, :target, :startTime, :endTime, :state, " +
+ ":currBytes, :maxBytes, :preferredAction, :autoResume)");
+ try {
+ for (let prop in data) {
+ stmt.params[prop] = data[prop];
+ }
+ stmt.execute();
+ }
+ finally {
+ stmt.reset();
+ }
+
+ is(downloadExists(gDownloadId), true,
+ "Sanity check: download " + gDownloadId +
+ " should exist after creating it");
+
+ return gDownloadId++;
+}
+
+/**
+ * Adds a form entry to history.
+ *
+ * @param aMinutesAgo
+ * The entry will be added this many minutes ago
+ */
+function addFormEntryWithMinutesAgo(then, aMinutesAgo) {
+ let name = aMinutesAgo + "-minutes-ago";
+
+ // Artifically age the entry to the proper vintage.
+ let timestamp = now_uSec - (aMinutesAgo * kUsecPerMin);
+
+ FormHistory.update({ op: "add", fieldname: name, value: "dummy", firstUsed: timestamp },
+ { handleError: function (error) {
+ do_throw("Error occurred updating form history: " + error);
+ },
+ handleCompletion: function (reason) { then.next(); }
+ });
+ return name;
+}
+
+/**
+ * Checks if a form entry exists.
+ */
+function formNameExists(name)
+{
+ let deferred = Promise.defer();
+
+ let count = 0;
+ FormHistory.count({ fieldname: name },
+ { handleResult: function (result) count = result,
+ handleError: function (error) {
+ do_throw("Error occurred searching form history: " + error);
+ deferred.reject(error);
+ },
+ handleCompletion: function (reason) {
+ if (!reason) deferred.resolve(count);
+ }
+ });
+
+ return deferred.promise;
+}
+
+/**
+ * Removes all history visits, downloads, and form entries.
+ */
+function blankSlate() {
+ PlacesUtils.bhistory.removeAllPages();
+ dm.cleanUp();
+
+ let deferred = Promise.defer();
+ FormHistory.update({ op: "remove" },
+ { handleError: function (error) {
+ do_throw("Error occurred updating form history: " + error);
+ deferred.reject(error);
+ },
+ handleCompletion: function (reason) { if (!reason) deferred.resolve(); }
+ });
+ return deferred.promise;
+}
+
+/**
+ * Ensures that the given pref is the expected value.
+ *
+ * @param aPrefName
+ * The pref's sub-branch under the privacy branch
+ * @param aExpectedVal
+ * The pref's expected value
+ * @param aMsg
+ * Passed to is()
+ */
+function boolPrefIs(aPrefName, aExpectedVal, aMsg) {
+ is(gPrefService.getBoolPref("privacy." + aPrefName), aExpectedVal, aMsg);
+}
+
+/**
+ * Checks to see if the download with the specified ID exists.
+ *
+ * @param aID
+ * The ID of the download to check
+ * @return True if the download exists, false otherwise
+ */
+function downloadExists(aID)
+{
+ let db = dm.DBConnection;
+ let stmt = db.createStatement(
+ "SELECT * " +
+ "FROM moz_downloads " +
+ "WHERE id = :id"
+ );
+ stmt.params.id = aID;
+ let rows = stmt.executeStep();
+ stmt.finalize();
+ return !!rows;
+}
+
+/**
+ * Runs the next test in the gAllTests array. If all tests have been run,
+ * finishes the entire suite.
+ */
+function doNextTest() {
+ if (gAllTests.length <= gCurrTest) {
+ blankSlate();
+ waitForAsyncUpdates(finish);
+ }
+ else {
+ let ct = gCurrTest;
+ gCurrTest++;
+ gAllTests[ct]();
+ }
+}
+
+/**
+ * Ensures that the specified downloads are either cleared or not.
+ *
+ * @param aDownloadIDs
+ * Array of download database IDs
+ * @param aShouldBeCleared
+ * True if each download should be cleared, false otherwise
+ */
+function ensureDownloadsClearedState(aDownloadIDs, aShouldBeCleared) {
+ let niceStr = aShouldBeCleared ? "no longer" : "still";
+ aDownloadIDs.forEach(function (id) {
+ is(downloadExists(id), !aShouldBeCleared,
+ "download " + id + " should " + niceStr + " exist");
+ });
+}
+
+/**
+ * Ensures that the given pref is the expected value.
+ *
+ * @param aPrefName
+ * The pref's sub-branch under the privacy branch
+ * @param aExpectedVal
+ * The pref's expected value
+ * @param aMsg
+ * Passed to is()
+ */
+function intPrefIs(aPrefName, aExpectedVal, aMsg) {
+ is(gPrefService.getIntPref("privacy." + aPrefName), aExpectedVal, aMsg);
+}
+
+/**
+ * Creates a visit time.
+ *
+ * @param aMinutesAgo
+ * The visit will be visited this many minutes ago
+ */
+function visitTimeForMinutesAgo(aMinutesAgo) {
+ return now_uSec - aMinutesAgo * kUsecPerMin;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+function test() {
+ requestLongerTimeout(2);
+ waitForExplicitFinish();
+ blankSlate();
+ // Kick off all the tests in the gAllTests array.
+ waitForAsyncUpdates(doNextTest);
+}
diff --git a/browser/base/content/test/browser_sanitizeDialog_treeView.js b/browser/base/content/test/browser_sanitizeDialog_treeView.js
new file mode 100644
index 000000000..10e726100
--- /dev/null
+++ b/browser/base/content/test/browser_sanitizeDialog_treeView.js
@@ -0,0 +1,632 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Tests the sanitize dialog (a.k.a. the clear recent history dialog).
+ * See bug 480169.
+ *
+ * The purpose of this test is not to fully flex the sanitize timespan code;
+ * browser/base/content/test/browser_sanitize-timespans.js does that. This
+ * test checks the UI of the dialog and makes sure it's correctly connected to
+ * the sanitize timespan code.
+ *
+ * Some of this code, especially the history creation parts, was taken from
+ * browser/base/content/test/browser_sanitize-timespans.js.
+ */
+
+Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader).
+ loadSubScript("chrome://browser/content/sanitize.js");
+
+const dm = Cc["@mozilla.org/download-manager;1"].
+ getService(Ci.nsIDownloadManager);
+const formhist = Cc["@mozilla.org/satchel/form-history;1"].
+ getService(Ci.nsIFormHistory2);
+
+// Add tests here. Each is a function that's called by doNextTest().
+var gAllTests = [
+
+ /**
+ * Moves the grippy around, makes sure it works OK.
+ */
+ function () {
+ // Add history (within the past hour) to get some rows in the tree.
+ let uris = [];
+ let places = [];
+ let pURI;
+ for (let i = 0; i < 30; i++) {
+ pURI = makeURI("http://" + i + "-minutes-ago.com/");
+ places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
+ uris.push(pURI);
+ }
+
+ addVisits(places, function() {
+ // Open the dialog and do our tests.
+ openWindow(function (aWin) {
+ let wh = new WindowHelper(aWin);
+ wh.selectDuration(Sanitizer.TIMESPAN_HOUR);
+ wh.checkGrippy("Grippy should be at last row after selecting HOUR " +
+ "duration",
+ wh.getRowCount() - 1);
+
+ // Move the grippy around.
+ let row = wh.getGrippyRow();
+ while (row !== 0) {
+ row--;
+ wh.moveGrippyBy(-1);
+ wh.checkGrippy("Grippy should be moved up one row", row);
+ }
+ wh.moveGrippyBy(-1);
+ wh.checkGrippy("Grippy should remain at first row after trying to move " +
+ "it up",
+ 0);
+ while (row !== wh.getRowCount() - 1) {
+ row++;
+ wh.moveGrippyBy(1);
+ wh.checkGrippy("Grippy should be moved down one row", row);
+ }
+ wh.moveGrippyBy(1);
+ wh.checkGrippy("Grippy should remain at last row after trying to move " +
+ "it down",
+ wh.getRowCount() - 1);
+
+ // Cancel the dialog, make sure history visits are not cleared.
+ wh.checkPrefCheckbox("history", false);
+
+ wh.cancelDialog();
+ yield promiseHistoryClearedState(uris, false);
+
+ // OK, done, cleanup after ourselves.
+ blankSlate();
+ yield promiseHistoryClearedState(uris, true);
+ });
+ });
+ },
+
+ /**
+ * Ensures that the combined history-downloads checkbox clears both history
+ * visits and downloads when checked; the dialog respects simple timespan.
+ */
+ function () {
+ // Add history (within the past hour).
+ let uris = [];
+ let places = [];
+ let pURI;
+ for (let i = 0; i < 30; i++) {
+ pURI = makeURI("http://" + i + "-minutes-ago.com/");
+ places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
+ uris.push(pURI);
+ }
+ // Add history (over an hour ago).
+ let olderURIs = [];
+ for (let i = 0; i < 5; i++) {
+ pURI = makeURI("http://" + (60 + i) + "-minutes-ago.com/");
+ places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(60 + i)});
+ olderURIs.push(pURI);
+ }
+
+ addVisits(places, function() {
+ // Add downloads (within the past hour).
+ let downloadIDs = [];
+ for (let i = 0; i < 5; i++) {
+ downloadIDs.push(addDownloadWithMinutesAgo(i));
+ }
+ // Add downloads (over an hour ago).
+ let olderDownloadIDs = [];
+ for (let i = 0; i < 5; i++) {
+ olderDownloadIDs.push(addDownloadWithMinutesAgo(61 + i));
+ }
+ let totalHistoryVisits = uris.length + olderURIs.length;
+
+ // Open the dialog and do our tests.
+ openWindow(function (aWin) {
+ let wh = new WindowHelper(aWin);
+ wh.selectDuration(Sanitizer.TIMESPAN_HOUR);
+ wh.checkGrippy("Grippy should be at proper row after selecting HOUR " +
+ "duration",
+ uris.length);
+
+ // Accept the dialog, make sure history visits and downloads within one
+ // hour are cleared.
+ wh.checkPrefCheckbox("history", true);
+ wh.acceptDialog();
+ yield promiseHistoryClearedState(uris, true);
+ ensureDownloadsClearedState(downloadIDs, true);
+
+ // Make sure visits and downloads > 1 hour still exist.
+ yield promiseHistoryClearedState(olderURIs, false);
+ ensureDownloadsClearedState(olderDownloadIDs, false);
+
+ // OK, done, cleanup after ourselves.
+ blankSlate();
+ yield promiseHistoryClearedState(olderURIs, true);
+ ensureDownloadsClearedState(olderDownloadIDs, true);
+ });
+ });
+ },
+
+ /**
+ * Ensures that the combined history-downloads checkbox removes neither
+ * history visits nor downloads when not checked.
+ */
+ function () {
+ // Add history, downloads, form entries (within the past hour).
+ let uris = [];
+ let places = [];
+ let pURI;
+ for (let i = 0; i < 5; i++) {
+ pURI = makeURI("http://" + i + "-minutes-ago.com/");
+ places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
+ uris.push(pURI);
+ }
+
+ addVisits(places, function() {
+ let downloadIDs = [];
+ for (let i = 0; i < 5; i++) {
+ downloadIDs.push(addDownloadWithMinutesAgo(i));
+ }
+ let formEntries = [];
+ for (let i = 0; i < 5; i++) {
+ formEntries.push(addFormEntryWithMinutesAgo(i));
+ }
+
+ // Open the dialog and do our tests.
+ openWindow(function (aWin) {
+ let wh = new WindowHelper(aWin);
+ wh.selectDuration(Sanitizer.TIMESPAN_HOUR);
+ wh.checkGrippy("Grippy should be at last row after selecting HOUR " +
+ "duration",
+ wh.getRowCount() - 1);
+
+ // Remove only form entries, leave history (including downloads).
+ wh.checkPrefCheckbox("history", false);
+ wh.checkPrefCheckbox("formdata", true);
+ wh.acceptDialog();
+
+ // Of the three only form entries should be cleared.
+ yield promiseHistoryClearedState(uris, false);
+ ensureDownloadsClearedState(downloadIDs, false);
+ ensureFormEntriesClearedState(formEntries, true);
+
+ // OK, done, cleanup after ourselves.
+ blankSlate();
+ yield promiseHistoryClearedState(uris, true);
+ ensureDownloadsClearedState(downloadIDs, true);
+ });
+ });
+ },
+
+ /**
+ * Ensures that the "Everything" duration option works.
+ */
+ function () {
+ // Add history.
+ let uris = [];
+ let places = [];
+ let pURI;
+ // within past hour, within past two hours, within past four hours and
+ // outside past four hours
+ [10, 70, 130, 250].forEach(function(aValue) {
+ pURI = makeURI("http://" + aValue + "-minutes-ago.com/");
+ places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(aValue)});
+ uris.push(pURI);
+ });
+ addVisits(places, function() {
+
+ // Open the dialog and do our tests.
+ openWindow(function (aWin) {
+ let wh = new WindowHelper(aWin);
+ wh.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
+ wh.checkPrefCheckbox("history", true);
+ wh.acceptDialog();
+ yield promiseHistoryClearedState(uris, true);
+ });
+ });
+ }
+];
+
+// Used as the download database ID for a new download. Incremented for each
+// new download. See addDownloadWithMinutesAgo().
+var gDownloadId = 5555551;
+
+// Index in gAllTests of the test currently being run. Incremented for each
+// test run. See doNextTest().
+var gCurrTest = 0;
+
+var now_uSec = Date.now() * 1000;
+
+///////////////////////////////////////////////////////////////////////////////
+
+/**
+ * This wraps the dialog and provides some convenience methods for interacting
+ * with it.
+ *
+ * A warning: Before you call any function that uses the tree (or any function
+ * that calls a function that uses the tree), you must set a non-everything
+ * duration by calling selectDuration(). The dialog does not initialize the
+ * tree if it does not yet need to be shown.
+ *
+ * @param aWin
+ * The dialog's nsIDOMWindow
+ */
+function WindowHelper(aWin) {
+ this.win = aWin;
+}
+
+WindowHelper.prototype = {
+ /**
+ * "Presses" the dialog's OK button.
+ */
+ acceptDialog: function () {
+ is(this.win.document.documentElement.getButton("accept").disabled, false,
+ "Dialog's OK button should not be disabled");
+ this.win.document.documentElement.acceptDialog();
+ },
+
+ /**
+ * "Presses" the dialog's Cancel button.
+ */
+ cancelDialog: function () {
+ this.win.document.documentElement.cancelDialog();
+ },
+
+ /**
+ * Ensures that the grippy row is in the right place, tree selection is OK,
+ * and that the grippy's visible.
+ *
+ * @param aMsg
+ * Passed to is() when checking grippy location
+ * @param aExpectedRow
+ * The row that the grippy should be at
+ */
+ checkGrippy: function (aMsg, aExpectedRow) {
+ is(this.getGrippyRow(), aExpectedRow, aMsg);
+ this.checkTreeSelection();
+ this.ensureGrippyIsVisible();
+ },
+
+ /**
+ * (Un)checks a history scope checkbox (browser & download history,
+ * form history, etc.).
+ *
+ * @param aPrefName
+ * The final portion of the checkbox's privacy.cpd.* preference name
+ * @param aCheckState
+ * True if the checkbox should be checked, false otherwise
+ */
+ checkPrefCheckbox: function (aPrefName, aCheckState) {
+ var pref = "privacy.cpd." + aPrefName;
+ var cb = this.win.document.querySelectorAll(
+ "#itemList > [preference='" + pref + "']");
+ is(cb.length, 1, "found checkbox for " + pref + " preference");
+ if (cb[0].checked != aCheckState)
+ cb[0].click();
+ },
+
+ /**
+ * Ensures that the tree selection is appropriate to the grippy row. (A
+ * single, contiguous selection should exist from the first row all the way
+ * to the grippy.)
+ */
+ checkTreeSelection: function () {
+ let grippyRow = this.getGrippyRow();
+ let sel = this.getTree().view.selection;
+ if (grippyRow === 0) {
+ is(sel.getRangeCount(), 0,
+ "Grippy row is 0, so no tree selection should exist");
+ }
+ else {
+ is(sel.getRangeCount(), 1,
+ "Grippy row > 0, so only one tree selection range should exist");
+ let min = {};
+ let max = {};
+ sel.getRangeAt(0, min, max);
+ is(min.value, 0, "Tree selection should start at first row");
+ is(max.value, grippyRow - 1,
+ "Tree selection should end at row before grippy");
+ }
+ },
+
+ /**
+ * The grippy should always be visible when it's moved directly. This method
+ * ensures that.
+ */
+ ensureGrippyIsVisible: function () {
+ let tbo = this.getTree().treeBoxObject;
+ let firstVis = tbo.getFirstVisibleRow();
+ let lastVis = tbo.getLastVisibleRow();
+ let grippyRow = this.getGrippyRow();
+ ok(firstVis <= grippyRow && grippyRow <= lastVis,
+ "Grippy row should be visible; this inequality should be true: " +
+ firstVis + " <= " + grippyRow + " <= " + lastVis);
+ },
+
+ /**
+ * @return The dialog's duration dropdown
+ */
+ getDurationDropdown: function () {
+ return this.win.document.getElementById("sanitizeDurationChoice");
+ },
+
+ /**
+ * @return The grippy row index
+ */
+ getGrippyRow: function () {
+ return this.win.gContiguousSelectionTreeHelper.getGrippyRow();
+ },
+
+ /**
+ * @return The tree's row count (includes the grippy row)
+ */
+ getRowCount: function () {
+ return this.getTree().view.rowCount;
+ },
+
+ /**
+ * @return The tree
+ */
+ getTree: function () {
+ return this.win.gContiguousSelectionTreeHelper.tree;
+ },
+
+ /**
+ * @return True if the "Everything" warning panel is visible (as opposed to
+ * the tree)
+ */
+ isWarningPanelVisible: function () {
+ return this.win.document.getElementById("durationDeck").selectedIndex == 1;
+ },
+
+ /**
+ * @return True if the tree is visible (as opposed to the warning panel)
+ */
+ isTreeVisible: function () {
+ return this.win.document.getElementById("durationDeck").selectedIndex == 0;
+ },
+
+ /**
+ * Moves the grippy one row at a time in the direction and magnitude specified.
+ * If aDelta < 0, moves the grippy up; if aDelta > 0, moves it down.
+ *
+ * @param aDelta
+ * The amount and direction to move
+ */
+ moveGrippyBy: function (aDelta) {
+ if (aDelta === 0)
+ return;
+ let key = aDelta < 0 ? "UP" : "DOWN";
+ let abs = Math.abs(aDelta);
+ let treechildren = this.getTree().treeBoxObject.treeBody;
+ treechildren.focus();
+ for (let i = 0; i < abs; i++) {
+ EventUtils.sendKey(key);
+ }
+ },
+
+ /**
+ * Selects a duration in the duration dropdown.
+ *
+ * @param aDurVal
+ * One of the Sanitizer.TIMESPAN_* values
+ */
+ selectDuration: function (aDurVal) {
+ this.getDurationDropdown().value = aDurVal;
+ if (aDurVal === Sanitizer.TIMESPAN_EVERYTHING) {
+ is(this.isTreeVisible(), false,
+ "Tree should not be visible for TIMESPAN_EVERYTHING");
+ is(this.isWarningPanelVisible(), true,
+ "Warning panel should be visible for TIMESPAN_EVERYTHING");
+ }
+ else {
+ is(this.isTreeVisible(), true,
+ "Tree should be visible for non-TIMESPAN_EVERYTHING");
+ is(this.isWarningPanelVisible(), false,
+ "Warning panel should not be visible for non-TIMESPAN_EVERYTHING");
+ }
+ }
+};
+
+/**
+ * Adds a download to history.
+ *
+ * @param aMinutesAgo
+ * The download will be downloaded this many minutes ago
+ */
+function addDownloadWithMinutesAgo(aMinutesAgo) {
+ let name = "fakefile-" + aMinutesAgo + "-minutes-ago";
+ let data = {
+ id: gDownloadId,
+ name: name,
+ source: "https://bugzilla.mozilla.org/show_bug.cgi?id=480169",
+ target: name,
+ startTime: now_uSec - (aMinutesAgo * 60 * 1000000),
+ endTime: now_uSec - ((aMinutesAgo + 1) *60 * 1000000),
+ state: Ci.nsIDownloadManager.DOWNLOAD_FINISHED,
+ currBytes: 0, maxBytes: -1, preferredAction: 0, autoResume: 0,
+ guid: "a1bcD23eF4g5"
+ };
+
+ let db = dm.DBConnection;
+ let stmt = db.createStatement(
+ "INSERT INTO moz_downloads (id, name, source, target, startTime, endTime, " +
+ "state, currBytes, maxBytes, preferredAction, autoResume, guid) " +
+ "VALUES (:id, :name, :source, :target, :startTime, :endTime, :state, " +
+ ":currBytes, :maxBytes, :preferredAction, :autoResume, :guid)");
+ try {
+ for (let prop in data) {
+ stmt.params[prop] = data[prop];
+ }
+ stmt.execute();
+ }
+ finally {
+ stmt.reset();
+ }
+
+ is(downloadExists(gDownloadId), true,
+ "Sanity check: download " + gDownloadId +
+ " should exist after creating it");
+
+ return gDownloadId++;
+}
+
+/**
+ * Adds a form entry to history.
+ *
+ * @param aMinutesAgo
+ * The entry will be added this many minutes ago
+ */
+function addFormEntryWithMinutesAgo(aMinutesAgo) {
+ let name = aMinutesAgo + "-minutes-ago";
+ formhist.addEntry(name, "dummy");
+
+ // Artifically age the entry to the proper vintage.
+ let db = formhist.DBConnection;
+ let timestamp = now_uSec - (aMinutesAgo * 60 * 1000000);
+ db.executeSimpleSQL("UPDATE moz_formhistory SET firstUsed = " +
+ timestamp + " WHERE fieldname = '" + name + "'");
+
+ is(formhist.nameExists(name), true,
+ "Sanity check: form entry " + name + " should exist after creating it");
+ return name;
+}
+
+/**
+ * Removes all history visits, downloads, and form entries.
+ */
+function blankSlate() {
+ PlacesUtils.bhistory.removeAllPages();
+ dm.cleanUp();
+ formhist.removeAllEntries();
+}
+
+/**
+ * Checks to see if the download with the specified ID exists.
+ *
+ * @param aID
+ * The ID of the download to check
+ * @return True if the download exists, false otherwise
+ */
+function downloadExists(aID)
+{
+ let db = dm.DBConnection;
+ let stmt = db.createStatement(
+ "SELECT * " +
+ "FROM moz_downloads " +
+ "WHERE id = :id"
+ );
+ stmt.params.id = aID;
+ let rows = stmt.executeStep();
+ stmt.finalize();
+ return !!rows;
+}
+
+/**
+ * Runs the next test in the gAllTests array. If all tests have been run,
+ * finishes the entire suite.
+ */
+function doNextTest() {
+ if (gAllTests.length <= gCurrTest) {
+ blankSlate();
+ waitForAsyncUpdates(finish);
+ }
+ else {
+ let ct = gCurrTest;
+ gCurrTest++;
+ gAllTests[ct]();
+ }
+}
+
+/**
+ * Ensures that the specified downloads are either cleared or not.
+ *
+ * @param aDownloadIDs
+ * Array of download database IDs
+ * @param aShouldBeCleared
+ * True if each download should be cleared, false otherwise
+ */
+function ensureDownloadsClearedState(aDownloadIDs, aShouldBeCleared) {
+ let niceStr = aShouldBeCleared ? "no longer" : "still";
+ aDownloadIDs.forEach(function (id) {
+ is(downloadExists(id), !aShouldBeCleared,
+ "download " + id + " should " + niceStr + " exist");
+ });
+}
+
+/**
+ * Ensures that the specified form entries are either cleared or not.
+ *
+ * @param aFormEntries
+ * Array of form entry names
+ * @param aShouldBeCleared
+ * True if each form entry should be cleared, false otherwise
+ */
+function ensureFormEntriesClearedState(aFormEntries, aShouldBeCleared) {
+ let niceStr = aShouldBeCleared ? "no longer" : "still";
+ aFormEntries.forEach(function (entry) {
+ is(formhist.nameExists(entry), !aShouldBeCleared,
+ "form entry " + entry + " should " + niceStr + " exist");
+ });
+}
+
+/**
+ * Opens the sanitize dialog and runs a callback once it's finished loading.
+ *
+ * @param aOnloadCallback
+ * A function that will be called once the dialog has loaded
+ */
+function openWindow(aOnloadCallback) {
+ function windowObserver(aSubject, aTopic, aData) {
+ if (aTopic != "domwindowopened")
+ return;
+
+ Services.ww.unregisterNotification(windowObserver);
+ let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
+ win.addEventListener("load", function onload(event) {
+ win.removeEventListener("load", onload, false);
+ executeSoon(function () {
+ // Some exceptions that reach here don't reach the test harness, but
+ // ok()/is() do...
+ try {
+ Task.spawn(function() {
+ aOnloadCallback(win);
+ }).then(function() {
+ waitForAsyncUpdates(doNextTest);
+ });
+ }
+ catch (exc) {
+ win.close();
+ ok(false, "Unexpected exception: " + exc + "\n" + exc.stack);
+ finish();
+ }
+ });
+ }, false);
+ }
+ Services.ww.registerNotification(windowObserver);
+ Services.ww.openWindow(null,
+ "chrome://browser/content/sanitize.xul",
+ "Sanitize",
+ "chrome,titlebar,dialog,centerscreen,modal",
+ null);
+}
+
+/**
+ * Creates a visit time.
+ *
+ * @param aMinutesAgo
+ * The visit will be visited this many minutes ago
+ */
+function visitTimeForMinutesAgo(aMinutesAgo) {
+ return now_uSec - (aMinutesAgo * 60 * 1000000);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+function test() {
+ blankSlate();
+ waitForExplicitFinish();
+ // Kick off all the tests in the gAllTests array.
+ waitForAsyncUpdates(doNextTest);
+}
diff --git a/browser/base/content/test/browser_save_link-perwindowpb.js b/browser/base/content/test/browser_save_link-perwindowpb.js
new file mode 100644
index 000000000..c44344761
--- /dev/null
+++ b/browser/base/content/test/browser_save_link-perwindowpb.js
@@ -0,0 +1,167 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var MockFilePicker = SpecialPowers.MockFilePicker;
+MockFilePicker.init(window);
+
+let tempScope = {};
+Cu.import("resource://gre/modules/NetUtil.jsm", tempScope);
+let NetUtil = tempScope.NetUtil;
+
+// Trigger a save of a link in public mode, then trigger an identical save
+// in private mode and ensure that the second request is differentiated from
+// the first by checking that cookies set by the first response are not sent
+// during the second request.
+function triggerSave(aWindow, aCallback) {
+ var fileName;
+ let testBrowser = aWindow.gBrowser.selectedBrowser;
+ // This page sets a cookie if and only if a cookie does not exist yet
+ testBrowser.loadURI("http://mochi.test:8888/browser/browser/base/content/test/bug792517-2.html");
+ testBrowser.addEventListener("pageshow", function pageShown(event) {
+ if (event.target.location == "about:blank")
+ return;
+ testBrowser.removeEventListener("pageshow", pageShown, false);
+
+ executeSoon(function () {
+ aWindow.document.addEventListener("popupshown", function(e) contextMenuOpened(aWindow, e), false);
+
+ var link = testBrowser.contentDocument.getElementById("fff");
+ EventUtils.synthesizeMouseAtCenter(link,
+ { type: "contextmenu", button: 2 },
+ testBrowser.contentWindow);
+ });
+ }, false);
+
+ function contextMenuOpened(aWindow, event) {
+ event.currentTarget.removeEventListener("popupshown", contextMenuOpened, false);
+
+ // Create the folder the link will be saved into.
+ var destDir = createTemporarySaveDirectory();
+ var destFile = destDir.clone();
+
+ MockFilePicker.displayDirectory = destDir;
+ MockFilePicker.showCallback = function(fp) {
+ fileName = fp.defaultString;
+ destFile.append (fileName);
+ MockFilePicker.returnFiles = [destFile];
+ MockFilePicker.filterIndex = 1; // kSaveAsType_URL
+ };
+
+ mockTransferCallback = function(downloadSuccess) {
+ onTransferComplete(aWindow, downloadSuccess, destDir);
+ destDir.remove(true);
+ ok(!destDir.exists(), "Destination dir should be removed");
+ ok(!destFile.exists(), "Destination file should be removed");
+ mockTransferCallback = function(){};
+ }
+
+ // Select "Save Link As" option from context menu
+ var saveLinkCommand = aWindow.document.getElementById("context-savelink");
+ saveLinkCommand.doCommand();
+
+ event.target.hidePopup();
+ }
+
+ function onTransferComplete(aWindow, downloadSuccess, destDir) {
+ ok(downloadSuccess, "Link should have been downloaded successfully");
+ aWindow.gBrowser.removeCurrentTab();
+
+ executeSoon(function() aCallback());
+ }
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ var windowsToClose = [];
+ var gNumSet = 0;
+ function testOnWindow(options, callback) {
+ var win = OpenBrowserWindow(options);
+ win.addEventListener("load", function onLoad() {
+ win.removeEventListener("load", onLoad, false);
+ windowsToClose.push(win);
+ executeSoon(function() callback(win));
+ }, false);
+ }
+
+ mockTransferRegisterer.register();
+
+ registerCleanupFunction(function () {
+ mockTransferRegisterer.unregister();
+ MockFilePicker.cleanup();
+ windowsToClose.forEach(function(win) {
+ win.close();
+ });
+ Services.obs.removeObserver(observer, "http-on-modify-request");
+ Services.obs.removeObserver(observer, "http-on-examine-response");
+ });
+
+ function observer(subject, topic, state) {
+ if (topic == "http-on-modify-request") {
+ onModifyRequest(subject);
+ } else if (topic == "http-on-examine-response") {
+ onExamineResponse(subject);
+ }
+ }
+
+ function onExamineResponse(subject) {
+ let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+ if (channel.URI.spec != "http://mochi.test:8888/browser/browser/base/content/test/bug792517.sjs") {
+ return;
+ }
+ try {
+ let cookies = channel.getResponseHeader("set-cookie");
+ // From browser/base/content/test/bug792715.sjs, we receive a Set-Cookie
+ // header with foopy=1 when there are no cookies for that domain.
+ is(cookies, "foopy=1", "Cookie should be foopy=1");
+ gNumSet += 1;
+ } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { }
+ }
+
+ function onModifyRequest(subject) {
+ let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+ if (channel.URI.spec != "http://mochi.test:8888/browser/browser/base/content/test/bug792517.sjs") {
+ return;
+ }
+ try {
+ let cookies = channel.getRequestHeader("cookie");
+ // From browser/base/content/test/bug792715.sjs, we should never send a
+ // cookie because we are making only 2 requests: one in public mode, and
+ // one in private mode.
+ throw "We should never send a cookie in this test";
+ } catch (ex if ex.result == Cr.NS_ERROR_NOT_AVAILABLE) { }
+ }
+
+ Services.obs.addObserver(observer, "http-on-modify-request", false);
+ Services.obs.addObserver(observer, "http-on-examine-response", false);
+
+ testOnWindow(undefined, function(win) {
+ // The first save from a regular window sets a cookie.
+ triggerSave(win, function() {
+ is(gNumSet, 1, "1 cookie should be set");
+
+ // The second save from a private window also sets a cookie.
+ testOnWindow({private: true}, function(win) {
+ triggerSave(win, function() {
+ is(gNumSet, 2, "2 cookies should be set");
+ finish();
+ });
+ });
+ });
+ });
+}
+
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
+ this);
+
+function createTemporarySaveDirectory() {
+ var saveDir = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("TmpD", Ci.nsIFile);
+ saveDir.append("testsavedir");
+ if (!saveDir.exists())
+ saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
+ return saveDir;
+}
diff --git a/browser/base/content/test/browser_save_private_link_perwindowpb.js b/browser/base/content/test/browser_save_private_link_perwindowpb.js
new file mode 100644
index 000000000..7365ab1b0
--- /dev/null
+++ b/browser/base/content/test/browser_save_private_link_perwindowpb.js
@@ -0,0 +1,131 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+function test() {
+ // initialization
+ waitForExplicitFinish();
+ let windowsToClose = [];
+ let testURI = "http://mochi.test:8888/browser/browser/base/content/test/bug792517.html";
+ let fileName;
+ let MockFilePicker = SpecialPowers.MockFilePicker;
+ let cache = Cc["@mozilla.org/network/cache-service;1"]
+ .getService(Ci.nsICacheService);
+
+ function checkDiskCacheFor(filename) {
+ let visitor = {
+ visitDevice: function(deviceID, deviceInfo) {
+ if (deviceID == "disk")
+ info(deviceID + " device contains " + deviceInfo.entryCount + " entries");
+ return deviceID == "disk";
+ },
+
+ visitEntry: function(deviceID, entryInfo) {
+ info(entryInfo.key);
+ is(entryInfo.key.contains(filename), false, "web content present in disk cache");
+ }
+ };
+ cache.visitEntries(visitor);
+ }
+
+ function contextMenuOpened(aWindow, event) {
+ cache.evictEntries(Ci.nsICache.STORE_ANYWHERE);
+
+ event.currentTarget.removeEventListener("popupshown", contextMenuOpened);
+
+ // Create the folder the image will be saved into.
+ var destDir = createTemporarySaveDirectory();
+ var destFile = destDir.clone();
+
+ MockFilePicker.displayDirectory = destDir;
+ MockFilePicker.showCallback = function(fp) {
+ fileName = fp.defaultString;
+ destFile.append (fileName);
+ MockFilePicker.returnFiles = [destFile];
+ MockFilePicker.filterIndex = 1; // kSaveAsType_URL
+ };
+
+ mockTransferCallback = onTransferComplete;
+ mockTransferRegisterer.register();
+
+ registerCleanupFunction(function () {
+ mockTransferRegisterer.unregister();
+ MockFilePicker.cleanup();
+ destDir.remove(true);
+ });
+
+ // Select "Save Image As" option from context menu
+ var saveVideoCommand = aWindow.document.getElementById("context-saveimage");
+ saveVideoCommand.doCommand();
+
+ event.target.hidePopup();
+ }
+
+ function onTransferComplete(downloadSuccess) {
+ ok(downloadSuccess, "Image file should have been downloaded successfully");
+
+ // Give the request a chance to finish and create a cache entry
+ executeSoon(function() {
+ checkDiskCacheFor(fileName);
+ finish();
+ });
+ }
+
+ function createTemporarySaveDirectory() {
+ var saveDir = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("TmpD", Ci.nsIFile);
+ saveDir.append("testsavedir");
+ if (!saveDir.exists())
+ saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
+ return saveDir;
+ }
+
+ function doTest(aIsPrivateMode, aWindow, aCallback) {
+ aWindow.gBrowser.addEventListener("pageshow", function pageShown(event) {
+ // If data: -url PAC file isn't loaded soon enough, we may get about:privatebrowsing loaded
+ if (event.target.location == "about:blank" ||
+ event.target.location == "about:privatebrowsing") {
+ aWindow.gBrowser.selectedBrowser.loadURI(testURI);
+ return;
+ }
+ aWindow.gBrowser.removeEventListener("pageshow", pageShown);
+
+ executeSoon(function () {
+ aWindow.document.addEventListener("popupshown",
+ function(e) contextMenuOpened(aWindow, e), false);
+ var img = aWindow.gBrowser.selectedBrowser.contentDocument.getElementById("img");
+ EventUtils.synthesizeMouseAtCenter(img,
+ { type: "contextmenu", button: 2 },
+ aWindow.gBrowser.contentWindow);
+ });
+ });
+ }
+
+ function testOnWindow(aOptions, aCallback) {
+ whenNewWindowLoaded(aOptions, function(aWin) {
+ windowsToClose.push(aWin);
+ // execute should only be called when need, like when you are opening
+ // web pages on the test. If calling executeSoon() is not necesary, then
+ // call whenNewWindowLoaded() instead of testOnWindow() on your test.
+ executeSoon(function() aCallback(aWin));
+ });
+ };
+
+ // this function is called after calling finish() on the test.
+ registerCleanupFunction(function() {
+ windowsToClose.forEach(function(aWin) {
+ aWin.close();
+ });
+ });
+
+ MockFilePicker.init(window);
+ // then test when on private mode
+ testOnWindow({private: true}, function(aWin) {
+ doTest(true, aWin, finish);
+ });
+}
+
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
+ this);
diff --git a/browser/base/content/test/browser_save_video.js b/browser/base/content/test/browser_save_video.js
new file mode 100644
index 000000000..fa00ea37b
--- /dev/null
+++ b/browser/base/content/test/browser_save_video.js
@@ -0,0 +1,91 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var MockFilePicker = SpecialPowers.MockFilePicker;
+MockFilePicker.init(window);
+
+/**
+ * TestCase for bug 564387
+ * <https://bugzilla.mozilla.org/show_bug.cgi?id=564387>
+ */
+function test() {
+ waitForExplicitFinish();
+ var fileName;
+
+ gBrowser.loadURI("http://mochi.test:8888/browser/browser/base/content/test/bug564387.html");
+
+ registerCleanupFunction(function () {
+ gBrowser.addTab();
+ gBrowser.removeCurrentTab();
+ });
+
+ gBrowser.addEventListener("pageshow", function pageShown(event) {
+ if (event.target.location == "about:blank")
+ return;
+ gBrowser.removeEventListener("pageshow", pageShown);
+
+ executeSoon(function () {
+ document.addEventListener("popupshown", contextMenuOpened);
+
+ var video1 = gBrowser.contentDocument.getElementById("video1");
+ EventUtils.synthesizeMouseAtCenter(video1,
+ { type: "contextmenu", button: 2 },
+ gBrowser.contentWindow);
+ });
+ });
+
+ function contextMenuOpened(event) {
+ event.currentTarget.removeEventListener("popupshown", contextMenuOpened);
+
+ // Create the folder the video will be saved into.
+ var destDir = createTemporarySaveDirectory();
+ var destFile = destDir.clone();
+
+ MockFilePicker.displayDirectory = destDir;
+ MockFilePicker.showCallback = function(fp) {
+ fileName = fp.defaultString;
+ destFile.append (fileName);
+ MockFilePicker.returnFiles = [destFile];
+ MockFilePicker.filterIndex = 1; // kSaveAsType_URL
+ };
+
+ mockTransferCallback = onTransferComplete;
+ mockTransferRegisterer.register();
+
+ registerCleanupFunction(function () {
+ mockTransferRegisterer.unregister();
+ MockFilePicker.cleanup();
+ destDir.remove(true);
+ });
+
+ // Select "Save Video As" option from context menu
+ var saveVideoCommand = document.getElementById("context-savevideo");
+ saveVideoCommand.doCommand();
+
+ event.target.hidePopup();
+ }
+
+ function onTransferComplete(downloadSuccess) {
+ ok(downloadSuccess, "Video file should have been downloaded successfully");
+
+ is(fileName, "Bug564387-expectedName.ogv",
+ "Video file name is correctly retrieved from Content-Disposition http header");
+
+ finish();
+ }
+}
+
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
+ this);
+
+function createTemporarySaveDirectory() {
+ var saveDir = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("TmpD", Ci.nsIFile);
+ saveDir.append("testsavedir");
+ if (!saveDir.exists())
+ saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
+ return saveDir;
+}
diff --git a/browser/base/content/test/browser_scope.js b/browser/base/content/test/browser_scope.js
new file mode 100644
index 000000000..e4edac1e0
--- /dev/null
+++ b/browser/base/content/test/browser_scope.js
@@ -0,0 +1,4 @@
+function test() {
+ ok(!!gBrowser, "gBrowser exists");
+ is(gBrowser, getBrowser(), "both ways of getting tabbrowser work");
+}
diff --git a/browser/base/content/test/browser_selectTabAtIndex.js b/browser/base/content/test/browser_selectTabAtIndex.js
new file mode 100644
index 000000000..e9a32184e
--- /dev/null
+++ b/browser/base/content/test/browser_selectTabAtIndex.js
@@ -0,0 +1,19 @@
+function test() {
+ for (let i = 0; i < 9; i++)
+ gBrowser.addTab();
+
+ var isLinux = navigator.platform.indexOf("Linux") == 0;
+ for (let i = 9; i >= 1; i--) {
+ EventUtils.synthesizeKey(i.toString(), { altKey: isLinux, accelKey: !isLinux });
+
+ is(gBrowser.tabContainer.selectedIndex, (i == 9 ? gBrowser.tabs.length : i) - 1,
+ (isLinux ? "Alt" : "Accel") + "+" + i + " selects expected tab");
+ }
+
+ gBrowser.selectTabAtIndex(-3);
+ is(gBrowser.tabContainer.selectedIndex, gBrowser.tabs.length - 3,
+ "gBrowser.selectTabAtIndex(-3) selects expected tab");
+
+ for (let i = 0; i < 9; i++)
+ gBrowser.removeCurrentTab();
+}
diff --git a/browser/base/content/test/browser_tabDrop.js b/browser/base/content/test/browser_tabDrop.js
new file mode 100644
index 000000000..c159270f3
--- /dev/null
+++ b/browser/base/content/test/browser_tabDrop.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+ waitForExplicitFinish();
+
+ let newTab = gBrowser.selectedTab = gBrowser.addTab("about:blank", {skipAnimation: true});
+ registerCleanupFunction(function () {
+ gBrowser.removeTab(newTab);
+ });
+
+ let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+ let ChromeUtils = {};
+ scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js", ChromeUtils);
+
+ let tabContainer = gBrowser.tabContainer;
+ var receivedDropCount = 0;
+ function dropListener() {
+ receivedDropCount++;
+ if (receivedDropCount == triggeredDropCount) {
+ is(openedTabs, validDropCount, "correct number of tabs were opened");
+ executeSoon(finish);
+ }
+ }
+ tabContainer.addEventListener("drop", dropListener, false);
+ registerCleanupFunction(function () {
+ tabContainer.removeEventListener("drop", dropListener, false);
+ });
+
+ var openedTabs = 0;
+ function tabOpenListener(e) {
+ openedTabs++;
+ let tab = e.target;
+ executeSoon(function () {
+ gBrowser.removeTab(tab);
+ });
+ }
+
+ tabContainer.addEventListener("TabOpen", tabOpenListener, false);
+ registerCleanupFunction(function () {
+ tabContainer.removeEventListener("TabOpen", tabOpenListener, false);
+ });
+
+ var triggeredDropCount = 0;
+ var validDropCount = 0;
+ function drop(text, valid) {
+ triggeredDropCount++;
+ if (valid)
+ validDropCount++;
+ executeSoon(function () {
+ // A drop type of "link" onto an existing tab would normally trigger a
+ // load in that same tab, but tabbrowser code in _getDragTargetTab treats
+ // drops on the outer edges of a tab differently (loading a new tab
+ // instead). The events created by synthesizeDrop have all of their
+ // coordinates set to 0 (screenX/screenY), so they're treated as drops
+ // on the outer edge of the tab, thus they open new tabs.
+ ChromeUtils.synthesizeDrop(newTab, newTab, [[{type: "text/plain", data: text}]], "link", window);
+ });
+ }
+
+ // Begin and end with valid drops to make sure we wait for all drops before
+ // ending the test
+ drop("mochi.test/first", true);
+ drop("javascript:'bad'");
+ drop("jAvascript:'bad'");
+ drop("search this", true);
+ drop("mochi.test/second", true);
+ drop("data:text/html,bad");
+ drop("mochi.test/third", true);
+}
diff --git a/browser/base/content/test/browser_tabMatchesInAwesomebar_perwindowpb.js b/browser/base/content/test/browser_tabMatchesInAwesomebar_perwindowpb.js
new file mode 100644
index 000000000..ef3344842
--- /dev/null
+++ b/browser/base/content/test/browser_tabMatchesInAwesomebar_perwindowpb.js
@@ -0,0 +1,249 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim:set ts=2 sw=2 sts=2 et:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const TEST_URL_BASES = [
+ "http://example.org/browser/browser/base/content/test/dummy_page.html#tabmatch",
+ "http://example.org/browser/browser/base/content/test/moz.png#tabmatch"
+];
+
+var gController = Cc["@mozilla.org/autocomplete/controller;1"].
+ getService(Ci.nsIAutoCompleteController);
+
+var gTabWaitCount = 0;
+var gTabCounter = 0;
+
+var gTestSteps = [
+ function() {
+ info("Running step 1");
+ for (let i = 0; i < 10; i++) {
+ let tab = gBrowser.addTab();
+ loadTab(tab, TEST_URL_BASES[0] + (++gTabCounter));
+ }
+ },
+ function() {
+ info("Running step 2");
+ gBrowser.selectTabAtIndex(1);
+ gBrowser.removeCurrentTab();
+ gBrowser.selectTabAtIndex(1);
+ gBrowser.removeCurrentTab();
+ for (let i = 1; i < gBrowser.tabs.length; i++)
+ loadTab(gBrowser.tabs[i], TEST_URL_BASES[1] + (++gTabCounter));
+ },
+ function() {
+ info("Running step 3");
+ for (let i = 1; i < gBrowser.tabs.length; i++)
+ loadTab(gBrowser.tabs[i], TEST_URL_BASES[0] + gTabCounter);
+ },
+ function() {
+ info("Running step 4 - ensure we don't register subframes as open pages");
+ let tab = gBrowser.addTab();
+ tab.linkedBrowser.addEventListener("load", function () {
+ tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+ // Start the sub-document load.
+ executeSoon(function () {
+ tab.linkedBrowser.addEventListener("load", function (e) {
+ tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+ ensure_opentabs_match_db(nextStep);
+ }, true);
+ tab.linkedBrowser.contentDocument.querySelector("iframe").src = "http://test2.example.org/";
+ });
+ }, true);
+ tab.linkedBrowser.loadURI('data:text/html,<body><iframe src=""></iframe></body>');
+ },
+ function() {
+ info("Running step 5 - remove tab immediately");
+ let tab = gBrowser.addTab("about:logo");
+ gBrowser.removeTab(tab);
+ ensure_opentabs_match_db(nextStep);
+ },
+ function() {
+ info("Running step 6 - check swapBrowsersAndCloseOther preserves registered switch-to-tab result");
+ let tabToKeep = gBrowser.addTab();
+ let tab = gBrowser.addTab();
+ tab.linkedBrowser.addEventListener("load", function () {
+ tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+ gBrowser.swapBrowsersAndCloseOther(tabToKeep, tab);
+ ensure_opentabs_match_db(function () {
+ gBrowser.removeTab(tabToKeep);
+ ensure_opentabs_match_db(nextStep);
+ });
+ }, true);
+ tab.linkedBrowser.loadURI("about:mozilla");
+ },
+ function() {
+ info("Running step 7 - close all tabs");
+
+ Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
+
+ gBrowser.addTab("about:blank", {skipAnimation: true});
+ while (gBrowser.tabs.length > 1) {
+ info("Removing tab: " + gBrowser.tabs[0].linkedBrowser.currentURI.spec);
+ gBrowser.selectTabAtIndex(0);
+ gBrowser.removeCurrentTab();
+ }
+ ensure_opentabs_match_db(nextStep);
+ }
+];
+
+
+
+function test() {
+ waitForExplicitFinish();
+ nextStep();
+}
+
+function loadTab(tab, url) {
+ // Because adding visits is async, we will not be notified immediately.
+ let visited = false;
+ let loaded = false;
+
+ function maybeCheckResults() {
+ if (visited && loaded && --gTabWaitCount == 0) {
+ ensure_opentabs_match_db(nextStep);
+ }
+ }
+
+ tab.linkedBrowser.addEventListener("load", function () {
+ tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+ loaded = true;
+ maybeCheckResults();
+ }, true);
+
+ if (!visited) {
+ Services.obs.addObserver(
+ function (aSubject, aTopic, aData) {
+ if (url != aSubject.QueryInterface(Ci.nsIURI).spec)
+ return;
+ Services.obs.removeObserver(arguments.callee, aTopic);
+ visited = true;
+ maybeCheckResults();
+ },
+ "uri-visit-saved",
+ false
+ );
+ }
+
+ gTabWaitCount++;
+ info("Loading page: " + url);
+ tab.linkedBrowser.loadURI(url);
+}
+
+function waitForRestoredTab(tab) {
+ gTabWaitCount++;
+
+ tab.linkedBrowser.addEventListener("load", function () {
+ tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+ if (--gTabWaitCount == 0) {
+ ensure_opentabs_match_db(nextStep);
+ }
+ }, true);
+}
+
+
+function nextStep() {
+ if (gTestSteps.length == 0) {
+ while (gBrowser.tabs.length > 1) {
+ gBrowser.selectTabAtIndex(1);
+ gBrowser.removeCurrentTab();
+ }
+
+ waitForClearHistory(finish);
+
+ return;
+ }
+
+ var stepFunc = gTestSteps.shift();
+ stepFunc();
+}
+
+function ensure_opentabs_match_db(aCallback) {
+ var tabs = {};
+
+ var winEnum = Services.wm.getEnumerator("navigator:browser");
+ while (winEnum.hasMoreElements()) {
+ let browserWin = winEnum.getNext();
+ // skip closed-but-not-destroyed windows
+ if (browserWin.closed)
+ continue;
+
+ for (let i = 0; i < browserWin.gBrowser.tabContainer.childElementCount; i++) {
+ let browser = browserWin.gBrowser.getBrowserAtIndex(i);
+ let url = browser.currentURI.spec;
+ if (browserWin.isBlankPageURL(url))
+ continue;
+ if (!(url in tabs))
+ tabs[url] = 1;
+ else
+ tabs[url]++;
+ }
+ }
+
+ checkAutocompleteResults(tabs, aCallback);
+}
+
+/**
+ * Clears history invoking callback when done.
+ */
+function waitForClearHistory(aCallback) {
+ const TOPIC_EXPIRATION_FINISHED = "places-expiration-finished";
+ let observer = {
+ observe: function(aSubject, aTopic, aData) {
+ Services.obs.removeObserver(this, TOPIC_EXPIRATION_FINISHED);
+ aCallback();
+ }
+ };
+ Services.obs.addObserver(observer, TOPIC_EXPIRATION_FINISHED, false);
+
+ PlacesUtils.bhistory.removeAllPages();
+}
+
+function checkAutocompleteResults(aExpected, aCallback)
+{
+ gController.input = {
+ timeout: 10,
+ textValue: "",
+ searches: ["history"],
+ searchParam: "enable-actions",
+ popupOpen: false,
+ minResultsForPopup: 0,
+ invalidate: function() {},
+ disableAutoComplete: false,
+ completeDefaultIndex: false,
+ get popup() { return this; },
+ onSearchBegin: function() {},
+ onSearchComplete: function ()
+ {
+ info("Found " + gController.matchCount + " matches.");
+ // Check to see the expected uris and titles match up (in any order)
+ for (let i = 0; i < gController.matchCount; i++) {
+ let uri = gController.getValueAt(i).replace(/^moz-action:[^,]+,/i, "");
+
+ info("Search for '" + uri + "' in open tabs.");
+ let expected = uri in aExpected;
+ ok(expected, uri + " was found in autocomplete, was " + (expected ? "" : "not ") + "expected");
+ // Remove the found entry from expected results.
+ delete aExpected[uri];
+ }
+
+ // Make sure there is no reported open page that is not open.
+ for (let entry in aExpected) {
+ ok(false, "'" + entry + "' should be found in autocomplete");
+ }
+
+ executeSoon(aCallback);
+ },
+ setSelectedIndex: function() {},
+ get searchCount() { return this.searches.length; },
+ getSearchAt: function(aIndex) this.searches[aIndex],
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIAutoCompleteInput,
+ Ci.nsIAutoCompletePopup,
+ ])
+ };
+
+ info("Searching open pages.");
+ gController.startSearch(Services.prefs.getCharPref("browser.urlbar.restrict.openpage"));
+}
diff --git a/browser/base/content/test/browser_tab_drag_drop_perwindow.js b/browser/base/content/test/browser_tab_drag_drop_perwindow.js
new file mode 100644
index 000000000..f787ae860
--- /dev/null
+++ b/browser/base/content/test/browser_tab_drag_drop_perwindow.js
@@ -0,0 +1,50 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ //initialization
+ waitForExplicitFinish();
+
+ let scriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+ let ChromeUtils = {};
+ scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js", ChromeUtils);
+
+ function testOnWindow(aIsPrivate, aCallback) {
+ whenNewWindowLoaded({private: aIsPrivate}, function(win) {
+ executeSoon(function() aCallback(win));
+ });
+ }
+
+ testOnWindow(false, function(aNormalWindow) {
+ testOnWindow(true, function(aPrivateWindow) {
+ // Open a tab in each window
+ let normalTab = aNormalWindow.gBrowser.addTab("about:blank", {skipAnimation: true});
+ let privateTab = aPrivateWindow.gBrowser.addTab("about:blank", {skipAnimation: true});
+
+ let effect = ChromeUtils.synthesizeDrop(normalTab, privateTab,
+ [[{type: TAB_DROP_TYPE, data: normalTab}]],
+ null, aNormalWindow, aPrivateWindow);
+ is(effect, "none", "Should not be able to drag a normal tab to a private window");
+
+ effect = ChromeUtils.synthesizeDrop(privateTab, normalTab,
+ [[{type: TAB_DROP_TYPE, data: privateTab}]],
+ null, aPrivateWindow, aNormalWindow);
+ is(effect, "none", "Should not be able to drag a private tab to a normal window");
+
+ aNormalWindow.gBrowser.swapBrowsersAndCloseOther(normalTab, privateTab);
+ is(aNormalWindow.gBrowser.tabs.length, 2, "Prevent moving a normal tab to a private tabbrowser");
+ is(aPrivateWindow.gBrowser.tabs.length, 2, "Prevent accepting a normal tab in a private tabbrowser");
+
+ aPrivateWindow.gBrowser.swapBrowsersAndCloseOther(privateTab, normalTab);
+ is(aPrivateWindow.gBrowser.tabs.length, 2, "Prevent moving a private tab to a normal tabbrowser");
+ is(aNormalWindow.gBrowser.tabs.length, 2, "Prevent accepting a private tab in a normal tabbrowser");
+
+ aNormalWindow.close();
+ aPrivateWindow.close();
+ finish();
+ });
+ });
+}
+
diff --git a/browser/base/content/test/browser_tab_dragdrop.js b/browser/base/content/test/browser_tab_dragdrop.js
new file mode 100644
index 000000000..04e4acdd7
--- /dev/null
+++ b/browser/base/content/test/browser_tab_dragdrop.js
@@ -0,0 +1,118 @@
+function test()
+{
+ var embed = '<embed type="application/x-test" allowscriptaccess="always" allowfullscreen="true" wmode="window" width="640" height="480"></embed>'
+
+ waitForExplicitFinish();
+
+ // create a few tabs
+ var tabs = [
+ gBrowser.tabs[0],
+ gBrowser.addTab("about:blank", {skipAnimation: true}),
+ gBrowser.addTab("about:blank", {skipAnimation: true}),
+ gBrowser.addTab("about:blank", {skipAnimation: true}),
+ gBrowser.addTab("about:blank", {skipAnimation: true})
+ ];
+
+ function setLocation(i, url) {
+ gBrowser.getBrowserForTab(tabs[i]).contentWindow.location = url;
+ }
+ function moveTabTo(a, b) {
+ gBrowser.swapBrowsersAndCloseOther(gBrowser.tabs[b], gBrowser.tabs[a]);
+ }
+ function clickTest(doc, win) {
+ var clicks = doc.defaultView.clicks;
+ EventUtils.synthesizeMouseAtCenter(doc.body, {}, win);
+ is(doc.defaultView.clicks, clicks+1, "adding 1 more click on BODY");
+ }
+ function test1() {
+ moveTabTo(2, 3); // now: 0 1 2 4
+ is(gBrowser.tabs[1], tabs[1], "tab1");
+ is(gBrowser.tabs[2], tabs[3], "tab3");
+
+ var plugin = gBrowser.getBrowserForTab(tabs[4]).docShell.contentViewer.DOMDocument.wrappedJSObject.body.firstChild;
+ var tab4_plugin_object = plugin.getObjectValue();
+
+ gBrowser.selectedTab = gBrowser.tabs[2];
+ moveTabTo(3, 2); // now: 0 1 4
+ gBrowser.selectedTab = tabs[4];
+ var doc = gBrowser.getBrowserForTab(gBrowser.tabs[2]).docShell.contentViewer.DOMDocument.wrappedJSObject;
+ plugin = doc.body.firstChild;
+ ok(plugin && plugin.checkObjectValue(tab4_plugin_object), "same plugin instance");
+ is(gBrowser.tabs[1], tabs[1], "tab1");
+ is(gBrowser.tabs[2], tabs[3], "tab4");
+ is(doc.defaultView.clicks, 0, "no click on BODY so far");
+ clickTest(doc, window);
+
+ moveTabTo(2, 1); // now: 0 4
+ is(gBrowser.tabs[1], tabs[1], "tab1");
+ doc = gBrowser.getBrowserForTab(gBrowser.tabs[1]).docShell.contentViewer.DOMDocument.wrappedJSObject;
+ plugin = doc.body.firstChild;
+ ok(plugin && plugin.checkObjectValue(tab4_plugin_object), "same plugin instance");
+ clickTest(doc, window);
+
+ // Load a new document (about:blank) in tab4, then detach that tab into a new window.
+ // In the new window, navigate back to the original document and click on its <body>,
+ // verify that its onclick was called.
+ var t = tabs[1];
+ var b = gBrowser.getBrowserForTab(t);
+ gBrowser.selectedTab = t;
+ b.addEventListener("load", function() {
+ b.removeEventListener("load", arguments.callee, true);
+
+ executeSoon(function () {
+ var win = gBrowser.replaceTabWithWindow(t);
+ whenDelayedStartupFinished(win, function () {
+ // Verify that the original window now only has the initial tab left in it.
+ is(gBrowser.tabs[0], tabs[0], "tab0");
+ is(gBrowser.getBrowserForTab(gBrowser.tabs[0]).contentWindow.location, "about:blank", "tab0 uri");
+
+ executeSoon(function () {
+ win.gBrowser.addEventListener("pageshow", function () {
+ win.gBrowser.removeEventListener("pageshow", arguments.callee, false);
+ executeSoon(function () {
+ t = win.gBrowser.tabs[0];
+ b = win.gBrowser.getBrowserForTab(t);
+ var doc = b.docShell.contentViewer.DOMDocument.wrappedJSObject;
+ clickTest(doc, win);
+ win.close();
+ finish();
+ });
+ }, false);
+ win.gBrowser.goBack();
+ });
+ });
+ });
+ }, true);
+ b.loadURI("about:blank");
+
+ }
+
+ var loads = 0;
+ function waitForLoad(event, tab, listenerContainer) {
+ var b = gBrowser.getBrowserForTab(gBrowser.tabs[tab]);
+ if (b.contentDocument != event.target) {
+ return;
+ }
+ gBrowser.getBrowserForTab(gBrowser.tabs[tab]).removeEventListener("load", listenerContainer.listener, true);
+ ++loads;
+ if (loads == tabs.length - 1) {
+ executeSoon(test1);
+ }
+ }
+
+ function fn(f, arg) {
+ var listenerContainer = { listener: null }
+ listenerContainer.listener = function (event) { return f(event, arg, listenerContainer); };
+ return listenerContainer.listener;
+ }
+ for (var i = 1; i < tabs.length; ++i) {
+ gBrowser.getBrowserForTab(tabs[i]).addEventListener("load", fn(waitForLoad,i), true);
+ }
+
+ setLocation(1, "data:text/html;charset=utf-8,<title>tab1</title><body>tab1<iframe>");
+ setLocation(2, "data:text/plain;charset=utf-8,tab2");
+ setLocation(3, "data:text/html;charset=utf-8,<title>tab3</title><body>tab3<iframe>");
+ setLocation(4, "data:text/html;charset=utf-8,<body onload='clicks=0' onclick='++clicks'>"+embed);
+ gBrowser.selectedTab = tabs[3];
+
+}
diff --git a/browser/base/content/test/browser_tab_dragdrop2.js b/browser/base/content/test/browser_tab_dragdrop2.js
new file mode 100644
index 000000000..e2f293e85
--- /dev/null
+++ b/browser/base/content/test/browser_tab_dragdrop2.js
@@ -0,0 +1,52 @@
+function test()
+{
+ waitForExplicitFinish();
+
+ var level1 = false;
+ var level2 = false;
+ function test1() {
+ // Load the following URI (which runs some child popup tests) in a new window (B),
+ // then add a blank tab to B and call replaceTabWithWindow to detach the URI tab
+ // into yet a new window (C), then close B.
+ // Now run the tests again and then close C.
+ // The test results does not matter, all this is just to exercise some code to
+ // catch assertions or crashes.
+ var chromeroot = getRootDirectory(gTestPath);
+ var uri = chromeroot + "browser_tab_dragdrop2_frame1.xul";
+ let window_B = openDialog(location, "_blank", "chrome,all,dialog=no,left=200,top=200,width=200,height=200", uri);
+ window_B.addEventListener("load", function(aEvent) {
+ window_B.removeEventListener("load", arguments.callee, false);
+ if (level1) return; level1=true;
+ executeSoon(function () {
+ window_B.gBrowser.addEventListener("load", function(aEvent) {
+ window_B.removeEventListener("load", arguments.callee, true);
+ if (level2) return; level2=true;
+ is(window_B.gBrowser.getBrowserForTab(window_B.gBrowser.tabs[0]).contentWindow.location, uri, "sanity check");
+ //alert("1:"+window_B.gBrowser.getBrowserForTab(window_B.gBrowser.tabs[0]).contentWindow.location);
+ var windowB_tab2 = window_B.gBrowser.addTab("about:blank", {skipAnimation: true});
+ setTimeout(function () {
+ //alert("2:"+window_B.gBrowser.getBrowserForTab(window_B.gBrowser.tabs[0]).contentWindow.location);
+ window_B.gBrowser.addEventListener("pagehide", function(aEvent) {
+ window_B.gBrowser.removeEventListener("pagehide", arguments.callee, true);
+ executeSoon(function () {
+ // alert("closing window_B which has "+ window_B.gBrowser.tabs.length+" tabs\n"+
+ // window_B.gBrowser.getBrowserForTab(window_B.gBrowser.tabs[0]).contentWindow.location);
+ window_B.close();
+
+ var doc = window_C.gBrowser.getBrowserForTab(window_C.gBrowser.tabs[0])
+ .docShell.contentViewer.DOMDocument;
+ var calls = doc.defaultView.test_panels();
+ window_C.close();
+ finish();
+ });
+ }, true);
+ window_B.gBrowser.selectedTab = window_B.gBrowser.tabs[0];
+ var window_C = window_B.gBrowser.replaceTabWithWindow(window_B.gBrowser.tabs[0]);
+ }, 1000); // 1 second to allow the tests to create the popups
+ }, true);
+ });
+ }, false);
+ }
+
+ test1();
+}
diff --git a/browser/base/content/test/browser_tab_dragdrop2_frame1.xul b/browser/base/content/test/browser_tab_dragdrop2_frame1.xul
new file mode 100644
index 000000000..ee3e62646
--- /dev/null
+++ b/browser/base/content/test/browser_tab_dragdrop2_frame1.xul
@@ -0,0 +1,167 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+<!--
+ XUL Widget Test for panels
+ -->
+<window title="Titlebar" width="200" height="200"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
+
+<tree id="tree" seltype="single" width="100" height="100">
+ <treecols>
+ <treecol flex="1"/>
+ <treecol flex="1"/>
+ </treecols>
+ <treechildren id="treechildren">
+ <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem>
+ <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem>
+ <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem>
+ <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem>
+ <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem>
+ <treeitem><treerow><treecell label="One"/><treecell label="Two"/></treerow></treeitem>
+ </treechildren>
+</tree>
+
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml" style="height: 300px; overflow: auto;"/>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+
+var currentTest = null;
+
+var i = 0;
+var my_debug = false;
+function test_panels()
+{
+ checkTreeCoords();
+
+ addEventListener("popupshown", popupShown, false);
+ addEventListener("popuphidden", nextTest, false);
+ return nextTest();
+}
+
+function nextTest()
+{
+ ok(true,"popuphidden " + i)
+ if (i == tests.length) {
+ return i;
+ }
+
+ currentTest = tests[i];
+ var panel = createPanel(currentTest.attrs);
+ currentTest.test(panel);
+ return i;
+}
+
+var waitSteps = 0;
+function popupShown(event)
+{
+ var panel = event.target;
+ if (waitSteps > 0 && navigator.platform.indexOf("Linux") >= 0 &&
+ panel.boxObject.screenY == 210) {
+ waitSteps--;
+ setTimeout(popupShown, 10, event);
+ return;
+ }
+ ++i;
+
+ currentTest.result(currentTest.testname + " ", panel);
+ panel.hidePopup();
+}
+
+function createPanel(attrs)
+{
+ var panel = document.createElement("panel");
+ for (var a in attrs) {
+ panel.setAttribute(a, attrs[a]);
+ }
+
+ var button = document.createElement("button");
+ panel.appendChild(button);
+ button.label = "OK";
+ button.width = 120;
+ button.height = 40;
+ button.setAttribute("style", "-moz-appearance: none; border: 0; margin: 0;");
+ panel.setAttribute("style", "-moz-appearance: none; border: 0; margin: 0;");
+ return document.documentElement.appendChild(panel);
+}
+
+function checkTreeCoords()
+{
+ var tree = $("tree");
+ var treechildren = $("treechildren");
+ tree.currentIndex = 0;
+ tree.treeBoxObject.scrollToRow(0);
+ synthesizeMouse(treechildren, 10, tree.treeBoxObject.rowHeight + 2, { });
+
+ tree.treeBoxObject.scrollToRow(2);
+ synthesizeMouse(treechildren, 10, tree.treeBoxObject.rowHeight + 2, { });
+}
+
+var tests = [
+ {
+ testname: "normal panel",
+ attrs: { },
+ test: function(panel) {
+ panel.openPopupAtScreen(200, 210);
+ },
+ result: function(testname, panel) {
+ if (my_debug) alert(testname);
+ var panelrect = panel.getBoundingClientRect();
+ }
+ },
+ {
+ // only noautohide panels support titlebars, so one shouldn't be shown here
+ testname: "autohide panel with titlebar",
+ attrs: { titlebar: "normal" },
+ test: function(panel) {
+ panel.openPopupAtScreen(200, 210);
+ },
+ result: function(testname, panel) {
+ if (my_debug) alert(testname);
+ var panelrect = panel.getBoundingClientRect();
+ }
+ },
+ {
+ testname: "noautohide panel with titlebar",
+ attrs: { noautohide: true, titlebar: "normal" },
+ test: function(panel) {
+ waitSteps = 25;
+ panel.openPopupAtScreen(200, 210);
+ },
+ result: function(testname, panel) {
+ if (my_debug) alert(testname);
+ var panelrect = panel.getBoundingClientRect();
+
+ var gotMouseEvent = false;
+ function mouseMoved(event)
+ {
+ gotMouseEvent = true;
+ }
+
+ panel.addEventListener("mousemove", mouseMoved, true);
+ synthesizeMouse(panel, 10, 10, { type: "mousemove" });
+ panel.removeEventListener("mousemove", mouseMoved, true);
+
+ var tree = $("tree");
+ tree.currentIndex = 0;
+ panel.appendChild(tree);
+ checkTreeCoords();
+ }
+ }
+];
+
+SimpleTest.waitForFocus(test_panels);
+
+]]>
+</script>
+
+</window>
diff --git a/browser/base/content/test/browser_tabfocus.js b/browser/base/content/test/browser_tabfocus.js
new file mode 100644
index 000000000..289c83c89
--- /dev/null
+++ b/browser/base/content/test/browser_tabfocus.js
@@ -0,0 +1,278 @@
+/*
+ * This test checks that focus is adjusted properly when switching tabs.
+ */
+
+let testPage1 = "data:text/html,<html id='tab1'><body><button id='button1'>Tab 1</button></body></html>";
+let testPage2 = "data:text/html,<html id='tab2'><body><button id='button2'>Tab 2</button></body></html>";
+let testPage3 = "data:text/html,<html id='tab3'><body><button id='button3'>Tab 3</button></body></html>";
+
+function test() {
+ waitForExplicitFinish();
+
+ var tab1 = gBrowser.addTab();
+ var browser1 = gBrowser.getBrowserForTab(tab1);
+
+ var tab2 = gBrowser.addTab();
+ var browser2 = gBrowser.getBrowserForTab(tab2);
+
+ gURLBar.focus();
+
+ var loadCount = 0;
+ function check()
+ {
+ // wait for both tabs to load
+ if (++loadCount != 2)
+ return;
+
+ browser1.removeEventListener("load", check, true);
+ browser2.removeEventListener("load", check, true);
+ executeSoon(_run_focus_tests);
+ }
+
+ function _run_focus_tests() {
+ window.focus();
+
+ _browser_tabfocus_test_lastfocus = gURLBar;
+ _browser_tabfocus_test_lastfocuswindow = window;
+
+ window.addEventListener("focus", _browser_tabfocus_test_eventOccured, true);
+ window.addEventListener("blur", _browser_tabfocus_test_eventOccured, true);
+
+ // make sure that the focus initially starts out blank
+ var fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
+ var focusedWindow = {};
+ is(fm.getFocusedElementForWindow(browser1.contentWindow, false, focusedWindow), null, "initial focus in tab 1");
+ is(focusedWindow.value, browser1.contentWindow, "initial frame focus in tab 1");
+ is(fm.getFocusedElementForWindow(browser2.contentWindow, false, focusedWindow), null, "initial focus in tab 2");
+ is(focusedWindow.value, browser2.contentWindow, "initial frame focus in tab 2");
+
+ expectFocusShift(function () gBrowser.selectedTab = tab2,
+ browser2.contentWindow, null, true,
+ "focusedElement after tab change, focus in new tab");
+
+ // switching tabs when nothing in the new tab is focused
+ // should focus the browser
+ expectFocusShift(function () gBrowser.selectedTab = tab1,
+ browser1.contentWindow, null, true,
+ "focusedElement after tab change, focus in new tab");
+
+ // focusing a button in the current tab should focus it
+ var button1 = browser1.contentDocument.getElementById("button1");
+ expectFocusShift(function () button1.focus(),
+ browser1.contentWindow, button1, true,
+ "focusedWindow after focus in focused tab");
+
+ // focusing a button in a background tab should not change the actual
+ // focus, but should set the focus that would be in that background tab to
+ // that button.
+ var button2 = browser2.contentDocument.getElementById("button2");
+ button2.focus();
+
+ expectFocusShift(function () button2.focus(),
+ browser1.contentWindow, button1, false,
+ "focusedWindow after focus in unfocused tab");
+ is(fm.getFocusedElementForWindow(browser2.contentWindow, false, {}), button2, "focus in unfocused tab");
+
+ // switching tabs should now make the button in the other tab focused
+ expectFocusShift(function () gBrowser.selectedTab = tab2,
+ browser2.contentWindow, button2, true,
+ "focusedWindow after tab change");
+
+ // blurring an element in a background tab should not change the active
+ // focus, but should clear the focus in that tab.
+ expectFocusShift(function () button1.blur(),
+ browser2.contentWindow, button2, false,
+ "focusedWindow after blur in unfocused tab");
+ is(fm.getFocusedElementForWindow(browser1.contentWindow, false, {}), null, "blur in unfocused tab");
+
+ // When focus is in the tab bar, it should be retained there
+ expectFocusShift(function () gBrowser.selectedTab.focus(),
+ window, gBrowser.selectedTab, true,
+ "focusing tab element");
+ expectFocusShift(function () gBrowser.selectedTab = tab1,
+ window, tab1, true,
+ "tab change when selected tab element was focused");
+ expectFocusShift(function () gBrowser.selectedTab = tab2,
+ window, tab2, true,
+ "tab change when selected tab element was focused");
+ expectFocusShift(function () gBrowser.selectedTab.blur(),
+ window, null, true,
+ "blurring tab element");
+
+ // focusing the url field should switch active focus away from the browser but
+ // not clear what would be the focus in the browser
+ button1.focus();
+ expectFocusShift(function () gURLBar.focus(),
+ window, gURLBar.inputField, true,
+ "focusedWindow after url field focused");
+ is(fm.getFocusedElementForWindow(browser2.contentWindow, false, {}), button2, "url field focused, button in browser");
+ expectFocusShift(function () gURLBar.blur(),
+ window, null, true,
+ "blurring url field");
+
+ // when a chrome element is focused, switching tabs to a tab with a button
+ // with the current focus should focus the button
+ expectFocusShift(function () gBrowser.selectedTab = tab1,
+ browser1.contentWindow, button1, true,
+ "focusedWindow after tab change, focus in url field, button focused in new tab");
+ is(fm.getFocusedElementForWindow(browser2.contentWindow, false, {}), button2, "after switch tab, focus in unfocused tab");
+
+ // blurring an element in the current tab should clear the active focus
+ expectFocusShift(function () button1.blur(),
+ browser1.contentWindow, null, true,
+ "focusedWindow after blur in focused tab");
+
+ // blurring an non-focused url field should have no effect
+ expectFocusShift(function () gURLBar.blur(),
+ browser1.contentWindow, null, false,
+ "focusedWindow after blur in unfocused url field");
+
+ // switch focus to a tab with a currently focused element
+ expectFocusShift(function () gBrowser.selectedTab = tab2,
+ browser2.contentWindow, button2, true,
+ "focusedWindow after switch from unfocused to focused tab");
+
+ // clearing focus on the chrome window should switch the focus to the
+ // chrome window
+ expectFocusShift(function () fm.clearFocus(window),
+ window, null, true,
+ "focusedWindow after switch to chrome with no focused element");
+
+ // switch focus to another tab when neither have an active focus
+ expectFocusShift(function () gBrowser.selectedTab = tab1,
+ browser1.contentWindow, null, true,
+ "focusedWindow after tab switch from no focus to no focus");
+
+ gURLBar.focus();
+ _browser_tabfocus_test_events = "";
+ _browser_tabfocus_test_lastfocus = gURLBar;
+ _browser_tabfocus_test_lastfocuswindow = window;
+
+ expectFocusShift(function () EventUtils.synthesizeKey("VK_F6", { }),
+ browser1.contentWindow, browser1.contentDocument.documentElement,
+ true, "switch document forward with f6");
+ EventUtils.synthesizeKey("VK_F6", { });
+ is(fm.focusedWindow, window, "switch document forward again with f6");
+
+ browser1.style.MozUserFocus = "ignore";
+ browser1.clientWidth;
+ EventUtils.synthesizeKey("VK_F6", { });
+ is(fm.focusedWindow, window, "switch document forward again with f6 when browser non-focusable");
+
+ window.removeEventListener("focus", _browser_tabfocus_test_eventOccured, true);
+ window.removeEventListener("blur", _browser_tabfocus_test_eventOccured, true);
+
+ // next, check whether navigating forward, focusing the urlbar and then
+ // navigating back maintains the focus in the urlbar.
+ browser1.addEventListener("pageshow", _browser_tabfocus_navigation_test_eventOccured, true);
+ button1.focus();
+ browser1.contentWindow.location = testPage3;
+ }
+
+ browser1.addEventListener("load", check, true);
+ browser2.addEventListener("load", check, true);
+ browser1.contentWindow.location = testPage1;
+ browser2.contentWindow.location = testPage2;
+}
+
+var _browser_tabfocus_test_lastfocus;
+var _browser_tabfocus_test_lastfocuswindow = null;
+var _browser_tabfocus_test_events = "";
+
+function _browser_tabfocus_test_eventOccured(event)
+{
+ var id;
+ if (event.target instanceof Window)
+ id = event.originalTarget.document.documentElement.id + "-window";
+ else if (event.target instanceof Document)
+ id = event.originalTarget.documentElement.id + "-document";
+ else if (event.target.id == "urlbar" && event.originalTarget.localName == "input")
+ id = "urlbar";
+ else
+ id = event.originalTarget.id;
+
+ if (_browser_tabfocus_test_events)
+ _browser_tabfocus_test_events += " ";
+ _browser_tabfocus_test_events += event.type + ": " + id;
+}
+
+function _browser_tabfocus_navigation_test_eventOccured(event)
+{
+ if (event.target instanceof Document) {
+ var contentwin = event.target.defaultView;
+ if (contentwin.location.toString().indexOf("3") > 0) {
+ // just moved forward, so focus the urlbar and go back
+ gURLBar.focus();
+ setTimeout(function () contentwin.history.back(), 0);
+ }
+ else if (contentwin.location.toString().indexOf("2") > 0) {
+ event.currentTarget.removeEventListener("pageshow", _browser_tabfocus_navigation_test_eventOccured, true);
+ is(window.document.activeElement, gURLBar.inputField, "urlbar still focused after navigating back");
+ gBrowser.removeCurrentTab();
+ gBrowser.removeCurrentTab();
+ finish();
+ }
+ }
+}
+
+function getId(element)
+{
+ return (element.localName == "input") ? "urlbar" : element.id;
+}
+
+function expectFocusShift(callback, expectedWindow, expectedElement, focusChanged, testid)
+{
+ var expectedEvents = "";
+ if (focusChanged) {
+ if (_browser_tabfocus_test_lastfocus)
+ expectedEvents += "blur: " + getId(_browser_tabfocus_test_lastfocus);
+
+ if (_browser_tabfocus_test_lastfocuswindow &&
+ _browser_tabfocus_test_lastfocuswindow != expectedWindow) {
+ if (expectedEvents)
+ expectedEvents += " ";
+ var windowid = _browser_tabfocus_test_lastfocuswindow.document.documentElement.id;
+ expectedEvents += "blur: " + windowid + "-document " +
+ "blur: " + windowid + "-window";
+ }
+
+ if (expectedWindow && _browser_tabfocus_test_lastfocuswindow != expectedWindow) {
+ if (expectedEvents)
+ expectedEvents += " ";
+ var windowid = expectedWindow.document.documentElement.id;
+ expectedEvents += "focus: " + windowid + "-document " +
+ "focus: " + windowid + "-window";
+ }
+
+ if (expectedElement && expectedElement != expectedElement.ownerDocument.documentElement) {
+ if (expectedEvents)
+ expectedEvents += " ";
+ expectedEvents += "focus: " + getId(expectedElement);
+ }
+
+ _browser_tabfocus_test_lastfocus = expectedElement;
+ _browser_tabfocus_test_lastfocuswindow = expectedWindow;
+ }
+
+ callback();
+
+ is(_browser_tabfocus_test_events, expectedEvents, testid + " events");
+ _browser_tabfocus_test_events = "";
+
+ var fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
+
+ var focusedElement = fm.focusedElement;
+ is(focusedElement ? getId(focusedElement) : "none",
+ expectedElement ? getId(expectedElement) : "none", testid + " focusedElement");
+ is(fm.focusedWindow, expectedWindow, testid + " focusedWindow");
+ var focusedWindow = {};
+ is(fm.getFocusedElementForWindow(expectedWindow, false, focusedWindow),
+ expectedElement, testid + " getFocusedElementForWindow");
+ is(focusedWindow.value, expectedWindow, testid + " getFocusedElementForWindow frame");
+ is(expectedWindow.document.hasFocus(), true, testid + " hasFocus");
+ var expectedActive = expectedElement;
+ if (!expectedActive)
+ expectedActive = expectedWindow.document instanceof XULDocument ?
+ expectedWindow.document.documentElement : expectedWindow.document.body;
+ is(expectedWindow.document.activeElement, expectedActive, testid + " activeElement");
+}
diff --git a/browser/base/content/test/browser_tabopen_reflows.js b/browser/base/content/test/browser_tabopen_reflows.js
new file mode 100644
index 000000000..d28c6304a
--- /dev/null
+++ b/browser/base/content/test/browser_tabopen_reflows.js
@@ -0,0 +1,126 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+XPCOMUtils.defineLazyGetter(this, "docShell", () => {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell);
+});
+
+const EXPECTED_REFLOWS = [
+ // tabbrowser.adjustTabstrip() call after tabopen animation has finished
+ "adjustTabstrip@chrome://browser/content/tabbrowser.xml|" +
+ "_handleNewTab@chrome://browser/content/tabbrowser.xml|" +
+ "onxbltransitionend@chrome://browser/content/tabbrowser.xml|",
+
+ // switching focus in updateCurrentBrowser() causes reflows
+ "updateCurrentBrowser@chrome://browser/content/tabbrowser.xml|" +
+ "onselect@chrome://browser/content/browser.xul|",
+
+ // switching focus in openLinkIn() causes reflows
+ "openLinkIn@chrome://browser/content/utilityOverlay.js|" +
+ "openUILinkIn@chrome://browser/content/utilityOverlay.js|" +
+ "BrowserOpenTab@chrome://browser/content/browser.js|",
+
+ // accessing element.scrollPosition in _fillTrailingGap() flushes layout
+ "get_scrollPosition@chrome://global/content/bindings/scrollbox.xml|" +
+ "_fillTrailingGap@chrome://browser/content/tabbrowser.xml|" +
+ "_handleNewTab@chrome://browser/content/tabbrowser.xml|" +
+ "onxbltransitionend@chrome://browser/content/tabbrowser.xml|",
+
+ // The TabView iframe causes reflows in the parent document.
+ "iQClass_height@chrome://browser/content/tabview.js|" +
+ "GroupItem_getContentBounds@chrome://browser/content/tabview.js|" +
+ "GroupItem_shouldStack@chrome://browser/content/tabview.js|" +
+ "GroupItem_arrange@chrome://browser/content/tabview.js|" +
+ "GroupItem_add@chrome://browser/content/tabview.js|" +
+ "GroupItems_newTab@chrome://browser/content/tabview.js|" +
+ "TabItem__reconnect@chrome://browser/content/tabview.js|" +
+ "TabItem@chrome://browser/content/tabview.js|" +
+ "TabItems_link@chrome://browser/content/tabview.js|" +
+ "@chrome://browser/content/tabview.js|" +
+ "addTab@chrome://browser/content/tabbrowser.xml|",
+
+ // SessionStore.getWindowDimensions()
+ "ssi_getWindowDimension@resource:///modules/sessionstore/SessionStore.jsm|" +
+ "@resource:///modules/sessionstore/SessionStore.jsm|" +
+ "ssi_updateWindowFeatures@resource:///modules/sessionstore/SessionStore.jsm|" +
+ "ssi_collectWindowData@resource:///modules/sessionstore/SessionStore.jsm|" +
+ "@resource:///modules/sessionstore/SessionStore.jsm|" +
+ "ssi_forEachBrowserWindow@resource:///modules/sessionstore/SessionStore.jsm|" +
+ "ssi_getCurrentState@resource:///modules/sessionstore/SessionStore.jsm|" +
+ "ssi_saveState@resource:///modules/sessionstore/SessionStore.jsm|" +
+ "ssi_onTimerCallback@resource:///modules/sessionstore/SessionStore.jsm|" +
+ "ssi_observe@resource:///modules/sessionstore/SessionStore.jsm|",
+
+ // tabPreviews.capture()
+ "tabPreviews_capture@chrome://browser/content/browser.js|" +
+ "tabPreviews_handleEvent/<@chrome://browser/content/browser.js|"
+];
+
+const PREF_PRELOAD = "browser.newtab.preload";
+
+/*
+ * This test ensures that there are no unexpected
+ * uninterruptible reflows when opening new tabs.
+ */
+function test() {
+ waitForExplicitFinish();
+
+ Services.prefs.setBoolPref(PREF_PRELOAD, false);
+ registerCleanupFunction(() => Services.prefs.clearUserPref(PREF_PRELOAD));
+
+ // Add a reflow observer and open a new tab.
+ docShell.addWeakReflowObserver(observer);
+ BrowserOpenTab();
+
+ // Wait until the tabopen animation has finished.
+ waitForTransitionEnd(function () {
+ // Remove reflow observer and clean up.
+ docShell.removeWeakReflowObserver(observer);
+ gBrowser.removeCurrentTab();
+
+ finish();
+ });
+}
+
+let observer = {
+ reflow: function (start, end) {
+ // Gather information about the current code path.
+ let path = (new Error().stack).split("\n").slice(1).map(line => {
+ return line.replace(/:\d+$/, "");
+ }).join("|");
+
+ // Stack trace is empty. Reflow was triggered by native code.
+ if (path === "") {
+ return;
+ }
+
+ // Check if this is an expected reflow.
+ for (let stack of EXPECTED_REFLOWS) {
+ if (path.startsWith(stack)) {
+ ok(true, "expected uninterruptible reflow '" + stack + "'");
+ return;
+ }
+ }
+
+ ok(false, "unexpected uninterruptible reflow '" + path + "'");
+ },
+
+ reflowInterruptible: function (start, end) {
+ // We're not interested in interruptible reflows.
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIReflowObserver,
+ Ci.nsISupportsWeakReference])
+};
+
+function waitForTransitionEnd(callback) {
+ let tab = gBrowser.selectedTab;
+ tab.addEventListener("transitionend", function onEnd(event) {
+ if (event.propertyName === "max-width") {
+ tab.removeEventListener("transitionend", onEnd);
+ executeSoon(callback);
+ }
+ });
+}
diff --git a/browser/base/content/test/browser_tabs_isActive.js b/browser/base/content/test/browser_tabs_isActive.js
new file mode 100644
index 000000000..57d86e821
--- /dev/null
+++ b/browser/base/content/test/browser_tabs_isActive.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+ test_tab("about:blank");
+ test_tab("about:license");
+}
+
+function test_tab(url) {
+ let originalTab = gBrowser.selectedTab;
+ let newTab = gBrowser.addTab(url, {skipAnimation: true});
+ is(tabIsActive(newTab), false, "newly added " + url + " tab is not active");
+ is(tabIsActive(originalTab), true, "original tab is active initially");
+
+ gBrowser.selectedTab = newTab;
+ is(tabIsActive(newTab), true, "newly added " + url + " tab is active after selection");
+ is(tabIsActive(originalTab), false, "original tab is not active while unselected");
+
+ gBrowser.selectedTab = originalTab;
+ is(tabIsActive(newTab), false, "newly added " + url + " tab is not active after switch back");
+ is(tabIsActive(originalTab), true, "original tab is active again after switch back");
+
+ gBrowser.removeTab(newTab);
+}
+
+function tabIsActive(tab) {
+ let browser = tab.linkedBrowser;
+ return browser.docShell.isActive;
+}
diff --git a/browser/base/content/test/browser_tabs_owner.js b/browser/base/content/test/browser_tabs_owner.js
new file mode 100644
index 000000000..d432eab24
--- /dev/null
+++ b/browser/base/content/test/browser_tabs_owner.js
@@ -0,0 +1,32 @@
+function test() {
+ gBrowser.addTab();
+ gBrowser.addTab();
+ gBrowser.addTab();
+
+ var tabs = gBrowser.tabs;
+ var owner;
+
+ is(tabs.length, 4, "4 tabs are open");
+
+ owner = gBrowser.selectedTab = tabs[2];
+ BrowserOpenTab();
+ is(gBrowser.selectedTab, tabs[4], "newly opened tab is selected");
+ gBrowser.removeCurrentTab();
+ is(gBrowser.selectedTab, owner, "owner is selected");
+
+ owner = gBrowser.selectedTab;
+ BrowserOpenTab();
+ gBrowser.selectedTab = tabs[1];
+ gBrowser.selectedTab = tabs[4];
+ gBrowser.removeCurrentTab();
+ isnot(gBrowser.selectedTab, owner, "selecting a different tab clears the owner relation");
+
+ owner = gBrowser.selectedTab;
+ BrowserOpenTab();
+ gBrowser.moveTabTo(gBrowser.selectedTab, 0);
+ gBrowser.removeCurrentTab();
+ is(gBrowser.selectedTab, owner, "owner relatitionship persists when tab is moved");
+
+ while (tabs.length > 1)
+ gBrowser.removeCurrentTab();
+}
diff --git a/browser/base/content/test/browser_typeAheadFind.js b/browser/base/content/test/browser_typeAheadFind.js
new file mode 100644
index 000000000..507a63b26
--- /dev/null
+++ b/browser/base/content/test/browser_typeAheadFind.js
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+let testWindow = null;
+
+function test() {
+ waitForExplicitFinish();
+
+ testWindow = OpenBrowserWindow();
+ whenDelayedStartupFinished(testWindow, function () {
+ let selectedBrowser = testWindow.gBrowser.selectedBrowser;
+ selectedBrowser.addEventListener("load", function onLoad() {
+ selectedBrowser.removeEventListener("load", onLoad, true);
+ ok(true, "load listener called");
+ waitForFocus(onFocus, testWindow.content);
+ }, true);
+ testWindow.gBrowser.loadURI("data:text/html,<h1>A Page</h1>");
+ });
+}
+
+function onFocus() {
+ ok(!testWindow.gFindBarInitialized, "find bar is not initialized");
+ EventUtils.synthesizeKey("/", {}, testWindow);
+ ok(testWindow.gFindBarInitialized, "find bar is now initialized");
+ testWindow.close();
+ finish();
+}
diff --git a/browser/base/content/test/browser_unloaddialogs.js b/browser/base/content/test/browser_unloaddialogs.js
new file mode 100644
index 000000000..b8dca5447
--- /dev/null
+++ b/browser/base/content/test/browser_unloaddialogs.js
@@ -0,0 +1,134 @@
+function notify(event)
+{
+ if (event.target.location == "about:blank")
+ return;
+
+ var eventname = event.type;
+ if (eventname == "pagehide")
+ details.pagehides++;
+ else if (eventname == "beforeunload")
+ details.beforeunloads++;
+ else if (eventname == "unload")
+ details.unloads++;
+}
+
+var details;
+
+var gUseFrame = false;
+
+const windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
+
+const TEST_BASE_URL = "data:text/html,<script>" +
+ "function note(event) { try { alert(event.type); } catch(ex) { return; } throw 'alert appeared'; }" +
+ "</script>" +
+ "<body onpagehide='note(event)' onbeforeunload='alert(event.type);' onunload='note(event)'>";
+
+const TEST_URL = TEST_BASE_URL + "Test</body>";
+const TEST_FRAME_URL = TEST_BASE_URL + "Frames</body>";
+
+function test()
+{
+ waitForExplicitFinish();
+ windowMediator.addListener(promptListener);
+ runTest();
+}
+
+function runTest()
+{
+ details = {
+ testNumber : 0,
+ beforeunloads : 0,
+ pagehides : 0,
+ unloads : 0,
+ prompts : 0
+ };
+
+ var tab = gBrowser.addTab(TEST_URL);
+ gBrowser.selectedTab = tab;
+ tab.linkedBrowser.addEventListener("pageshow", shown, true);
+
+ tab.linkedBrowser.addEventListener("pagehide", notify, true);
+ tab.linkedBrowser.addEventListener("beforeunload", notify, true);
+ tab.linkedBrowser.addEventListener("unload", notify, true);
+}
+
+function shown(event)
+{
+ if (details.testNumber == 0) {
+ var browser;
+ var iframe;
+ if (gUseFrame) {
+ iframe = event.target.createElement("iframe");
+ iframe.src = TEST_FRAME_URL;
+ event.target.documentElement.appendChild(iframe);
+ browser = iframe.contentWindow;
+ }
+ else {
+ browser = gBrowser.selectedTab.linkedBrowser;
+ details.testNumber = 1; // Move onto to the next step immediately
+ }
+ }
+
+ if (details.testNumber == 1) {
+ // Test going to another page
+ executeSoon(function () {
+ const urlToLoad = "data:text/html,<body>Another Page</body>";
+ if (gUseFrame) {
+ event.target.location = urlToLoad;
+ }
+ else {
+ gBrowser.selectedBrowser.loadURI(urlToLoad);
+ }
+ });
+ }
+ else if (details.testNumber == 2) {
+ is(details.pagehides, 1, "pagehides after next page")
+ is(details.beforeunloads, 1, "beforeunloads after next page")
+ is(details.unloads, 1, "unloads after next page")
+ is(details.prompts, 1, "prompts after next page")
+
+ executeSoon(function () gUseFrame ? gBrowser.goBack() : event.target.defaultView.back());
+ }
+ else if (details.testNumber == 3) {
+ is(details.pagehides, 2, "pagehides after back")
+ is(details.beforeunloads, 2, "beforeunloads after back")
+ // No cache, so frame is unloaded
+ is(details.unloads, gUseFrame ? 2 : 1, "unloads after back")
+ is(details.prompts, 1, "prompts after back")
+
+ // Test closing the tab
+ gBrowser.selectedBrowser.removeEventListener("pageshow", shown, true);
+ gBrowser.removeTab(gBrowser.selectedTab);
+
+ // When the frame is present, there is are two beforeunload and prompts,
+ // one for the frame and the other for the parent.
+ is(details.pagehides, 3, "pagehides after close")
+ is(details.beforeunloads, gUseFrame ? 4 : 3, "beforeunloads after close")
+ is(details.unloads, gUseFrame ? 3 : 2, "unloads after close")
+ is(details.prompts, gUseFrame ? 3 : 2, "prompts after close")
+
+ // Now run the test again using a child frame.
+ if (gUseFrame) {
+ windowMediator.removeListener(promptListener);
+ finish();
+ }
+ else {
+ gUseFrame = true;
+ runTest();
+ }
+
+ return;
+ }
+
+ details.testNumber++;
+}
+
+var promptListener = {
+ onWindowTitleChange: function () {},
+ onOpenWindow: function (win) {
+ details.prompts++;
+ let domWin = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
+ executeSoon(function () { domWin.close() });
+ },
+ onCloseWindow: function () {},
+};
diff --git a/browser/base/content/test/browser_urlHighlight.js b/browser/base/content/test/browser_urlHighlight.js
new file mode 100644
index 000000000..3ab312738
--- /dev/null
+++ b/browser/base/content/test/browser_urlHighlight.js
@@ -0,0 +1,112 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function testVal(aExpected) {
+ gURLBar.value = aExpected.replace(/[<>]/g, "");
+
+ let selectionController = gURLBar.editor.selectionController;
+ let selection = selectionController.getSelection(selectionController.SELECTION_URLSECONDARY);
+ let value = gURLBar.editor.rootElement.textContent;
+ let result = "";
+ for (let i = 0; i < selection.rangeCount; i++) {
+ let range = selection.getRangeAt(i).toString();
+ let pos = value.indexOf(range);
+ result += value.substring(0, pos) + "<" + range + ">";
+ value = value.substring(pos + range.length);
+ }
+ result += value;
+ is(result, aExpected);
+}
+
+function test() {
+ const prefname = "browser.urlbar.formatting.enabled";
+
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(prefname);
+ URLBarSetURI();
+ });
+
+ Services.prefs.setBoolPref(prefname, true);
+
+ gURLBar.focus();
+
+ testVal("https://mozilla.org");
+
+ gBrowser.selectedBrowser.focus();
+
+ testVal("<https://>mozilla.org");
+ testVal("<https://>mözilla.org");
+ testVal("<https://>mozilla.imaginatory");
+
+ testVal("<https://www.>mozilla.org");
+ testVal("<https://sub.>mozilla.org");
+ testVal("<https://sub1.sub2.sub3.>mozilla.org");
+ testVal("<www.>mozilla.org");
+ testVal("<sub.>mozilla.org");
+ testVal("<sub1.sub2.sub3.>mozilla.org");
+
+ testVal("<http://ftp.>mozilla.org");
+ testVal("<ftp://ftp.>mozilla.org");
+
+ testVal("<https://sub.>mozilla.org");
+ testVal("<https://sub1.sub2.sub3.>mozilla.org");
+ testVal("<https://user:pass@sub1.sub2.sub3.>mozilla.org");
+ testVal("<https://user:pass@>mozilla.org");
+
+ testVal("<https://>mozilla.org</file.ext>");
+ testVal("<https://>mozilla.org</sub/file.ext>");
+ testVal("<https://>mozilla.org</sub/file.ext?foo>");
+ testVal("<https://>mozilla.org</sub/file.ext?foo&bar>");
+ testVal("<https://>mozilla.org</sub/file.ext?foo&bar#top>");
+ testVal("<https://>mozilla.org</sub/file.ext?foo&bar#top>");
+
+ testVal("<https://sub.>mozilla.org<:666/file.ext>");
+ testVal("<sub.>mozilla.org<:666/file.ext>");
+ testVal("localhost<:666/file.ext>");
+
+ let IPs = ["192.168.1.1",
+ "[::]",
+ "[::1]",
+ "[1::]",
+ "[::]",
+ "[::1]",
+ "[1::]",
+ "[1:2:3:4:5:6:7::]",
+ "[::1:2:3:4:5:6:7]",
+ "[1:2:a:B:c:D:e:F]",
+ "[1::8]",
+ "[1:2::8]",
+ "[fe80::222:19ff:fe11:8c76]",
+ "[0000:0123:4567:89AB:CDEF:abcd:ef00:0000]",
+ "[::192.168.1.1]",
+ "[1::0.0.0.0]",
+ "[1:2::255.255.255.255]",
+ "[1:2:3::255.255.255.255]",
+ "[1:2:3:4::255.255.255.255]",
+ "[1:2:3:4:5::255.255.255.255]",
+ "[1:2:3:4:5:6:255.255.255.255]"];
+ IPs.forEach(function (IP) {
+ testVal(IP);
+ testVal(IP + "</file.ext>");
+ testVal(IP + "<:666/file.ext>");
+ testVal("<https://>" + IP);
+ testVal("<https://>" + IP + "</file.ext>");
+ testVal("<https://user:pass@>" + IP + "<:666/file.ext>");
+ testVal("<http://user:pass@>" + IP + "<:666/file.ext>");
+ });
+
+ testVal("mailto:admin@mozilla.org");
+ testVal("gopher://mozilla.org/");
+ testVal("about:config");
+ testVal("jar:http://mozilla.org/example.jar!/");
+ testVal("view-source:http://mozilla.org/");
+ testVal("foo9://mozilla.org/");
+ testVal("foo+://mozilla.org/");
+ testVal("foo.://mozilla.org/");
+ testVal("foo-://mozilla.org/");
+
+ Services.prefs.setBoolPref(prefname, false);
+
+ testVal("https://mozilla.org");
+}
diff --git a/browser/base/content/test/browser_urlbarAutoFillTrimURLs.js b/browser/base/content/test/browser_urlbarAutoFillTrimURLs.js
new file mode 100644
index 000000000..3219898a9
--- /dev/null
+++ b/browser/base/content/test/browser_urlbarAutoFillTrimURLs.js
@@ -0,0 +1,85 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This test ensures that autoFilled values are not trimmed, unless the user
+// selects from the autocomplete popup.
+
+function test() {
+ waitForExplicitFinish();
+
+ const PREF_TRIMURL = "browser.urlbar.trimURLs";
+ const PREF_AUTOFILL = "browser.urlbar.autoFill";
+
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(PREF_TRIMURL);
+ Services.prefs.clearUserPref(PREF_AUTOFILL);
+ gURLBar.handleRevert();
+ });
+ Services.prefs.setBoolPref(PREF_TRIMURL, true);
+ Services.prefs.setBoolPref(PREF_AUTOFILL, true);
+
+ // Adding a tab would hit switch-to-tab, so it's safer to just add a visit.
+ let callback = {
+ handleError: function () {},
+ handleResult: function () {},
+ handleCompletion: continue_test
+ };
+ let history = Cc["@mozilla.org/browser/history;1"]
+ .getService(Ci.mozIAsyncHistory);
+ history.updatePlaces({ uri: NetUtil.newURI("http://www.autofilltrimurl.com/")
+ , visits: [ { transitionType: Ci.nsINavHistoryService.TRANSITION_TYPED
+ , visitDate: Date.now() * 1000
+ } ]
+ }, callback);
+}
+
+function continue_test() {
+ function test_autoFill(aTyped, aExpected, aCallback) {
+ gURLBar.inputField.value = aTyped.substr(0, aTyped.length - 1);
+ gURLBar.focus();
+ gURLBar.selectionStart = aTyped.length - 1;
+ gURLBar.selectionEnd = aTyped.length - 1;
+
+ EventUtils.synthesizeKey(aTyped.substr(-1), {});
+ is(gURLBar.value, aExpected, "trim was applied correctly");
+
+ aCallback();
+ }
+
+ test_autoFill("http://", "http://", function () {
+ test_autoFill("http://a", "http://autofilltrimurl.com/", function () {
+ test_autoFill("http://www.autofilltrimurl.com", "http://www.autofilltrimurl.com/", function () {
+ // Now ensure selecting from the popup correctly trims.
+ waitForSearchComplete(function () {
+ EventUtils.synthesizeKey("VK_DOWN", {});
+ is(gURLBar.value, "www.autofilltrimurl.com", "trim was applied correctly");
+ gURLBar.closePopup();
+ waitForClearHistory(finish);
+ });
+ });
+ });
+ });
+}
+
+function waitForClearHistory(aCallback) {
+ Services.obs.addObserver(function observeCH(aSubject, aTopic, aData) {
+ Services.obs.removeObserver(observeCH, PlacesUtils.TOPIC_EXPIRATION_FINISHED);
+ aCallback();
+ }, PlacesUtils.TOPIC_EXPIRATION_FINISHED, false);
+ PlacesUtils.bhistory.removeAllPages();
+}
+
+function waitForSearchComplete(aCallback) {
+ info("Waiting for onSearchComplete");
+ let onSearchComplete = gURLBar.onSearchComplete;
+ registerCleanupFunction(function () {
+ gURLBar.onSearchComplete = onSearchComplete;
+ });
+ gURLBar.onSearchComplete = function () {
+ ok(gURLBar.popupOpen, "The autocomplete popup is correctly open");
+ is(gURLBar.controller.matchCount, 1, "Found the expected number of matches")
+ onSearchComplete.apply(gURLBar);
+ aCallback();
+ }
+}
diff --git a/browser/base/content/test/browser_urlbarCopying.js b/browser/base/content/test/browser_urlbarCopying.js
new file mode 100644
index 000000000..7f0a511f8
--- /dev/null
+++ b/browser/base/content/test/browser_urlbarCopying.js
@@ -0,0 +1,204 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const trimPref = "browser.urlbar.trimURLs";
+const phishyUserPassPref = "network.http.phishy-userpass-length";
+
+function test() {
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+
+ registerCleanupFunction(function () {
+ gBrowser.removeTab(tab);
+ Services.prefs.clearUserPref(trimPref);
+ Services.prefs.clearUserPref(phishyUserPassPref);
+ URLBarSetURI();
+ });
+
+ Services.prefs.setBoolPref(trimPref, true);
+ Services.prefs.setIntPref(phishyUserPassPref, 32); // avoid prompting about phishing
+
+ waitForExplicitFinish();
+
+ nextTest();
+}
+
+var tests = [
+ // pageproxystate="invalid"
+ {
+ setURL: "http://example.com/",
+ expectedURL: "example.com",
+ copyExpected: "example.com"
+ },
+ {
+ copyVal: "<e>xample.com",
+ copyExpected: "e"
+ },
+
+ // pageproxystate="valid" from this point on (due to the load)
+ {
+ loadURL: "http://example.com/",
+ expectedURL: "example.com",
+ copyExpected: "http://example.com/"
+ },
+ {
+ copyVal: "<example.co>m",
+ copyExpected: "example.co"
+ },
+ {
+ copyVal: "e<x>ample.com",
+ copyExpected: "x"
+ },
+ {
+ copyVal: "<e>xample.com",
+ copyExpected: "e"
+ },
+
+ {
+ loadURL: "http://example.com/foo",
+ expectedURL: "example.com/foo",
+ copyExpected: "http://example.com/foo"
+ },
+ {
+ copyVal: "<example.com>/foo",
+ copyExpected: "http://example.com"
+ },
+ {
+ copyVal: "<example>.com/foo",
+ copyExpected: "example"
+ },
+
+ // Test that userPass is stripped out
+ {
+ loadURL: "http://user:pass@mochi.test:8888/browser/browser/base/content/test/authenticate.sjs?user=user&pass=pass",
+ expectedURL: "mochi.test:8888/browser/browser/base/content/test/authenticate.sjs?user=user&pass=pass",
+ copyExpected: "http://mochi.test:8888/browser/browser/base/content/test/authenticate.sjs?user=user&pass=pass"
+ },
+
+ // Test escaping
+ {
+ loadURL: "http://example.com/()%C3%A9",
+ expectedURL: "example.com/()\xe9",
+ copyExpected: "http://example.com/%28%29%C3%A9"
+ },
+ {
+ copyVal: "<example.com/(>)\xe9",
+ copyExpected: "http://example.com/("
+ },
+ {
+ copyVal: "e<xample.com/(>)\xe9",
+ copyExpected: "xample.com/("
+ },
+
+ {
+ loadURL: "http://example.com/%C3%A9%C3%A9",
+ expectedURL: "example.com/\xe9\xe9",
+ copyExpected: "http://example.com/%C3%A9%C3%A9"
+ },
+ {
+ copyVal: "e<xample.com/\xe9>\xe9",
+ copyExpected: "xample.com/\xe9"
+ },
+ {
+ copyVal: "<example.com/\xe9>\xe9",
+ copyExpected: "http://example.com/\xe9"
+ },
+
+ {
+ loadURL: "http://example.com/?%C3%B7%C3%B7",
+ expectedURL: "example.com/?\xf7\xf7",
+ copyExpected: "http://example.com/?%C3%B7%C3%B7"
+ },
+ {
+ copyVal: "e<xample.com/?\xf7>\xf7",
+ copyExpected: "xample.com/?\xf7"
+ },
+ {
+ copyVal: "<example.com/?\xf7>\xf7",
+ copyExpected: "http://example.com/?\xf7"
+ },
+
+ // data: and javsacript: URIs shouldn't be encoded
+ {
+ loadURL: "javascript:('%C3%A9')",
+ expectedURL: "javascript:('\xe9')",
+ copyExpected: "javascript:('\xe9')"
+ },
+ {
+ copyVal: "<javascript:(>'\xe9')",
+ copyExpected: "javascript:("
+ },
+
+ {
+ loadURL: "data:text/html,(%C3%A9)",
+ expectedURL: "data:text/html,(\xe9)",
+ copyExpected: "data:text/html,(\xe9)"
+ },
+ {
+ copyVal: "<data:text/html,(>\xe9)",
+ copyExpected: "data:text/html,("
+ },
+ {
+ copyVal: "data:<text/html,(\xe9>)",
+ copyExpected: "text/html,(\xe9"
+ }
+];
+
+function nextTest() {
+ let test = tests.shift();
+ if (tests.length == 0)
+ runTest(test, finish);
+ else
+ runTest(test, nextTest);
+}
+
+function runTest(test, cb) {
+ function doCheck() {
+ if (test.setURL || test.loadURL) {
+ gURLBar.valueIsTyped = !!test.setURL;
+ is(gURLBar.value, test.expectedURL, "url bar value set");
+ }
+
+ testCopy(test.copyVal, test.copyExpected, cb);
+ }
+
+ if (test.loadURL) {
+ loadURL(test.loadURL, doCheck);
+ } else {
+ if (test.setURL)
+ gURLBar.value = test.setURL;
+ doCheck();
+ }
+}
+
+function testCopy(copyVal, targetValue, cb) {
+ info("Expecting copy of: " + targetValue);
+ waitForClipboard(targetValue, function () {
+ gURLBar.focus();
+ if (copyVal) {
+ let startBracket = copyVal.indexOf("<");
+ let endBracket = copyVal.indexOf(">");
+ if (startBracket == -1 || endBracket == -1 ||
+ startBracket > endBracket ||
+ copyVal.replace("<", "").replace(">", "") != gURLBar.value) {
+ ok(false, "invalid copyVal: " + copyVal);
+ }
+ gURLBar.selectionStart = startBracket;
+ gURLBar.selectionEnd = endBracket - 1;
+ } else {
+ gURLBar.select();
+ }
+
+ goDoCommand("cmd_copy");
+ }, cb, cb);
+}
+
+function loadURL(aURL, aCB) {
+ gBrowser.selectedBrowser.addEventListener("load", function () {
+ gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+ is(gBrowser.currentURI.spec, aURL, "loaded expected URL");
+ aCB();
+ }, true);
+
+ gBrowser.loadURI(aURL);
+}
diff --git a/browser/base/content/test/browser_urlbarEnter.js b/browser/base/content/test/browser_urlbarEnter.js
new file mode 100644
index 000000000..defea1396
--- /dev/null
+++ b/browser/base/content/test/browser_urlbarEnter.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_VALUE = "example.com/\xF7?\xF7";
+const START_VALUE = "example.com/%C3%B7?%C3%B7";
+
+function test() {
+ waitForExplicitFinish();
+ runNextTest();
+}
+
+function locationBarEnter(aEvent, aClosure) {
+ executeSoon(function() {
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_RETURN", aEvent);
+ addPageShowListener(aClosure);
+ });
+}
+
+function runNextTest() {
+ let test = gTests.shift();
+ if (!test) {
+ finish();
+ return;
+ }
+
+ info("Running test: " + test.desc);
+ let tab = gBrowser.selectedTab = gBrowser.addTab(START_VALUE);
+ addPageShowListener(function() {
+ locationBarEnter(test.event, function() {
+ test.check(tab);
+
+ // Clean up
+ while (gBrowser.tabs.length > 1)
+ gBrowser.removeTab(gBrowser.selectedTab)
+ runNextTest();
+ });
+ });
+}
+
+let gTests = [
+ { desc: "Simple return keypress",
+ event: {},
+ check: checkCurrent
+ },
+
+ { desc: "Alt+Return keypress",
+ event: { altKey: true },
+ check: checkNewTab,
+ },
+]
+
+function checkCurrent(aTab) {
+ is(gURLBar.value, TEST_VALUE, "Urlbar should preserve the value on return keypress");
+ is(gBrowser.selectedTab, aTab, "New URL was loaded in the current tab");
+}
+
+function checkNewTab(aTab) {
+ is(gURLBar.value, TEST_VALUE, "Urlbar should preserve the value on return keypress");
+ isnot(gBrowser.selectedTab, aTab, "New URL was loaded in a new tab");
+}
+
+function addPageShowListener(aFunc) {
+ gBrowser.selectedBrowser.addEventListener("pageshow", function loadListener() {
+ gBrowser.selectedBrowser.removeEventListener("pageshow", loadListener, false);
+ aFunc();
+ });
+}
+
diff --git a/browser/base/content/test/browser_urlbarRevert.js b/browser/base/content/test/browser_urlbarRevert.js
new file mode 100644
index 000000000..2bd596efc
--- /dev/null
+++ b/browser/base/content/test/browser_urlbarRevert.js
@@ -0,0 +1,29 @@
+function test() {
+ waitForExplicitFinish();
+
+ let tab = gBrowser.addTab("http://example.com");
+ gBrowser.selectedTab = tab;
+
+ onLoad(function () {
+ let originalValue = gURLBar.value;
+
+ gBrowser.userTypedValue = "foobar";
+ gBrowser.selectedTab = gBrowser.tabs[0];
+ gBrowser.selectedTab = tab;
+ is(gURLBar.value, "foobar", "location bar displays typed value");
+
+ gURLBar.focus();
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ is(gURLBar.value, originalValue, "ESC reverted the location bar value");
+
+ gBrowser.removeTab(tab);
+ finish();
+ });
+}
+
+function onLoad(callback) {
+ gBrowser.selectedBrowser.addEventListener("pageshow", function loadListener() {
+ gBrowser.selectedBrowser.removeEventListener("pageshow", loadListener, false);
+ executeSoon(callback);
+ });
+}
diff --git a/browser/base/content/test/browser_urlbarStop.js b/browser/base/content/test/browser_urlbarStop.js
new file mode 100644
index 000000000..2d9a87a71
--- /dev/null
+++ b/browser/base/content/test/browser_urlbarStop.js
@@ -0,0 +1,40 @@
+const goodURL = "http://mochi.test:8888/";
+const badURL = "http://mochi.test:8888/whatever.html";
+
+function test() {
+ waitForExplicitFinish();
+
+ gBrowser.selectedTab = gBrowser.addTab(goodURL);
+ gBrowser.selectedBrowser.addEventListener("load", onload, true);
+}
+
+function onload() {
+ gBrowser.selectedBrowser.removeEventListener("load", onload, true);
+
+ is(gURLBar.value, gURLBar.trimValue(goodURL), "location bar reflects loaded page");
+
+ typeAndSubmit(badURL);
+ is(gURLBar.value, gURLBar.trimValue(badURL), "location bar reflects loading page");
+
+ gBrowser.contentWindow.stop();
+ is(gURLBar.value, gURLBar.trimValue(goodURL), "location bar reflects loaded page after stop()");
+ gBrowser.removeCurrentTab();
+
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ is(gURLBar.value, "", "location bar is empty");
+
+ typeAndSubmit(badURL);
+ is(gURLBar.value, gURLBar.trimValue(badURL), "location bar reflects loading page");
+
+ gBrowser.contentWindow.stop();
+ is(gURLBar.value, gURLBar.trimValue(badURL), "location bar reflects stopped page in an empty tab");
+ gBrowser.removeCurrentTab();
+
+ finish();
+}
+
+function typeAndSubmit(value) {
+ gBrowser.userTypedValue = value;
+ URLBarSetURI();
+ gURLBar.handleCommand();
+}
diff --git a/browser/base/content/test/browser_urlbarTrimURLs.js b/browser/base/content/test/browser_urlbarTrimURLs.js
new file mode 100644
index 000000000..095ccc4a6
--- /dev/null
+++ b/browser/base/content/test/browser_urlbarTrimURLs.js
@@ -0,0 +1,89 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function testVal(originalValue, targetValue) {
+ gURLBar.value = originalValue;
+ gURLBar.valueIsTyped = false;
+ is(gURLBar.value, targetValue || originalValue, "url bar value set");
+}
+
+function test() {
+ const prefname = "browser.urlbar.trimURLs";
+
+ gBrowser.selectedTab = gBrowser.addTab();
+
+ registerCleanupFunction(function () {
+ gBrowser.removeCurrentTab();
+ Services.prefs.clearUserPref(prefname);
+ URLBarSetURI();
+ });
+
+ Services.prefs.setBoolPref(prefname, true);
+
+ testVal("http://mozilla.org/", "mozilla.org");
+ testVal("https://mozilla.org/", "https://mozilla.org");
+ testVal("http://mözilla.org/", "mözilla.org");
+ testVal("http://mozilla.imaginatory/", "mozilla.imaginatory");
+ testVal("http://www.mozilla.org/", "www.mozilla.org");
+ testVal("http://sub.mozilla.org/", "sub.mozilla.org");
+ testVal("http://sub1.sub2.sub3.mozilla.org/", "sub1.sub2.sub3.mozilla.org");
+ testVal("http://mozilla.org/file.ext", "mozilla.org/file.ext");
+ testVal("http://mozilla.org/sub/", "mozilla.org/sub/");
+
+ testVal("http://ftp.mozilla.org/", "http://ftp.mozilla.org");
+ testVal("http://ftp1.mozilla.org/", "http://ftp1.mozilla.org");
+ testVal("http://ftp42.mozilla.org/", "http://ftp42.mozilla.org");
+ testVal("http://ftpx.mozilla.org/", "ftpx.mozilla.org");
+ testVal("ftp://ftp.mozilla.org/", "ftp://ftp.mozilla.org");
+ testVal("ftp://ftp1.mozilla.org/", "ftp://ftp1.mozilla.org");
+ testVal("ftp://ftp42.mozilla.org/", "ftp://ftp42.mozilla.org");
+ testVal("ftp://ftpx.mozilla.org/", "ftp://ftpx.mozilla.org");
+
+ testVal("https://user:pass@mozilla.org/", "https://user:pass@mozilla.org");
+ testVal("http://user:pass@mozilla.org/", "http://user:pass@mozilla.org");
+ testVal("http://sub.mozilla.org:666/", "sub.mozilla.org:666");
+
+ testVal("https://[fe80::222:19ff:fe11:8c76]/file.ext");
+ testVal("http://[fe80::222:19ff:fe11:8c76]/", "[fe80::222:19ff:fe11:8c76]");
+ testVal("https://user:pass@[fe80::222:19ff:fe11:8c76]:666/file.ext");
+ testVal("http://user:pass@[fe80::222:19ff:fe11:8c76]:666/file.ext");
+
+ testVal("mailto:admin@mozilla.org");
+ testVal("gopher://mozilla.org/");
+ testVal("about:config");
+ testVal("jar:http://mozilla.org/example.jar!/");
+ testVal("view-source:http://mozilla.org/");
+
+ Services.prefs.setBoolPref(prefname, false);
+
+ testVal("http://mozilla.org/");
+
+ Services.prefs.setBoolPref(prefname, true);
+
+ waitForExplicitFinish();
+
+ gBrowser.selectedBrowser.addEventListener("load", function () {
+ gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+
+ is(gBrowser.currentURI.spec, "http://example.com/", "expected page should have loaded");
+
+ testCopy("example.com", "http://example.com/", function () {
+ SetPageProxyState("invalid");
+ gURLBar.valueIsTyped = true;
+ testCopy("example.com", "example.com", finish);
+ });
+ }, true);
+
+ gBrowser.loadURI("http://example.com/");
+}
+
+function testCopy(originalValue, targetValue, cb) {
+ waitForClipboard(targetValue, function () {
+ is(gURLBar.value, originalValue, "url bar copy value set");
+
+ gURLBar.focus();
+ gURLBar.select();
+ goDoCommand("cmd_copy");
+ }, cb, cb);
+}
diff --git a/browser/base/content/test/browser_urlbar_search_healthreport.js b/browser/base/content/test/browser_urlbar_search_healthreport.js
new file mode 100644
index 000000000..4315a9864
--- /dev/null
+++ b/browser/base/content/test/browser_urlbar_search_healthreport.js
@@ -0,0 +1,65 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function test() {
+ waitForExplicitFinish();
+ try {
+ let cm = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
+ cm.getCategoryEntry("healthreport-js-provider-default", "SearchesProvider");
+ } catch (ex) {
+ // Health Report disabled, or no SearchesProvider.
+ ok(true, "Firefox Health Report is not enabled.");
+ finish();
+ return;
+ }
+
+ let reporter = Cc["@mozilla.org/datareporting/service;1"]
+ .getService()
+ .wrappedJSObject
+ .healthReporter;
+ ok(reporter, "Health Reporter available.");
+ reporter.onInit().then(function onInit() {
+ let provider = reporter.getProvider("org.mozilla.searches");
+ ok(provider, "Searches provider is available.");
+ let m = provider.getMeasurement("counts", 2);
+
+ m.getValues().then(function onData(data) {
+ let now = new Date();
+ let oldCount = 0;
+
+ // This will to be need changed if default search engine is not Google.
+ let field = "google.urlbar";
+
+ if (data.days.hasDay(now)) {
+ let day = data.days.getDay(now);
+ if (day.has(field)) {
+ oldCount = day.get(field);
+ }
+ }
+
+ let tab = gBrowser.addTab();
+ gBrowser.selectedTab = tab;
+
+ gURLBar.value = "firefox health report";
+ gURLBar.handleCommand();
+
+ executeSoon(() => executeSoon(() => {
+ gBrowser.removeTab(tab);
+
+ m.getValues().then(function onData(data) {
+ ok(data.days.hasDay(now), "FHR has data for today.");
+ let day = data.days.getDay(now);
+ ok(day.has(field), "FHR has url bar count for today.");
+
+ let newCount = day.get(field);
+
+ is(newCount, oldCount + 1, "Exactly one search has been recorded.");
+ finish();
+ });
+ }));
+ });
+ });
+}
+
diff --git a/browser/base/content/test/browser_utilityOverlay.js b/browser/base/content/test/browser_utilityOverlay.js
new file mode 100644
index 000000000..a3d909eb7
--- /dev/null
+++ b/browser/base/content/test/browser_utilityOverlay.js
@@ -0,0 +1,62 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const gTests = [
+ test_getTopWin,
+ test_getBoolPref,
+ test_openNewTabWith,
+ test_openUILink
+];
+
+function test () {
+ waitForExplicitFinish();
+ executeSoon(runNextTest);
+}
+
+function runNextTest() {
+ if (gTests.length) {
+ let testFun = gTests.shift();
+ info("Running " + testFun.name);
+ testFun()
+ }
+ else {
+ finish();
+ }
+}
+
+function test_getTopWin() {
+ is(getTopWin(), window, "got top window");
+ runNextTest();
+}
+
+
+function test_getBoolPref() {
+ is(getBoolPref("browser.search.openintab", false), false, "getBoolPref");
+ is(getBoolPref("this.pref.doesnt.exist", true), true, "getBoolPref fallback");
+ is(getBoolPref("this.pref.doesnt.exist", false), false, "getBoolPref fallback #2");
+ runNextTest();
+}
+
+function test_openNewTabWith() {
+ openNewTabWith("http://example.com/");
+ let tab = gBrowser.selectedTab = gBrowser.tabs[1];
+ tab.linkedBrowser.addEventListener("load", function onLoad(event) {
+ tab.linkedBrowser.removeEventListener("load", onLoad, true);
+ is(tab.linkedBrowser.currentURI.spec, "http://example.com/", "example.com loaded");
+ gBrowser.removeCurrentTab();
+ runNextTest();
+ }, true);
+}
+
+function test_openUILink() {
+ let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ tab.linkedBrowser.addEventListener("load", function onLoad(event) {
+ tab.linkedBrowser.removeEventListener("load", onLoad, true);
+ is(tab.linkedBrowser.currentURI.spec, "http://example.org/", "example.org loaded");
+ gBrowser.removeCurrentTab();
+ runNextTest();
+ }, true);
+
+ openUILink("http://example.org/"); // defaults to "current"
+}
diff --git a/browser/base/content/test/browser_visibleFindSelection.js b/browser/base/content/test/browser_visibleFindSelection.js
new file mode 100644
index 000000000..b46104ad4
--- /dev/null
+++ b/browser/base/content/test/browser_visibleFindSelection.js
@@ -0,0 +1,39 @@
+
+function test() {
+ waitForExplicitFinish();
+
+ let tab = gBrowser.addTab();
+ gBrowser.selectedTab = tab;
+ tab.linkedBrowser.addEventListener("load", function(aEvent) {
+ tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+ ok(true, "Load listener called");
+ waitForFocus(onFocus, content);
+ }, true);
+
+ content.location = "data:text/html,<div style='position: absolute; left: 2200px; background: green; width: 200px; height: 200px;'>div</div><div style='position: absolute; left: 0px; background: red; width: 200px; height: 200px;'><span id='s'>div</span></div>";
+}
+
+function onFocus() {
+ EventUtils.synthesizeKey("f", { accelKey: true });
+ ok(gFindBarInitialized, "find bar is now initialized");
+
+ EventUtils.synthesizeKey("d", {});
+ EventUtils.synthesizeKey("i", {});
+ EventUtils.synthesizeKey("v", {});
+ // finds the div in the green box
+
+ EventUtils.synthesizeKey("g", { accelKey: true });
+ // finds the div in the red box
+
+ var rect = content.document.getElementById("s").getBoundingClientRect();
+ ok(rect.left >= 0, "scroll should include find result");
+
+ // clear the find bar
+ EventUtils.synthesizeKey("a", { accelKey: true });
+ EventUtils.synthesizeKey("VK_DELETE", { });
+
+ gFindBar.close();
+ gBrowser.removeCurrentTab();
+ finish();
+}
diff --git a/browser/base/content/test/browser_visibleTabs.js b/browser/base/content/test/browser_visibleTabs.js
new file mode 100644
index 000000000..d02bb64cb
--- /dev/null
+++ b/browser/base/content/test/browser_visibleTabs.js
@@ -0,0 +1,93 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ // There should be one tab when we start the test
+ let [origTab] = gBrowser.visibleTabs;
+
+ // Add a tab that will get pinned
+ let pinned = gBrowser.addTab();
+ gBrowser.pinTab(pinned);
+
+ let testTab = gBrowser.addTab();
+
+ let visible = gBrowser.visibleTabs;
+ is(visible.length, 3, "3 tabs should be open");
+ is(visible[0], pinned, "the pinned tab is first");
+ is(visible[1], origTab, "original tab is next");
+ is(visible[2], testTab, "last created tab is last");
+
+ // Only show the test tab (but also get pinned and selected)
+ is(gBrowser.selectedTab, origTab, "sanity check that we're on the original tab");
+ gBrowser.showOnlyTheseTabs([testTab]);
+ is(gBrowser.visibleTabs.length, 3, "all 3 tabs are still visible");
+
+ // Select the test tab and only show that (and pinned)
+ gBrowser.selectedTab = testTab;
+ gBrowser.showOnlyTheseTabs([testTab]);
+
+ visible = gBrowser.visibleTabs;
+ is(visible.length, 2, "2 tabs should be visible including the pinned");
+ is(visible[0], pinned, "first is pinned");
+ is(visible[1], testTab, "next is the test tab");
+ is(gBrowser.tabs.length, 3, "3 tabs should still be open");
+
+ gBrowser.selectTabAtIndex(0);
+ is(gBrowser.selectedTab, pinned, "first tab is pinned");
+ gBrowser.selectTabAtIndex(1);
+ is(gBrowser.selectedTab, testTab, "second tab is the test tab");
+ gBrowser.selectTabAtIndex(2);
+ is(gBrowser.selectedTab, testTab, "no third tab, so no change");
+ gBrowser.selectTabAtIndex(0);
+ is(gBrowser.selectedTab, pinned, "switch back to the pinned");
+ gBrowser.selectTabAtIndex(2);
+ is(gBrowser.selectedTab, pinned, "no third tab, so no change");
+ gBrowser.selectTabAtIndex(-1);
+ is(gBrowser.selectedTab, testTab, "last tab is the test tab");
+
+ gBrowser.tabContainer.advanceSelectedTab(1, true);
+ is(gBrowser.selectedTab, pinned, "wrapped around the end to pinned");
+ gBrowser.tabContainer.advanceSelectedTab(1, true);
+ is(gBrowser.selectedTab, testTab, "next to test tab");
+ gBrowser.tabContainer.advanceSelectedTab(1, true);
+ is(gBrowser.selectedTab, pinned, "next to pinned again");
+
+ gBrowser.tabContainer.advanceSelectedTab(-1, true);
+ is(gBrowser.selectedTab, testTab, "going backwards to last tab");
+ gBrowser.tabContainer.advanceSelectedTab(-1, true);
+ is(gBrowser.selectedTab, pinned, "next to pinned");
+ gBrowser.tabContainer.advanceSelectedTab(-1, true);
+ is(gBrowser.selectedTab, testTab, "next to test tab again");
+
+ // Try showing all tabs
+ gBrowser.showOnlyTheseTabs(Array.slice(gBrowser.tabs));
+ is(gBrowser.visibleTabs.length, 3, "all 3 tabs are visible again");
+
+ // Select the pinned tab and show the testTab to make sure selection updates
+ gBrowser.selectedTab = pinned;
+ gBrowser.showOnlyTheseTabs([testTab]);
+ is(gBrowser.tabs[1], origTab, "make sure origTab is in the middle");
+ is(origTab.hidden, true, "make sure it's hidden");
+ gBrowser.removeTab(pinned);
+ is(gBrowser.selectedTab, testTab, "making sure origTab was skipped");
+ is(gBrowser.visibleTabs.length, 1, "only testTab is there");
+
+ // Only show one of the non-pinned tabs (but testTab is selected)
+ gBrowser.showOnlyTheseTabs([origTab]);
+ is(gBrowser.visibleTabs.length, 2, "got 2 tabs");
+
+ // Now really only show one of the tabs
+ gBrowser.showOnlyTheseTabs([testTab]);
+ visible = gBrowser.visibleTabs;
+ is(visible.length, 1, "only the original tab is visible");
+ is(visible[0], testTab, "it's the original tab");
+ is(gBrowser.tabs.length, 2, "still have 2 open tabs");
+
+ // Close the last visible tab and make sure we still get a visible tab
+ gBrowser.removeTab(testTab);
+ is(gBrowser.visibleTabs.length, 1, "only orig is left and visible");
+ is(gBrowser.tabs.length, 1, "sanity check that it matches");
+ is(gBrowser.selectedTab, origTab, "got the orig tab");
+ is(origTab.hidden, false, "and it's not hidden -- visible!");
+}
diff --git a/browser/base/content/test/browser_visibleTabs_bookmarkAllPages.js b/browser/base/content/test/browser_visibleTabs_bookmarkAllPages.js
new file mode 100644
index 000000000..0d9a12067
--- /dev/null
+++ b/browser/base/content/test/browser_visibleTabs_bookmarkAllPages.js
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ let tabOne = gBrowser.addTab("about:blank");
+ let tabTwo = gBrowser.addTab("http://mochi.test:8888/");
+ gBrowser.selectedTab = tabTwo;
+
+ let browser = gBrowser.getBrowserForTab(tabTwo);
+ let onLoad = function() {
+ browser.removeEventListener("load", onLoad, true);
+
+ gBrowser.showOnlyTheseTabs([tabTwo]);
+
+ is(gBrowser.visibleTabs.length, 1, "Only one tab is visible");
+
+ let uris = PlacesCommandHook.uniqueCurrentPages;
+ is(uris.length, 1, "Only one uri is returned");
+
+ is(uris[0].spec, tabTwo.linkedBrowser.currentURI.spec, "It's the correct URI");
+
+ gBrowser.removeTab(tabOne);
+ gBrowser.removeTab(tabTwo);
+ Array.forEach(gBrowser.tabs, function(tab) {
+ gBrowser.showTab(tab);
+ });
+
+ finish();
+ }
+ browser.addEventListener("load", onLoad, true);
+}
diff --git a/browser/base/content/test/browser_visibleTabs_bookmarkAllTabs.js b/browser/base/content/test/browser_visibleTabs_bookmarkAllTabs.js
new file mode 100644
index 000000000..09d790b94
--- /dev/null
+++ b/browser/base/content/test/browser_visibleTabs_bookmarkAllTabs.js
@@ -0,0 +1,66 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ // There should be one tab when we start the test
+ let [origTab] = gBrowser.visibleTabs;
+ is(gBrowser.visibleTabs.length, 1, "1 tab should be open");
+ is(Disabled(), true, "Bookmark All Tabs should be disabled");
+
+ // Add a tab
+ let testTab1 = gBrowser.addTab();
+ is(gBrowser.visibleTabs.length, 2, "2 tabs should be open");
+ is(Disabled(), true, "Bookmark All Tabs should be disabled since there are two tabs with the same address");
+
+ let testTab2 = gBrowser.addTab("about:mozilla");
+ is(gBrowser.visibleTabs.length, 3, "3 tabs should be open");
+ // Wait for tab load, the code checks for currentURI.
+ testTab2.linkedBrowser.addEventListener("load", function () {
+ testTab2.linkedBrowser.removeEventListener("load", arguments.callee, true);
+ is(Disabled(), false, "Bookmark All Tabs should be enabled since there are two tabs with different addresses");
+
+ // Hide the original tab
+ gBrowser.selectedTab = testTab2;
+ gBrowser.showOnlyTheseTabs([testTab2]);
+ is(gBrowser.visibleTabs.length, 1, "1 tab should be visible");
+ is(Disabled(), true, "Bookmark All Tabs should be disabled as there is only one visible tab");
+
+ // Add a tab that will get pinned
+ let pinned = gBrowser.addTab();
+ is(gBrowser.visibleTabs.length, 2, "2 tabs should be visible now");
+ is(Disabled(), false, "Bookmark All Tabs should be available as there are two visible tabs");
+ gBrowser.pinTab(pinned);
+ is(Hidden(), false, "Bookmark All Tabs should be visible on a normal tab");
+ is(Disabled(), true, "Bookmark All Tabs should not be available since one tab is pinned");
+ gBrowser.selectedTab = pinned;
+ is(Hidden(), true, "Bookmark All Tabs should be hidden on a pinned tab");
+
+ // Show all tabs
+ let allTabs = [tab for each (tab in gBrowser.tabs)];
+ gBrowser.showOnlyTheseTabs(allTabs);
+
+ // reset the environment
+ gBrowser.removeTab(testTab2);
+ gBrowser.removeTab(testTab1);
+ gBrowser.removeTab(pinned);
+ is(gBrowser.visibleTabs.length, 1, "only orig is left and visible");
+ is(gBrowser.tabs.length, 1, "sanity check that it matches");
+ is(Disabled(), true, "Bookmark All Tabs should be hidden");
+ is(gBrowser.selectedTab, origTab, "got the orig tab");
+ is(origTab.hidden, false, "and it's not hidden -- visible!");
+ finish();
+ }, true);
+}
+
+function Disabled() {
+ updateTabContextMenu();
+ return document.getElementById("Browser:BookmarkAllTabs").getAttribute("disabled") == "true";
+}
+
+function Hidden() {
+ updateTabContextMenu();
+ return document.getElementById("context_bookmarkAllTabs").hidden;
+}
diff --git a/browser/base/content/test/browser_visibleTabs_contextMenu.js b/browser/base/content/test/browser_visibleTabs_contextMenu.js
new file mode 100644
index 000000000..0539c8106
--- /dev/null
+++ b/browser/base/content/test/browser_visibleTabs_contextMenu.js
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ // There should be one tab when we start the test
+ let [origTab] = gBrowser.visibleTabs;
+ is(gBrowser.visibleTabs.length, 1, "there is one visible tab");
+ let testTab = gBrowser.addTab();
+ is(gBrowser.visibleTabs.length, 2, "there are now two visible tabs");
+
+ // Check the context menu with two tabs
+ updateTabContextMenu(origTab);
+ is(document.getElementById("context_closeTab").disabled, false, "Close Tab is enabled");
+ is(document.getElementById("context_reloadAllTabs").disabled, false, "Reload All Tabs is enabled");
+
+ // Hide the original tab.
+ gBrowser.selectedTab = testTab;
+ gBrowser.showOnlyTheseTabs([testTab]);
+ is(gBrowser.visibleTabs.length, 1, "now there is only one visible tab");
+
+ // Check the context menu with one tab.
+ updateTabContextMenu(testTab);
+ is(document.getElementById("context_closeTab").disabled, false, "Close Tab is enabled when more than one tab exists");
+ is(document.getElementById("context_reloadAllTabs").disabled, true, "Reload All Tabs is disabled");
+
+ // Add a tab that will get pinned
+ // So now there's one pinned tab, one visible unpinned tab, and one hidden tab
+ let pinned = gBrowser.addTab();
+ gBrowser.pinTab(pinned);
+ is(gBrowser.visibleTabs.length, 2, "now there are two visible tabs");
+
+ // Check the context menu on the unpinned visible tab
+ updateTabContextMenu(testTab);
+ is(document.getElementById("context_closeOtherTabs").disabled, true, "Close Other Tabs is disabled");
+ is(document.getElementById("context_closeTabsToTheEnd").disabled, true, "Close Tabs To The End is disabled");
+
+ // Show all tabs
+ let allTabs = [tab for each (tab in gBrowser.tabs)];
+ gBrowser.showOnlyTheseTabs(allTabs);
+
+ // Check the context menu now
+ updateTabContextMenu(testTab);
+ is(document.getElementById("context_closeOtherTabs").disabled, false, "Close Other Tabs is enabled");
+ is(document.getElementById("context_closeTabsToTheEnd").disabled, true, "Close Tabs To The End is disabled");
+
+ // Check the context menu of the original tab
+ // Close Tabs To The End should now be enabled
+ updateTabContextMenu(origTab);
+ is(document.getElementById("context_closeTabsToTheEnd").disabled, false, "Close Tabs To The End is enabled");
+
+ gBrowser.removeTab(testTab);
+ gBrowser.removeTab(pinned);
+}
diff --git a/browser/base/content/test/browser_visibleTabs_tabPreview.js b/browser/base/content/test/browser_visibleTabs_tabPreview.js
new file mode 100644
index 000000000..9491690cb
--- /dev/null
+++ b/browser/base/content/test/browser_visibleTabs_tabPreview.js
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ gPrefService.setBoolPref("browser.ctrlTab.previews", true);
+
+ let [origTab] = gBrowser.visibleTabs;
+ let tabOne = gBrowser.addTab();
+ let tabTwo = gBrowser.addTab();
+
+ // test the ctrlTab.tabList
+ pressCtrlTab();
+ ok(ctrlTab.tabList.length, 3, "Show 3 tabs in tab preview");
+ releaseCtrl();
+
+ gBrowser.showOnlyTheseTabs([origTab]);
+ pressCtrlTab();
+ ok(ctrlTab.tabList.length, 1, "Show 1 tab in tab preview");
+ ok(!ctrlTab.isOpen, "With 1 tab open, Ctrl+Tab doesn't open the preview panel");
+
+ gBrowser.showOnlyTheseTabs([origTab, tabOne, tabTwo]);
+ pressCtrlTab();
+ ok(ctrlTab.isOpen, "With 3 tabs open, Ctrl+Tab does open the preview panel");
+ releaseCtrl();
+
+ // cleanup
+ gBrowser.removeTab(tabOne);
+ gBrowser.removeTab(tabTwo);
+
+ if (gPrefService.prefHasUserValue("browser.ctrlTab.previews"))
+ gPrefService.clearUserPref("browser.ctrlTab.previews");
+}
+
+function pressCtrlTab(aShiftKey) {
+ EventUtils.synthesizeKey("VK_TAB", { ctrlKey: true, shiftKey: !!aShiftKey });
+}
+
+function releaseCtrl() {
+ EventUtils.synthesizeKey("VK_CONTROL", { type: "keyup" });
+}
diff --git a/browser/base/content/test/browser_wyciwyg_urlbarCopying.js b/browser/base/content/test/browser_wyciwyg_urlbarCopying.js
new file mode 100644
index 000000000..f908e5254
--- /dev/null
+++ b/browser/base/content/test/browser_wyciwyg_urlbarCopying.js
@@ -0,0 +1,39 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+ waitForExplicitFinish();
+
+ let url = "http://mochi.test:8888/browser/browser/base/content/test/test_wyciwyg_copying.html";
+ let tab = gBrowser.selectedTab = gBrowser.addTab(url);
+ tab.linkedBrowser.addEventListener("pageshow", function () {
+ let btn = content.document.getElementById("btn");
+ executeSoon(function () {
+ EventUtils.synthesizeMouseAtCenter(btn, {}, content);
+ let currentURL = gBrowser.currentURI.spec;
+ ok(/^wyciwyg:\/\//i.test(currentURL), currentURL + " is a wyciwyg URI");
+
+ executeSoon(function () {
+ testURLBarCopy(url, endTest);
+ });
+ });
+ }, false);
+
+ function endTest() {
+ while (gBrowser.tabs.length > 1)
+ gBrowser.removeCurrentTab();
+ finish();
+ }
+
+ function testURLBarCopy(targetValue, cb) {
+ info("Expecting copy of: " + targetValue);
+ waitForClipboard(targetValue, function () {
+ gURLBar.focus();
+ gURLBar.select();
+
+ goDoCommand("cmd_copy");
+ }, cb, cb);
+ }
+}
+
+
diff --git a/browser/base/content/test/browser_zbug569342.js b/browser/base/content/test/browser_zbug569342.js
new file mode 100644
index 000000000..8ca6674ce
--- /dev/null
+++ b/browser/base/content/test/browser_zbug569342.js
@@ -0,0 +1,78 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var gTab = null;
+
+function load(url, cb) {
+ gTab = gBrowser.addTab(url);
+ gBrowser.addEventListener("load", function (event) {
+ if (event.target.location != url)
+ return;
+
+ gBrowser.removeEventListener("load", arguments.callee, true);
+ // Trigger onLocationChange by switching tabs.
+ gBrowser.selectedTab = gTab;
+ cb();
+ }, true);
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ ok(gFindBar.hidden, "Find bar should not be visible by default");
+
+ // Open the Find bar before we navigate to pages that shouldn't have it.
+ EventUtils.synthesizeKey("f", { accelKey: true });
+ ok(!gFindBar.hidden, "Find bar should be visible");
+
+ nextTest();
+}
+
+let urls = [
+ "about:config",
+ "about:addons",
+ "about:permissions"
+];
+
+function nextTest() {
+ let url = urls.shift();
+ if (url) {
+ testFindDisabled(url, nextTest);
+ } else {
+ // Make sure the find bar is re-enabled after disabled page is closed.
+ testFindEnabled("about:blank", finish);
+ }
+}
+
+function testFindDisabled(url, cb) {
+ load(url, function() {
+ ok(gFindBar.hidden, "Find bar should not be visible");
+ EventUtils.synthesizeKey("/", {}, gTab.linkedBrowser.contentWindow);
+ ok(gFindBar.hidden, "Find bar should not be visible");
+ EventUtils.synthesizeKey("f", { accelKey: true });
+ ok(gFindBar.hidden, "Find bar should not be visible");
+ ok(document.getElementById("cmd_find").getAttribute("disabled"),
+ "Find command should be disabled");
+
+ gBrowser.removeTab(gTab);
+ cb();
+ });
+}
+
+function testFindEnabled(url, cb) {
+ load(url, function() {
+ ok(!document.getElementById("cmd_find").getAttribute("disabled"),
+ "Find command should not be disabled");
+
+ ok(!gFindBar.hidden, "Find bar should be visible again");
+
+ // Give focus to the Find bar and then close it.
+ EventUtils.synthesizeKey("f", { accelKey: true });
+ EventUtils.synthesizeKey("VK_ESCAPE", { });
+ ok(gFindBar.hidden, "Find bar should now be hidden");
+
+ gBrowser.removeTab(gTab);
+ cb();
+ });
+}
diff --git a/browser/base/content/test/bug364677-data.xml b/browser/base/content/test/bug364677-data.xml
new file mode 100644
index 000000000..b48915c05
--- /dev/null
+++ b/browser/base/content/test/bug364677-data.xml
@@ -0,0 +1,5 @@
+<rss version="2.0">
+ <channel>
+ <title>t</title>
+ </channel>
+</rss>
diff --git a/browser/base/content/test/bug364677-data.xml^headers^ b/browser/base/content/test/bug364677-data.xml^headers^
new file mode 100644
index 000000000..f203c6368
--- /dev/null
+++ b/browser/base/content/test/bug364677-data.xml^headers^
@@ -0,0 +1 @@
+Content-Type: text/xml
diff --git a/browser/base/content/test/bug395533-data.txt b/browser/base/content/test/bug395533-data.txt
new file mode 100644
index 000000000..e0ed39850
--- /dev/null
+++ b/browser/base/content/test/bug395533-data.txt
@@ -0,0 +1,6 @@
+<rss version="2.0">
+ <channel>
+ <link>http://example.org/</link>
+ <title>t</title>
+ </channel>
+</rss>
diff --git a/browser/base/content/test/bug564387.html b/browser/base/content/test/bug564387.html
new file mode 100644
index 000000000..51ba4d04b
--- /dev/null
+++ b/browser/base/content/test/bug564387.html
@@ -0,0 +1,11 @@
+<html>
+ <!-- https://bugzilla.mozilla.org/show_bug.cgi?id=564387 -->
+ <head>
+ <title> Bug 564387 test</title>
+ </head>
+ <body>
+ Testing for Mozilla Bug: 564387
+ <br>
+ <video src="bug564387_video1.ogv" id="video1"> </video>
+ </body>
+</html>
diff --git a/browser/base/content/test/bug564387_video1.ogv b/browser/base/content/test/bug564387_video1.ogv
new file mode 100644
index 000000000..093158432
--- /dev/null
+++ b/browser/base/content/test/bug564387_video1.ogv
Binary files differ
diff --git a/browser/base/content/test/bug564387_video1.ogv^headers^ b/browser/base/content/test/bug564387_video1.ogv^headers^
new file mode 100644
index 000000000..f880d0ac3
--- /dev/null
+++ b/browser/base/content/test/bug564387_video1.ogv^headers^
@@ -0,0 +1,3 @@
+Content-Disposition: filename="Bug564387-expectedName.ogv"
+Content-Type: video/ogg
+
diff --git a/browser/base/content/test/bug592338.html b/browser/base/content/test/bug592338.html
new file mode 100644
index 000000000..159b21a76
--- /dev/null
+++ b/browser/base/content/test/bug592338.html
@@ -0,0 +1,24 @@
+<html>
+<head>
+<script type="text/javascript">
+var theme = {
+ id: "test",
+ name: "Test Background",
+ headerURL: "http://example.com/firefox/personas/01/header.jpg",
+ footerURL: "http://example.com/firefox/personas/01/footer.jpg",
+ textcolor: "#fff",
+ accentcolor: "#6b6b6b"
+};
+
+function setTheme(node) {
+ node.setAttribute("data-browsertheme", JSON.stringify(theme));
+ var event = document.createEvent("Events");
+ event.initEvent("InstallBrowserTheme", true, false);
+ node.dispatchEvent(event);
+}
+</script>
+</head>
+<body>
+<a id="theme-install" href="#" onclick="setTheme(this)">Install</a>
+</body>
+</html>
diff --git a/browser/base/content/test/bug792517-2.html b/browser/base/content/test/bug792517-2.html
new file mode 100644
index 000000000..bfc24d817
--- /dev/null
+++ b/browser/base/content/test/bug792517-2.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+<a href="bug792517.sjs" id="fff">this is a link</a>
+</body>
+</html>
diff --git a/browser/base/content/test/bug792517.html b/browser/base/content/test/bug792517.html
new file mode 100644
index 000000000..e7c040bf1
--- /dev/null
+++ b/browser/base/content/test/bug792517.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+<img src="moz.png" id="img">
+</body>
+</html>
diff --git a/browser/base/content/test/bug792517.sjs b/browser/base/content/test/bug792517.sjs
new file mode 100644
index 000000000..91e5aa23f
--- /dev/null
+++ b/browser/base/content/test/bug792517.sjs
@@ -0,0 +1,13 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function handleRequest(aRequest, aResponse) {
+ aResponse.setStatusLine(aRequest.httpVersion, 200);
+ if (aRequest.hasHeader('Cookie')) {
+ aResponse.write("cookie-present");
+ } else {
+ aResponse.setHeader("Set-Cookie", "foopy=1");
+ aResponse.write("cookie-not-present");
+ }
+}
diff --git a/browser/base/content/test/bug839103.css b/browser/base/content/test/bug839103.css
new file mode 100644
index 000000000..611907d3d
--- /dev/null
+++ b/browser/base/content/test/bug839103.css
@@ -0,0 +1 @@
+* {}
diff --git a/browser/base/content/test/ctxmenu-image.png b/browser/base/content/test/ctxmenu-image.png
new file mode 100644
index 000000000..4c3be5084
--- /dev/null
+++ b/browser/base/content/test/ctxmenu-image.png
Binary files differ
diff --git a/browser/base/content/test/disablechrome.html b/browser/base/content/test/disablechrome.html
new file mode 100644
index 000000000..7879e1ce9
--- /dev/null
+++ b/browser/base/content/test/disablechrome.html
@@ -0,0 +1,4 @@
+<html>
+<body>
+</body>
+</html>
diff --git a/browser/base/content/test/discovery.html b/browser/base/content/test/discovery.html
new file mode 100644
index 000000000..1679e6545
--- /dev/null
+++ b/browser/base/content/test/discovery.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<html>
+ <head id="linkparent">
+ <title>Autodiscovery Test</title>
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/browser/base/content/test/domplate_test.js b/browser/base/content/test/domplate_test.js
new file mode 100644
index 000000000..75f2b25e2
--- /dev/null
+++ b/browser/base/content/test/domplate_test.js
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+let doc;
+let div;
+let plate;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource:///modules/domplate.jsm");
+
+function createDocument()
+{
+ doc.body.innerHTML = '<div id="first">no</div>';
+ doc.title = "Domplate Test";
+ setupDomplateTests();
+}
+
+function setupDomplateTests()
+{
+ ok(domplate, "domplate is defined");
+ plate = domplate({tag: domplate.DIV("Hello!")});
+ ok(plate, "template is defined");
+ div = doc.getElementById("first");
+ ok(div, "we have our div");
+ plate.tag.replace({}, div, template);
+ is(div.innerText, "Hello!", "Is the div's innerText replaced?");
+ finishUp();
+}
+
+function finishUp()
+{
+ gBrowser.removeCurrentTab();
+ finish();
+}
+
+function test()
+{
+ waitForExplicitFinish();
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", function() {
+ gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+ doc = content.document;
+ waitForFocus(createDocument, content);
+ }, true);
+
+ content.location = "data:text/html,basic domplate tests";
+}
+
diff --git a/browser/base/content/test/download_page.html b/browser/base/content/test/download_page.html
new file mode 100644
index 000000000..541f6f88b
--- /dev/null
+++ b/browser/base/content/test/download_page.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=676619
+-->
+ <head>
+ <title>Test for the download attribute</title>
+
+ </head>
+ <body>
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=676619">Bug 676619</a>
+ <br/>
+ <ul>
+ <li><a href="data:text/plain,Hey What are you looking for?"
+ download="test.txt" id="link1">Download "test.txt"</a></li>
+ <li><a href="video.ogg"
+ download id="link2">Download "video.ogg"</a></li>
+ <li><a href="video.ogg"
+ download="just some video" id="link3">Download "just some video"</a></li>
+ <li><a href="data:text/plain,test"
+ download="with-target.txt" id="link4">Download "with-target.txt"</a></li>
+ <li><a href="javascript:1+2"
+ download="javascript.txt" id="link5">Download "javascript.txt"</a></li>
+ </ul>
+ <script>
+ var li = document.createElement('li');
+ var a = document.createElement('a');
+
+ a.href = window.URL.createObjectURL(new Blob(["just text"])) ;
+ a.download = "test.blob";
+ a.id = "link6";
+ a.textContent = 'Download "test.blob"';
+
+ li.appendChild(a);
+ document.getElementsByTagName('ul')[0].appendChild(li);
+
+ window.addEventListener("beforeunload", function (evt) {
+ document.getElementById("unload-flag").textContent = "Fail";
+ });
+ </script>
+ <ul>
+ <li><a href="http://example.com/"
+ download="example.com" id="link7" target="_blank">Download "example.com"</a></li>
+ <ul>
+ <div id="unload-flag">Okay</div>
+ </body>
+</html>
diff --git a/browser/base/content/test/dummy_page.html b/browser/base/content/test/dummy_page.html
new file mode 100644
index 000000000..578567564
--- /dev/null
+++ b/browser/base/content/test/dummy_page.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+<title>Dummy test page</title>
+</head>
+<body>
+<p>Dummy test page</p>
+</body>
+</html>
diff --git a/browser/base/content/test/feed_discovery.html b/browser/base/content/test/feed_discovery.html
new file mode 100644
index 000000000..f7a30091c
--- /dev/null
+++ b/browser/base/content/test/feed_discovery.html
@@ -0,0 +1,113 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=377611
+-->
+ <head>
+ <title>Test for feed discovery</title>
+
+ <!-- Straight up standard -->
+ <link rel="alternate" type="application/atom+xml" title="1" href="/1.atom" />
+ <link rel="alternate" type="application/rss+xml" title="2" href="/2.rss" />
+ <link rel="feed" title="3" href="/3.xml" />
+
+ <!-- rel is a space-separated list -->
+ <link rel=" alternate " type="application/atom+xml" title="4" href="/4.atom" />
+ <link rel="foo alternate" type="application/atom+xml" title="5" href="/5.atom" />
+ <link rel="alternate foo" type="application/atom+xml" title="6" href="/6.atom" />
+ <link rel="foo alternate foo" type="application/atom+xml" title="7" href="/7.atom" />
+ <link rel="meat feed cake" title="8" href="/8.atom" />
+
+ <!-- rel is case-insensitive -->
+ <link rel="ALTERNate" type="application/atom+xml" title="9" href="/9.atom" />
+ <link rel="fEEd" title="10" href="/10.atom" />
+
+ <!-- type can have leading and trailing whitespace -->
+ <link rel="alternate" type=" application/atom+xml " title="11" href="/11.atom" />
+
+ <!-- type is case-insensitive -->
+ <link rel="alternate" type="aPPliCAtion/ATom+xML" title="12" href="/12.atom" />
+
+ <!-- "feed stylesheet" is a feed, though "alternate stylesheet" isn't -->
+ <link rel="feed stylesheet" title="13" href="/13.atom" />
+
+ <!-- hyphens or letters around rel not allowed -->
+ <link rel="disabled-alternate" type="application/atom+xml" title="Bogus1" href="/Bogus1" />
+ <link rel="alternates" type="application/atom+xml" title="Bogus2" href="/Bogus2" />
+ <link rel=" alternate-like" type="application/atom+xml" title="Bogus3" href="/Bogus3" />
+
+ <!-- don't tolerate text/xml if title includes 'rss' not as a word -->
+ <link rel="alternate" type="text/xml" title="Bogus4 scissorsshaped" href="/Bogus4" />
+
+ <!-- don't tolerate application/xml if title includes 'rss' not as a word -->
+ <link rel="alternate" type="application/xml" title="Bogus5 scissorsshaped" href="/Bogus5" />
+
+ <!-- don't tolerate application/rdf+xml if title includes 'rss' not as a word -->
+ <link rel="alternate" type="application/rdf+xml" title="Bogus6 scissorsshaped" href="/Bogus6" />
+
+ <!-- don't tolerate random types -->
+ <link rel="alternate" type="text/plain" title="Bogus7 rss" href="/Bogus7" />
+
+ <!-- don't find Atom by title -->
+ <link rel="foopy" type="application/atom+xml" title="Bogus8 Atom and RSS" href="/Bogus8" />
+
+ <!-- don't find application/rss+xml by title -->
+ <link rel="goats" type="application/rss+xml" title="Bogus9 RSS and Atom" href="/Bogus9" />
+
+ <!-- don't find application/rdf+xml by title -->
+ <link rel="alternate" type="application/rdf+xml" title="Bogus10 RSS and Atom" href="/Bogus10" />
+
+ <!-- don't find application/xml by title -->
+ <link rel="alternate" type="application/xml" title="Bogus11 RSS and Atom" href="/Bogus11" />
+
+ <!-- don't find text/xml by title -->
+ <link rel="alternate" type="text/xml" title="Bogus12 RSS and Atom" href="/Bogus12" />
+
+ <!-- alternate and stylesheet isn't a feed -->
+ <link rel="alternate stylesheet" type="application/rss+xml" title="Bogus13 RSS" href="/Bogus13" />
+ </head>
+ <body>
+ <script type="text/javascript">
+ window.onload = function() {
+ netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+
+ var tests = new Array();
+
+ var currentWindow =
+ window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebNavigation)
+ .QueryInterface(Components.interfaces.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindow);
+ var browser = currentWindow.gBrowser.selectedBrowser;
+
+ var discovered = browser.feeds;
+ tests.push({ check: discovered.length > 0,
+ message: "some feeds should be discovered" });
+
+ var feeds = [];
+
+ for (var aFeed of discovered) {
+ feeds[aFeed.href] = true;
+ }
+
+ for (var aLink of document.getElementsByTagName("link")) {
+ // ignore real stylesheets, and anything without an href property
+ if (aLink.type != "text/css" && aLink.href) {
+ if (/bogus/i.test(aLink.title)) {
+ tests.push({ check: !feeds[aLink.href],
+ message: "don't discover " + aLink.href });
+ } else {
+ tests.push({ check: feeds[aLink.href],
+ message: "should discover " + aLink.href });
+ }
+ }
+ }
+ window.arguments[0].tests = tests;
+ window.close();
+ }
+ </script>
+ </body>
+</html>
+
diff --git a/browser/base/content/test/feed_tab.html b/browser/base/content/test/feed_tab.html
new file mode 100644
index 000000000..50903f48b
--- /dev/null
+++ b/browser/base/content/test/feed_tab.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=458579
+-->
+ <head>
+ <title>Test for page info feeds tab</title>
+
+ <!-- Straight up standard -->
+ <link rel="alternate" type="application/atom+xml" title="1" href="/1.atom" />
+ <link rel="alternate" type="application/rss+xml" title="2" href="/2.rss" />
+ <link rel="feed" title="3" href="/3.xml" />
+
+ </head>
+ <body>
+ </body>
+</html>
diff --git a/browser/base/content/test/file_bug550565_favicon.ico b/browser/base/content/test/file_bug550565_favicon.ico
new file mode 100644
index 000000000..d44438903
--- /dev/null
+++ b/browser/base/content/test/file_bug550565_favicon.ico
Binary files differ
diff --git a/browser/base/content/test/file_bug550565_popup.html b/browser/base/content/test/file_bug550565_popup.html
new file mode 100644
index 000000000..b4cddf971
--- /dev/null
+++ b/browser/base/content/test/file_bug550565_popup.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test file for bug 550565.</title>
+
+ <!--Set a favicon; that's the whole point of this file.-->
+ <link rel="icon" href="file_bug550565_favicon.ico">
+</head>
+<body>
+ Test file for bug 550565.
+</body>
+</html>
diff --git a/browser/base/content/test/file_bug822367_1.html b/browser/base/content/test/file_bug822367_1.html
new file mode 100644
index 000000000..0bb5011b8
--- /dev/null
+++ b/browser/base/content/test/file_bug822367_1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test 1 for Mixed Content Blocker User Override - Mixed Script
+https://bugzilla.mozilla.org/show_bug.cgi?id=822367
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 1 for Bug 822367</title>
+</head>
+<body>
+ <div id="testContent">
+ <p id="p1"></p>
+ </div>
+ <script src="http://example.com/browser/browser/base/content/test/file_bug822367_1.js">
+ </script>
+</body>
+</html>
diff --git a/browser/base/content/test/file_bug822367_1.js b/browser/base/content/test/file_bug822367_1.js
new file mode 100644
index 000000000..175de363b
--- /dev/null
+++ b/browser/base/content/test/file_bug822367_1.js
@@ -0,0 +1 @@
+document.getElementById('p1').innerHTML="hello";
diff --git a/browser/base/content/test/file_bug822367_2.html b/browser/base/content/test/file_bug822367_2.html
new file mode 100644
index 000000000..fe56ee213
--- /dev/null
+++ b/browser/base/content/test/file_bug822367_2.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test 2 for Mixed Content Blocker User Override - Mixed Display
+https://bugzilla.mozilla.org/show_bug.cgi?id=822367
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 2 for Bug 822367 - Mixed Display</title>
+</head>
+<body>
+ <div id="testContent">
+ <img src="http://example.com/tests/image/test/mochitest/blue.png">
+ </div>
+</body>
+</html>
diff --git a/browser/base/content/test/file_bug822367_3.html b/browser/base/content/test/file_bug822367_3.html
new file mode 100644
index 000000000..646e61206
--- /dev/null
+++ b/browser/base/content/test/file_bug822367_3.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test 3 for Mixed Content Blocker User Override - Mixed Script and Display
+https://bugzilla.mozilla.org/show_bug.cgi?id=822367
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 3 for Bug 822367</title>
+ <script>
+ function foo() {
+ var x = document.createElement('p');
+ x.setAttribute("id", "p2");
+ x.innerHTML = "bye";
+ document.getElementById("testContent").appendChild(x);
+ }
+ </script>
+</head>
+<body>
+ <div id="testContent">
+ <p id="p1"></p>
+ <img src="http://example.com/tests/image/test/mochitest/blue.png" onload="foo()">
+ </div>
+ <script src="http://example.com/browser/browser/base/content/test/file_bug822367_1.js">
+ </script>
+</body>
+</html>
diff --git a/browser/base/content/test/file_bug822367_4.html b/browser/base/content/test/file_bug822367_4.html
new file mode 100644
index 000000000..490692af9
--- /dev/null
+++ b/browser/base/content/test/file_bug822367_4.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test 4 for Mixed Content Blocker User Override - Mixed Script and Display
+https://bugzilla.mozilla.org/show_bug.cgi?id=822367
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 4 for Bug 822367</title>
+</head>
+<body>
+ <div id="testContent">
+ <p id="p1"></p>
+ </div>
+ <script src="http://example.com/browser/browser/base/content/test/file_bug822367_4.js">
+ </script>
+</body>
+</html>
diff --git a/browser/base/content/test/file_bug822367_4.js b/browser/base/content/test/file_bug822367_4.js
new file mode 100644
index 000000000..70462f05f
--- /dev/null
+++ b/browser/base/content/test/file_bug822367_4.js
@@ -0,0 +1 @@
+document.location = "https://example.com/browser/browser/base/content/test/file_bug822367_4B.html";
diff --git a/browser/base/content/test/file_bug822367_4B.html b/browser/base/content/test/file_bug822367_4B.html
new file mode 100644
index 000000000..05e11d2d0
--- /dev/null
+++ b/browser/base/content/test/file_bug822367_4B.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test 4B for Mixed Content Blocker User Override - Location Changed
+https://bugzilla.mozilla.org/show_bug.cgi?id=822367
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 4B Location Change for Bug 822367</title>
+</head>
+<body>
+ <div id="testContent">
+ <p id="p1"></p>
+ </div>
+ <script src="http://example.com/browser/browser/base/content/test/file_bug822367_1.js">
+ </script>
+</body>
+</html>
diff --git a/browser/base/content/test/file_bug822367_5.html b/browser/base/content/test/file_bug822367_5.html
new file mode 100644
index 000000000..e45408761
--- /dev/null
+++ b/browser/base/content/test/file_bug822367_5.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test 5 for Mixed Content Blocker User Override - Mixed Script in document.open()
+https://bugzilla.mozilla.org/show_bug.cgi?id=822367
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 5 for Bug 822367</title>
+ <script>
+ function createDoc()
+ {
+ var doc=document.open("text/html","replace");
+ doc.write('<!DOCTYPE html><html><body><p id="p1">This is some content</p><script src="http://example.com/browser/browser/base/content/test/file_bug822367_1.js">\<\/script\>\<\/body>\<\/html>');
+ doc.close();
+ }
+ </script>
+</head>
+<body>
+ <div id="testContent">
+ <img src="https://example.com/tests/image/test/mochitest/blue.png" onload="createDoc()">
+ </div>
+</body>
+</html>
diff --git a/browser/base/content/test/file_bug822367_6.html b/browser/base/content/test/file_bug822367_6.html
new file mode 100644
index 000000000..c158772f5
--- /dev/null
+++ b/browser/base/content/test/file_bug822367_6.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test 6 for Mixed Content Blocker User Override - Mixed Script in document.open() within an iframe
+https://bugzilla.mozilla.org/show_bug.cgi?id=822367
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 6 for Bug 822367</title>
+</head>
+<body>
+ <div id="testContent">
+ <iframe name="f1" id="f1" src="https://example.com/browser/browser/base/content/test/file_bug822367_5.html"></iframe>
+ </div>
+</body>
+</html>
diff --git a/browser/base/content/test/file_bug902156.js b/browser/base/content/test/file_bug902156.js
new file mode 100644
index 000000000..806667204
--- /dev/null
+++ b/browser/base/content/test/file_bug902156.js
@@ -0,0 +1,5 @@
+/*
+ * Once the mixed content blocker is disabled for the page, this scripts loads
+ * and updates the text inside the div container.
+ */
+document.getElementById("mctestdiv").innerHTML = "Mixed Content Blocker disabled";
diff --git a/browser/base/content/test/file_bug902156_1.html b/browser/base/content/test/file_bug902156_1.html
new file mode 100644
index 000000000..04d525817
--- /dev/null
+++ b/browser/base/content/test/file_bug902156_1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test 1 for Bug 902156 - See file browser_bug902156.js for description.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=902156
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 1 for Bug 902156</title>
+</head>
+<body>
+ <div id="mctestdiv">Mixed Content Blocker enabled</div>
+ <script src="http://test1.example.com/browser/browser/base/content/test/file_bug902156.js" ></script>
+</body>
+</html>
diff --git a/browser/base/content/test/file_bug902156_2.html b/browser/base/content/test/file_bug902156_2.html
new file mode 100644
index 000000000..396142520
--- /dev/null
+++ b/browser/base/content/test/file_bug902156_2.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test 2 for Bug 902156 - See file browser_bug902156.js for description.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=902156
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 2 for Bug 902156</title>
+</head>
+<body>
+ <div id="mctestdiv">Mixed Content Blocker enabled</div>
+ <a href="https://test2.example.com/browser/browser/base/content/test/file_bug902156_1.html"
+ id="mctestlink" target="_top">Go to http site</a>
+ <script src="http://test2.example.com/browser/browser/base/content/test/file_bug902156.js" ></script>
+</body>
+</html>
diff --git a/browser/base/content/test/file_bug902156_3.html b/browser/base/content/test/file_bug902156_3.html
new file mode 100644
index 000000000..23d1b11ba
--- /dev/null
+++ b/browser/base/content/test/file_bug902156_3.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ Test 3 for Bug 902156 - See file browser_bug902156.js for description.
+ https://bugzilla.mozilla.org/show_bug.cgi?id=902156
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test 3 for Bug 902156</title>
+</head>
+<body>
+ <div id="mctestdiv">Mixed Content Blocker enabled</div>
+ <script src="http://test1.example.com/browser/browser/base/content/test/file_bug902156.js" ></script>
+</body>
+</html>
diff --git a/browser/base/content/test/file_fullscreen-window-open.html b/browser/base/content/test/file_fullscreen-window-open.html
new file mode 100644
index 000000000..1584f4c98
--- /dev/null
+++ b/browser/base/content/test/file_fullscreen-window-open.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test for window.open() when browser is in fullscreen</title>
+ </head>
+ <body>
+ <script>
+ window.addEventListener("load", function onLoad() {
+ window.removeEventListener("load", onLoad, true);
+
+ document.getElementById("test").addEventListener("click", onClick, true);
+ }, true);
+
+ function onClick(aEvent) {
+ aEvent.preventDefault();
+
+ var dataStr = aEvent.target.getAttribute("data-test-param");
+ var data = JSON.parse(dataStr);
+ window.open(data.uri, data.title, data.option);
+ }
+ </script>
+ <a id="test" href="" data-test-param="">Test</a>
+ </body>
+</html>
diff --git a/browser/base/content/test/gZipOfflineChild.cacheManifest b/browser/base/content/test/gZipOfflineChild.cacheManifest
new file mode 100644
index 000000000..ae0545d12
--- /dev/null
+++ b/browser/base/content/test/gZipOfflineChild.cacheManifest
@@ -0,0 +1,2 @@
+CACHE MANIFEST
+gZipOfflineChild.html
diff --git a/browser/base/content/test/gZipOfflineChild.cacheManifest^headers^ b/browser/base/content/test/gZipOfflineChild.cacheManifest^headers^
new file mode 100644
index 000000000..257f2eb60
--- /dev/null
+++ b/browser/base/content/test/gZipOfflineChild.cacheManifest^headers^
@@ -0,0 +1 @@
+Content-Type: text/cache-manifest
diff --git a/browser/base/content/test/gZipOfflineChild.html b/browser/base/content/test/gZipOfflineChild.html
new file mode 100644
index 000000000..bd2d62ee0
--- /dev/null
+++ b/browser/base/content/test/gZipOfflineChild.html
Binary files differ
diff --git a/browser/base/content/test/gZipOfflineChild.html^headers^ b/browser/base/content/test/gZipOfflineChild.html^headers^
new file mode 100644
index 000000000..4204d8601
--- /dev/null
+++ b/browser/base/content/test/gZipOfflineChild.html^headers^
@@ -0,0 +1,2 @@
+Content-Type: text/html
+Content-Encoding: gzip
diff --git a/browser/base/content/test/gZipOfflineChild_uncompressed.html b/browser/base/content/test/gZipOfflineChild_uncompressed.html
new file mode 100644
index 000000000..4ab8f8d5e
--- /dev/null
+++ b/browser/base/content/test/gZipOfflineChild_uncompressed.html
@@ -0,0 +1,21 @@
+<html manifest="gZipOfflineChild.cacheManifest">
+<head>
+ <!-- This file is gzipped to create gZipOfflineChild.html -->
+<title></title>
+<script type="text/javascript">
+
+function finish(success) {
+ window.parent.postMessage(success, "*");
+}
+
+applicationCache.oncached = function() { finish("oncache"); }
+applicationCache.onnoupdate = function() { finish("onupdate"); }
+applicationCache.onerror = function() { finish("onerror"); }
+
+</script>
+</head>
+
+<body>
+<h1>Child</h1>
+</body>
+</html>
diff --git a/browser/base/content/test/head.js b/browser/base/content/test/head.js
new file mode 100644
index 000000000..fbc28f41e
--- /dev/null
+++ b/browser/base/content/test/head.js
@@ -0,0 +1,400 @@
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/commonjs/sdk/core/promise.js");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+
+function whenDelayedStartupFinished(aWindow, aCallback) {
+ Services.obs.addObserver(function observer(aSubject, aTopic) {
+ if (aWindow == aSubject) {
+ Services.obs.removeObserver(observer, aTopic);
+ executeSoon(aCallback);
+ }
+ }, "browser-delayed-startup-finished", false);
+}
+
+function findChromeWindowByURI(aURI) {
+ let windows = Services.wm.getEnumerator(null);
+ while (windows.hasMoreElements()) {
+ let win = windows.getNext();
+ if (win.location.href == aURI)
+ return win;
+ }
+ return null;
+}
+
+function updateTabContextMenu(tab) {
+ let menu = document.getElementById("tabContextMenu");
+ if (!tab)
+ tab = gBrowser.selectedTab;
+ var evt = new Event("");
+ tab.dispatchEvent(evt);
+ menu.openPopup(tab, "end_after", 0, 0, true, false, evt);
+ is(TabContextMenu.contextTab, tab, "TabContextMenu context is the expected tab");
+ menu.hidePopup();
+}
+
+function findToolbarCustomizationWindow(aBrowserWin) {
+ if (!aBrowserWin)
+ aBrowserWin = window;
+
+ let iframe = aBrowserWin.document.getElementById("customizeToolbarSheetIFrame");
+ let win = iframe && iframe.contentWindow;
+ if (win)
+ return win;
+
+ win = findChromeWindowByURI("chrome://global/content/customizeToolbar.xul");
+ if (win && win.opener == aBrowserWin)
+ return win;
+
+ throw Error("Failed to find the customization window");
+}
+
+function openToolbarCustomizationUI(aCallback, aBrowserWin) {
+ if (!aBrowserWin)
+ aBrowserWin = window;
+
+ aBrowserWin.document.getElementById("cmd_CustomizeToolbars").doCommand();
+
+ aBrowserWin.gNavToolbox.addEventListener("beforecustomization", function UI_loaded() {
+ aBrowserWin.gNavToolbox.removeEventListener("beforecustomization", UI_loaded);
+
+ let win = findToolbarCustomizationWindow(aBrowserWin);
+ waitForFocus(function () {
+ aCallback(win);
+ }, win);
+ });
+}
+
+function closeToolbarCustomizationUI(aCallback, aBrowserWin) {
+ let win = findToolbarCustomizationWindow(aBrowserWin);
+
+ win.addEventListener("unload", function unloaded() {
+ win.removeEventListener("unload", unloaded);
+ executeSoon(aCallback);
+ });
+
+ let button = win.document.getElementById("donebutton");
+ button.focus();
+ button.doCommand();
+}
+
+function waitForCondition(condition, nextTest, errorMsg) {
+ var tries = 0;
+ var interval = setInterval(function() {
+ if (tries >= 30) {
+ ok(false, errorMsg);
+ moveOn();
+ }
+ if (condition()) {
+ moveOn();
+ }
+ tries++;
+ }, 100);
+ var moveOn = function() { clearInterval(interval); nextTest(); };
+}
+
+function getTestPlugin(aName) {
+ var pluginName = aName || "Test Plug-in";
+ var ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+ var tags = ph.getPluginTags();
+
+ // Find the test plugin
+ for (var i = 0; i < tags.length; i++) {
+ if (tags[i].name == pluginName)
+ return tags[i];
+ }
+ ok(false, "Unable to find plugin");
+ return null;
+}
+
+// after a test is done using the plugin doorhanger, we should just clear
+// any permissions that may have crept in
+function clearAllPluginPermissions() {
+ let perms = Services.perms.enumerator;
+ while (perms.hasMoreElements()) {
+ let perm = perms.getNext();
+ if (perm.type.startsWith('plugin')) {
+ Services.perms.remove(perm.host, perm.type);
+ }
+ }
+}
+
+function updateBlocklist(aCallback) {
+ var blocklistNotifier = Cc["@mozilla.org/extensions/blocklist;1"]
+ .getService(Ci.nsITimerCallback);
+ var observer = function() {
+ Services.obs.removeObserver(observer, "blocklist-updated");
+ SimpleTest.executeSoon(aCallback);
+ };
+ Services.obs.addObserver(observer, "blocklist-updated", false);
+ blocklistNotifier.notify(null);
+}
+
+var _originalTestBlocklistURL = null;
+function setAndUpdateBlocklist(aURL, aCallback) {
+ if (!_originalTestBlocklistURL)
+ _originalTestBlocklistURL = Services.prefs.getCharPref("extensions.blocklist.url");
+ Services.prefs.setCharPref("extensions.blocklist.url", aURL);
+ updateBlocklist(aCallback);
+}
+
+function resetBlocklist() {
+ Services.prefs.setCharPref("extensions.blocklist.url", _originalTestBlocklistURL);
+}
+
+function whenNewWindowLoaded(aOptions, aCallback) {
+ let win = OpenBrowserWindow(aOptions);
+ win.addEventListener("load", function onLoad() {
+ win.removeEventListener("load", onLoad, false);
+ aCallback(win);
+ }, false);
+}
+
+/**
+ * Waits for all pending async statements on the default connection, before
+ * proceeding with aCallback.
+ *
+ * @param aCallback
+ * Function to be called when done.
+ * @param aScope
+ * Scope for the callback.
+ * @param aArguments
+ * Arguments array for the callback.
+ *
+ * @note The result is achieved by asynchronously executing a query requiring
+ * a write lock. Since all statements on the same connection are
+ * serialized, the end of this write operation means that all writes are
+ * complete. Note that WAL makes so that writers don't block readers, but
+ * this is a problem only across different connections.
+ */
+function waitForAsyncUpdates(aCallback, aScope, aArguments) {
+ let scope = aScope || this;
+ let args = aArguments || [];
+ let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
+ .DBConnection;
+ let begin = db.createAsyncStatement("BEGIN EXCLUSIVE");
+ begin.executeAsync();
+ begin.finalize();
+
+ let commit = db.createAsyncStatement("COMMIT");
+ commit.executeAsync({
+ handleResult: function() {},
+ handleError: function() {},
+ handleCompletion: function(aReason) {
+ aCallback.apply(scope, args);
+ }
+ });
+ commit.finalize();
+}
+
+/**
+ * Asynchronously check a url is visited.
+
+ * @param aURI The URI.
+ * @param aExpectedValue The expected value.
+ * @return {Promise}
+ * @resolves When the check has been added successfully.
+ * @rejects JavaScript exception.
+ */
+function promiseIsURIVisited(aURI, aExpectedValue) {
+ let deferred = Promise.defer();
+ PlacesUtils.asyncHistory.isURIVisited(aURI, function(aURI, aIsVisited) {
+ deferred.resolve(aIsVisited);
+ });
+
+ return deferred.promise;
+}
+
+function whenNewTabLoaded(aWindow, aCallback) {
+ aWindow.BrowserOpenTab();
+
+ let browser = aWindow.gBrowser.selectedBrowser;
+ if (browser.contentDocument.readyState === "complete") {
+ aCallback();
+ return;
+ }
+
+ browser.addEventListener("load", function onLoad() {
+ browser.removeEventListener("load", onLoad, true);
+ aCallback();
+ }, true);
+}
+
+function addVisits(aPlaceInfo, aCallback) {
+ let places = [];
+ if (aPlaceInfo instanceof Ci.nsIURI) {
+ places.push({ uri: aPlaceInfo });
+ } else if (Array.isArray(aPlaceInfo)) {
+ places = places.concat(aPlaceInfo);
+ } else {
+ places.push(aPlaceInfo);
+ }
+
+ // Create mozIVisitInfo for each entry.
+ let now = Date.now();
+ for (let i = 0; i < places.length; i++) {
+ if (!places[i].title) {
+ places[i].title = "test visit for " + places[i].uri.spec;
+ }
+ places[i].visits = [{
+ transitionType: places[i].transition === undefined ? Ci.nsINavHistoryService.TRANSITION_LINK
+ : places[i].transition,
+ visitDate: places[i].visitDate || (now++) * 1000,
+ referrerURI: places[i].referrer
+ }];
+ }
+
+ PlacesUtils.asyncHistory.updatePlaces(
+ places,
+ {
+ handleError: function AAV_handleError() {
+ throw("Unexpected error in adding visit.");
+ },
+ handleResult: function () {},
+ handleCompletion: function UP_handleCompletion() {
+ if (aCallback)
+ aCallback();
+ }
+ }
+ );
+}
+
+/**
+ * Ensures that the specified URIs are either cleared or not.
+ *
+ * @param aURIs
+ * Array of page URIs
+ * @param aShouldBeCleared
+ * True if each visit to the URI should be cleared, false otherwise
+ */
+function promiseHistoryClearedState(aURIs, aShouldBeCleared) {
+ let deferred = Promise.defer();
+ let callbackCount = 0;
+ let niceStr = aShouldBeCleared ? "no longer" : "still";
+ function callbackDone() {
+ if (++callbackCount == aURIs.length)
+ deferred.resolve();
+ }
+ aURIs.forEach(function (aURI) {
+ PlacesUtils.asyncHistory.isURIVisited(aURI, function(aURI, aIsVisited) {
+ is(aIsVisited, !aShouldBeCleared,
+ "history visit " + aURI.spec + " should " + niceStr + " exist");
+ callbackDone();
+ });
+ });
+
+ return deferred.promise;
+}
+
+let FullZoomHelper = {
+
+ selectTabAndWaitForLocationChange: function selectTabAndWaitForLocationChange(tab) {
+ if (!tab)
+ throw new Error("tab must be given.");
+ if (gBrowser.selectedTab == tab)
+ return Promise.resolve();
+ gBrowser.selectedTab = tab;
+ return this.waitForLocationChange();
+ },
+
+ removeTabAndWaitForLocationChange: function removeTabAndWaitForLocationChange(tab) {
+ tab = tab || gBrowser.selectedTab;
+ let selected = gBrowser.selectedTab == tab;
+ gBrowser.removeTab(tab);
+ if (selected)
+ return this.waitForLocationChange();
+ return Promise.resolve();
+ },
+
+ waitForLocationChange: function waitForLocationChange() {
+ let deferred = Promise.defer();
+ Services.obs.addObserver(function obs() {
+ Services.obs.removeObserver(obs, "browser-fullZoom:locationChange");
+ deferred.resolve();
+ }, "browser-fullZoom:locationChange", false);
+ return deferred.promise;
+ },
+
+ load: function load(tab, url) {
+ let deferred = Promise.defer();
+ let didLoad = false;
+ let didZoom = false;
+
+ tab.linkedBrowser.addEventListener("load", function (event) {
+ event.currentTarget.removeEventListener("load", arguments.callee, true);
+ didLoad = true;
+ if (didZoom)
+ deferred.resolve();
+ }, true);
+
+ this.waitForLocationChange().then(function () {
+ didZoom = true;
+ if (didLoad)
+ deferred.resolve();
+ });
+
+ tab.linkedBrowser.loadURI(url);
+
+ return deferred.promise;
+ },
+
+ zoomTest: function zoomTest(tab, val, msg) {
+ is(ZoomManager.getZoomForBrowser(tab.linkedBrowser), val, msg);
+ },
+
+ enlarge: function enlarge() {
+ let deferred = Promise.defer();
+ FullZoom.enlarge(function () deferred.resolve());
+ return deferred.promise;
+ },
+
+ reduce: function reduce() {
+ let deferred = Promise.defer();
+ FullZoom.reduce(function () deferred.resolve());
+ return deferred.promise;
+ },
+
+ reset: function reset() {
+ let deferred = Promise.defer();
+ FullZoom.reset(function () deferred.resolve());
+ return deferred.promise;
+ },
+
+ BACK: 0,
+ FORWARD: 1,
+ navigate: function navigate(direction) {
+ let deferred = Promise.defer();
+ let didPs = false;
+ let didZoom = false;
+
+ gBrowser.addEventListener("pageshow", function (event) {
+ gBrowser.removeEventListener("pageshow", arguments.callee, true);
+ didPs = true;
+ if (didZoom)
+ deferred.resolve();
+ }, true);
+
+ if (direction == this.BACK)
+ gBrowser.goBack();
+ else if (direction == this.FORWARD)
+ gBrowser.goForward();
+
+ this.waitForLocationChange().then(function () {
+ didZoom = true;
+ if (didPs)
+ deferred.resolve();
+ });
+ return deferred.promise;
+ },
+
+ failAndContinue: function failAndContinue(func) {
+ return function (err) {
+ ok(false, err);
+ func();
+ };
+ },
+};
diff --git a/browser/base/content/test/head_plain.js b/browser/base/content/test/head_plain.js
new file mode 100644
index 000000000..62f9afb2e
--- /dev/null
+++ b/browser/base/content/test/head_plain.js
@@ -0,0 +1,15 @@
+
+function waitForCondition(condition, nextTest, errorMsg) {
+ var tries = 0;
+ var interval = setInterval(function() {
+ if (tries >= 30) {
+ ok(false, errorMsg);
+ moveOn();
+ }
+ if (condition()) {
+ moveOn();
+ }
+ tries++;
+ }, 100);
+ var moveOn = function() { clearInterval(interval); nextTest(); };
+}
diff --git a/browser/base/content/test/healthreport_testRemoteCommands.html b/browser/base/content/test/healthreport_testRemoteCommands.html
new file mode 100644
index 000000000..1cd6de841
--- /dev/null
+++ b/browser/base/content/test/healthreport_testRemoteCommands.html
@@ -0,0 +1,128 @@
+<html>
+ <head>
+ <meta charset="utf-8">
+
+<script>
+
+function init() {
+ window.addEventListener("message", function process(e) {doTest(e)}, false);
+ doTest();
+}
+
+function checkSubmissionValue(payload, expectedValue) {
+ return payload.enabled == expectedValue;
+}
+
+function validatePayload(payload) {
+ payload = JSON.parse(payload);
+
+ // xxxmpc - this is some pretty low-bar validation, but we have plenty of tests of that API elsewhere
+ if (!payload.thisPingDate)
+ return false;
+
+ return true;
+}
+
+var tests = [
+{
+ info: "Checking initial value is enabled",
+ event: "RequestCurrentPrefs",
+ payloadType: "prefs",
+ validateResponse: function(payload) {
+ return checkSubmissionValue(payload, true);
+ },
+},
+{
+ info: "Verifying disabling works",
+ event: "DisableDataSubmission",
+ payloadType: "prefs",
+ validateResponse: function(payload) {
+ return checkSubmissionValue(payload, false);
+ },
+},
+{
+ info: "Verifying we're still disabled",
+ event: "RequestCurrentPrefs",
+ payloadType: "prefs",
+ validateResponse: function(payload) {
+ return checkSubmissionValue(payload, false);
+ },
+},
+{
+ info: "Verifying we can get a payload while submission is disabled",
+ event: "RequestCurrentPayload",
+ payloadType: "payload",
+ validateResponse: function(payload) {
+ return validatePayload(payload);
+ },
+},
+{
+ info: "Verifying enabling works",
+ event: "EnableDataSubmission",
+ payloadType: "prefs",
+ validateResponse: function(payload) {
+ return checkSubmissionValue(payload, true);
+ },
+},
+{
+ info: "Verifying we're still re-enabled",
+ event: "RequestCurrentPrefs",
+ payloadType: "prefs",
+ validateResponse: function(payload) {
+ return checkSubmissionValue(payload, true);
+ },
+},
+{
+ info: "Verifying we can get a payload after re-enabling",
+ event: "RequestCurrentPayload",
+ payloadType: "payload",
+ validateResponse: function(payload) {
+ return validatePayload(payload);
+ },
+},
+];
+
+var currentTest = -1;
+function doTest(evt) {
+ if (evt) {
+ if (currentTest < 0 || !evt.data.content)
+ return; // not yet testing
+
+ var test = tests[currentTest];
+ if (evt.data.type != test.payloadType)
+ return; // skip unrequested events
+
+ var error = JSON.stringify(evt.data.content);
+ var pass = false;
+ try {
+ pass = test.validateResponse(evt.data.content)
+ } catch (e) {}
+ reportResult(test.info, pass, error);
+ }
+ // start the next test if there are any left
+ if (tests[++currentTest])
+ sendToBrowser(tests[currentTest].event);
+ else
+ reportFinished();
+}
+
+function reportResult(info, pass, error) {
+ var data = {type: "testResult", info: info, pass: pass, error: error};
+ window.parent.postMessage(data, "*");
+}
+
+function reportFinished(cmd) {
+ var data = {type: "testsComplete", count: tests.length};
+ window.parent.postMessage(data, "*");
+}
+
+function sendToBrowser(type) {
+ var event = new CustomEvent("RemoteHealthReportCommand", {detail: {command: type}, bubbles: true});
+ document.dispatchEvent(event);
+}
+
+</script>
+ </head>
+ <body onload="init()">
+ </body>
+</html>
diff --git a/browser/base/content/test/moz.build b/browser/base/content/test/moz.build
new file mode 100644
index 000000000..27399e075
--- /dev/null
+++ b/browser/base/content/test/moz.build
@@ -0,0 +1,8 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += ['newtab', 'social']
+
diff --git a/browser/base/content/test/moz.png b/browser/base/content/test/moz.png
new file mode 100644
index 000000000..769c63634
--- /dev/null
+++ b/browser/base/content/test/moz.png
Binary files differ
diff --git a/browser/base/content/test/newtab/Makefile.in b/browser/base/content/test/newtab/Makefile.in
new file mode 100644
index 000000000..6ac460d14
--- /dev/null
+++ b/browser/base/content/test/newtab/Makefile.in
@@ -0,0 +1,38 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEPTH = @DEPTH@
+topsrcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+relativesrcdir = @relativesrcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MOCHITEST_BROWSER_FILES = \
+ browser_newtab_block.js \
+ browser_newtab_disable.js \
+ browser_newtab_drag_drop.js \
+ browser_newtab_drag_drop_ext.js \
+ browser_newtab_drop_preview.js \
+ browser_newtab_focus.js \
+ browser_newtab_reset.js \
+ browser_newtab_tabsync.js \
+ browser_newtab_undo.js \
+ browser_newtab_unpin.js \
+ browser_newtab_bug721442.js \
+ browser_newtab_bug722273.js \
+ browser_newtab_bug723102.js \
+ browser_newtab_bug723121.js \
+ browser_newtab_bug725996.js \
+ browser_newtab_bug734043.js \
+ browser_newtab_bug735987.js \
+ browser_newtab_bug752841.js \
+ browser_newtab_bug765628.js \
+ browser_newtab_bug876313.js \
+ browser_newtab_perwindow_private_browsing.js \
+ head.js \
+ $(NULL)
+
+include $(topsrcdir)/config/rules.mk
diff --git a/browser/base/content/test/newtab/browser_newtab_block.js b/browser/base/content/test/newtab/browser_newtab_block.js
new file mode 100644
index 000000000..bcb3d7baf
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_block.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests make sure that blocking/removing sites from the grid works
+ * as expected. Pinned tabs should not be moved. Gaps will be re-filled
+ * if more sites are available.
+ */
+function runTests() {
+ // we remove sites and expect the gaps to be filled as long as there still
+ // are some sites available
+ yield setLinks("0,1,2,3,4,5,6,7,8,9");
+ setPinnedLinks("");
+
+ yield addNewTabPageTab();
+ checkGrid("0,1,2,3,4,5,6,7,8");
+
+ yield blockCell(4);
+ checkGrid("0,1,2,3,5,6,7,8,9");
+
+ yield blockCell(4);
+ checkGrid("0,1,2,3,6,7,8,9,");
+
+ yield blockCell(4);
+ checkGrid("0,1,2,3,7,8,9,,");
+
+ // we removed a pinned site
+ yield restore();
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks(",1");
+
+ yield addNewTabPageTab();
+ checkGrid("0,1p,2,3,4,5,6,7,8");
+
+ yield blockCell(1);
+ checkGrid("0,2,3,4,5,6,7,8,");
+
+ // we remove the last site on the grid (which is pinned) and expect the gap
+ // to be re-filled and the new site to be unpinned
+ yield restore();
+ yield setLinks("0,1,2,3,4,5,6,7,8,9");
+ setPinnedLinks(",,,,,,,,8");
+
+ yield addNewTabPageTab();
+ checkGrid("0,1,2,3,4,5,6,7,8p");
+
+ yield blockCell(8);
+ checkGrid("0,1,2,3,4,5,6,7,9");
+
+ // we remove the first site on the grid with the last one pinned. all cells
+ // but the last one should shift to the left and a new site fades in
+ yield restore();
+ yield setLinks("0,1,2,3,4,5,6,7,8,9");
+ setPinnedLinks(",,,,,,,,8");
+
+ yield addNewTabPageTab();
+ checkGrid("0,1,2,3,4,5,6,7,8p");
+
+ yield blockCell(0);
+ checkGrid("1,2,3,4,5,6,7,9,8p");
+}
diff --git a/browser/base/content/test/newtab/browser_newtab_bug721442.js b/browser/base/content/test/newtab/browser_newtab_bug721442.js
new file mode 100644
index 000000000..597aed251
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug721442.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function runTests() {
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks([
+ {url: "http://example.com/#7", title: ""},
+ {url: "http://example.com/#8", title: "title"},
+ {url: "http://example.com/#9", title: "http://example.com/#9"}
+ ]);
+
+ yield addNewTabPageTab();
+ checkGrid("7p,8p,9p,0,1,2,3,4,5");
+
+ checkTooltip(0, "http://example.com/#7", "1st tooltip is correct");
+ checkTooltip(1, "title\nhttp://example.com/#8", "2nd tooltip is correct");
+ checkTooltip(2, "http://example.com/#9", "3rd tooltip is correct");
+}
+
+function checkTooltip(aIndex, aExpected, aMessage) {
+ let link = getCell(aIndex).node.querySelector(".newtab-link");
+ is(link.getAttribute("title"), aExpected, aMessage);
+}
diff --git a/browser/base/content/test/newtab/browser_newtab_bug722273.js b/browser/base/content/test/newtab/browser_newtab_bug722273.js
new file mode 100644
index 000000000..bc561b321
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug722273.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const NOW = Date.now() * 1000;
+const URL = "http://fake-site.com/";
+
+let tmp = {};
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://browser/content/sanitize.js", tmp);
+
+let {Sanitizer} = tmp;
+
+function runTests() {
+ sanitizeHistory();
+ yield addFakeVisits();
+ yield addNewTabPageTab();
+
+ is(getCell(0).site.url, URL, "first site is our fake site");
+
+ whenPagesUpdated();
+ yield sanitizeHistory();
+
+ ok(!getCell(0).site, "the fake site is gone");
+}
+
+function addFakeVisits() {
+ let visits = [];
+ for (let i = 59; i > 0; i--) {
+ visits.push({
+ visitDate: NOW - i * 60 * 1000000,
+ transitionType: Ci.nsINavHistoryService.TRANSITION_LINK
+ });
+ }
+ let place = {
+ uri: makeURI(URL),
+ title: "fake site",
+ visits: visits
+ };
+ PlacesUtils.asyncHistory.updatePlaces(place, {
+ handleError: function () ok(false, "couldn't add visit"),
+ handleResult: function () {},
+ handleCompletion: function () {
+ NewTabUtils.links.populateCache(function () {
+ NewTabUtils.allPages.update();
+ TestRunner.next();
+ }, true);
+ }
+ });
+}
+
+function sanitizeHistory() {
+ let s = new Sanitizer();
+ s.prefDomain = "privacy.cpd.";
+
+ let prefs = gPrefService.getBranch(s.prefDomain);
+ prefs.setBoolPref("history", true);
+ prefs.setBoolPref("downloads", false);
+ prefs.setBoolPref("cache", false);
+ prefs.setBoolPref("cookies", false);
+ prefs.setBoolPref("formdata", false);
+ prefs.setBoolPref("offlineApps", false);
+ prefs.setBoolPref("passwords", false);
+ prefs.setBoolPref("sessions", false);
+ prefs.setBoolPref("siteSettings", false);
+
+ s.sanitize();
+}
diff --git a/browser/base/content/test/newtab/browser_newtab_bug723102.js b/browser/base/content/test/newtab/browser_newtab_bug723102.js
new file mode 100644
index 000000000..aa04b1150
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug723102.js
@@ -0,0 +1,19 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function runTests() {
+ // create a new tab page and hide it.
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ yield addNewTabPageTab();
+ let firstTab = gBrowser.selectedTab;
+
+ yield addNewTabPageTab();
+ gBrowser.removeTab(firstTab);
+
+ ok(NewTabUtils.allPages.enabled, "page is enabled");
+ NewTabUtils.allPages.enabled = false;
+ ok(getGrid().node.hasAttribute("page-disabled"), "page is disabled");
+ NewTabUtils.allPages.enabled = true;
+}
diff --git a/browser/base/content/test/newtab/browser_newtab_bug723121.js b/browser/base/content/test/newtab/browser_newtab_bug723121.js
new file mode 100644
index 000000000..5ad8e7ca0
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug723121.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function runTests() {
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ yield addNewTabPageTab();
+ checkGridLocked(false, "grid is unlocked");
+
+ let cell = getCell(0).node;
+ let site = getCell(0).site.node;
+ let link = site.querySelector(".newtab-link");
+
+ sendDragEvent("dragstart", link);
+ checkGridLocked(true, "grid is now locked");
+
+ sendDragEvent("dragend", link);
+ checkGridLocked(false, "grid isn't locked anymore");
+
+ sendDragEvent("dragstart", cell);
+ checkGridLocked(false, "grid isn't locked - dragstart was ignored");
+
+ sendDragEvent("dragstart", site);
+ checkGridLocked(false, "grid isn't locked - dragstart was ignored");
+}
+
+function checkGridLocked(aLocked, aMessage) {
+ is(getGrid().node.hasAttribute("locked"), aLocked, aMessage);
+}
diff --git a/browser/base/content/test/newtab/browser_newtab_bug725996.js b/browser/base/content/test/newtab/browser_newtab_bug725996.js
new file mode 100644
index 000000000..4d3ef7d5e
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug725996.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function runTests() {
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ yield addNewTabPageTab();
+ checkGrid("0,1,2,3,4,5,6,7,8");
+
+ let cell = getCell(0).node;
+
+ sendDragEvent("drop", cell, "http://example.com/#99\nblank");
+ is(NewTabUtils.pinnedLinks.links[0].url, "http://example.com/#99",
+ "first cell is pinned and contains the dropped site");
+
+ yield whenPagesUpdated();
+ checkGrid("99p,0,1,2,3,4,5,6,7");
+
+ sendDragEvent("drop", cell, "");
+ is(NewTabUtils.pinnedLinks.links[0].url, "http://example.com/#99",
+ "first cell is still pinned with the site we dropped before");
+}
diff --git a/browser/base/content/test/newtab/browser_newtab_bug734043.js b/browser/base/content/test/newtab/browser_newtab_bug734043.js
new file mode 100644
index 000000000..dff7a14b4
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug734043.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function runTests() {
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ yield addNewTabPageTab();
+ checkGrid("0,1,2,3,4,5,6,7,8");
+
+ let receivedError = false;
+ let block = getContentDocument().querySelector(".newtab-control-block");
+
+ function onError() {
+ receivedError = true;
+ }
+
+ let cw = getContentWindow();
+ cw.addEventListener("error", onError);
+
+ for (let i = 0; i < 3; i++)
+ EventUtils.synthesizeMouseAtCenter(block, {}, cw);
+
+ yield whenPagesUpdated();
+ ok(!receivedError, "we got here without any errors");
+ cw.removeEventListener("error", onError);
+}
diff --git a/browser/base/content/test/newtab/browser_newtab_bug735987.js b/browser/base/content/test/newtab/browser_newtab_bug735987.js
new file mode 100644
index 000000000..8dda601b9
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug735987.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function runTests() {
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ yield addNewTabPageTab();
+ checkGrid("0,1,2,3,4,5,6,7,8");
+
+ yield simulateDrop(1);
+ checkGrid("0,99p,1,2,3,4,5,6,7");
+
+ yield blockCell(1);
+ checkGrid("0,1,2,3,4,5,6,7,8");
+
+ yield simulateDrop(1);
+ checkGrid("0,99p,1,2,3,4,5,6,7");
+
+ NewTabUtils.blockedLinks.resetCache();
+ yield addNewTabPageTab();
+ checkGrid("0,99p,1,2,3,4,5,6,7");
+
+ yield blockCell(1);
+ checkGrid("0,1,2,3,4,5,6,7,8");
+}
diff --git a/browser/base/content/test/newtab/browser_newtab_bug752841.js b/browser/base/content/test/newtab/browser_newtab_bug752841.js
new file mode 100644
index 000000000..91c347b0c
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug752841.js
@@ -0,0 +1,53 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const PREF_NEWTAB_ROWS = "browser.newtabpage.rows";
+const PREF_NEWTAB_COLUMNS = "browser.newtabpage.columns";
+
+function runTests() {
+ let testValues = [
+ {row: 0, column: 0},
+ {row: -1, column: -1},
+ {row: -1, column: 0},
+ {row: 0, column: -1},
+ {row: 2, column: 4},
+ {row: 2, column: 5},
+ ];
+
+ // Expected length of grid
+ let expectedValues = [1, 1, 1, 1, 8, 10];
+
+ // Values before setting new pref values (9 is the default value -> 3 x 3)
+ let previousValues = [9, 1, 1, 1, 1, 8];
+
+ let existingTab, existingTabGridLength, newTab, newTabGridLength;
+ yield addNewTabPageTab();
+ existingTab = gBrowser.selectedTab;
+
+ for (let i = 0; i < expectedValues.length; i++) {
+ gBrowser.selectedTab = existingTab;
+ existingTabGridLength = getGrid().cells.length;
+ is(existingTabGridLength, previousValues[i],
+ "Grid length of existing page before update is correctly.");
+
+ Services.prefs.setIntPref(PREF_NEWTAB_ROWS, testValues[i].row);
+ Services.prefs.setIntPref(PREF_NEWTAB_COLUMNS, testValues[i].column);
+
+ existingTabGridLength = getGrid().cells.length;
+ is(existingTabGridLength, expectedValues[i],
+ "Existing page grid is updated correctly.");
+
+ yield addNewTabPageTab();
+ newTab = gBrowser.selectedTab;
+ newTabGridLength = getGrid().cells.length;
+ is(newTabGridLength, expectedValues[i],
+ "New page grid is updated correctly.");
+
+ gBrowser.removeTab(newTab);
+ }
+
+ gBrowser.removeTab(existingTab);
+
+ Services.prefs.clearUserPref(PREF_NEWTAB_ROWS);
+ Services.prefs.clearUserPref(PREF_NEWTAB_COLUMNS);
+}
diff --git a/browser/base/content/test/newtab/browser_newtab_bug765628.js b/browser/base/content/test/newtab/browser_newtab_bug765628.js
new file mode 100644
index 000000000..6b93c8e6d
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug765628.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const BAD_DRAG_DATA = "javascript:alert('h4ck0rz');\nbad stuff";
+const GOOD_DRAG_DATA = "http://example.com/#99\nsite 99";
+
+function runTests() {
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ yield addNewTabPageTab();
+ checkGrid("0,1,2,3,4,5,6,7,8");
+
+ sendDropEvent(0, BAD_DRAG_DATA);
+ sendDropEvent(1, GOOD_DRAG_DATA);
+
+ yield whenPagesUpdated();
+ checkGrid("0,99p,1,2,3,4,5,6,7");
+}
+
+function sendDropEvent(aCellIndex, aDragData) {
+ let ifaceReq = getContentWindow().QueryInterface(Ci.nsIInterfaceRequestor);
+ let windowUtils = ifaceReq.getInterface(Ci.nsIDOMWindowUtils);
+
+ let event = createDragEvent("drop", aDragData);
+ windowUtils.dispatchDOMEventViaPresShell(getCell(aCellIndex).node, event, true);
+}
diff --git a/browser/base/content/test/newtab/browser_newtab_bug876313.js b/browser/base/content/test/newtab/browser_newtab_bug876313.js
new file mode 100644
index 000000000..ed9e8fbb3
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_bug876313.js
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * This test makes sure that the changes made by unpinning
+ * a site are actually written to NewTabUtils' storage.
+ */
+function runTests() {
+ // Second cell is pinned with page #99.
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks(",99");
+
+ yield addNewTabPageTab();
+ checkGrid("0,99p,1,2,3,4,5,6,7");
+
+ // Unpin the second cell's site.
+ yield unpinCell(1);
+ checkGrid("0,1,2,3,4,5,6,7,8");
+
+ // Clear the pinned cache to force NewTabUtils to read the pref again.
+ NewTabUtils.pinnedLinks.resetCache();
+ NewTabUtils.allPages.update();
+ checkGrid("0,1,2,3,4,5,6,7,8");
+}
diff --git a/browser/base/content/test/newtab/browser_newtab_disable.js b/browser/base/content/test/newtab/browser_newtab_disable.js
new file mode 100644
index 000000000..57aa59761
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_disable.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests make sure that the 'New Tab Page' feature can be disabled if the
+ * decides not to use it.
+ */
+function runTests() {
+ // create a new tab page and hide it.
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ yield addNewTabPageTab();
+ let gridNode = getGrid().node;
+
+ ok(!gridNode.hasAttribute("page-disabled"), "page is not disabled");
+
+ NewTabUtils.allPages.enabled = false;
+ ok(gridNode.hasAttribute("page-disabled"), "page is disabled");
+
+ let oldGridNode = gridNode;
+
+ // create a second new tage page and make sure it's disabled. enable it
+ // again and check if the former page gets enabled as well.
+ yield addNewTabPageTab();
+ ok(gridNode.hasAttribute("page-disabled"), "page is disabled");
+
+ // check that no sites have been rendered
+ is(0, getContentDocument().querySelectorAll(".site").length, "no sites have been rendered");
+
+ NewTabUtils.allPages.enabled = true;
+ ok(!gridNode.hasAttribute("page-disabled"), "page is not disabled");
+ ok(!oldGridNode.hasAttribute("page-disabled"), "old page is not disabled");
+}
diff --git a/browser/base/content/test/newtab/browser_newtab_drag_drop.js b/browser/base/content/test/newtab/browser_newtab_drag_drop.js
new file mode 100644
index 000000000..1c64ddf72
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_drag_drop.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests make sure that dragging and dropping sites works as expected.
+ * Sites contained in the grid need to shift around to indicate the result
+ * of the drag-and-drop operation. If the grid is full and we're dragging
+ * a new site into it another one gets pushed out.
+ */
+function runTests() {
+ requestLongerTimeout(2);
+
+ // test a simple drag-and-drop scenario
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ yield addNewTabPageTab();
+ checkGrid("0,1,2,3,4,5,6,7,8");
+
+ yield simulateDrop(1, 0);
+ checkGrid("1,0p,2,3,4,5,6,7,8");
+
+ // drag a cell to its current cell and make sure it's not pinned afterwards
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ yield addNewTabPageTab();
+ checkGrid("0,1,2,3,4,5,6,7,8");
+
+ yield simulateDrop(0, 0);
+ checkGrid("0,1,2,3,4,5,6,7,8");
+
+ // ensure that pinned pages aren't moved if that's not necessary
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks(",1,2");
+
+ yield addNewTabPageTab();
+ checkGrid("0,1p,2p,3,4,5,6,7,8");
+
+ yield simulateDrop(3, 0);
+ checkGrid("3,1p,2p,0p,4,5,6,7,8");
+
+ // pinned sites should always be moved around as blocks. if a pinned site is
+ // moved around, neighboring pinned are affected as well
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("0,1");
+
+ yield addNewTabPageTab();
+ checkGrid("0p,1p,2,3,4,5,6,7,8");
+
+ yield simulateDrop(0, 2);
+ checkGrid("2p,0p,1p,3,4,5,6,7,8");
+
+ // pinned sites should not be pushed out of the grid (unless there are only
+ // pinned ones left on the grid)
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks(",,,,,,,7,8");
+
+ yield addNewTabPageTab();
+ checkGrid("0,1,2,3,4,5,6,7p,8p");
+
+ yield simulateDrop(8, 2);
+ checkGrid("0,1,3,4,5,6,7p,8p,2p");
+
+ // make sure that pinned sites are re-positioned correctly
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("0,1,2,,,5");
+
+ yield addNewTabPageTab();
+ checkGrid("0p,1p,2p,3,4,5p,6,7,8");
+
+ yield simulateDrop(4, 0);
+ checkGrid("3,1p,2p,4,0p,5p,6,7,8");
+}
diff --git a/browser/base/content/test/newtab/browser_newtab_drag_drop_ext.js b/browser/base/content/test/newtab/browser_newtab_drag_drop_ext.js
new file mode 100644
index 000000000..527ea2cc7
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_drag_drop_ext.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests make sure that dragging and dropping sites works as expected.
+ * Sites contained in the grid need to shift around to indicate the result
+ * of the drag-and-drop operation. If the grid is full and we're dragging
+ * a new site into it another one gets pushed out.
+ * This is a continuation of browser_newtab_drag_drop.js
+ * to decrease test run time, focusing on external sites.
+ */
+function runTests() {
+ // drag a new site onto the very first cell
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks(",,,,,,,7,8");
+
+ yield addNewTabPageTab();
+ checkGrid("0,1,2,3,4,5,6,7p,8p");
+
+ yield simulateDrop(0);
+ checkGrid("99p,0,1,2,3,4,5,7p,8p");
+
+ // drag a new site onto the grid and make sure that pinned cells don't get
+ // pushed out
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks(",,,,,,,7,8");
+
+ yield addNewTabPageTab();
+ checkGrid("0,1,2,3,4,5,6,7p,8p");
+
+ yield simulateDrop(7);
+ checkGrid("0,1,2,3,4,5,7p,99p,8p");
+
+ // drag a new site beneath a pinned cell and make sure the pinned cell is
+ // not moved
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks(",,,,,,,,8");
+
+ yield addNewTabPageTab();
+ checkGrid("0,1,2,3,4,5,6,7,8p");
+
+ yield simulateDrop(7);
+ checkGrid("0,1,2,3,4,5,6,99p,8p");
+
+ // drag a new site onto a block of pinned sites and make sure they're shifted
+ // around accordingly
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("0,1,2,,,,,,");
+
+ yield addNewTabPageTab();
+ checkGrid("0p,1p,2p");
+
+ yield simulateDrop(1);
+ checkGrid("0p,99p,1p,2p,3,4,5,6,7");
+} \ No newline at end of file
diff --git a/browser/base/content/test/newtab/browser_newtab_drop_preview.js b/browser/base/content/test/newtab/browser_newtab_drop_preview.js
new file mode 100644
index 000000000..61c163d9d
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_drop_preview.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests ensure that the drop preview correctly arranges sites when
+ * dragging them around.
+ */
+function runTests() {
+ // the first three sites are pinned - make sure they're re-arranged correctly
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("0,1,2,,,5");
+
+ yield addNewTabPageTab();
+ checkGrid("0p,1p,2p,3,4,5p,6,7,8");
+
+ let cw = getContentWindow();
+ cw.gDrag._draggedSite = getCell(0).site;
+ let sites = cw.gDropPreview.rearrange(getCell(4));
+ cw.gDrag._draggedSite = null;
+
+ checkGrid("3,1p,2p,4,0p,5p,6,7,8", sites);
+}
diff --git a/browser/base/content/test/newtab/browser_newtab_focus.js b/browser/base/content/test/newtab/browser_newtab_focus.js
new file mode 100644
index 000000000..e841d3537
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_focus.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests make sure that focusing the 'New Tage Page' works as expected.
+ */
+function runTests() {
+ // Handle the OSX full keyboard access setting
+ Services.prefs.setIntPref("accessibility.tabfocus", 7);
+
+ // Focus count in new tab page.
+ // 28 = 9 * 3 + 1 = 9 sites and 1 toggle button, each site has a link, a pin
+ // and a remove button.
+ let FOCUS_COUNT = 28;
+
+ // Create a new tab page.
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ yield addNewTabPageTab();
+ gURLBar.focus();
+
+ // Count the focus with the enabled page.
+ yield countFocus(FOCUS_COUNT);
+
+ // Disable page and count the focus with the disabled page.
+ NewTabUtils.allPages.enabled = false;
+ yield countFocus(1);
+
+ Services.prefs.clearUserPref("accessibility.tabfocus");
+ NewTabUtils.allPages.enabled = true;
+}
+
+/**
+ * Focus the urlbar and count how many focus stops to return again to the urlbar.
+ */
+function countFocus(aExpectedCount) {
+ let focusCount = 0;
+ let contentDoc = getContentDocument();
+
+ window.addEventListener("focus", function onFocus() {
+ let focusedElement = document.commandDispatcher.focusedElement;
+ if (focusedElement && focusedElement.classList.contains("urlbar-input")) {
+ window.removeEventListener("focus", onFocus, true);
+ is(focusCount, aExpectedCount, "Validate focus count in the new tab page.");
+ executeSoon(TestRunner.next);
+ } else {
+ if (focusedElement && focusedElement.ownerDocument == contentDoc &&
+ focusedElement instanceof HTMLElement) {
+ focusCount++;
+ }
+ document.commandDispatcher.advanceFocus();
+ }
+ }, true);
+
+ document.commandDispatcher.advanceFocus();
+}
diff --git a/browser/base/content/test/newtab/browser_newtab_perwindow_private_browsing.js b/browser/base/content/test/newtab/browser_newtab_perwindow_private_browsing.js
new file mode 100644
index 000000000..68717a304
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_perwindow_private_browsing.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests ensure that all changes made to the new tab page in private
+ * browsing mode are discarded after switching back to normal mode again.
+ * The private browsing mode should start with the current grid shown in normal
+ * mode.
+ */
+
+function runTests() {
+ // prepare the grid
+ yield testOnWindow(undefined);
+ yield setLinks("0,1,2,3,4,5,6,7,8,9");
+
+ yield addNewTabPageTab();
+ pinCell(0);
+ checkGrid("0p,1,2,3,4,5,6,7,8");
+
+ // open private window
+ yield testOnWindow({private: true});
+
+ yield addNewTabPageTab();
+ checkGrid("0p,1,2,3,4,5,6,7,8");
+
+ // modify the grid while we're in pb mode
+ yield blockCell(1);
+ checkGrid("0p,2,3,4,5,6,7,8");
+
+ yield unpinCell(0);
+ checkGrid("0,2,3,4,5,6,7,8");
+
+ // open normal window
+ yield testOnWindow(undefined);
+
+ // check that the grid is the same as before entering pb mode
+ yield addNewTabPageTab();
+ checkGrid("0,2,3,4,5,6,7,8")
+}
+
+var windowsToClose = [];
+function testOnWindow(options) {
+ var win = OpenBrowserWindow(options);
+ win.addEventListener("load", function onLoad() {
+ win.removeEventListener("load", onLoad, false);
+ windowsToClose.push(win);
+ gWindow = win;
+ whenDelayedStartupFinished(win, TestRunner.next);
+ }, false);
+}
+
+function whenDelayedStartupFinished(win, callback) {
+ const topic = "browser-delayed-startup-finished";
+ Services.obs.addObserver(function onStartup(subject) {
+ if (win == subject) {
+ Services.obs.removeObserver(onStartup, topic);
+ executeSoon(callback);
+ }
+ }, topic, false);
+}
+
+registerCleanupFunction(function () {
+ gWindow = window;
+ windowsToClose.forEach(function(win) {
+ win.close();
+ });
+});
+
diff --git a/browser/base/content/test/newtab/browser_newtab_reset.js b/browser/base/content/test/newtab/browser_newtab_reset.js
new file mode 100644
index 000000000..3503fbb8d
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_reset.js
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests make sure that resetting the 'New Tage Page' works as expected.
+ */
+function runTests() {
+ // Disabled until bug 716543 is fixed.
+ return;
+
+ // create a new tab page and check its modified state after blocking a site
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("");
+
+ yield addNewTabPageTab();
+ let resetButton = getContentDocument().getElementById("toolbar-button-reset");
+
+ checkGrid("0,1,2,3,4,5,6,7,8");
+ ok(!resetButton.hasAttribute("modified"), "page is not modified");
+
+ yield blockCell(4);
+ checkGrid("0,1,2,3,5,6,7,8,");
+ ok(resetButton.hasAttribute("modified"), "page is modified");
+
+ yield getContentWindow().gToolbar.reset(TestRunner.next);
+ checkGrid("0,1,2,3,4,5,6,7,8");
+ ok(!resetButton.hasAttribute("modified"), "page is not modified");
+}
diff --git a/browser/base/content/test/newtab/browser_newtab_tabsync.js b/browser/base/content/test/newtab/browser_newtab_tabsync.js
new file mode 100644
index 000000000..2ffd11b30
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_tabsync.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests make sure that all changes that are made to a specific
+ * 'New Tab Page' are synchronized with all other open 'New Tab Pages'
+ * automatically. All about:newtab pages should always be in the same
+ * state.
+ */
+function runTests() {
+ // Disabled until bug 716543 is fixed.
+ return;
+
+ yield setLinks("0,1,2,3,4,5,6,7,8,9");
+ setPinnedLinks(",1");
+
+ yield addNewTabPageTab();
+ checkGrid("0,1p,2,3,4,5,6,7,8");
+
+ let resetButton = getContentDocument().getElementById("toolbar-button-reset");
+ ok(!resetButton.hasAttribute("modified"), "page is not modified");
+
+ let oldSites = getGrid().sites;
+ let oldResetButton = resetButton;
+
+ // create the new tab page
+ yield addNewTabPageTab();
+ checkGrid("0,1p,2,3,4,5,6,7,8");
+
+ resetButton = getContentDocument().getElementById("toolbar-button-reset");
+ ok(!resetButton.hasAttribute("modified"), "page is not modified");
+
+ // unpin a cell
+ yield unpinCell(1);
+ checkGrid("0,1,2,3,4,5,6,7,8");
+ checkGrid("0,1,2,3,4,5,6,7,8", oldSites);
+
+ // remove a cell
+ yield blockCell(1);
+ checkGrid("0,2,3,4,5,6,7,8,9");
+ checkGrid("0,2,3,4,5,6,7,8,9", oldSites);
+ ok(resetButton.hasAttribute("modified"), "page is modified");
+ ok(oldResetButton.hasAttribute("modified"), "page is modified");
+
+ // insert a new cell by dragging
+ yield simulateDrop(1);
+ checkGrid("0,99p,2,3,4,5,6,7,8");
+ checkGrid("0,99p,2,3,4,5,6,7,8", oldSites);
+
+ // drag a cell around
+ yield simulateDrop(1, 2);
+ checkGrid("0,2p,99p,3,4,5,6,7,8");
+ checkGrid("0,2p,99p,3,4,5,6,7,8", oldSites);
+
+ // reset the new tab page
+ yield getContentWindow().gToolbar.reset(TestRunner.next);
+ checkGrid("0,1,2,3,4,5,6,7,8");
+ checkGrid("0,1,2,3,4,5,6,7,8", oldSites);
+ ok(!resetButton.hasAttribute("modified"), "page is not modified");
+ ok(!oldResetButton.hasAttribute("modified"), "page is not modified");
+}
diff --git a/browser/base/content/test/newtab/browser_newtab_undo.js b/browser/base/content/test/newtab/browser_newtab_undo.js
new file mode 100644
index 000000000..bc0eb3df2
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_undo.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests make sure that the undo dialog works as expected.
+ */
+function runTests() {
+ // remove unpinned sites and undo it
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks("5");
+
+ yield addNewTabPageTab();
+ checkGrid("5p,0,1,2,3,4,6,7,8");
+
+ yield blockCell(4);
+ yield blockCell(4);
+ checkGrid("5p,0,1,2,6,7,8");
+
+ yield undo();
+ checkGrid("5p,0,1,2,4,6,7,8");
+
+ // now remove a pinned site and undo it
+ yield blockCell(0);
+ checkGrid("0,1,2,4,6,7,8");
+
+ yield undo();
+ checkGrid("5p,0,1,2,4,6,7,8");
+
+ // remove a site and restore all
+ yield blockCell(1);
+ checkGrid("5p,1,2,4,6,7,8");
+
+ yield undoAll();
+ checkGrid("5p,0,1,2,3,4,6,7,8");
+}
+
+function undo() {
+ let cw = getContentWindow();
+ let target = cw.document.getElementById("newtab-undo-button");
+ EventUtils.synthesizeMouseAtCenter(target, {}, cw);
+ whenPagesUpdated();
+}
+
+function undoAll() {
+ let cw = getContentWindow();
+ let target = cw.document.getElementById("newtab-undo-restore-button");
+ EventUtils.synthesizeMouseAtCenter(target, {}, cw);
+ whenPagesUpdated();
+} \ No newline at end of file
diff --git a/browser/base/content/test/newtab/browser_newtab_unpin.js b/browser/base/content/test/newtab/browser_newtab_unpin.js
new file mode 100644
index 000000000..6d2d45b1e
--- /dev/null
+++ b/browser/base/content/test/newtab/browser_newtab_unpin.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * These tests make sure that when a site gets unpinned it is either moved to
+ * its actual place in the grid or removed in case it's not on the grid anymore.
+ */
+function runTests() {
+ // we have a pinned link that didn't change its position since it was pinned.
+ // nothing should happend when we unpin it.
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks(",1");
+
+ yield addNewTabPageTab();
+ checkGrid("0,1p,2,3,4,5,6,7,8");
+
+ yield unpinCell(1);
+ checkGrid("0,1,2,3,4,5,6,7,8");
+
+ // we have a pinned link that is not anymore in the list of the most-visited
+ // links. this should disappear, the remaining links adjust their positions
+ // and a new link will appear at the end of the grid.
+ yield setLinks("0,1,2,3,4,5,6,7,8");
+ setPinnedLinks(",99");
+
+ yield addNewTabPageTab();
+ checkGrid("0,99p,1,2,3,4,5,6,7");
+
+ yield unpinCell(1);
+ checkGrid("0,1,2,3,4,5,6,7,8");
+
+ // we have a pinned link that changed its position since it was pinned. it
+ // should be moved to its new position after being unpinned.
+ yield setLinks("0,1,2,3,4,5,6,7");
+ setPinnedLinks(",1,,,,,,,0");
+
+ yield addNewTabPageTab();
+ checkGrid("2,1p,3,4,5,6,7,,0p");
+
+ yield unpinCell(1);
+ checkGrid("1,2,3,4,5,6,7,,0p");
+
+ yield unpinCell(8);
+ checkGrid("0,1,2,3,4,5,6,7,");
+
+ // we have pinned link that changed its position since it was pinned. the
+ // link will disappear from the grid because it's now a much lower priority
+ yield setLinks("0,1,2,3,4,5,6,7,8,9");
+ setPinnedLinks("9");
+
+ yield addNewTabPageTab();
+ checkGrid("9p,0,1,2,3,4,5,6,7");
+
+ yield unpinCell(0);
+ checkGrid("0,1,2,3,4,5,6,7,8");
+}
diff --git a/browser/base/content/test/newtab/head.js b/browser/base/content/test/newtab/head.js
new file mode 100644
index 000000000..b14d20f27
--- /dev/null
+++ b/browser/base/content/test/newtab/head.js
@@ -0,0 +1,390 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const PREF_NEWTAB_ENABLED = "browser.newtabpage.enabled";
+
+Services.prefs.setBoolPref(PREF_NEWTAB_ENABLED, true);
+
+let tmp = {};
+Cu.import("resource://gre/modules/NewTabUtils.jsm", tmp);
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+ .getService(Ci.mozIJSSubScriptLoader)
+ .loadSubScript("chrome://browser/content/sanitize.js", tmp);
+
+let {NewTabUtils, Sanitizer} = tmp;
+
+let uri = Services.io.newURI("about:newtab", null, null);
+let principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(uri);
+
+let gWindow = window;
+
+registerCleanupFunction(function () {
+ while (gWindow.gBrowser.tabs.length > 1)
+ gWindow.gBrowser.removeTab(gWindow.gBrowser.tabs[1]);
+
+ Services.prefs.clearUserPref(PREF_NEWTAB_ENABLED);
+});
+
+/**
+ * Provide the default test function to start our test runner.
+ */
+function test() {
+ TestRunner.run();
+}
+
+/**
+ * The test runner that controls the execution flow of our tests.
+ */
+let TestRunner = {
+ /**
+ * Starts the test runner.
+ */
+ run: function () {
+ waitForExplicitFinish();
+
+ this._iter = runTests();
+ this.next();
+ },
+
+ /**
+ * Runs the next available test or finishes if there's no test left.
+ */
+ next: function () {
+ try {
+ TestRunner._iter.next();
+ } catch (e if e instanceof StopIteration) {
+ TestRunner.finish();
+ }
+ },
+
+ /**
+ * Finishes all tests and cleans up.
+ */
+ finish: function () {
+ function cleanupAndFinish() {
+ clearHistory(function () {
+ whenPagesUpdated(finish);
+ NewTabUtils.restore();
+ });
+ }
+
+ let callbacks = NewTabUtils.links._populateCallbacks;
+ let numCallbacks = callbacks.length;
+
+ if (numCallbacks)
+ callbacks.splice(0, numCallbacks, cleanupAndFinish);
+ else
+ cleanupAndFinish();
+ }
+};
+
+/**
+ * Returns the selected tab's content window.
+ * @return The content window.
+ */
+function getContentWindow() {
+ return gWindow.gBrowser.selectedBrowser.contentWindow;
+}
+
+/**
+ * Returns the selected tab's content document.
+ * @return The content document.
+ */
+function getContentDocument() {
+ return gWindow.gBrowser.selectedBrowser.contentDocument;
+}
+
+/**
+ * Returns the newtab grid of the selected tab.
+ * @return The newtab grid.
+ */
+function getGrid() {
+ return getContentWindow().gGrid;
+}
+
+/**
+ * Returns the cell at the given index of the selected tab's newtab grid.
+ * @param aIndex The cell index.
+ * @return The newtab cell.
+ */
+function getCell(aIndex) {
+ return getGrid().cells[aIndex];
+}
+
+/**
+ * Allows to provide a list of links that is used to construct the grid.
+ * @param aLinksPattern the pattern (see below)
+ *
+ * Example: setLinks("1,2,3")
+ * Result: [{url: "http://example.com/#1", title: "site#1"},
+ * {url: "http://example.com/#2", title: "site#2"}
+ * {url: "http://example.com/#3", title: "site#3"}]
+ */
+function setLinks(aLinks) {
+ let links = aLinks;
+
+ if (typeof links == "string") {
+ links = aLinks.split(/\s*,\s*/).map(function (id) {
+ return {url: "http://example.com/#" + id, title: "site#" + id};
+ });
+ }
+
+ // Call populateCache() once to make sure that all link fetching that is
+ // currently in progress has ended. We clear the history, fill it with the
+ // given entries and call populateCache() now again to make sure the cache
+ // has the desired contents.
+ NewTabUtils.links.populateCache(function () {
+ clearHistory(function () {
+ fillHistory(links, function () {
+ NewTabUtils.links.populateCache(function () {
+ NewTabUtils.allPages.update();
+ TestRunner.next();
+ }, true);
+ });
+ });
+ });
+}
+
+function clearHistory(aCallback) {
+ Services.obs.addObserver(function observe(aSubject, aTopic, aData) {
+ Services.obs.removeObserver(observe, aTopic);
+ executeSoon(aCallback);
+ }, PlacesUtils.TOPIC_EXPIRATION_FINISHED, false);
+
+ PlacesUtils.history.removeAllPages();
+}
+
+function fillHistory(aLinks, aCallback) {
+ let numLinks = aLinks.length;
+ let transitionLink = Ci.nsINavHistoryService.TRANSITION_LINK;
+
+ for (let link of aLinks.reverse()) {
+ let place = {
+ uri: makeURI(link.url),
+ title: link.title,
+ visits: [{visitDate: Date.now() * 1000, transitionType: transitionLink}]
+ };
+
+ PlacesUtils.asyncHistory.updatePlaces(place, {
+ handleError: function () ok(false, "couldn't add visit to history"),
+ handleResult: function () {},
+ handleCompletion: function () {
+ if (--numLinks == 0)
+ aCallback();
+ }
+ });
+ }
+}
+
+/**
+ * Allows to specify the list of pinned links (that have a fixed position in
+ * the grid.
+ * @param aLinksPattern the pattern (see below)
+ *
+ * Example: setPinnedLinks("3,,1")
+ * Result: 'http://example.com/#3' is pinned in the first cell. 'http://example.com/#1' is
+ * pinned in the third cell.
+ */
+function setPinnedLinks(aLinks) {
+ let links = aLinks;
+
+ if (typeof links == "string") {
+ links = aLinks.split(/\s*,\s*/).map(function (id) {
+ if (id)
+ return {url: "http://example.com/#" + id, title: "site#" + id};
+ });
+ }
+
+ let string = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(Ci.nsISupportsString);
+ string.data = JSON.stringify(links);
+ Services.prefs.setComplexValue("browser.newtabpage.pinned",
+ Ci.nsISupportsString, string);
+
+ NewTabUtils.pinnedLinks.resetCache();
+ NewTabUtils.allPages.update();
+}
+
+/**
+ * Restore the grid state.
+ */
+function restore() {
+ whenPagesUpdated();
+ NewTabUtils.restore();
+}
+
+/**
+ * Creates a new tab containing 'about:newtab'.
+ */
+function addNewTabPageTab() {
+ let tab = gWindow.gBrowser.selectedTab = gWindow.gBrowser.addTab("about:newtab");
+ let browser = tab.linkedBrowser;
+
+ function whenNewTabLoaded() {
+ if (NewTabUtils.allPages.enabled) {
+ // Continue when the link cache has been populated.
+ NewTabUtils.links.populateCache(function () {
+ executeSoon(TestRunner.next);
+ });
+ } else {
+ // It's important that we call next() asynchronously.
+ // 'yield addNewTabPageTab()' would fail if next() is called
+ // synchronously because the iterator is already executing.
+ executeSoon(TestRunner.next);
+ }
+ }
+
+ // The new tab page might have been preloaded in the background.
+ if (browser.contentDocument.readyState == "complete") {
+ whenNewTabLoaded();
+ return;
+ }
+
+ // Wait for the new tab page to be loaded.
+ browser.addEventListener("load", function onLoad() {
+ browser.removeEventListener("load", onLoad, true);
+ whenNewTabLoaded();
+ }, true);
+}
+
+/**
+ * Compares the current grid arrangement with the given pattern.
+ * @param the pattern (see below)
+ * @param the array of sites to compare with (optional)
+ *
+ * Example: checkGrid("3p,2,,1p")
+ * Result: We expect the first cell to contain the pinned site 'http://example.com/#3'.
+ * The second cell contains 'http://example.com/#2'. The third cell is empty.
+ * The fourth cell contains the pinned site 'http://example.com/#4'.
+ */
+function checkGrid(aSitesPattern, aSites) {
+ let length = aSitesPattern.split(",").length;
+ let sites = (aSites || getGrid().sites).slice(0, length);
+ let current = sites.map(function (aSite) {
+ if (!aSite)
+ return "";
+
+ let pinned = aSite.isPinned();
+ let pinButton = aSite.node.querySelector(".newtab-control-pin");
+ let hasPinnedAttr = pinButton.hasAttribute("pinned");
+
+ if (pinned != hasPinnedAttr)
+ ok(false, "invalid state (site.isPinned() != site[pinned])");
+
+ return aSite.url.replace(/^http:\/\/example\.com\/#(\d+)$/, "$1") + (pinned ? "p" : "");
+ });
+
+ is(current, aSitesPattern, "grid status = " + aSitesPattern);
+}
+
+/**
+ * Blocks a site from the grid.
+ * @param aIndex The cell index.
+ */
+function blockCell(aIndex) {
+ whenPagesUpdated();
+ getCell(aIndex).site.block();
+}
+
+/**
+ * Pins a site on a given position.
+ * @param aIndex The cell index.
+ * @param aPinIndex The index the defines where the site should be pinned.
+ */
+function pinCell(aIndex, aPinIndex) {
+ getCell(aIndex).site.pin(aPinIndex);
+}
+
+/**
+ * Unpins the given cell's site.
+ * @param aIndex The cell index.
+ */
+function unpinCell(aIndex) {
+ whenPagesUpdated();
+ getCell(aIndex).site.unpin();
+}
+
+/**
+ * Simulates a drop and drop operation.
+ * @param aDropIndex The cell index of the drop target.
+ * @param aDragIndex The cell index containing the dragged site (optional).
+ */
+function simulateDrop(aDropIndex, aDragIndex) {
+ let draggedSite;
+ let {gDrag: drag, gDrop: drop} = getContentWindow();
+ let event = createDragEvent("drop", "http://example.com/#99\nblank");
+
+ if (typeof aDragIndex != "undefined")
+ draggedSite = getCell(aDragIndex).site;
+
+ if (draggedSite)
+ drag.start(draggedSite, event);
+
+ whenPagesUpdated();
+ drop.drop(getCell(aDropIndex), event);
+
+ if (draggedSite)
+ drag.end(draggedSite);
+}
+
+/**
+ * Sends a custom drag event to a given DOM element.
+ * @param aEventType The drag event's type.
+ * @param aTarget The DOM element that the event is dispatched to.
+ * @param aData The event's drag data (optional).
+ */
+function sendDragEvent(aEventType, aTarget, aData) {
+ let event = createDragEvent(aEventType, aData);
+ let ifaceReq = getContentWindow().QueryInterface(Ci.nsIInterfaceRequestor);
+ let windowUtils = ifaceReq.getInterface(Ci.nsIDOMWindowUtils);
+ windowUtils.dispatchDOMEventViaPresShell(aTarget, event, true);
+}
+
+/**
+ * Creates a custom drag event.
+ * @param aEventType The drag event's type.
+ * @param aData The event's drag data (optional).
+ * @return The drag event.
+ */
+function createDragEvent(aEventType, aData) {
+ let dataTransfer = {
+ mozUserCancelled: false,
+ setData: function () null,
+ setDragImage: function () null,
+ getData: function () aData,
+
+ types: {
+ contains: function (aType) aType == "text/x-moz-url"
+ },
+
+ mozGetDataAt: function (aType, aIndex) {
+ if (aIndex || aType != "text/x-moz-url")
+ return null;
+
+ return aData;
+ }
+ };
+
+ let event = getContentDocument().createEvent("DragEvents");
+ event.initDragEvent(aEventType, true, true, getContentWindow(), 0, 0, 0, 0, 0,
+ false, false, false, false, 0, null, dataTransfer);
+
+ return event;
+}
+
+/**
+ * Resumes testing when all pages have been updated.
+ */
+function whenPagesUpdated(aCallback) {
+ let page = {
+ update: function () {
+ NewTabUtils.allPages.unregister(this);
+ executeSoon(aCallback || TestRunner.next);
+ }
+ };
+
+ NewTabUtils.allPages.register(page);
+ registerCleanupFunction(function () {
+ NewTabUtils.allPages.unregister(page);
+ });
+}
diff --git a/browser/base/content/test/newtab/moz.build b/browser/base/content/test/newtab/moz.build
new file mode 100644
index 000000000..895d11993
--- /dev/null
+++ b/browser/base/content/test/newtab/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
diff --git a/browser/base/content/test/offlineChild.cacheManifest b/browser/base/content/test/offlineChild.cacheManifest
new file mode 100644
index 000000000..091fe7194
--- /dev/null
+++ b/browser/base/content/test/offlineChild.cacheManifest
@@ -0,0 +1,2 @@
+CACHE MANIFEST
+offlineChild.html
diff --git a/browser/base/content/test/offlineChild.cacheManifest^headers^ b/browser/base/content/test/offlineChild.cacheManifest^headers^
new file mode 100644
index 000000000..257f2eb60
--- /dev/null
+++ b/browser/base/content/test/offlineChild.cacheManifest^headers^
@@ -0,0 +1 @@
+Content-Type: text/cache-manifest
diff --git a/browser/base/content/test/offlineChild.html b/browser/base/content/test/offlineChild.html
new file mode 100644
index 000000000..43f225b3b
--- /dev/null
+++ b/browser/base/content/test/offlineChild.html
@@ -0,0 +1,20 @@
+<html manifest="offlineChild.cacheManifest">
+<head>
+<title></title>
+<script type="text/javascript">
+
+function finish(success) {
+ window.parent.postMessage(success ? "success" : "failure", "*");
+}
+
+applicationCache.oncached = function() { finish(true); }
+applicationCache.onnoupdate = function() { finish(true); }
+applicationCache.onerror = function() { finish(false); }
+
+</script>
+</head>
+
+<body>
+<h1>Child</h1>
+</body>
+</html>
diff --git a/browser/base/content/test/offlineChild2.cacheManifest b/browser/base/content/test/offlineChild2.cacheManifest
new file mode 100644
index 000000000..19efe54fe
--- /dev/null
+++ b/browser/base/content/test/offlineChild2.cacheManifest
@@ -0,0 +1,2 @@
+CACHE MANIFEST
+offlineChild2.html
diff --git a/browser/base/content/test/offlineChild2.cacheManifest^headers^ b/browser/base/content/test/offlineChild2.cacheManifest^headers^
new file mode 100644
index 000000000..257f2eb60
--- /dev/null
+++ b/browser/base/content/test/offlineChild2.cacheManifest^headers^
@@ -0,0 +1 @@
+Content-Type: text/cache-manifest
diff --git a/browser/base/content/test/offlineChild2.html b/browser/base/content/test/offlineChild2.html
new file mode 100644
index 000000000..ac762e759
--- /dev/null
+++ b/browser/base/content/test/offlineChild2.html
@@ -0,0 +1,20 @@
+<html manifest="offlineChild2.cacheManifest">
+<head>
+<title></title>
+<script type="text/javascript">
+
+function finish(success) {
+ window.parent.postMessage(success ? "success" : "failure", "*");
+}
+
+applicationCache.oncached = function() { finish(true); }
+applicationCache.onnoupdate = function() { finish(true); }
+applicationCache.onerror = function() { finish(false); }
+
+</script>
+</head>
+
+<body>
+<h1>Child</h1>
+</body>
+</html>
diff --git a/browser/base/content/test/offlineEvent.cacheManifest b/browser/base/content/test/offlineEvent.cacheManifest
new file mode 100644
index 000000000..091fe7194
--- /dev/null
+++ b/browser/base/content/test/offlineEvent.cacheManifest
@@ -0,0 +1,2 @@
+CACHE MANIFEST
+offlineChild.html
diff --git a/browser/base/content/test/offlineEvent.cacheManifest^headers^ b/browser/base/content/test/offlineEvent.cacheManifest^headers^
new file mode 100644
index 000000000..257f2eb60
--- /dev/null
+++ b/browser/base/content/test/offlineEvent.cacheManifest^headers^
@@ -0,0 +1 @@
+Content-Type: text/cache-manifest
diff --git a/browser/base/content/test/offlineEvent.html b/browser/base/content/test/offlineEvent.html
new file mode 100644
index 000000000..f6e2494e2
--- /dev/null
+++ b/browser/base/content/test/offlineEvent.html
@@ -0,0 +1,9 @@
+<html manifest="offlineEvent.cacheManifest">
+<head>
+<title></title>
+</head>
+
+<body>
+<h1>Child</h1>
+</body>
+</html>
diff --git a/browser/base/content/test/offlineQuotaNotification.cacheManifest b/browser/base/content/test/offlineQuotaNotification.cacheManifest
new file mode 100644
index 000000000..2e210abd2
--- /dev/null
+++ b/browser/base/content/test/offlineQuotaNotification.cacheManifest
@@ -0,0 +1,7 @@
+CACHE MANIFEST
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+# store a "large" file so an "over quota warning" will be issued - any file
+# larger than 1kb and in '_BROWSER_FILES' should be right...
+title_test.svg
diff --git a/browser/base/content/test/offlineQuotaNotification.html b/browser/base/content/test/offlineQuotaNotification.html
new file mode 100644
index 000000000..b1b91bf9e
--- /dev/null
+++ b/browser/base/content/test/offlineQuotaNotification.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html manifest="offlineQuotaNotification.cacheManifest">
+<head>
+ <meta charset="utf-8">
+ <title>Test offline app quota notification</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+</html>
diff --git a/browser/base/content/test/page_style_sample.html b/browser/base/content/test/page_style_sample.html
new file mode 100644
index 000000000..56637a58d
--- /dev/null
+++ b/browser/base/content/test/page_style_sample.html
@@ -0,0 +1,40 @@
+<html>
+ <head>
+ <title>Test for page style menu</title>
+ <!-- data-state values:
+ 0: should not appear in the page style menu
+ 0-todo: should not appear in the page style menu, but does
+ 1: should appear in the page style menu
+ 2: should appear in the page style menu as the selected stylesheet -->
+ <link data-state="1" href="404.css" title="1" rel="alternate stylesheet">
+ <link data-state="0" title="2" rel="alternate stylesheet">
+ <link data-state="0" href="404.css" rel="alternate stylesheet">
+ <link data-state="0" href="404.css" title="" rel="alternate stylesheet">
+ <link data-state="1" href="404.css" title="3" rel="stylesheet alternate">
+ <link data-state="1" href="404.css" title="4" rel=" alternate stylesheet ">
+ <link data-state="1" href="404.css" title="5" rel="alternate stylesheet">
+ <link data-state="2" href="404.css" title="6" rel="stylesheet">
+ <link data-state="1" href="404.css" title="7" rel="foo stylesheet">
+ <link data-state="0" href="404.css" title="8" rel="alternate">
+ <link data-state="1" href="404.css" title="9" rel="alternate STYLEsheet">
+ <link data-state="1" href="404.css" title="10" rel="alternate stylesheet" media="">
+ <link data-state="1" href="404.css" title="11" rel="alternate stylesheet" media="all">
+ <link data-state="1" href="404.css" title="12" rel="alternate stylesheet" media="ALL ">
+ <link data-state="1" href="404.css" title="13" rel="alternate stylesheet" media="screen">
+ <link data-state="1" href="404.css" title="14" rel="alternate stylesheet" media=" Screen">
+ <link data-state="0" href="404.css" title="15" rel="alternate stylesheet" media="screen foo">
+ <link data-state="0" href="404.css" title="16" rel="alternate stylesheet" media="all screen">
+ <link data-state="0" href="404.css" title="17" rel="alternate stylesheet" media="foo bar">
+ <link data-state="1" href="404.css" title="18" rel="alternate stylesheet" media="all,screen">
+ <link data-state="1" href="404.css" title="19" rel="alternate stylesheet" media="all, screen">
+ <link data-state="0" href="404.css" title="20" rel="alternate stylesheet" media="all screen">
+ <link data-state="0" href="404.css" title="21" rel="alternate stylesheet" media="foo">
+ <link data-state="0" href="404.css" title="22" rel="alternate stylesheet" media="allscreen">
+ <link data-state="0" href="404.css" title="23" rel="alternate stylesheet" media="_all">
+ <link data-state="0" href="404.css" title="24" rel="alternate stylesheet" media="not screen">
+ <link data-state="1" href="404.css" title="25" rel="alternate stylesheet" media="only screen">
+ <link data-state="1" href="404.css" title="26" rel="alternate stylesheet" media="screen and (min-device-width: 1px)">
+ <link data-state="0" href="404.css" title="27" rel="alternate stylesheet" media="screen and (max-device-width: 1px)">
+ </head>
+ <body></body>
+</html>
diff --git a/browser/base/content/test/pluginCrashCommentAndURL.html b/browser/base/content/test/pluginCrashCommentAndURL.html
new file mode 100644
index 000000000..711a19ed3
--- /dev/null
+++ b/browser/base/content/test/pluginCrashCommentAndURL.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script type="text/javascript">
+ function crash() {
+ var plugin = document.getElementById("plugin");
+ var argStr = decodeURIComponent(window.location.search.substr(1));
+ if (argStr) {
+ var args = JSON.parse(argStr);
+ for (var key in args)
+ plugin.setAttribute(key, args[key]);
+ }
+ try {
+ plugin.crash();
+ }
+ catch (err) {}
+ }
+ </script>
+ </head>
+ <body onload="crash();">
+ <embed id="plugin" type="application/x-test"
+ width="400" height="400"
+ drawmode="solid" color="FF00FFFF">
+ </embed>
+ </body>
+</html>
diff --git a/browser/base/content/test/plugin_add_dynamically.html b/browser/base/content/test/plugin_add_dynamically.html
new file mode 100644
index 000000000..3fdaf110c
--- /dev/null
+++ b/browser/base/content/test/plugin_add_dynamically.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<script>
+function addPlugin(type="application/x-test") {
+ var embed = document.createElement("embed");
+ embed.style.width = "200px";
+ embed.style.height = "200px";
+ embed.setAttribute("type", type);
+ return document.body.appendChild(embed);
+}
+</script>
+</body>
+</html>
diff --git a/browser/base/content/test/plugin_alternate_content.html b/browser/base/content/test/plugin_alternate_content.html
new file mode 100644
index 000000000..f8acc833c
--- /dev/null
+++ b/browser/base/content/test/plugin_alternate_content.html
@@ -0,0 +1,9 @@
+<!-- bug 739575 -->
+<html>
+<head><meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
+</head>
+<body>
+<object id="test" type="application/x-test" style="height: 200px; width:200px">
+<p><a href="about:blank">you should not see this link when plugins are click-to-play</a></p>
+</object>
+</body></html>
diff --git a/browser/base/content/test/plugin_both.html b/browser/base/content/test/plugin_both.html
new file mode 100644
index 000000000..2f3d2efe8
--- /dev/null
+++ b/browser/base/content/test/plugin_both.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="unknown" style="width: 100px; height: 100px" type="application/x-unknown">
+<embed id="test" style="width: 100px; height: 100px" type="application/x-test">
+</body>
+</html>
diff --git a/browser/base/content/test/plugin_both2.html b/browser/base/content/test/plugin_both2.html
new file mode 100644
index 000000000..ba605d6e8
--- /dev/null
+++ b/browser/base/content/test/plugin_both2.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="test" style="width: 100px; height: 100px" type="application/x-test">
+<embed id="unknown" style="width: 100px; height: 100px" type="application/x-unknown">
+</body>
+</html>
diff --git a/browser/base/content/test/plugin_bug744745.html b/browser/base/content/test/plugin_bug744745.html
new file mode 100644
index 000000000..d0691c9c0
--- /dev/null
+++ b/browser/base/content/test/plugin_bug744745.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head><meta charset="utf-8"/></head>
+<body>
+<style>
+.x {
+ opacity: 0 !important;
+}
+</style>
+<object id="test" class="x" type="application/x-test" width=200 height=200></object>
+</body>
+</html>
diff --git a/browser/base/content/test/plugin_bug749455.html b/browser/base/content/test/plugin_bug749455.html
new file mode 100644
index 000000000..831dc82f7
--- /dev/null
+++ b/browser/base/content/test/plugin_bug749455.html
@@ -0,0 +1,8 @@
+<!-- bug 749455 -->
+<html>
+<head><meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
+</head>
+<body>
+<embed src="plugin_bug749455.html" type="application/x-test" width="100px" height="100px"></embed>
+</body>
+</html>
diff --git a/browser/base/content/test/plugin_bug752516.html b/browser/base/content/test/plugin_bug752516.html
new file mode 100644
index 000000000..6121e7068
--- /dev/null
+++ b/browser/base/content/test/plugin_bug752516.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8"/>
+ <style type="text/css">
+ div {
+ padding: 2%;
+ position: absolute;
+ top: 0; bottom: 0;
+ left: 0; right: 0;
+ text-align: center;
+ border: 4px solid red;
+ }
+ </style>
+</head>
+<body>
+ <div id="container">
+ <object id="test" type="application/x-test" width="159" height="91"></object>
+ </div>
+ <div id="overlay">
+ <h1>overlay</h1>
+ </div>
+</body>
+</html>
diff --git a/browser/base/content/test/plugin_bug787619.html b/browser/base/content/test/plugin_bug787619.html
new file mode 100644
index 000000000..cb91116f0
--- /dev/null
+++ b/browser/base/content/test/plugin_bug787619.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head><meta charset="utf-8"/></head>
+<body>
+ <a id="wrapper">
+ <embed id="plugin" style="width: 200px; height: 200px" type="application/x-test">
+ </a>
+</body>
+</html>
diff --git a/browser/base/content/test/plugin_bug797677.html b/browser/base/content/test/plugin_bug797677.html
new file mode 100644
index 000000000..1545f3647
--- /dev/null
+++ b/browser/base/content/test/plugin_bug797677.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html>
+<head><meta charset="utf-8"/></head>
+<body><embed id="plugin" type="9000"></embed></body>
+</html>
diff --git a/browser/base/content/test/plugin_bug820497.html b/browser/base/content/test/plugin_bug820497.html
new file mode 100644
index 000000000..4884e9dbe
--- /dev/null
+++ b/browser/base/content/test/plugin_bug820497.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head><meta charset="utf-8"/></head>
+<body>
+<object id="test" type="application/x-test" width=200 height=200></object>
+<script>
+ function addSecondPlugin() {
+ var object = document.createElement("object");
+ object.type = "application/x-second-test";
+ object.width = 200;
+ object.height = 200;
+ object.id = "secondtest";
+ document.body.appendChild(object);
+ }
+</script>
+</body>
+</html>
diff --git a/browser/base/content/test/plugin_clickToPlayAllow.html b/browser/base/content/test/plugin_clickToPlayAllow.html
new file mode 100644
index 000000000..3f5df1984
--- /dev/null
+++ b/browser/base/content/test/plugin_clickToPlayAllow.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="test" style="width: 200px; height: 200px" type="application/x-test">
+</body>
+</html>
diff --git a/browser/base/content/test/plugin_clickToPlayDeny.html b/browser/base/content/test/plugin_clickToPlayDeny.html
new file mode 100644
index 000000000..3f5df1984
--- /dev/null
+++ b/browser/base/content/test/plugin_clickToPlayDeny.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="test" style="width: 200px; height: 200px" type="application/x-test">
+</body>
+</html>
diff --git a/browser/base/content/test/plugin_data_url.html b/browser/base/content/test/plugin_data_url.html
new file mode 100644
index 000000000..77e101144
--- /dev/null
+++ b/browser/base/content/test/plugin_data_url.html
@@ -0,0 +1,11 @@
+<html>
+<body>
+ <a id="data-link-1" href='data:text/html,<embed id="test" style="width: 200px; height: 200px" type="application/x-test"/>'>
+ data: with one plugin
+ </a><br />
+ <a id="data-link-2" href='data:text/html,<embed id="test1" style="width: 200px; height: 200px" type="application/x-test"/><embed id="test2" style="width: 200px; height: 200px" type="application/x-second-test"/>'>
+ data: with two plugins
+ </a><br />
+ <object id="test" style="width: 200px; height: 200px" type="application/x-test"></object>
+</body>
+</html>
diff --git a/browser/base/content/test/plugin_hidden_to_visible.html b/browser/base/content/test/plugin_hidden_to_visible.html
new file mode 100644
index 000000000..e8d92c68c
--- /dev/null
+++ b/browser/base/content/test/plugin_hidden_to_visible.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+ <div id="container" style="display: none">
+ <object id="plugin" type="application/x-test" style="width: 200px; height: 200px;"></object>
+ </div>
+</body>
+</html>
diff --git a/browser/base/content/test/plugin_test.html b/browser/base/content/test/plugin_test.html
new file mode 100644
index 000000000..3f5df1984
--- /dev/null
+++ b/browser/base/content/test/plugin_test.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="test" style="width: 200px; height: 200px" type="application/x-test">
+</body>
+</html>
diff --git a/browser/base/content/test/plugin_test2.html b/browser/base/content/test/plugin_test2.html
new file mode 100644
index 000000000..95614c930
--- /dev/null
+++ b/browser/base/content/test/plugin_test2.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="test1" style="width: 200px; height: 200px" type="application/x-test">
+<embed id="test2" style="width: 200px; height: 200px" type="application/x-test">
+</body>
+</html>
diff --git a/browser/base/content/test/plugin_test3.html b/browser/base/content/test/plugin_test3.html
new file mode 100644
index 000000000..af14c0024
--- /dev/null
+++ b/browser/base/content/test/plugin_test3.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="test" style="width: 0px; height: 0px" type="application/x-test">
+</body>
+</html>
diff --git a/browser/base/content/test/plugin_two_types.html b/browser/base/content/test/plugin_two_types.html
new file mode 100644
index 000000000..2359d2ec1
--- /dev/null
+++ b/browser/base/content/test/plugin_two_types.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head><meta charset="utf-8"/></head>
+<body>
+<embed id="test" style="width: 200px; height: 200px" type="application/x-test"/>
+<embed id="secondtestA" style="width: 200px; height: 200px" type="application/x-second-test"/>
+<embed id="secondtestB" style="width: 200px; height: 200px" type="application/x-second-test"/>
+</body>
+</html>
diff --git a/browser/base/content/test/plugin_unknown.html b/browser/base/content/test/plugin_unknown.html
new file mode 100644
index 000000000..a35674549
--- /dev/null
+++ b/browser/base/content/test/plugin_unknown.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+</head>
+<body>
+<embed id="unknown" style="width: 100px; height: 100px" type="application/x-unknown">
+</body>
+</html>
diff --git a/browser/base/content/test/print_postdata.sjs b/browser/base/content/test/print_postdata.sjs
new file mode 100644
index 000000000..4175a2480
--- /dev/null
+++ b/browser/base/content/test/print_postdata.sjs
@@ -0,0 +1,22 @@
+const CC = Components.Constructor;
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream",
+ "setInputStream");
+
+function handleRequest(request, response) {
+ response.setHeader("Content-Type", "text/plain", false);
+ if (request.method == "GET") {
+ response.write(request.queryString);
+ } else {
+ var body = new BinaryInputStream(request.bodyInputStream);
+
+ var avail;
+ var bytes = [];
+
+ while ((avail = body.available()) > 0)
+ Array.prototype.push.apply(bytes, body.readByteArray(avail));
+
+ var data = String.fromCharCode.apply(null, bytes);
+ response.bodyOutputStream.write(data, data.length);
+ }
+}
diff --git a/browser/base/content/test/privateBrowsingMode.js b/browser/base/content/test/privateBrowsingMode.js
new file mode 100644
index 000000000..a624d5281
--- /dev/null
+++ b/browser/base/content/test/privateBrowsingMode.js
@@ -0,0 +1,3 @@
+// This file is only present in per-window private browsing buikds.
+var perWindowPrivateBrowsing = true;
+
diff --git a/browser/base/content/test/redirect_bug623155.sjs b/browser/base/content/test/redirect_bug623155.sjs
new file mode 100644
index 000000000..64c6f143b
--- /dev/null
+++ b/browser/base/content/test/redirect_bug623155.sjs
@@ -0,0 +1,16 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const REDIRECT_TO = "https://www.bank1.com/"; // Bad-cert host.
+
+function handleRequest(aRequest, aResponse) {
+ // Set HTTP Status
+ aResponse.setStatusLine(aRequest.httpVersion, 301, "Moved Permanently");
+
+ // Set redirect URI, mirroring the hash value.
+ let hash = (/\#.+/.test(aRequest.path))?
+ "#" + aRequest.path.split("#")[1]:
+ "";
+ aResponse.setHeader("Location", REDIRECT_TO + hash);
+}
diff --git a/browser/base/content/test/social/Makefile.in b/browser/base/content/test/social/Makefile.in
new file mode 100644
index 000000000..1aabbcdca
--- /dev/null
+++ b/browser/base/content/test/social/Makefile.in
@@ -0,0 +1,47 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEPTH = @DEPTH@
+topsrcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+relativesrcdir = @relativesrcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MOCHITEST_BROWSER_FILES = \
+ head.js \
+ blocklist.xml \
+ browser_blocklist.js \
+ browser_defaults.js \
+ browser_addons.js \
+ browser_chat_tearoff.js \
+ browser_social_activation.js \
+ browser_social_perwindowPB.js \
+ browser_social_toolbar.js \
+ browser_social_markButton.js \
+ browser_social_sidebar.js \
+ browser_social_flyout.js \
+ browser_social_mozSocial_API.js \
+ browser_social_isVisible.js \
+ browser_social_chatwindow.js \
+ browser_social_chatwindow_resize.js \
+ browser_social_chatwindowfocus.js \
+ browser_social_multiprovider.js \
+ browser_social_errorPage.js \
+ browser_social_window.js \
+ social_activate.html \
+ social_activate_iframe.html \
+ browser_share.js \
+ social_panel.html \
+ social_mark_image.png \
+ social_sidebar.html \
+ social_chat.html \
+ social_flyout.html \
+ social_window.html \
+ social_worker.js \
+ share.html \
+ $(NULL)
+
+include $(topsrcdir)/config/rules.mk
diff --git a/browser/base/content/test/social/blocklist.xml b/browser/base/content/test/social/blocklist.xml
new file mode 100644
index 000000000..2e3665c36
--- /dev/null
+++ b/browser/base/content/test/social/blocklist.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist">
+ <emItems>
+ <emItem blockID="s1" id="test1.example.com@services.mozilla.org"></emItem>
+ </emItems>
+</blocklist>
diff --git a/browser/base/content/test/social/browser_addons.js b/browser/base/content/test/social/browser_addons.js
new file mode 100644
index 000000000..1e3b336bd
--- /dev/null
+++ b/browser/base/content/test/social/browser_addons.js
@@ -0,0 +1,327 @@
+
+
+let AddonManager = Cu.import("resource://gre/modules/AddonManager.jsm", {}).AddonManager;
+let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
+
+const ADDON_TYPE_SERVICE = "service";
+const ID_SUFFIX = "@services.mozilla.org";
+const STRING_TYPE_NAME = "type.%ID%.name";
+const XPINSTALL_URL = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
+
+let manifest = { // builtin provider
+ name: "provider 1",
+ origin: "https://example.com",
+ sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
+ workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
+ iconURL: "https://example.com/browser/browser/base/content/test/moz.png"
+};
+let manifest2 = { // used for testing install
+ name: "provider 2",
+ origin: "https://test1.example.com",
+ sidebarURL: "https://test1.example.com/browser/browser/base/content/test/social/social_sidebar.html",
+ workerURL: "https://test1.example.com/browser/browser/base/content/test/social/social_worker.js",
+ iconURL: "https://test1.example.com/browser/browser/base/content/test/moz.png",
+ version: 1
+};
+
+function test() {
+ waitForExplicitFinish();
+
+ let prefname = getManifestPrefname(manifest);
+ setBuiltinManifestPref(prefname, manifest);
+ // ensure that manifest2 is NOT showing as builtin
+ is(SocialService.getOriginActivationType(manifest.origin), "builtin", "manifest is builtin");
+ is(SocialService.getOriginActivationType(manifest2.origin), "foreign", "manifest2 is not builtin");
+
+ Services.prefs.setBoolPref("social.remote-install.enabled", true);
+ runSocialTests(tests, undefined, undefined, function () {
+ Services.prefs.clearUserPref("social.remote-install.enabled");
+ // clear our builtin pref
+ ok(!Services.prefs.prefHasUserValue(prefname), "manifest is not in user-prefs");
+ resetBuiltinManifestPref(prefname);
+ // just in case the tests failed, clear these here as well
+ Services.prefs.clearUserPref("social.whitelist");
+ Services.prefs.clearUserPref("social.directories");
+ finish();
+ });
+}
+
+function installListener(next, aManifest) {
+ let expectEvent = "onInstalling";
+ let prefname = getManifestPrefname(aManifest);
+ // wait for the actual removal to call next
+ SocialService.registerProviderListener(function providerListener(topic, data) {
+ if (topic == "provider-removed") {
+ SocialService.unregisterProviderListener(providerListener);
+ executeSoon(next);
+ }
+ });
+
+ return {
+ onInstalling: function(addon) {
+ is(expectEvent, "onInstalling", "install started");
+ is(addon.manifest.origin, aManifest.origin, "provider about to be installed");
+ ok(!Services.prefs.prefHasUserValue(prefname), "manifest is not in user-prefs");
+ expectEvent = "onInstalled";
+ },
+ onInstalled: function(addon) {
+ is(addon.manifest.origin, aManifest.origin, "provider installed");
+ ok(addon.installDate.getTime() > 0, "addon has installDate");
+ ok(addon.updateDate.getTime() > 0, "addon has updateDate");
+ ok(Services.prefs.prefHasUserValue(prefname), "manifest is in user-prefs");
+ expectEvent = "onUninstalling";
+ },
+ onUninstalling: function(addon) {
+ is(expectEvent, "onUninstalling", "uninstall started");
+ is(addon.manifest.origin, aManifest.origin, "provider about to be uninstalled");
+ ok(Services.prefs.prefHasUserValue(prefname), "manifest is in user-prefs");
+ expectEvent = "onUninstalled";
+ },
+ onUninstalled: function(addon) {
+ is(expectEvent, "onUninstalled", "provider has been uninstalled");
+ is(addon.manifest.origin, aManifest.origin, "provider uninstalled");
+ ok(!Services.prefs.prefHasUserValue(prefname), "manifest is not in user-prefs");
+ AddonManager.removeAddonListener(this);
+ }
+ };
+}
+
+var tests = {
+ testAddonEnableToggle: function(next) {
+ let expectEvent;
+ let prefname = getManifestPrefname(manifest);
+ let listener = {
+ onEnabled: function(addon) {
+ is(expectEvent, "onEnabled", "provider onEnabled");
+ ok(!addon.userDisabled, "provider enabled");
+ executeSoon(function() {
+ expectEvent = "onDisabling";
+ addon.userDisabled = true;
+ });
+ },
+ onEnabling: function(addon) {
+ is(expectEvent, "onEnabling", "provider onEnabling");
+ expectEvent = "onEnabled";
+ },
+ onDisabled: function(addon) {
+ is(expectEvent, "onDisabled", "provider onDisabled");
+ ok(addon.userDisabled, "provider disabled");
+ AddonManager.removeAddonListener(listener);
+ // clear the provider user-level pref
+ Services.prefs.clearUserPref(prefname);
+ executeSoon(next);
+ },
+ onDisabling: function(addon) {
+ is(expectEvent, "onDisabling", "provider onDisabling");
+ expectEvent = "onDisabled";
+ }
+ };
+ AddonManager.addAddonListener(listener);
+
+ // we're only testing enable disable, so we quickly set the user-level pref
+ // for this provider and test enable/disable toggling
+ setManifestPref(prefname, manifest);
+ ok(Services.prefs.prefHasUserValue(prefname), "manifest is in user-prefs");
+ AddonManager.getAddonsByTypes([ADDON_TYPE_SERVICE], function(addons) {
+ for (let addon of addons) {
+ if (addon.userDisabled) {
+ expectEvent = "onEnabling";
+ addon.userDisabled = false;
+ // only test with one addon
+ return;
+ }
+ }
+ ok(false, "no addons toggled");
+ next();
+ });
+ },
+ testProviderEnableToggle: function(next) {
+ // enable and disabel a provider from the SocialService interface, check
+ // that the addon manager is updated
+
+ let expectEvent;
+ let prefname = getManifestPrefname(manifest);
+
+ let listener = {
+ onEnabled: function(addon) {
+ is(expectEvent, "onEnabled", "provider onEnabled");
+ is(addon.manifest.origin, manifest.origin, "provider enabled");
+ ok(!addon.userDisabled, "provider !userDisabled");
+ },
+ onEnabling: function(addon) {
+ is(expectEvent, "onEnabling", "provider onEnabling");
+ is(addon.manifest.origin, manifest.origin, "provider about to be enabled");
+ expectEvent = "onEnabled";
+ },
+ onDisabled: function(addon) {
+ is(expectEvent, "onDisabled", "provider onDisabled");
+ is(addon.manifest.origin, manifest.origin, "provider disabled");
+ ok(addon.userDisabled, "provider userDisabled");
+ },
+ onDisabling: function(addon) {
+ is(expectEvent, "onDisabling", "provider onDisabling");
+ is(addon.manifest.origin, manifest.origin, "provider about to be disabled");
+ expectEvent = "onDisabled";
+ }
+ };
+ AddonManager.addAddonListener(listener);
+
+ expectEvent = "onEnabling";
+ setManifestPref(prefname, manifest);
+ SocialService.addBuiltinProvider(manifest.origin, function(provider) {
+ expectEvent = "onDisabling";
+ SocialService.removeProvider(provider.origin, function() {
+ AddonManager.removeAddonListener(listener);
+ Services.prefs.clearUserPref(prefname);
+ next();
+ });
+ });
+ },
+ testForeignInstall: function(next) {
+ AddonManager.addAddonListener(installListener(next, manifest2));
+
+ // we expect the addon install dialog to appear, we need to accept the
+ // install from the dialog.
+ info("Waiting for install dialog");
+ let panel = document.getElementById("servicesInstall-notification");
+ PopupNotifications.panel.addEventListener("popupshown", function onpopupshown() {
+ PopupNotifications.panel.removeEventListener("popupshown", onpopupshown);
+ info("servicesInstall-notification panel opened");
+ panel.button.click();
+ })
+
+ let activationURL = manifest2.origin + "/browser/browser/base/content/test/social/social_activate.html"
+ addTab(activationURL, function(tab) {
+ let doc = tab.linkedBrowser.contentDocument;
+ let installFrom = doc.nodePrincipal.origin;
+ Services.prefs.setCharPref("social.whitelist", "");
+ is(SocialService.getOriginActivationType(installFrom), "foreign", "testing foriegn install");
+ Social.installProvider(doc, manifest2, function(addonManifest) {
+ Services.prefs.clearUserPref("social.whitelist");
+ SocialService.addBuiltinProvider(addonManifest.origin, function(provider) {
+ Social.uninstallProvider(addonManifest.origin);
+ gBrowser.removeTab(tab);
+ });
+ });
+ });
+ },
+ testBuiltinInstallWithoutManifest: function(next) {
+ // send installProvider null for the manifest
+ AddonManager.addAddonListener(installListener(next, manifest));
+
+ let prefname = getManifestPrefname(manifest);
+ let activationURL = manifest.origin + "/browser/browser/base/content/test/social/social_activate.html"
+ addTab(activationURL, function(tab) {
+ let doc = tab.linkedBrowser.contentDocument;
+ let installFrom = doc.nodePrincipal.origin;
+ is(SocialService.getOriginActivationType(installFrom), "builtin", "testing builtin install");
+ ok(!Services.prefs.prefHasUserValue(prefname), "manifest is not in user-prefs");
+ Social.installProvider(doc, null, function(addonManifest) {
+ ok(Services.prefs.prefHasUserValue(prefname), "manifest is in user-prefs");
+ SocialService.addBuiltinProvider(addonManifest.origin, function(provider) {
+ Social.uninstallProvider(addonManifest.origin);
+ gBrowser.removeTab(tab);
+ });
+ });
+ });
+ },
+ testBuiltinInstall: function(next) {
+ // send installProvider a json object for the manifest
+ AddonManager.addAddonListener(installListener(next, manifest));
+
+ let prefname = getManifestPrefname(manifest);
+ let activationURL = manifest.origin + "/browser/browser/base/content/test/social/social_activate.html"
+ addTab(activationURL, function(tab) {
+ let doc = tab.linkedBrowser.contentDocument;
+ let installFrom = doc.nodePrincipal.origin;
+ is(SocialService.getOriginActivationType(installFrom), "builtin", "testing builtin install");
+ ok(!Services.prefs.prefHasUserValue(prefname), "manifest is not in user-prefs");
+ Social.installProvider(doc, manifest, function(addonManifest) {
+ ok(Services.prefs.prefHasUserValue(prefname), "manifest is in user-prefs");
+ SocialService.addBuiltinProvider(addonManifest.origin, function(provider) {
+ Social.uninstallProvider(addonManifest.origin);
+ gBrowser.removeTab(tab);
+ });
+ });
+ });
+ },
+ testWhitelistInstall: function(next) {
+ AddonManager.addAddonListener(installListener(next, manifest2));
+
+ let activationURL = manifest2.origin + "/browser/browser/base/content/test/social/social_activate.html"
+ addTab(activationURL, function(tab) {
+ let doc = tab.linkedBrowser.contentDocument;
+ let installFrom = doc.nodePrincipal.origin;
+ Services.prefs.setCharPref("social.whitelist", installFrom);
+ is(SocialService.getOriginActivationType(installFrom), "whitelist", "testing whitelist install");
+ Social.installProvider(doc, manifest2, function(addonManifest) {
+ Services.prefs.clearUserPref("social.whitelist");
+ SocialService.addBuiltinProvider(addonManifest.origin, function(provider) {
+ Social.uninstallProvider(addonManifest.origin);
+ gBrowser.removeTab(tab);
+ });
+ });
+ });
+ },
+ testDirectoryInstall: function(next) {
+ AddonManager.addAddonListener(installListener(next, manifest2));
+
+ let activationURL = manifest2.origin + "/browser/browser/base/content/test/social/social_activate.html"
+ addTab(activationURL, function(tab) {
+ let doc = tab.linkedBrowser.contentDocument;
+ let installFrom = doc.nodePrincipal.origin;
+ Services.prefs.setCharPref("social.directories", installFrom);
+ is(SocialService.getOriginActivationType(installFrom), "directory", "testing directory install");
+ Social.installProvider(doc, manifest2, function(addonManifest) {
+ Services.prefs.clearUserPref("social.directories");
+ SocialService.addBuiltinProvider(addonManifest.origin, function(provider) {
+ Social.uninstallProvider(addonManifest.origin);
+ gBrowser.removeTab(tab);
+ });
+ });
+ });
+ },
+ testUpgradeProviderFromWorker: function(next) {
+ // add the provider, change the pref, add it again. The provider at that
+ // point should be upgraded
+ let activationURL = manifest2.origin + "/browser/browser/base/content/test/social/social_activate.html"
+ addTab(activationURL, function(tab) {
+ let doc = tab.linkedBrowser.contentDocument;
+ let installFrom = doc.nodePrincipal.origin;
+ Services.prefs.setCharPref("social.whitelist", installFrom);
+ Social.installProvider(doc, manifest2, function(addonManifest) {
+ SocialService.addBuiltinProvider(addonManifest.origin, function(provider) {
+ is(provider.manifest.version, 1, "manifest version is 1");
+ Social.enabled = true;
+
+ // watch for the provider-update and test the new version
+ SocialService.registerProviderListener(function providerListener(topic, data) {
+ if (topic != "provider-update")
+ return;
+ SocialService.unregisterProviderListener(providerListener);
+ Services.prefs.clearUserPref("social.whitelist");
+ let provider = Social._getProviderFromOrigin(addonManifest.origin);
+ is(provider.manifest.version, 2, "manifest version is 2");
+ Social.uninstallProvider(addonManifest.origin, function() {
+ gBrowser.removeTab(tab);
+ next();
+ });
+ });
+
+ let port = provider.getWorkerPort();
+ port.onmessage = function (e) {
+ let topic = e.data.topic;
+ switch (topic) {
+ case "got-sidebar-message":
+ ok(true, "got the sidebar message from provider 1");
+ port.postMessage({topic: "worker.update", data: true});
+ break;
+ }
+ };
+ port.postMessage({topic: "test-init"});
+
+ });
+ });
+ });
+ }
+}
diff --git a/browser/base/content/test/social/browser_blocklist.js b/browser/base/content/test/social/browser_blocklist.js
new file mode 100644
index 000000000..6f61b1ac2
--- /dev/null
+++ b/browser/base/content/test/social/browser_blocklist.js
@@ -0,0 +1,179 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// a place for miscellaneous social tests
+
+let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
+
+const URI_EXTENSION_BLOCKLIST_DIALOG = "chrome://mozapps/content/extensions/blocklist.xul";
+let blocklistURL = "http://example.org/browser/browser/base/content/test/social/blocklist.xml";
+
+let manifest = { // normal provider
+ name: "provider ok",
+ origin: "https://example.com",
+ sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
+ workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
+ iconURL: "https://example.com/browser/browser/base/content/test/moz.png"
+};
+let manifest_bad = { // normal provider
+ name: "provider blocked",
+ origin: "https://test1.example.com",
+ sidebarURL: "https://test1.example.com/browser/browser/base/content/test/social/social_sidebar.html",
+ workerURL: "https://test1.example.com/browser/browser/base/content/test/social/social_worker.js",
+ iconURL: "https://test1.example.com/browser/browser/base/content/test/moz.png"
+};
+
+function test() {
+ waitForExplicitFinish();
+
+ runSocialTests(tests, undefined, undefined, function () {
+ resetBlocklist(finish); //restore to original pref
+ });
+}
+
+var tests = {
+ testSimpleBlocklist: function(next) {
+ // this really just tests adding and clearing our blocklist for later tests
+ setAndUpdateBlocklist(blocklistURL, function() {
+ ok(Services.blocklist.isAddonBlocklisted("test1.example.com@services.mozilla.org", "0", "0", "0"), "blocking 'blocked'");
+ ok(!Services.blocklist.isAddonBlocklisted("example.com@services.mozilla.org", "0", "0", "0"), "not blocking 'good'");
+ resetBlocklist(function() {
+ ok(!Services.blocklist.isAddonBlocklisted("test1.example.com@services.mozilla.org", "0", "0", "0"), "blocklist cleared");
+ next();
+ });
+ });
+ },
+ testAddingNonBlockedProvider: function(next) {
+ function finish(isgood) {
+ ok(isgood, "adding non-blocked provider ok");
+ Services.prefs.clearUserPref("social.manifest.good");
+ resetBlocklist(next);
+ }
+ setManifestPref("social.manifest.good", manifest);
+ setAndUpdateBlocklist(blocklistURL, function() {
+ try {
+ SocialService.addProvider(manifest, function(provider) {
+ try {
+ SocialService.removeProvider(provider.origin, function() {
+ ok(true, "added and removed provider");
+ finish(true);
+ });
+ } catch(e) {
+ ok(false, "SocialService.removeProvider threw exception: " + e);
+ finish(false);
+ }
+ });
+ } catch(e) {
+ ok(false, "SocialService.addProvider threw exception: " + e);
+ finish(false);
+ }
+ });
+ },
+ testAddingBlockedProvider: function(next) {
+ function finish(good) {
+ ok(good, "Unable to add blocklisted provider");
+ Services.prefs.clearUserPref("social.manifest.blocked");
+ resetBlocklist(next);
+ }
+ setManifestPref("social.manifest.blocked", manifest_bad);
+ setAndUpdateBlocklist(blocklistURL, function() {
+ try {
+ SocialService.addProvider(manifest_bad, function(provider) {
+ ok(false, "SocialService.addProvider should throw blocklist exception");
+ finish(false);
+ });
+ } catch(e) {
+ ok(true, "SocialService.addProvider should throw blocklist exception: " + e);
+ finish(true);
+ }
+ });
+ },
+ testInstallingBlockedProvider: function(next) {
+ function finish(good) {
+ ok(good, "Unable to add blocklisted provider");
+ Services.prefs.clearUserPref("social.whitelist");
+ resetBlocklist(next);
+ }
+ let activationURL = manifest_bad.origin + "/browser/browser/base/content/test/social/social_activate.html"
+ addTab(activationURL, function(tab) {
+ let doc = tab.linkedBrowser.contentDocument;
+ let installFrom = doc.nodePrincipal.origin;
+ // whitelist to avoid the 3rd party install dialog, we only want to test
+ // the blocklist inside installProvider.
+ Services.prefs.setCharPref("social.whitelist", installFrom);
+ setAndUpdateBlocklist(blocklistURL, function() {
+ try {
+ // expecting an exception when attempting to install a hard blocked
+ // provider
+ Social.installProvider(doc, manifest_bad, function(addonManifest) {
+ gBrowser.removeTab(tab);
+ finish(false);
+ });
+ } catch(e) {
+ gBrowser.removeTab(tab);
+ finish(true);
+ }
+ });
+ });
+ },
+ testBlockingExistingProvider: function(next) {
+ let windowWasClosed = false;
+ function finish() {
+ waitForCondition(function() windowWasClosed, function() {
+ Services.wm.removeListener(listener);
+ next();
+ }, "blocklist dialog was closed");
+ }
+
+ let listener = {
+ _window: null,
+ onOpenWindow: function(aXULWindow) {
+ Services.wm.removeListener(this);
+ this._window = aXULWindow;
+ let domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+
+ domwindow.addEventListener("unload", function _unload() {
+ domwindow.removeEventListener("unload", _unload, false);
+ windowWasClosed = true;
+ }, false);
+ info("dialog opened, waiting for focus");
+ waitForFocus(function() {
+ is(domwindow.document.location.href, URI_EXTENSION_BLOCKLIST_DIALOG, "dialog opened and focused");
+ executeSoon(function() {
+ domwindow.close();
+ });
+ }, domwindow);
+ },
+ onCloseWindow: function(aXULWindow) { },
+ onWindowTitleChange: function(aXULWindow, aNewTitle) { }
+ };
+
+ Services.wm.addListener(listener);
+
+ setManifestPref("social.manifest.blocked", manifest_bad);
+ try {
+ SocialService.addProvider(manifest_bad, function(provider) {
+ // the act of blocking should cause a 'provider-removed' notification
+ // from SocialService.
+ SocialService.registerProviderListener(function providerListener(topic) {
+ if (topic != "provider-removed")
+ return;
+ SocialService.unregisterProviderListener(providerListener);
+ SocialService.getProvider(provider.origin, function(p) {
+ ok(p==null, "blocklisted provider removed");
+ Services.prefs.clearUserPref("social.manifest.blocked");
+ resetBlocklist(finish);
+ });
+ });
+ // no callback - the act of updating should cause the listener above
+ // to fire.
+ setAndUpdateBlocklist(blocklistURL);
+ });
+ } catch(e) {
+ ok(false, "unable to add provider " + e);
+ finish();
+ }
+ }
+}
diff --git a/browser/base/content/test/social/browser_chat_tearoff.js b/browser/base/content/test/social/browser_chat_tearoff.js
new file mode 100644
index 000000000..7ee8acfab
--- /dev/null
+++ b/browser/base/content/test/social/browser_chat_tearoff.js
@@ -0,0 +1,121 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ requestLongerTimeout(2); // only debug builds seem to need more time...
+ waitForExplicitFinish();
+
+ let manifest = { // normal provider
+ name: "provider 1",
+ origin: "https://example.com",
+ sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
+ workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
+ iconURL: "https://example.com/browser/browser/base/content/test/moz.png"
+ };
+
+ let postSubTest = function(cb) {
+ let chats = document.getElementById("pinnedchats");
+ ok(chats.children.length == 0, "no chatty children left behind");
+ cb();
+ };
+ runSocialTestWithProvider(manifest, function (finishcb) {
+ runSocialTests(tests, undefined, postSubTest, function() {
+ finishcb();
+ });
+ });
+}
+
+var tests = {
+ testTearoffChat: function(next) {
+ let chats = document.getElementById("pinnedchats");
+ let chatTitle;
+ let port = Social.provider.getWorkerPort();
+ ok(port, "provider has a port");
+ port.onmessage = function (e) {
+ let topic = e.data.topic;
+ switch (topic) {
+ case "got-sidebar-message":
+ port.postMessage({topic: "test-chatbox-open"});
+ break;
+ case "got-chatbox-visibility":
+ // chatbox is open, lets detach. The new chat window will be caught in
+ // the window watcher below
+ let doc = chats.selectedChat.contentDocument;
+ // This message is (sometimes!) received a second time
+ // before we start our tests from the onCloseWindow
+ // callback.
+ if (doc.location == "about:blank")
+ return;
+ chatTitle = doc.title;
+ ok(chats.selectedChat.getAttribute("label") == chatTitle,
+ "the new chatbox should show the title of the chat window");
+ let div = doc.createElement("div");
+ div.setAttribute("id", "testdiv");
+ div.setAttribute("test", "1");
+ doc.body.appendChild(div);
+ let swap = document.getAnonymousElementByAttribute(chats.selectedChat, "anonid", "swap");
+ swap.click();
+ break;
+ case "got-chatbox-message":
+ ok(true, "got chatbox message");
+ ok(e.data.result == "ok", "got chatbox windowRef result: "+e.data.result);
+ chats.selectedChat.toggle();
+ break;
+ }
+ }
+
+ Services.wm.addListener({
+ onWindowTitleChange: function() {},
+ onCloseWindow: function(xulwindow) {},
+ onOpenWindow: function(xulwindow) {
+ var domwindow = xulwindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindow);
+ Services.wm.removeListener(this);
+ // wait for load to ensure the window is ready for us to test
+ domwindow.addEventListener("load", function _load() {
+ domwindow.removeEventListener("load", _load, false);
+ let doc = domwindow.document;
+ is(doc.documentElement.getAttribute("windowtype"), "Social:Chat", "Social:Chat window opened");
+ is(doc.location.href, "chrome://browser/content/chatWindow.xul", "Should have seen the right window open");
+ // window is loaded, but the docswap does not happen until after load,
+ // and we have no event to wait on, so we'll wait for document state
+ // to be ready
+ let chatbox = doc.getElementById("chatter");
+ waitForCondition(function() {
+ return chats.selectedChat == null &&
+ chatbox.contentDocument &&
+ chatbox.contentDocument.readyState == "complete";
+ },function() {
+ ok(chatbox.getAttribute("label") == chatTitle,
+ "detached window should show the title of the chat window");
+ let testdiv = chatbox.contentDocument.getElementById("testdiv");
+ is(testdiv.getAttribute("test"), "1", "docshell should have been swapped");
+ testdiv.setAttribute("test", "2");
+ // swap the window back to the chatbar
+ let swap = doc.getAnonymousElementByAttribute(chatbox, "anonid", "swap");
+ swap.click();
+ }, domwindow);
+ }, false);
+ domwindow.addEventListener("unload", function _close() {
+ domwindow.removeEventListener("unload", _close, false);
+ info("window has been closed");
+ waitForCondition(function() {
+ return chats.selectedChat && chats.selectedChat.contentDocument &&
+ chats.selectedChat.contentDocument.readyState == "complete";
+ },function () {
+ ok(chats.selectedChat, "should have a chatbox in our window again");
+ ok(chats.selectedChat.getAttribute("label") == chatTitle,
+ "the new chatbox should show the title of the chat window again");
+ let testdiv = chats.selectedChat.contentDocument.getElementById("testdiv");
+ is(testdiv.getAttribute("test"), "2", "docshell should have been swapped");
+ chats.selectedChat.close();
+ next();
+ });
+ }, false);
+ }
+ });
+
+ port.postMessage({topic: "test-init", data: { id: 1 }});
+ }
+} \ No newline at end of file
diff --git a/browser/base/content/test/social/browser_defaults.js b/browser/base/content/test/social/browser_defaults.js
new file mode 100644
index 000000000..653509a98
--- /dev/null
+++ b/browser/base/content/test/social/browser_defaults.js
@@ -0,0 +1,14 @@
+
+let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
+
+// this test ensures that any builtin providers have the builtin flag that we
+// need to help with "install" of a builtin.
+function test() {
+ let manifestPrefs = Services.prefs.getDefaultBranch("social.manifest.");
+ let prefs = manifestPrefs.getChildList("", []);
+ ok(prefs.length > 0, "we have builtin providers");
+ for (let pref of prefs) {
+ let manifest = JSON.parse(manifestPrefs.getComplexValue(pref, Ci.nsISupportsString).data);
+ ok(manifest.builtin, "manifest is builtin " + manifest.origin);
+ }
+}
diff --git a/browser/base/content/test/social/browser_share.js b/browser/base/content/test/social/browser_share.js
new file mode 100644
index 000000000..146bb6fca
--- /dev/null
+++ b/browser/base/content/test/social/browser_share.js
@@ -0,0 +1,140 @@
+
+let baseURL = "https://example.com/browser/browser/base/content/test/social/";
+
+function test() {
+ waitForExplicitFinish();
+
+ let manifest = { // normal provider
+ name: "provider 1",
+ origin: "https://example.com",
+ sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
+ workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
+ iconURL: "https://example.com/browser/browser/base/content/test/moz.png",
+ shareURL: "https://example.com/browser/browser/base/content/test/social/share.html"
+ };
+ runSocialTestWithProvider(manifest, function (finishcb) {
+ runSocialTests(tests, undefined, undefined, finishcb);
+ });
+}
+
+let corpus = [
+ {
+ url: baseURL+"opengraph/opengraph.html",
+ options: {
+ // og:title
+ title: ">This is my title<",
+ // og:description
+ description: "A test corpus file for open graph tags we care about",
+ //medium: this.getPageMedium(),
+ //source: this.getSourceURL(),
+ // og:url
+ url: "https://www.mozilla.org/",
+ //shortUrl: this.getShortURL(),
+ // og:image
+ previews:["https://www.mozilla.org/favicon.png"],
+ // og:site_name
+ siteName: ">My simple test page<"
+ }
+ },
+ {
+ // tests that og:url doesn't override the page url if it is bad
+ url: baseURL+"opengraph/og_invalid_url.html",
+ options: {
+ description: "A test corpus file for open graph tags passing a bad url",
+ url: baseURL+"opengraph/og_invalid_url.html",
+ previews: [],
+ siteName: "Evil chrome delivering website"
+ }
+ },
+ {
+ url: baseURL+"opengraph/shorturl_link.html",
+ options: {
+ previews: ["http://example.com/1234/56789.jpg"],
+ url: "http://www.example.com/photos/56789/",
+ shortUrl: "http://imshort/p/abcde"
+ }
+ },
+ {
+ url: baseURL+"opengraph/shorturl_linkrel.html",
+ options: {
+ previews: ["http://example.com/1234/56789.jpg"],
+ url: "http://www.example.com/photos/56789/",
+ shortUrl: "http://imshort/p/abcde"
+ }
+ },
+ {
+ url: baseURL+"opengraph/shortlink_linkrel.html",
+ options: {
+ previews: ["http://example.com/1234/56789.jpg"],
+ url: "http://www.example.com/photos/56789/",
+ shortUrl: "http://imshort/p/abcde"
+ }
+ }
+];
+
+function loadURLInTab(url, callback) {
+ info("Loading tab with "+url);
+ let tab = gBrowser.selectedTab = gBrowser.addTab(url);
+ tab.linkedBrowser.addEventListener("load", function listener() {
+ is(tab.linkedBrowser.currentURI.spec, url, "tab loaded")
+ tab.linkedBrowser.removeEventListener("load", listener, true);
+ callback(tab);
+ }, true);
+}
+
+function hasoptions(testOptions, options) {
+ let msg;
+ for (let option in testOptions) {
+ let data = testOptions[option];
+ info("data: "+JSON.stringify(data));
+ let message_data = options[option];
+ info("message_data: "+JSON.stringify(message_data));
+ if (Array.isArray(data)) {
+ // the message may have more array elements than we are testing for, this
+ // is ok since some of those are hard to test. So we just test that
+ // anything in our test data IS in the message.
+ ok(Array.every(data, function(item) { return message_data.indexOf(item) >= 0 }), "option "+option);
+ } else {
+ is(message_data, data, "option "+option);
+ }
+ }
+}
+
+var tests = {
+ testSharePage: function(next) {
+ let panel = document.getElementById("social-flyout-panel");
+ let port = Social.provider.getWorkerPort();
+ ok(port, "provider has a port");
+ let testTab;
+ let testIndex = 0;
+ let testData = corpus[testIndex++];
+
+ function runOneTest() {
+ loadURLInTab(testData.url, function(tab) {
+ testTab = tab;
+ SocialShare.sharePage();
+ });
+ }
+
+ port.onmessage = function (e) {
+ let topic = e.data.topic;
+ switch (topic) {
+ case "got-sidebar-message":
+ // open a tab with share data, then open the share panel
+ runOneTest();
+ break;
+ case "got-share-data-message":
+ gBrowser.removeTab(testTab);
+ hasoptions(testData.options, e.data.result);
+ testData = corpus[testIndex++];
+ if (testData) {
+ runOneTest();
+ } else {
+ next();
+ }
+ break;
+ }
+ }
+ port.postMessage({topic: "test-init"});
+ }
+}
diff --git a/browser/base/content/test/social/browser_social_activation.js b/browser/base/content/test/social/browser_social_activation.js
new file mode 100644
index 000000000..7d7b499a7
--- /dev/null
+++ b/browser/base/content/test/social/browser_social_activation.js
@@ -0,0 +1,348 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
+
+let tabsToRemove = [];
+
+function postTestCleanup(callback) {
+ Social.provider = null;
+ // any tabs opened by the test.
+ for (let tab of tabsToRemove)
+ gBrowser.removeTab(tab);
+ tabsToRemove = [];
+ // theses tests use the notification panel but don't bother waiting for it
+ // to fully open - the end result is that the panel might stay open
+ SocialUI.activationPanel.hidePopup();
+
+ Services.prefs.clearUserPref("social.whitelist");
+
+ // all providers may have had their manifests added.
+ for (let manifest of gProviders)
+ Services.prefs.clearUserPref("social.manifest." + manifest.origin);
+
+ // all the providers may have been added.
+ let providers = gProviders.slice(0)
+ function removeProviders() {
+ if (providers.length < 1) {
+ executeSoon(function() {
+ is(Social.providers.length, 0, "all providers removed");
+ callback();
+ });
+ return;
+ }
+
+ let provider = providers.pop();
+ try {
+ SocialService.removeProvider(provider.origin, removeProviders);
+ } catch(ex) {
+ removeProviders();
+ }
+ }
+ removeProviders();
+}
+
+function addBuiltinManifest(manifest) {
+ let prefname = getManifestPrefname(manifest);
+ setBuiltinManifestPref(prefname, manifest);
+ return prefname;
+}
+
+function addTab(url, callback) {
+ let tab = gBrowser.selectedTab = gBrowser.addTab(url, {skipAnimation: true});
+ tab.linkedBrowser.addEventListener("load", function tabLoad(event) {
+ tab.linkedBrowser.removeEventListener("load", tabLoad, true);
+ tabsToRemove.push(tab);
+ executeSoon(function() {callback(tab)});
+ }, true);
+}
+
+function sendActivationEvent(tab, callback, nullManifest) {
+ // hack Social.lastEventReceived so we don't hit the "too many events" check.
+ Social.lastEventReceived = 0;
+ let doc = tab.linkedBrowser.contentDocument;
+ // if our test has a frame, use it
+ if (doc.defaultView.frames[0])
+ doc = doc.defaultView.frames[0].document;
+ let button = doc.getElementById(nullManifest ? "activation-old" : "activation");
+ EventUtils.synthesizeMouseAtCenter(button, {}, doc.defaultView);
+ executeSoon(callback);
+}
+
+function activateProvider(domain, callback, nullManifest) {
+ let activationURL = domain+"/browser/browser/base/content/test/social/social_activate.html"
+ addTab(activationURL, function(tab) {
+ sendActivationEvent(tab, callback, nullManifest);
+ });
+}
+
+function activateIFrameProvider(domain, callback) {
+ let activationURL = domain+"/browser/browser/base/content/test/social/social_activate_iframe.html"
+ addTab(activationURL, function(tab) {
+ sendActivationEvent(tab, callback, false);
+ });
+}
+
+function waitForProviderLoad(cb) {
+ Services.obs.addObserver(function providerSet(subject, topic, data) {
+ Services.obs.removeObserver(providerSet, "social:provider-set");
+ info("social:provider-set observer was notified");
+ waitForCondition(function() {
+ let sbrowser = document.getElementById("social-sidebar-browser");
+ return Social.provider &&
+ Social.provider.profile &&
+ Social.provider.profile.displayName &&
+ sbrowser.docShellIsActive;
+ }, function() {
+ // executeSoon to let the browser UI observers run first
+ executeSoon(cb);
+ },
+ "waitForProviderLoad: provider profile was not set");
+ }, "social:provider-set", false);
+}
+
+
+function getAddonItemInList(aId, aList) {
+ var item = aList.firstChild;
+ while (item) {
+ if ("mAddon" in item && item.mAddon.id == aId) {
+ aList.ensureElementIsVisible(item);
+ return item;
+ }
+ item = item.nextSibling;
+ }
+ return null;
+}
+
+function clickAddonRemoveButton(tab, aCallback) {
+ AddonManager.getAddonsByTypes(["service"], function(aAddons) {
+ let addon = aAddons[0];
+
+ let doc = tab.linkedBrowser.contentDocument;
+ let list = doc.getElementById("addon-list");
+
+ let item = getAddonItemInList(addon.id, list);
+ isnot(item, null, "Should have found the add-on in the list");
+
+ var button = doc.getAnonymousElementByAttribute(item, "anonid", "remove-btn");
+ isnot(button, null, "Should have a remove button");
+ ok(!button.disabled, "Button should not be disabled");
+
+ EventUtils.synthesizeMouseAtCenter(button, { }, doc.defaultView);
+
+ // Force XBL to apply
+ item.clientTop;
+
+ is(item.getAttribute("pending"), "uninstall", "Add-on should be uninstalling");
+
+ executeSoon(function() { aCallback(addon); });
+ });
+}
+
+function activateOneProvider(manifest, finishActivation, aCallback) {
+ activateProvider(manifest.origin, function() {
+ waitForProviderLoad(function() {
+ ok(!SocialUI.activationPanel.hidden, "activation panel is showing");
+ is(Social.provider.origin, manifest.origin, "new provider is active");
+ checkSocialUI();
+
+ if (finishActivation)
+ document.getElementById("social-activation-button").click();
+ else
+ document.getElementById("social-undoactivation-button").click();
+
+ executeSoon(aCallback);
+ });
+ });
+}
+
+let gTestDomains = ["https://example.com", "https://test1.example.com", "https://test2.example.com"];
+let gProviders = [
+ {
+ name: "provider 1",
+ origin: "https://example.com",
+ sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html?provider1",
+ workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js#no-profile,no-recommend",
+ iconURL: "chrome://branding/content/icon48.png"
+ },
+ {
+ name: "provider 2",
+ origin: "https://test1.example.com",
+ sidebarURL: "https://test1.example.com/browser/browser/base/content/test/social/social_sidebar.html?provider2",
+ workerURL: "https://test1.example.com/browser/browser/base/content/test/social/social_worker.js#no-profile,no-recommend",
+ iconURL: "chrome://branding/content/icon64.png"
+ },
+ {
+ name: "provider 3",
+ origin: "https://test2.example.com",
+ sidebarURL: "https://test2.example.com/browser/browser/base/content/test/social/social_sidebar.html?provider2",
+ workerURL: "https://test2.example.com/browser/browser/base/content/test/social/social_worker.js#no-profile,no-recommend",
+ iconURL: "chrome://branding/content/about-logo.png"
+ }
+];
+
+
+function test() {
+ waitForExplicitFinish();
+ runSocialTests(tests, undefined, postTestCleanup);
+}
+
+var tests = {
+ testActivationWrongOrigin: function(next) {
+ // At this stage none of our providers exist, so we expect failure.
+ Services.prefs.setBoolPref("social.remote-install.enabled", false);
+ activateProvider(gTestDomains[0], function() {
+ is(SocialUI.enabled, false, "SocialUI is not enabled");
+ ok(SocialUI.activationPanel.hidden, "activation panel still hidden");
+ checkSocialUI();
+ Services.prefs.clearUserPref("social.remote-install.enabled");
+ next();
+ });
+ },
+
+ testIFrameActivation: function(next) {
+ Services.prefs.setCharPref("social.whitelist", gTestDomains.join(","));
+ activateIFrameProvider(gTestDomains[0], function() {
+ is(SocialUI.enabled, false, "SocialUI is not enabled");
+ ok(!Social.provider, "provider is not installed");
+ ok(SocialUI.activationPanel.hidden, "activation panel still hidden");
+ checkSocialUI();
+ Services.prefs.clearUserPref("social.whitelist");
+ next();
+ });
+ },
+
+ testActivationFirstProvider: function(next) {
+ Services.prefs.setCharPref("social.whitelist", gTestDomains.join(","));
+ // first up we add a manifest entry for a single provider.
+ activateOneProvider(gProviders[0], false, function() {
+ // we deactivated leaving no providers left, so Social is disabled.
+ ok(!Social.provider, "should be no provider left after disabling");
+ checkSocialUI();
+ Services.prefs.clearUserPref("social.whitelist");
+ next();
+ });
+ },
+
+ testActivationBuiltin: function(next) {
+ let prefname = addBuiltinManifest(gProviders[0]);
+ is(SocialService.getOriginActivationType(gTestDomains[0]), "builtin", "manifest is builtin");
+ // first up we add a manifest entry for a single provider.
+ activateOneProvider(gProviders[0], false, function() {
+ // we deactivated leaving no providers left, so Social is disabled.
+ ok(!Social.provider, "should be no provider left after disabling");
+ checkSocialUI();
+ resetBuiltinManifestPref(prefname);
+ next();
+ });
+ },
+
+ testActivationMultipleProvider: function(next) {
+ // The trick with this test is to make sure that Social.providers[1] is
+ // the current provider when doing the undo - this makes sure that the
+ // Social code doesn't fallback to Social.providers[0], which it will
+ // do in some cases (but those cases do not include what this test does)
+ // first enable the 2 providers
+ Services.prefs.setCharPref("social.whitelist", gTestDomains.join(","));
+ SocialService.addProvider(gProviders[0], function() {
+ SocialService.addProvider(gProviders[1], function() {
+ Social.provider = Social.providers[1];
+ checkSocialUI();
+ // activate the last provider.
+ let prefname = addBuiltinManifest(gProviders[2]);
+ activateOneProvider(gProviders[2], false, function() {
+ // we deactivated - the first provider should be enabled.
+ is(Social.provider.origin, Social.providers[1].origin, "original provider should have been reactivated");
+ checkSocialUI();
+ Services.prefs.clearUserPref("social.whitelist");
+ resetBuiltinManifestPref(prefname);
+ next();
+ });
+ });
+ });
+ },
+
+ testRemoveNonCurrentProvider: function(next) {
+ Services.prefs.setCharPref("social.whitelist", gTestDomains.join(","));
+ SocialService.addProvider(gProviders[0], function() {
+ SocialService.addProvider(gProviders[1], function() {
+ Social.provider = Social.providers[1];
+ checkSocialUI();
+ // activate the last provider.
+ let prefname = addBuiltinManifest(gProviders[2]);
+ activateProvider(gTestDomains[2], function() {
+ waitForProviderLoad(function() {
+ ok(!SocialUI.activationPanel.hidden, "activation panel is showing");
+ is(Social.provider.origin, gTestDomains[2], "new provider is active");
+ checkSocialUI();
+ // A bit contrived, but set a new provider current while the
+ // activation ui is up.
+ Social.provider = Social.providers[1];
+ // hit "undo"
+ document.getElementById("social-undoactivation-button").click();
+ executeSoon(function() {
+ // we deactivated - the same provider should be enabled.
+ is(Social.provider.origin, Social.providers[1].origin, "original provider still be active");
+ checkSocialUI();
+ Services.prefs.clearUserPref("social.whitelist");
+ resetBuiltinManifestPref(prefname);
+ next();
+ });
+ });
+ });
+ });
+ });
+ },
+
+ testAddonManagerDoubleInstall: function(next) {
+ Services.prefs.setCharPref("social.whitelist", gTestDomains.join(","));
+ // Create a new tab and load about:addons
+ let blanktab = gBrowser.addTab();
+ gBrowser.selectedTab = blanktab;
+ BrowserOpenAddonsMgr('addons://list/service');
+
+ is(blanktab, gBrowser.selectedTab, "Current tab should be blank tab");
+
+ gBrowser.selectedBrowser.addEventListener("load", function tabLoad() {
+ gBrowser.selectedBrowser.removeEventListener("load", tabLoad, true);
+ let browser = blanktab.linkedBrowser;
+ is(browser.currentURI.spec, "about:addons", "about:addons should load into blank tab.");
+
+ let prefname = addBuiltinManifest(gProviders[0]);
+ activateOneProvider(gProviders[0], true, function() {
+ gBrowser.removeTab(gBrowser.selectedTab);
+ tabsToRemove.pop();
+ // uninstall the provider
+ clickAddonRemoveButton(blanktab, function(addon) {
+ checkSocialUI();
+ activateOneProvider(gProviders[0], true, function() {
+
+ // after closing the addons tab, verify provider is still installed
+ gBrowser.tabContainer.addEventListener("TabClose", function onTabClose() {
+ gBrowser.tabContainer.removeEventListener("TabClose", onTabClose);
+ AddonManager.getAddonsByTypes(["service"], function(aAddons) {
+ is(aAddons.length, 1, "there can be only one");
+ Services.prefs.clearUserPref("social.whitelist");
+ resetBuiltinManifestPref(prefname);
+ next();
+ });
+ });
+
+ // verify only one provider in list
+ AddonManager.getAddonsByTypes(["service"], function(aAddons) {
+ is(aAddons.length, 1, "there can be only one");
+
+ let doc = blanktab.linkedBrowser.contentDocument;
+ let list = doc.getElementById("addon-list");
+ is(list.childNodes.length, 1, "only one addon is displayed");
+
+ gBrowser.removeTab(blanktab);
+ });
+
+ });
+ });
+ });
+ }, true);
+ }
+}
diff --git a/browser/base/content/test/social/browser_social_chatwindow.js b/browser/base/content/test/social/browser_social_chatwindow.js
new file mode 100644
index 000000000..9fb47d3f9
--- /dev/null
+++ b/browser/base/content/test/social/browser_social_chatwindow.js
@@ -0,0 +1,472 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ requestLongerTimeout(2); // only debug builds seem to need more time...
+ waitForExplicitFinish();
+
+ let manifest = { // normal provider
+ name: "provider 1",
+ origin: "https://example.com",
+ sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
+ workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
+ iconURL: "https://example.com/browser/browser/base/content/test/moz.png"
+ };
+ let oldwidth = window.outerWidth; // we futz with these, so we restore them
+ let oldleft = window.screenX;
+ window.moveTo(0, window.screenY)
+ let postSubTest = function(cb) {
+ let chats = document.getElementById("pinnedchats");
+ ok(chats.children.length == 0, "no chatty children left behind");
+ cb();
+ };
+ runSocialTestWithProvider(manifest, function (finishcb) {
+ runSocialTests(tests, undefined, postSubTest, function() {
+ window.moveTo(oldleft, window.screenY)
+ window.resizeTo(oldwidth, window.outerHeight);
+ finishcb();
+ });
+ });
+}
+
+var tests = {
+ testOpenCloseChat: function(next) {
+ let chats = document.getElementById("pinnedchats");
+ let port = Social.provider.getWorkerPort();
+ ok(port, "provider has a port");
+ port.onmessage = function (e) {
+ let topic = e.data.topic;
+ switch (topic) {
+ case "got-sidebar-message":
+ port.postMessage({topic: "test-chatbox-open"});
+ break;
+ case "got-chatbox-visibility":
+ if (e.data.result == "hidden") {
+ ok(true, "chatbox got minimized");
+ chats.selectedChat.toggle();
+ } else if (e.data.result == "shown") {
+ ok(true, "chatbox got shown");
+ // close it now
+ let content = chats.selectedChat.content;
+ content.addEventListener("unload", function chatUnload() {
+ content.removeEventListener("unload", chatUnload, true);
+ ok(true, "got chatbox unload on close");
+ port.close();
+ next();
+ }, true);
+ chats.selectedChat.close();
+ }
+ break;
+ case "got-chatbox-message":
+ ok(true, "got chatbox message");
+ ok(e.data.result == "ok", "got chatbox windowRef result: "+e.data.result);
+ chats.selectedChat.toggle();
+ break;
+ }
+ }
+ port.postMessage({topic: "test-init", data: { id: 1 }});
+ },
+ testOpenMinimized: function(next) {
+ // In this case the sidebar opens a chat (without specifying minimized).
+ // We then minimize it and have the sidebar reopen the chat (again without
+ // minimized). On that second call the chat should open and no longer
+ // be minimized.
+ let chats = document.getElementById("pinnedchats");
+ let port = Social.provider.getWorkerPort();
+ let seen_opened = false;
+ port.onmessage = function (e) {
+ let topic = e.data.topic;
+ switch (topic) {
+ case "test-init-done":
+ port.postMessage({topic: "test-chatbox-open"});
+ break;
+ case "chatbox-opened":
+ is(e.data.result, "ok", "the sidebar says it got a chatbox");
+ if (!seen_opened) {
+ // first time we got the opened message, so minimize the chat then
+ // re-request the same chat to be opened - we should get the
+ // message again and the chat should be restored.
+ ok(!chats.selectedChat.minimized, "chat not initially minimized")
+ chats.selectedChat.minimized = true
+ seen_opened = true;
+ port.postMessage({topic: "test-chatbox-open"});
+ } else {
+ // This is the second time we've seen this message - there should
+ // be exactly 1 chat open and it should no longer be minimized.
+ let chats = document.getElementById("pinnedchats");
+ ok(!chats.selectedChat.minimized, "chat no longer minimized")
+ chats.selectedChat.close();
+ is(chats.selectedChat, null, "should only have been one chat open");
+ port.close();
+ next();
+ }
+ }
+ }
+ port.postMessage({topic: "test-init", data: { id: 1 }});
+ },
+ testManyChats: function(next) {
+ // open enough chats to overflow the window, then check
+ // if the menupopup is visible
+ let port = Social.provider.getWorkerPort();
+ let chats = document.getElementById("pinnedchats");
+ ok(port, "provider has a port");
+ ok(chats.menupopup.parentNode.collapsed, "popup nub collapsed at start");
+ port.postMessage({topic: "test-init"});
+ // we should *never* find a test box that needs more than this to cause
+ // an overflow!
+ let maxToOpen = 20;
+ let numOpened = 0;
+ let maybeOpenAnother = function() {
+ if (numOpened++ >= maxToOpen) {
+ ok(false, "We didn't find a collapsed chat after " + maxToOpen + "chats!");
+ closeAllChats();
+ next();
+ }
+ port.postMessage({topic: "test-chatbox-open", data: { id: numOpened }});
+ }
+ port.onmessage = function (e) {
+ let topic = e.data.topic;
+ switch (topic) {
+ case "got-chatbox-message":
+ if (!chats.menupopup.parentNode.collapsed) {
+ maybeOpenAnother();
+ break;
+ }
+ ok(true, "popup nub became visible");
+ // close our chats now
+ while (chats.selectedChat) {
+ chats.selectedChat.close();
+ }
+ ok(!chats.selectedChat, "chats are all closed");
+ port.close();
+ next();
+ break;
+ }
+ }
+ maybeOpenAnother();
+ },
+ testWorkerChatWindow: function(next) {
+ const chatUrl = "https://example.com/browser/browser/base/content/test/social/social_chat.html";
+ let chats = document.getElementById("pinnedchats");
+ let port = Social.provider.getWorkerPort();
+ ok(port, "provider has a port");
+ port.postMessage({topic: "test-init"});
+ port.onmessage = function (e) {
+ let topic = e.data.topic;
+ switch (topic) {
+ case "got-chatbox-message":
+ ok(true, "got a chat window opened");
+ ok(chats.selectedChat, "chatbox from worker opened");
+ while (chats.selectedChat) {
+ chats.selectedChat.close();
+ }
+ ok(!chats.selectedChat, "chats are all closed");
+ gURLsNotRemembered.push(chatUrl);
+ port.close();
+ next();
+ break;
+ }
+ }
+ ok(!chats.selectedChat, "chats are all closed");
+ port.postMessage({topic: "test-worker-chat", data: chatUrl});
+ },
+ testCloseSelf: function(next) {
+ let chats = document.getElementById("pinnedchats");
+ let port = Social.provider.getWorkerPort();
+ ok(port, "provider has a port");
+ port.onmessage = function (e) {
+ let topic = e.data.topic;
+ switch (topic) {
+ case "test-init-done":
+ port.postMessage({topic: "test-chatbox-open"});
+ break;
+ case "got-chatbox-visibility":
+ is(e.data.result, "shown", "chatbox shown");
+ port.close(); // don't want any more visibility messages.
+ let chat = chats.selectedChat;
+ ok(chat.parentNode, "chat has a parent node before it is closed");
+ // ask it to close itself.
+ let doc = chat.contentDocument;
+ let evt = doc.createEvent("CustomEvent");
+ evt.initCustomEvent("socialTest-CloseSelf", true, true, {});
+ doc.documentElement.dispatchEvent(evt);
+ ok(!chat.parentNode, "chat is now closed");
+ port.close();
+ next();
+ break;
+ }
+ }
+ port.postMessage({topic: "test-init", data: { id: 1 }});
+ },
+ testSameChatCallbacks: function(next) {
+ let chats = document.getElementById("pinnedchats");
+ let port = Social.provider.getWorkerPort();
+ let seen_opened = false;
+ port.onmessage = function (e) {
+ let topic = e.data.topic;
+ switch (topic) {
+ case "test-init-done":
+ port.postMessage({topic: "test-chatbox-open"});
+ break;
+ case "chatbox-opened":
+ is(e.data.result, "ok", "the sidebar says it got a chatbox");
+ if (seen_opened) {
+ // This is the second time we've seen this message - there should
+ // be exactly 1 chat open.
+ let chats = document.getElementById("pinnedchats");
+ chats.selectedChat.close();
+ is(chats.selectedChat, null, "should only have been one chat open");
+ port.close();
+ next();
+ } else {
+ // first time we got the opened message, so re-request the same
+ // chat to be opened - we should get the message again.
+ seen_opened = true;
+ port.postMessage({topic: "test-chatbox-open"});
+ }
+ }
+ }
+ port.postMessage({topic: "test-init", data: { id: 1 }});
+ },
+
+ // check removeAll does the right thing
+ testRemoveAll: function(next, mode) {
+ let port = Social.provider.getWorkerPort();
+ port.postMessage({topic: "test-init"});
+ get3ChatsForCollapsing(mode || "normal", function() {
+ let chatbar = window.SocialChatBar.chatbar;
+ chatbar.removeAll();
+ // should be no evidence of any chats left.
+ is(chatbar.childNodes.length, 0, "should be no chats left");
+ checkPopup();
+ is(chatbar.selectedChat, null, "nothing should be selected");
+ is(chatbar.chatboxForURL.size, 0, "chatboxForURL map should be empty");
+ port.close();
+ next();
+ });
+ },
+
+ testRemoveAllMinimized: function(next) {
+ this.testRemoveAll(next, "minimized");
+ },
+
+ // Check what happens when you close the only visible chat.
+ testCloseOnlyVisible: function(next) {
+ let chatbar = window.SocialChatBar.chatbar;
+ let chatWidth = undefined;
+ let num = 0;
+ is(chatbar.childNodes.length, 0, "chatbar starting empty");
+ is(chatbar.menupopup.childNodes.length, 0, "popup starting empty");
+
+ makeChat("normal", "first chat", function() {
+ // got the first one.
+ checkPopup();
+ ok(chatbar.menupopup.parentNode.collapsed, "menu selection isn't visible");
+ // we kinda cheat here and get the width of the first chat, assuming
+ // that all future chats will have the same width when open.
+ chatWidth = chatbar.calcTotalWidthOf(chatbar.selectedChat);
+ let desired = chatWidth * 1.5;
+ resizeWindowToChatAreaWidth(desired, function(sizedOk) {
+ ok(sizedOk, "can't do any tests without this width");
+ checkPopup();
+ makeChat("normal", "second chat", function() {
+ is(chatbar.childNodes.length, 2, "now have 2 chats");
+ let first = chatbar.childNodes[0];
+ let second = chatbar.childNodes[1];
+ is(chatbar.selectedChat, first, "first chat is selected");
+ ok(second.collapsed, "second chat is currently collapsed");
+ // closing the first chat will leave enough room for the second
+ // chat to appear, and thus become selected.
+ chatbar.selectedChat.close();
+ is(chatbar.selectedChat, second, "second chat is selected");
+ closeAllChats();
+ next();
+ });
+ });
+ });
+ },
+
+ testShowWhenCollapsed: function(next) {
+ let port = Social.provider.getWorkerPort();
+ port.postMessage({topic: "test-init"});
+ get3ChatsForCollapsing("normal", function(first, second, third) {
+ let chatbar = window.SocialChatBar.chatbar;
+ chatbar.showChat(first);
+ ok(!first.collapsed, "first should no longer be collapsed");
+ ok(second.collapsed || third.collapsed, false, "one of the others should be collapsed");
+ closeAllChats();
+ port.close();
+ next();
+ });
+ },
+
+ testActivity: function(next) {
+ let port = Social.provider.getWorkerPort();
+ port.postMessage({topic: "test-init"});
+ get3ChatsForCollapsing("normal", function(first, second, third) {
+ let chatbar = window.SocialChatBar.chatbar;
+ is(chatbar.selectedChat, third, "third chat should be selected");
+ ok(!chatbar.selectedChat.hasAttribute("activity"), "third chat should have no activity");
+ // send an activity message to the second.
+ ok(!second.hasAttribute("activity"), "second chat should have no activity");
+ let chat2 = second.content;
+ let evt = chat2.contentDocument.createEvent("CustomEvent");
+ evt.initCustomEvent("socialChatActivity", true, true, {});
+ chat2.contentDocument.documentElement.dispatchEvent(evt);
+ // second should have activity.
+ ok(second.hasAttribute("activity"), "second chat should now have activity");
+ // select the second - it should lose "activity"
+ chatbar.selectedChat = second;
+ ok(!second.hasAttribute("activity"), "second chat should no longer have activity");
+ // Now try the first - it is collapsed, so the 'nub' also gets activity attr.
+ ok(!first.hasAttribute("activity"), "first chat should have no activity");
+ let chat1 = first.content;
+ let evt = chat1.contentDocument.createEvent("CustomEvent");
+ evt.initCustomEvent("socialChatActivity", true, true, {});
+ chat1.contentDocument.documentElement.dispatchEvent(evt);
+ ok(first.hasAttribute("activity"), "first chat should now have activity");
+ ok(chatbar.nub.hasAttribute("activity"), "nub should also have activity");
+ // first is collapsed, so use openChat to get it.
+ chatbar.openChat(Social.provider, first.getAttribute("src"));
+ ok(!first.hasAttribute("activity"), "first chat should no longer have activity");
+ // The nub should lose the activity flag here too
+ todo(!chatbar.nub.hasAttribute("activity"), "Bug 806266 - nub should no longer have activity");
+ // TODO: tests for bug 806266 should arrange to have 2 chats collapsed
+ // then open them checking the nub is updated correctly.
+ // Now we will go and change the embedded browser in the second chat and
+ // ensure the activity magic still works (ie, check that the unload for
+ // the browser didn't cause our event handlers to be removed.)
+ ok(!second.hasAttribute("activity"), "second chat should have no activity");
+ let subiframe = chat2.contentDocument.getElementById("iframe");
+ subiframe.contentWindow.addEventListener("unload", function subunload() {
+ subiframe.contentWindow.removeEventListener("unload", subunload);
+ // ensure all other unload listeners have fired.
+ executeSoon(function() {
+ let evt = chat2.contentDocument.createEvent("CustomEvent");
+ evt.initCustomEvent("socialChatActivity", true, true, {});
+ chat2.contentDocument.documentElement.dispatchEvent(evt);
+ ok(second.hasAttribute("activity"), "second chat still has activity after unloading sub-iframe");
+ closeAllChats();
+ port.close();
+ next();
+ })
+ })
+ subiframe.setAttribute("src", "data:text/plain:new location for iframe");
+ });
+ },
+
+ testOnlyOneCallback: function(next) {
+ let chats = document.getElementById("pinnedchats");
+ let port = Social.provider.getWorkerPort();
+ let numOpened = 0;
+ port.onmessage = function (e) {
+ let topic = e.data.topic;
+ switch (topic) {
+ case "test-init-done":
+ port.postMessage({topic: "test-chatbox-open"});
+ break;
+ case "chatbox-opened":
+ numOpened += 1;
+ port.postMessage({topic: "ping"});
+ break;
+ case "pong":
+ executeSoon(function() {
+ is(numOpened, 1, "only got one open message");
+ chats.removeAll();
+ port.close();
+ next();
+ });
+ }
+ }
+ port.postMessage({topic: "test-init", data: { id: 1 }});
+ },
+
+ testSecondTopLevelWindow: function(next) {
+ // Bug 817782 - check chats work in new top-level windows.
+ const chatUrl = "https://example.com/browser/browser/base/content/test/social/social_chat.html";
+ let port = Social.provider.getWorkerPort();
+ let secondWindow;
+ port.onmessage = function(e) {
+ if (e.data.topic == "test-init-done") {
+ secondWindow = OpenBrowserWindow();
+ secondWindow.addEventListener("load", function loadListener() {
+ secondWindow.removeEventListener("load", loadListener);
+ port.postMessage({topic: "test-worker-chat", data: chatUrl});
+ });
+ } else if (e.data.topic == "got-chatbox-message") {
+ // the chat was created - let's make sure it was created in the second window.
+ is(secondWindow.SocialChatBar.chatbar.childElementCount, 1);
+ secondWindow.close();
+ next();
+ }
+ }
+ port.postMessage({topic: "test-init"});
+ },
+
+ testChatWindowChooser: function(next) {
+ // Tests that when a worker creates a chat, it is opened in the correct
+ // window.
+ const chatUrl = "https://example.com/browser/browser/base/content/test/social/social_chat.html";
+ let chatId = 1;
+ let port = Social.provider.getWorkerPort();
+ port.postMessage({topic: "test-init"});
+
+ function openChat(callback) {
+ port.onmessage = function(e) {
+ if (e.data.topic == "got-chatbox-message")
+ callback();
+ }
+ let url = chatUrl + "?" + (chatId++);
+ port.postMessage({topic: "test-worker-chat", data: url});
+ }
+
+ // open a chat (it will open in the main window)
+ ok(!window.SocialChatBar.hasChats, "first window should start with no chats");
+ openChat(function() {
+ ok(window.SocialChatBar.hasChats, "first window has the chat");
+ // create a second window - this will be the "most recent" and will
+ // therefore be the window that hosts the new chat (see bug 835111)
+ let secondWindow = OpenBrowserWindow();
+ secondWindow.addEventListener("load", function loadListener() {
+ secondWindow.removeEventListener("load", loadListener);
+ ok(!secondWindow.SocialChatBar.hasChats, "second window has no chats");
+ openChat(function() {
+ ok(secondWindow.SocialChatBar.hasChats, "second window now has chats");
+ is(window.SocialChatBar.chatbar.childElementCount, 1, "first window still has 1 chat");
+ window.SocialChatBar.chatbar.removeAll();
+ // now open another chat - it should still open in the second.
+ openChat(function() {
+ ok(!window.SocialChatBar.hasChats, "first window has no chats");
+ ok(secondWindow.SocialChatBar.hasChats, "second window has a chat");
+ secondWindow.close();
+ next();
+ });
+ });
+ })
+ });
+ },
+
+ // XXX - note this must be the last test until we restore the login state
+ // between tests...
+ testCloseOnLogout: function(next) {
+ const chatUrl = "https://example.com/browser/browser/base/content/test/social/social_chat.html";
+ let port = Social.provider.getWorkerPort();
+ ok(port, "provider has a port");
+ port.postMessage({topic: "test-init"});
+ port.onmessage = function (e) {
+ let topic = e.data.topic;
+ switch (topic) {
+ case "got-chatbox-message":
+ ok(true, "got a chat window opened");
+ port.postMessage({topic: "test-logout"});
+ port.close();
+ waitForCondition(function() document.getElementById("pinnedchats").firstChild == null,
+ next,
+ "chat windows didn't close");
+ break;
+ }
+ }
+ port.postMessage({topic: "test-worker-chat", data: chatUrl});
+ },
+}
diff --git a/browser/base/content/test/social/browser_social_chatwindow_resize.js b/browser/base/content/test/social/browser_social_chatwindow_resize.js
new file mode 100644
index 000000000..c6bd72078
--- /dev/null
+++ b/browser/base/content/test/social/browser_social_chatwindow_resize.js
@@ -0,0 +1,90 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ requestLongerTimeout(2); // only debug builds seem to need more time...
+ waitForExplicitFinish();
+
+ let manifest = { // normal provider
+ name: "provider 1",
+ origin: "https://example.com",
+ sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
+ workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
+ iconURL: "https://example.com/browser/browser/base/content/test/moz.png",
+ // added for test purposes
+ chatURL: "https://example.com/browser/browser/base/content/test/social/social_chat.html"
+ };
+ let oldwidth = window.outerWidth; // we futz with these, so we restore them
+ let oldleft = window.screenX;
+ window.moveTo(0, window.screenY)
+ let postSubTest = function(cb) {
+ let chats = document.getElementById("pinnedchats");
+ ok(chats.children.length == 0, "no chatty children left behind");
+ cb();
+ };
+
+ runSocialTestWithProvider(manifest, function (finishcb) {
+ let port = Social.provider.getWorkerPort();
+ ok(port, "provider has a port");
+ port.postMessage({topic: "test-init"});
+ // we require a logged in user for chats, wait for that
+ waitForCondition(function() {
+ let sbrowser = document.getElementById("social-sidebar-browser");
+ return Social.provider &&
+ Social.provider.profile &&
+ Social.provider.profile.displayName &&
+ sbrowser.docShellIsActive;
+ }, function() {
+ // executeSoon to let the browser UI observers run first
+ runSocialTests(tests, undefined, postSubTest, function() {
+ window.moveTo(oldleft, window.screenY)
+ window.resizeTo(oldwidth, window.outerHeight);
+ port.close();
+ finishcb();
+ });
+ },
+ "waitForProviderLoad: provider profile was not set");
+ });
+}
+
+var tests = {
+
+ // resize and collapse testing.
+ testBrowserResize: function(next, mode) {
+ let chats = document.getElementById("pinnedchats");
+ get3ChatsForCollapsing(mode || "normal", function(first, second, third) {
+ let chatWidth = chats.getTotalChildWidth(first);
+ ok(chatWidth, "have a chatwidth");
+ let popupWidth = getPopupWidth();
+ ok(popupWidth, "have a popupwidth");
+ info("starting resize tests - each chat's width is " + chatWidth +
+ " and the popup width is " + popupWidth);
+ // Note that due to a difference between "device", "app" and "css" pixels
+ // we allow use 2 pixels as the minimum size difference.
+ resizeAndCheckWidths(first, second, third, [
+ [chatWidth-2, 1, "to < 1 chat width - only last should be visible."],
+ [chatWidth+2, 1, "2 pixels more then one fully exposed (not counting popup) - still only 1."],
+ [chatWidth+popupWidth+2, 1, "2 pixels more than one fully exposed (including popup) - still only 1."],
+ [chatWidth*2-2, 1, "second not showing by 2 pixels (not counting popup) - only 1 exposed."],
+ [chatWidth*2+popupWidth-2, 1, "second not showing by 2 pixelx (including popup) - only 1 exposed."],
+ [chatWidth*2+popupWidth+2, 2, "big enough to fit 2 - nub remains visible as first is still hidden"],
+ [chatWidth*3+popupWidth-2, 2, "one smaller than the size necessary to display all three - first still hidden"],
+ [chatWidth*3+popupWidth+2, 3, "big enough to fit all - all exposed (which removes the nub)"],
+ [chatWidth*3+2, 3, "now the nub is hidden we can resize back down to chatWidth*3 before overflow."],
+ [chatWidth*3-2, 2, "2 pixels less and the first is again collapsed (and the nub re-appears)"],
+ [chatWidth*2+popupWidth+2, 2, "back down to just big enough to fit 2"],
+ [chatWidth*2+popupWidth-2, 1, "back down to just not enough to fit 2"],
+ [chatWidth*3+popupWidth+2, 3, "now a large jump to make all 3 visible (ie, affects 2)"],
+ [chatWidth*1.5, 1, "and a large jump back down to 1 visible (ie, affects 2)"],
+ ], function() {
+ closeAllChats();
+ next();
+ });
+ });
+ },
+
+ testBrowserResizeMinimized: function(next) {
+ this.testBrowserResize(next);
+ }
+}
diff --git a/browser/base/content/test/social/browser_social_chatwindowfocus.js b/browser/base/content/test/social/browser_social_chatwindowfocus.js
new file mode 100644
index 000000000..96824326b
--- /dev/null
+++ b/browser/base/content/test/social/browser_social_chatwindowfocus.js
@@ -0,0 +1,360 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Is the currently opened tab focused?
+function isTabFocused() {
+ let tabb = gBrowser.getBrowserForTab(gBrowser.selectedTab);
+ return Services.focus.focusedWindow == tabb.contentWindow;
+}
+
+function isChatFocused(chat) {
+ return SocialChatBar.chatbar._isChatFocused(chat);
+}
+
+function openChatViaUser() {
+ let sidebarDoc = document.getElementById("social-sidebar-browser").contentDocument;
+ let button = sidebarDoc.getElementById("chat-opener");
+ // Note we must use synthesizeMouseAtCenter() rather than calling
+ // .click() directly as this causes nsIDOMWindowUtils.isHandlingUserInput
+ // to be true.
+ EventUtils.synthesizeMouseAtCenter(button, {}, sidebarDoc.defaultView);
+}
+
+function openChatViaSidebarMessage(port, data, callback) {
+ port.onmessage = function (e) {
+ if (e.data.topic == "chatbox-opened")
+ callback();
+ }
+ port.postMessage({topic: "test-chatbox-open", data: data});
+}
+
+function openChatViaWorkerMessage(port, data, callback) {
+ // sadly there is no message coming back to tell us when the chat has
+ // been opened, so we wait until one appears.
+ let chatbar = SocialChatBar.chatbar;
+ let numExpected = chatbar.childElementCount + 1;
+ port.postMessage({topic: "test-worker-chat", data: data});
+ waitForCondition(function() chatbar.childElementCount == numExpected,
+ function() {
+ // so the child has been added, but we don't know if it
+ // has been intialized - re-request it and the callback
+ // means it's done. Minimized, same as the worker.
+ SocialChatBar.openChat(Social.provider,
+ data,
+ function() {
+ callback();
+ },
+ "minimized");
+ },
+ "No new chat appeared");
+}
+
+
+let isSidebarLoaded = false;
+
+function startTestAndWaitForSidebar(callback) {
+ let doneCallback;
+ let port = Social.provider.getWorkerPort();
+ function maybeCallback() {
+ if (!doneCallback)
+ callback(port);
+ doneCallback = true;
+ }
+ port.onmessage = function(e) {
+ let topic = e.data.topic;
+ switch (topic) {
+ case "got-sidebar-message":
+ // if sidebar loaded too fast, we need a backup ping
+ case "got-isVisible-response":
+ isSidebarLoaded = true;
+ maybeCallback();
+ break;
+ case "test-init-done":
+ if (isSidebarLoaded)
+ maybeCallback();
+ else
+ port.postMessage({topic: "test-isVisible"});
+ break;
+ }
+ }
+ port.postMessage({topic: "test-init"});
+}
+
+let manifest = { // normal provider
+ name: "provider 1",
+ origin: "https://example.com",
+ sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
+ workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
+ iconURL: "https://example.com/browser/browser/base/content/test/moz.png"
+};
+
+function test() {
+ waitForExplicitFinish();
+
+ // Note that (probably) due to bug 604289, if a tab is focused but the
+ // focused element is null, our chat windows can "steal" focus. This is
+ // avoided if we explicitly focus an element in the tab.
+ // So we load a page with an <input> field and focus that before testing.
+ let url = "data:text/html;charset=utf-8," + encodeURI('<input id="theinput">');
+ let tab = gBrowser.selectedTab = gBrowser.addTab(url, {skipAnimation: true});
+ tab.linkedBrowser.addEventListener("load", function tabLoad(event) {
+ tab.linkedBrowser.removeEventListener("load", tabLoad, true);
+ // before every test we focus the input field.
+ let preSubTest = function(cb) {
+ // XXX - when bug 604289 is fixed it should be possible to just do:
+ // tab.linkedBrowser.contentWindow.focus()
+ // but instead we must do:
+ tab.linkedBrowser.contentDocument.getElementById("theinput").focus();
+ waitForCondition(function() isTabFocused(), cb, "tab should have focus");
+ }
+ let postSubTest = function(cb) {
+ window.SocialChatBar.chatbar.removeAll();
+ cb();
+ }
+ // and run the tests.
+ runSocialTestWithProvider(manifest, function (finishcb) {
+ runSocialTests(tests, preSubTest, postSubTest, function () {
+ finishcb();
+ });
+ });
+ }, true);
+ registerCleanupFunction(function() {
+ gBrowser.removeTab(tab);
+ });
+
+}
+
+var tests = {
+ // In this test the worker asks the sidebar to open a chat. As that means
+ // we aren't handling user-input we will not focus the chatbar.
+ // Then we do it again - should still not be focused.
+ // Then we perform a user-initiated request - it should get focus.
+ testNoFocusWhenViaWorker: function(next) {
+ startTestAndWaitForSidebar(function(port) {
+ openChatViaSidebarMessage(port, {stealFocus: 1}, function() {
+ ok(true, "got chatbox message");
+ is(SocialChatBar.chatbar.childElementCount, 1, "exactly 1 chat open");
+ ok(isTabFocused(), "tab should still be focused");
+ // re-request the same chat via a message.
+ openChatViaSidebarMessage(port, {stealFocus: 1}, function() {
+ is(SocialChatBar.chatbar.childElementCount, 1, "still exactly 1 chat open");
+ ok(isTabFocused(), "tab should still be focused");
+ // re-request the same chat via user event.
+ openChatViaUser();
+ waitForCondition(function() isChatFocused(SocialChatBar.chatbar.selectedChat),
+ function() {
+ is(SocialChatBar.chatbar.childElementCount, 1, "still exactly 1 chat open");
+ is(SocialChatBar.chatbar.selectedChat, SocialChatBar.chatbar.firstElementChild, "chat should be selected");
+ next();
+ }, "chat should be focused");
+ });
+ });
+ });
+ },
+
+ // In this test we arrange for the sidebar to open the chat via a simulated
+ // click. This should cause the new chat to be opened and focused.
+ testFocusWhenViaUser: function(next) {
+ startTestAndWaitForSidebar(function(port) {
+ openChatViaUser();
+ ok(SocialChatBar.chatbar.firstElementChild, "chat opened");
+ waitForCondition(function() isChatFocused(SocialChatBar.chatbar.selectedChat),
+ function() {
+ is(SocialChatBar.chatbar.selectedChat, SocialChatBar.chatbar.firstElementChild, "chat is selected");
+ next();
+ }, "chat should be focused");
+ });
+ },
+
+ // Open a chat via the worker - it will open and not have focus.
+ // Then open the same chat via a sidebar message - it will be restored but
+ // should still not have grabbed focus.
+ testNoFocusOnAutoRestore: function(next) {
+ const chatUrl = "https://example.com/browser/browser/base/content/test/social/social_chat.html?id=1";
+ let chatbar = SocialChatBar.chatbar;
+ startTestAndWaitForSidebar(function(port) {
+ openChatViaWorkerMessage(port, chatUrl, function() {
+ is(chatbar.childElementCount, 1, "exactly 1 chat open");
+ // bug 865086 opening minimized still sets the window as selected
+ todo(chatbar.selectedChat != chatbar.firstElementChild, "chat is not selected");
+ ok(isTabFocused(), "tab should be focused");
+ openChatViaSidebarMessage(port, {stealFocus: 1, id: 1}, function() {
+ is(chatbar.childElementCount, 1, "still 1 chat open");
+ ok(!chatbar.firstElementChild.minimized, "chat no longer minimized");
+ // bug 865086 because we marked it selected on open, it still is
+ todo(chatbar.selectedChat != chatbar.firstElementChild, "chat is not selected");
+ ok(isTabFocused(), "tab should still be focused");
+ next();
+ });
+ });
+ });
+ },
+
+ // Here we open a chat, which will not be focused. Then we minimize it and
+ // restore it via a titlebar clock - it should get focus at that point.
+ testFocusOnExplicitRestore: function(next) {
+ startTestAndWaitForSidebar(function(port) {
+ openChatViaSidebarMessage(port, {stealFocus: 1}, function() {
+ ok(true, "got chatbox message");
+ ok(isTabFocused(), "tab should still be focused");
+ let chatbox = SocialChatBar.chatbar.firstElementChild;
+ ok(chatbox, "chat opened");
+ chatbox.minimized = true;
+ ok(isTabFocused(), "tab should still be focused");
+ // pretend we clicked on the titlebar
+ chatbox.onTitlebarClick({button: 0});
+ waitForCondition(function() isChatFocused(SocialChatBar.chatbar.selectedChat),
+ function() {
+ ok(!chatbox.minimized, "chat should have been restored");
+ ok(isChatFocused(chatbox), "chat should be focused");
+ is(chatbox, SocialChatBar.chatbar.selectedChat, "chat is marked selected");
+ next();
+ }, "chat should have focus");
+ });
+ });
+ },
+
+ // Open 2 chats and give 1 focus. Minimize the focused one - the second
+ // should get focus.
+ testMinimizeFocused: function(next) {
+ let chatbar = SocialChatBar.chatbar;
+ startTestAndWaitForSidebar(function(port) {
+ openChatViaSidebarMessage(port, {stealFocus: 1, id: 1}, function() {
+ let chat1 = chatbar.firstElementChild;
+ openChatViaSidebarMessage(port, {stealFocus: 1, id: 2}, function() {
+ is(chatbar.childElementCount, 2, "exactly 2 chats open");
+ let chat2 = chat1.nextElementSibling || chat1.previousElementSibling;
+ chatbar.selectedChat = chat1;
+ chatbar.focus();
+ waitForCondition(function() isChatFocused(chat1),
+ function() {
+ is(chat1, SocialChatBar.chatbar.selectedChat, "chat1 is marked selected");
+ isnot(chat2, SocialChatBar.chatbar.selectedChat, "chat2 is not marked selected");
+ chat1.minimized = true;
+ waitForCondition(function() isChatFocused(chat2),
+ function() {
+ // minimizing the chat with focus should give it to another.
+ isnot(chat1, SocialChatBar.chatbar.selectedChat, "chat1 is not marked selected");
+ is(chat2, SocialChatBar.chatbar.selectedChat, "chat2 is marked selected");
+ next();
+ }, "chat2 should have focus");
+ }, "chat1 should have focus");
+ });
+ });
+ });
+ },
+
+ // Open 2 chats, select (but not focus) one, then re-request it be
+ // opened via a message. Focus should not move.
+ testReopenNonFocused: function(next) {
+ let chatbar = SocialChatBar.chatbar;
+ startTestAndWaitForSidebar(function(port) {
+ openChatViaSidebarMessage(port, {id: 1}, function() {
+ let chat1 = chatbar.firstElementChild;
+ openChatViaSidebarMessage(port, {id: 2}, function() {
+ let chat2 = chat1.nextElementSibling || chat1.previousElementSibling;
+ chatbar.selectedChat = chat2;
+ // tab still has focus
+ ok(isTabFocused(), "tab should still be focused");
+ // re-request the first.
+ openChatViaSidebarMessage(port, {id: 1}, function() {
+ is(chatbar.selectedChat, chat1, "chat1 now selected");
+ ok(isTabFocused(), "tab should still be focused");
+ next();
+ });
+ });
+ });
+ });
+ },
+
+ // Open 2 chats, select and focus the second. Pressing the TAB key should
+ // cause focus to move between all elements in our chat window before moving
+ // to the next chat window.
+ testTab: function(next) {
+ function sendTabAndWaitForFocus(chat, eltid, callback) {
+ // ideally we would use the 'focus' event here, but that doesn't work
+ // as expected for the iframe - the iframe itself never gets the focus
+ // event (apparently the sub-document etc does.)
+ // So just poll for the correct element getting focus...
+ let doc = chat.contentDocument;
+ EventUtils.sendKey("tab");
+ waitForCondition(function() {
+ let elt = eltid ? doc.getElementById(eltid) : doc.documentElement;
+ return doc.activeElement == elt;
+ }, callback, "element " + eltid + " never got focus");
+ }
+
+ let chatbar = SocialChatBar.chatbar;
+ startTestAndWaitForSidebar(function(port) {
+ openChatViaSidebarMessage(port, {id: 1}, function() {
+ let chat1 = chatbar.firstElementChild;
+ openChatViaSidebarMessage(port, {id: 2}, function() {
+ let chat2 = chat1.nextElementSibling || chat1.previousElementSibling;
+ chatbar.selectedChat = chat2;
+ chatbar.focus();
+ waitForCondition(function() isChatFocused(chatbar.selectedChat),
+ function() {
+ // Our chats have 3 focusable elements, so it takes 4 TABs to move
+ // to the new chat.
+ sendTabAndWaitForFocus(chat2, "input1", function() {
+ is(chat2.contentDocument.activeElement.getAttribute("id"), "input1",
+ "first input field has focus");
+ ok(isChatFocused(chat2), "new chat still focused after first tab");
+ sendTabAndWaitForFocus(chat2, "input2", function() {
+ ok(isChatFocused(chat2), "new chat still focused after tab");
+ is(chat2.contentDocument.activeElement.getAttribute("id"), "input2",
+ "second input field has focus");
+ sendTabAndWaitForFocus(chat2, "iframe", function() {
+ ok(isChatFocused(chat2), "new chat still focused after tab");
+ is(chat2.contentDocument.activeElement.getAttribute("id"), "iframe",
+ "iframe has focus");
+ // this tab now should move to the next chat, but focus the
+ // document element itself (hence the null eltid)
+ sendTabAndWaitForFocus(chat1, null, function() {
+ ok(isChatFocused(chat1), "first chat is focused");
+ next();
+ });
+ });
+ });
+ });
+ }, "chat should have focus");
+ });
+ });
+ });
+ },
+
+ // Open a chat and focus an element other than the first. Move focus to some
+ // other item (the tab itself in this case), then focus the chatbar - the
+ // same element that was previously focused should still have focus.
+ testFocusedElement: function(next) {
+ let chatbar = SocialChatBar.chatbar;
+ startTestAndWaitForSidebar(function(port) {
+ openChatViaUser();
+ let chat = chatbar.firstElementChild;
+ // need to wait for the content to load before we can focus it.
+ chat.addEventListener("DOMContentLoaded", function DOMContentLoaded() {
+ chat.removeEventListener("DOMContentLoaded", DOMContentLoaded);
+ chat.contentDocument.getElementById("input2").focus();
+ waitForCondition(function() isChatFocused(chat),
+ function() {
+ is(chat.contentDocument.activeElement.getAttribute("id"), "input2",
+ "correct input field has focus");
+ // set focus to the tab.
+ let tabb = gBrowser.getBrowserForTab(gBrowser.selectedTab);
+ Services.focus.moveFocus(tabb.contentWindow, null, Services.focus.MOVEFOCUS_ROOT, 0);
+ waitForCondition(function() isTabFocused(),
+ function() {
+ chatbar.focus();
+ waitForCondition(function() isChatFocused(chat),
+ function() {
+ is(chat.contentDocument.activeElement.getAttribute("id"), "input2",
+ "correct input field still has focus");
+ next();
+ }, "chat took focus");
+ }, "tab has focus");
+ }, "chat took focus");
+ });
+ });
+ },
+};
diff --git a/browser/base/content/test/social/browser_social_errorPage.js b/browser/base/content/test/social/browser_social_errorPage.js
new file mode 100644
index 000000000..325d5c80e
--- /dev/null
+++ b/browser/base/content/test/social/browser_social_errorPage.js
@@ -0,0 +1,183 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function gc() {
+ Cu.forceGC();
+ let wu = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindowUtils);
+ wu.garbageCollect();
+}
+
+// Support for going on and offline.
+// (via browser/base/content/test/browser_bookmark_titles.js)
+let origProxyType = Services.prefs.getIntPref('network.proxy.type');
+
+function goOffline() {
+ // Simulate a network outage with offline mode. (Localhost is still
+ // accessible in offline mode, so disable the test proxy as well.)
+ if (!Services.io.offline)
+ BrowserOffline.toggleOfflineStatus();
+ Services.prefs.setIntPref('network.proxy.type', 0);
+ // LOAD_FLAGS_BYPASS_CACHE isn't good enough. So clear the cache.
+ Services.cache.evictEntries(Components.interfaces.nsICache.STORE_ANYWHERE);
+}
+
+function goOnline(callback) {
+ Services.prefs.setIntPref('network.proxy.type', origProxyType);
+ if (Services.io.offline)
+ BrowserOffline.toggleOfflineStatus();
+ if (callback)
+ callback();
+}
+
+function openPanel(url, panelCallback, loadCallback) {
+ // open a flyout
+ SocialFlyout.open(url, 0, panelCallback);
+ SocialFlyout.panel.firstChild.addEventListener("load", function panelLoad() {
+ SocialFlyout.panel.firstChild.removeEventListener("load", panelLoad, true);
+ loadCallback();
+ }, true);
+}
+
+function openChat(url, panelCallback, loadCallback) {
+ // open a chat window
+ SocialChatBar.openChat(Social.provider, url, panelCallback);
+ SocialChatBar.chatbar.firstChild.addEventListener("DOMContentLoaded", function panelLoad() {
+ SocialChatBar.chatbar.firstChild.removeEventListener("DOMContentLoaded", panelLoad, true);
+ loadCallback();
+ }, true);
+}
+
+function onSidebarLoad(callback) {
+ let sbrowser = document.getElementById("social-sidebar-browser");
+ sbrowser.addEventListener("load", function load() {
+ sbrowser.removeEventListener("load", load, true);
+ callback();
+ }, true);
+}
+
+function ensureWorkerLoaded(provider, callback) {
+ // once the worker responds to a ping we know it must be up.
+ let port = provider.getWorkerPort();
+ port.onmessage = function(msg) {
+ if (msg.data.topic == "pong") {
+ port.close();
+ callback();
+ }
+ }
+ port.postMessage({topic: "ping"})
+}
+
+let manifest = { // normal provider
+ name: "provider 1",
+ origin: "https://example.com",
+ sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
+ workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
+ iconURL: "https://example.com/browser/browser/base/content/test/moz.png"
+};
+
+function test() {
+ waitForExplicitFinish();
+ // we don't want the sidebar to auto-load in these tests..
+ Services.prefs.setBoolPref("social.sidebar.open", false);
+ registerCleanupFunction(function() {
+ Services.prefs.clearUserPref("social.sidebar.open");
+ });
+
+ runSocialTestWithProvider(manifest, function (finishcb) {
+ runSocialTests(tests, undefined, goOnline, finishcb);
+ });
+}
+
+var tests = {
+ testSidebar: function(next) {
+ let sbrowser = document.getElementById("social-sidebar-browser");
+ onSidebarLoad(function() {
+ ok(sbrowser.contentDocument.location.href.indexOf("about:socialerror?")==0, "is on social error page");
+ gc();
+ // Add a new load listener, then find and click the "try again" button.
+ onSidebarLoad(function() {
+ // should still be on the error page.
+ ok(sbrowser.contentDocument.location.href.indexOf("about:socialerror?")==0, "is still on social error page");
+ // go online and try again - this should work.
+ goOnline();
+ onSidebarLoad(function() {
+ // should now be on the correct page.
+ is(sbrowser.contentDocument.location.href, manifest.sidebarURL, "is now on social sidebar page");
+ next();
+ });
+ sbrowser.contentDocument.getElementById("btnTryAgain").click();
+ });
+ sbrowser.contentDocument.getElementById("btnTryAgain").click();
+ });
+ // we want the worker to be fully loaded before going offline, otherwise
+ // it might fail due to going offline.
+ ensureWorkerLoaded(Social.provider, function() {
+ // go offline then attempt to load the sidebar - it should fail.
+ goOffline();
+ Services.prefs.setBoolPref("social.sidebar.open", true);
+ });
+ },
+
+ testFlyout: function(next) {
+ let panelCallbackCount = 0;
+ let panel = document.getElementById("social-flyout-panel");
+ // go offline and open a flyout.
+ goOffline();
+ openPanel(
+ "https://example.com/browser/browser/base/content/test/social/social_panel.html",
+ function() { // the panel api callback
+ panelCallbackCount++;
+ },
+ function() { // the "load" callback.
+ executeSoon(function() {
+ todo_is(panelCallbackCount, 0, "Bug 833207 - should be no callback when error page loads.");
+ ok(panel.firstChild.contentDocument.location.href.indexOf("about:socialerror?")==0, "is on social error page");
+ // Bug 832943 - the listeners previously stopped working after a GC, so
+ // force a GC now and try again.
+ gc();
+ openPanel(
+ "https://example.com/browser/browser/base/content/test/social/social_panel.html",
+ function() { // the panel api callback
+ panelCallbackCount++;
+ },
+ function() { // the "load" callback.
+ executeSoon(function() {
+ todo_is(panelCallbackCount, 0, "Bug 833207 - should be no callback when error page loads.");
+ ok(panel.firstChild.contentDocument.location.href.indexOf("about:socialerror?")==0, "is on social error page");
+ gc();
+ SocialFlyout.unload();
+ next();
+ });
+ }
+ );
+ });
+ }
+ );
+ },
+
+ testChatWindow: function(next) {
+ let panelCallbackCount = 0;
+ // go offline and open a flyout.
+ goOffline();
+ openChat(
+ "https://example.com/browser/browser/base/content/test/social/social_chat.html",
+ function() { // the panel api callback
+ panelCallbackCount++;
+ },
+ function() { // the "load" callback.
+ executeSoon(function() {
+ todo_is(panelCallbackCount, 0, "Bug 833207 - should be no callback when error page loads.");
+ let chat = SocialChatBar.chatbar.selectedChat;
+ waitForCondition(function() chat.contentDocument.location.href.indexOf("about:socialerror?")==0,
+ function() {
+ chat.close();
+ next();
+ },
+ "error page didn't appear");
+ });
+ }
+ );
+ }
+}
diff --git a/browser/base/content/test/social/browser_social_flyout.js b/browser/base/content/test/social/browser_social_flyout.js
new file mode 100644
index 000000000..b8642e338
--- /dev/null
+++ b/browser/base/content/test/social/browser_social_flyout.js
@@ -0,0 +1,164 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ let manifest = { // normal provider
+ name: "provider 1",
+ origin: "https://example.com",
+ sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
+ workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
+ iconURL: "https://example.com/browser/browser/base/content/test/moz.png"
+ };
+ runSocialTestWithProvider(manifest, function (finishcb) {
+ runSocialTests(tests, undefined, undefined, finishcb);
+ });
+}
+
+var tests = {
+ testOpenCloseFlyout: function(next) {
+ let panel = document.getElementById("social-flyout-panel");
+ panel.addEventListener("popupshowing", function onShowing() {
+ panel.removeEventListener("popupshowing", onShowing);
+ is(panel.firstChild.contentDocument.readyState, "complete", "panel is loaded prior to showing");
+ });
+ let port = Social.provider.getWorkerPort();
+ ok(port, "provider has a port");
+ port.onmessage = function (e) {
+ let topic = e.data.topic;
+ switch (topic) {
+ case "got-sidebar-message":
+ port.postMessage({topic: "test-flyout-open"});
+ break;
+ case "got-flyout-visibility":
+ if (e.data.result == "hidden") {
+ ok(true, "flyout visibility is 'hidden'");
+ is(panel.state, "closed", "panel really is closed");
+ port.close();
+ next();
+ } else if (e.data.result == "shown") {
+ ok(true, "flyout visibility is 'shown");
+ port.postMessage({topic: "test-flyout-close"});
+ }
+ break;
+ case "got-flyout-message":
+ ok(e.data.result == "ok", "got flyout message");
+ break;
+ }
+ }
+ port.postMessage({topic: "test-init"});
+ },
+
+ testResizeFlyout: function(next) {
+ let panel = document.getElementById("social-flyout-panel");
+ let port = Social.provider.getWorkerPort();
+ ok(port, "provider has a port");
+ port.onmessage = function (e) {
+ let topic = e.data.topic;
+ switch (topic) {
+ case "test-init-done":
+ port.postMessage({topic: "test-flyout-open"});
+ break;
+ case "got-flyout-visibility":
+ if (e.data.result != "shown")
+ return;
+ // The width of the flyout should be 400px initially
+ let iframe = panel.firstChild;
+ let body = iframe.contentDocument.body;
+ let cs = iframe.contentWindow.getComputedStyle(body);
+
+ is(cs.width, "400px", "should be 400px wide");
+ is(iframe.boxObject.width, 400, "iframe should now be 400px wide");
+ is(cs.height, "400px", "should be 400px high");
+ is(iframe.boxObject.height, 400, "iframe should now be 400px high");
+
+ iframe.contentWindow.addEventListener("resize", function _doneHandler() {
+ iframe.contentWindow.removeEventListener("resize", _doneHandler, false);
+ cs = iframe.contentWindow.getComputedStyle(body);
+
+ is(cs.width, "500px", "should now be 500px wide");
+ is(iframe.boxObject.width, 500, "iframe should now be 500px wide");
+ is(cs.height, "500px", "should now be 500px high");
+ is(iframe.boxObject.height, 500, "iframe should now be 500px high");
+ panel.hidePopup();
+ port.close();
+ next();
+ }, false);
+ SocialFlyout.dispatchPanelEvent("socialTest-MakeWider");
+ break;
+ }
+ }
+ port.postMessage({topic: "test-init"});
+ },
+
+ testCloseSelf: function(next) {
+ // window.close is affected by the pref dom.allow_scripts_to_close_windows,
+ // which defaults to false, but is set to true by the test harness.
+ // so temporarily set it back.
+ const ALLOW_SCRIPTS_TO_CLOSE_PREF = "dom.allow_scripts_to_close_windows";
+ // note clearUserPref doesn't do what we expect, as the test harness itself
+ // changes the pref value - so clearUserPref resets it to false rather than
+ // the true setup by the test harness.
+ let oldAllowScriptsToClose = Services.prefs.getBoolPref(ALLOW_SCRIPTS_TO_CLOSE_PREF);
+ Services.prefs.setBoolPref(ALLOW_SCRIPTS_TO_CLOSE_PREF, false);
+ let panel = document.getElementById("social-flyout-panel");
+ let port = Social.provider.getWorkerPort();
+ ok(port, "provider has a port");
+ port.onmessage = function (e) {
+ let topic = e.data.topic;
+ switch (topic) {
+ case "test-init-done":
+ port.postMessage({topic: "test-flyout-open"});
+ break;
+ case "got-flyout-visibility":
+ let iframe = panel.firstChild;
+ iframe.contentDocument.addEventListener("SocialTest-DoneCloseSelf", function _doneHandler() {
+ iframe.contentDocument.removeEventListener("SocialTest-DoneCloseSelf", _doneHandler, false);
+ is(panel.state, "closed", "flyout should have closed itself");
+ Services.prefs.setBoolPref(ALLOW_SCRIPTS_TO_CLOSE_PREF, oldAllowScriptsToClose);
+ next();
+ }, false);
+ is(panel.state, "open", "flyout should be open");
+ port.close(); // so we don't get the -visibility message as it hides...
+ SocialFlyout.dispatchPanelEvent("socialTest-CloseSelf");
+ break;
+ }
+ }
+ port.postMessage({topic: "test-init"});
+ },
+
+ testCloseOnLinkTraversal: function(next) {
+
+ function onTabOpen(event) {
+ gBrowser.tabContainer.removeEventListener("TabOpen", onTabOpen, true);
+ waitForCondition(function() { return panel.state == "closed" }, function() {
+ gBrowser.removeTab(event.target);
+ next();
+ }, "panel should close after tab open");
+ }
+
+ let panel = document.getElementById("social-flyout-panel");
+ let port = Social.provider.getWorkerPort();
+ ok(port, "provider has a port");
+ port.onmessage = function (e) {
+ let topic = e.data.topic;
+ switch (topic) {
+ case "test-init-done":
+ port.postMessage({topic: "test-flyout-open"});
+ break;
+ case "got-flyout-visibility":
+ if (e.data.result == "shown") {
+ // click on our test link
+ is(panel.state, "open", "flyout should be open");
+ gBrowser.tabContainer.addEventListener("TabOpen", onTabOpen, true);
+ let iframe = panel.firstChild;
+ iframe.contentDocument.getElementById('traversal').click();
+ }
+ break;
+ }
+ }
+ port.postMessage({topic: "test-init"});
+ }
+}
diff --git a/browser/base/content/test/social/browser_social_isVisible.js b/browser/base/content/test/social/browser_social_isVisible.js
new file mode 100644
index 000000000..6ae6b9d1f
--- /dev/null
+++ b/browser/base/content/test/social/browser_social_isVisible.js
@@ -0,0 +1,67 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ let manifest = { // normal provider
+ name: "provider 1",
+ origin: "https://example.com",
+ sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
+ workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
+ iconURL: "https://example.com/browser/browser/base/content/test/moz.png"
+ };
+ runSocialTestWithProvider(manifest, function (finishcb) {
+ runSocialTests(tests, undefined, undefined, finishcb);
+ });
+}
+
+var tests = {
+ testSidebarMessage: function(next) {
+ let port = Social.provider.getWorkerPort();
+ ok(port, "provider has a port");
+ port.postMessage({topic: "test-init"});
+ port.onmessage = function (e) {
+ let topic = e.data.topic;
+ switch (topic) {
+ case "got-sidebar-message":
+ // The sidebar message will always come first, since it loads by default
+ ok(true, "got sidebar message");
+ port.close();
+ next();
+ break;
+ }
+ };
+ },
+ testIsVisible: function(next) {
+ let port = Social.provider.getWorkerPort();
+ port.postMessage({topic: "test-init"});
+ port.onmessage = function (e) {
+ let topic = e.data.topic;
+ switch (topic) {
+ case "got-isVisible-response":
+ is(e.data.result, true, "Sidebar should be visible by default");
+ Social.toggleSidebar();
+ port.close();
+ next();
+ }
+ };
+ port.postMessage({topic: "test-isVisible"});
+ },
+ testIsNotVisible: function(next) {
+ let port = Social.provider.getWorkerPort();
+ port.postMessage({topic: "test-init"});
+ port.onmessage = function (e) {
+ let topic = e.data.topic;
+ switch (topic) {
+ case "got-isVisible-response":
+ is(e.data.result, false, "Sidebar should be hidden");
+ Services.prefs.clearUserPref("social.sidebar.open");
+ port.close();
+ next();
+ }
+ };
+ port.postMessage({topic: "test-isVisible"});
+ }
+}
diff --git a/browser/base/content/test/social/browser_social_markButton.js b/browser/base/content/test/social/browser_social_markButton.js
new file mode 100644
index 000000000..eb7b6dead
--- /dev/null
+++ b/browser/base/content/test/social/browser_social_markButton.js
@@ -0,0 +1,187 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+let prefName = "social.enabled",
+ gFinishCB;
+
+function test() {
+ waitForExplicitFinish();
+
+ // Need to load a http/https/ftp/ftps page for the social mark button to appear
+ let tab = gBrowser.selectedTab = gBrowser.addTab("https://test1.example.com", {skipAnimation: true});
+ tab.linkedBrowser.addEventListener("load", function tabLoad(event) {
+ tab.linkedBrowser.removeEventListener("load", tabLoad, true);
+ executeSoon(tabLoaded);
+ }, true);
+
+ registerCleanupFunction(function() {
+ Services.prefs.clearUserPref(prefName);
+ gBrowser.removeTab(tab);
+ });
+}
+
+function tabLoaded() {
+ ok(Social, "Social module loaded");
+
+ let manifest = { // normal provider
+ name: "provider 1",
+ origin: "https://example.com",
+ sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
+ workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
+ iconURL: "https://example.com/browser/browser/base/content/test/moz.png"
+ };
+ runSocialTestWithProvider(manifest, function (finishcb) {
+ gFinishCB = finishcb;
+ testInitial();
+ });
+}
+
+function testInitial(finishcb) {
+ ok(Social.provider, "Social provider is active");
+ ok(Social.provider.enabled, "Social provider is enabled");
+ let port = Social.provider.getWorkerPort();
+ ok(port, "Social provider has a port to its FrameWorker");
+ port.close();
+
+ let markButton = SocialMark.button;
+ ok(markButton, "mark button exists");
+
+ // ensure the worker initialization and handshakes are all done and we
+ // have a profile and the worker has sent a page-mark-config msg.
+ waitForCondition(function() Social.provider.pageMarkInfo != null, function() {
+ is(markButton.hasAttribute("marked"), false, "SocialMark button should not have 'marked' attribute before mark button is clicked");
+ // Check the strings from our worker actually ended up on the button.
+ is(markButton.getAttribute("tooltiptext"), "Mark this page", "check tooltip text is correct");
+ // Check the relative URL was resolved correctly (note this image has offsets of zero...)
+ is(markButton.style.listStyleImage, 'url("https://example.com/browser/browser/base/content/test/social/social_mark_image.png")', "check image url is correct");
+
+ // Test the mark button command handler
+ SocialMark.togglePageMark(function() {
+ is(markButton.hasAttribute("marked"), true, "mark button should have 'marked' attribute after mark button is clicked");
+ is(markButton.getAttribute("tooltiptext"), "Unmark this page", "check tooltip text is correct");
+ // Check the URL and offsets were applied correctly
+ is(markButton.style.listStyleImage, 'url("https://example.com/browser/browser/base/content/test/social/social_mark_image.png")', "check image url is correct");
+ SocialMark.togglePageMark(function() {
+ is(markButton.hasAttribute("marked"), false, "mark button should not be marked");
+ executeSoon(function() {
+ testStillMarkedIn2Tabs();
+ });
+ });
+ });
+ }, "provider didn't provide page-mark-config");
+}
+
+function testStillMarkedIn2Tabs() {
+ let toMark = "http://test2.example.com";
+ let markUri = Services.io.newURI(toMark, null, null);
+ let markButton = SocialMark.button;
+ let initialTab = gBrowser.selectedTab;
+ info("initialTab has loaded " + gBrowser.currentURI.spec);
+ is(markButton.hasAttribute("marked"), false, "SocialMark button should not have 'marked' for the initial tab");
+
+ addTab(toMark, function(tab1) {
+ addTab(toMark, function(tab2) {
+ // should start without either page being marked.
+ is(markButton.hasAttribute("marked"), false, "SocialMark button should not have 'marked' before we've done anything");
+ Social.isURIMarked(markUri, function(marked) {
+ ok(!marked, "page is unmarked in annotations");
+ markButton.click();
+ waitForCondition(function() markButton.hasAttribute("marked"), function() {
+ Social.isURIMarked(markUri, function(marked) {
+ ok(marked, "page is marked in annotations");
+ // and switching to the first tab (with the same URL) should still reflect marked.
+ selectBrowserTab(tab1, function() {
+ is(markButton.hasAttribute("marked"), true, "SocialMark button should reflect the marked state");
+ // wait for tabselect
+ selectBrowserTab(initialTab, function() {
+ waitForCondition(function() !markButton.hasAttribute("marked"), function() {
+ gBrowser.selectedTab = tab1;
+
+ SocialMark.togglePageMark(function() {
+ Social.isURIMarked(gBrowser.currentURI, function(marked) {
+ ok(!marked, "page is unmarked in annotations");
+ is(markButton.hasAttribute("marked"), false, "mark button should not be marked");
+ gBrowser.removeTab(tab1);
+ gBrowser.removeTab(tab2);
+ executeSoon(testStillMarkedAfterReopen);
+ });
+ });
+ }, "button has been unmarked");
+ });
+ });
+ });
+ }, "button has been marked");
+ });
+ });
+ });
+}
+
+function testStillMarkedAfterReopen() {
+ let toMark = "http://test2.example.com";
+ let markButton = SocialMark.button;
+
+ is(markButton.hasAttribute("marked"), false, "Reopen: SocialMark button should not have 'marked' for the initial tab");
+ addTab(toMark, function(tab) {
+ SocialMark.togglePageMark(function() {
+ is(markButton.hasAttribute("marked"), true, "SocialMark button should reflect the marked state");
+ gBrowser.removeTab(tab);
+ // should be on the initial unmarked tab now.
+ waitForCondition(function() !markButton.hasAttribute("marked"), function() {
+ // now open the same URL - should be back to Marked.
+ addTab(toMark, function(tab) {
+ is(markButton.hasAttribute("marked"), true, "New tab to previously marked URL should reflect marked state");
+ SocialMark.togglePageMark(function() {
+ gBrowser.removeTab(tab);
+ executeSoon(testOnlyMarkCertainUrlsTabSwitch);
+ });
+ });
+ }, "button is now unmarked");
+ });
+ });
+}
+
+function testOnlyMarkCertainUrlsTabSwitch() {
+ let toMark = "http://test2.example.com";
+ let notSharable = "about:blank";
+ let markButton = SocialMark.button;
+ addTab(toMark, function(tab) {
+ ok(!markButton.hidden, "SocialMark button not hidden for http url");
+ addTab(notSharable, function(tab2) {
+ ok(markButton.disabled, "SocialMark button disabled for about:blank");
+ selectBrowserTab(tab, function() {
+ ok(!markButton.disabled, "SocialMark button re-shown when switching back to http: url");
+ selectBrowserTab(tab2, function() {
+ ok(markButton.disabled, "SocialMark button re-hidden when switching back to about:blank");
+ gBrowser.removeTab(tab);
+ gBrowser.removeTab(tab2);
+ executeSoon(testOnlyMarkCertainUrlsSameTab);
+ });
+ });
+ });
+ });
+}
+
+function testOnlyMarkCertainUrlsSameTab() {
+ let toMark = "http://test2.example.com";
+ let notSharable = "about:blank";
+ let markButton = SocialMark.button;
+ addTab(toMark, function(tab) {
+ ok(!markButton.disabled, "SocialMark button not disabled for http url");
+ loadIntoTab(tab, notSharable, function() {
+ ok(markButton.disabled, "SocialMark button disabled for about:blank");
+ loadIntoTab(tab, toMark, function() {
+ ok(!markButton.disabled, "SocialMark button re-enabled http url");
+ gBrowser.removeTab(tab);
+ executeSoon(testDisable);
+ });
+ });
+ });
+}
+
+function testDisable() {
+ let markButton = SocialMark.button;
+ Services.prefs.setBoolPref(prefName, false);
+ is(markButton.hidden, true, "SocialMark button should be hidden when pref is disabled");
+ gFinishCB();
+}
diff --git a/browser/base/content/test/social/browser_social_mozSocial_API.js b/browser/base/content/test/social/browser_social_mozSocial_API.js
new file mode 100644
index 000000000..5a1e08b31
--- /dev/null
+++ b/browser/base/content/test/social/browser_social_mozSocial_API.js
@@ -0,0 +1,81 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ let manifest = { // normal provider
+ name: "provider 1",
+ origin: "https://example.com",
+ sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
+ workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
+ iconURL: "https://example.com/browser/browser/base/content/test/moz.png"
+ };
+ runSocialTestWithProvider(manifest, function (finishcb) {
+ runSocialTests(tests, undefined, undefined, finishcb);
+ });
+}
+
+var tests = {
+ testStatusIcons: function(next) {
+ let iconsReady = false;
+ let gotSidebarMessage = false;
+
+ function checkNext() {
+ if (iconsReady && gotSidebarMessage)
+ triggerIconPanel();
+ }
+
+ function triggerIconPanel() {
+ waitForCondition(function() {
+ let mButton = document.getElementById("social-mark-button");
+ let pButton = document.getElementById("social-provider-button");
+ // wait for a new button to be inserted inbetween the provider and mark
+ // button
+ return pButton.nextSibling != mButton;
+ }, function() {
+ // Click the button to trigger its contentPanel
+ let statusIcon = document.getElementById("social-provider-button").nextSibling;
+ EventUtils.synthesizeMouseAtCenter(statusIcon, {});
+ }, "Status icon didn't become non-hidden");
+ }
+
+ let port = Social.provider.getWorkerPort();
+ ok(port, "provider has a port");
+ port.onmessage = function (e) {
+ let topic = e.data.topic;
+ switch (topic) {
+ case "test-init-done":
+ iconsReady = true;
+ checkNext();
+ break;
+ case "got-panel-message":
+ ok(true, "got panel message");
+ // Check the panel isn't in our history.
+ gURLsNotRemembered.push(e.data.location);
+ break;
+ case "got-social-panel-visibility":
+ if (e.data.result == "shown") {
+ ok(true, "panel shown");
+ let panel = document.getElementById("social-notification-panel");
+ panel.hidePopup();
+ } else if (e.data.result == "hidden") {
+ ok(true, "panel hidden");
+ port.close();
+ next();
+ }
+ break;
+ case "got-sidebar-message":
+ // The sidebar message will always come first, since it loads by default
+ ok(true, "got sidebar message");
+ gotSidebarMessage = true;
+ // load a status panel
+ port.postMessage({topic: "test-ambient-notification"});
+ checkNext();
+ break;
+ }
+ }
+ port.postMessage({topic: "test-init"});
+ }
+}
diff --git a/browser/base/content/test/social/browser_social_multiprovider.js b/browser/base/content/test/social/browser_social_multiprovider.js
new file mode 100644
index 000000000..7ec7f0e2a
--- /dev/null
+++ b/browser/base/content/test/social/browser_social_multiprovider.js
@@ -0,0 +1,111 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ runSocialTestWithProvider(gProviders, function (finishcb) {
+ runSocialTests(tests, undefined, undefined, finishcb);
+ });
+}
+
+let gProviders = [
+ {
+ name: "provider 1",
+ origin: "https://example.com",
+ sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html?provider1",
+ workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
+ iconURL: "chrome://branding/content/icon48.png"
+ },
+ {
+ name: "provider 2",
+ origin: "https://test1.example.com",
+ sidebarURL: "https://test1.example.com/browser/browser/base/content/test/social/social_sidebar.html?provider2",
+ workerURL: "https://test1.example.com/browser/browser/base/content/test/social/social_worker.js",
+ iconURL: "chrome://branding/content/icon48.png"
+ }
+];
+
+var tests = {
+ testProviderSwitch: function(next) {
+ function checkProviderMenu(selectedProvider) {
+ let menu = document.getElementById("social-statusarea-popup");
+ let menuProviders = menu.querySelectorAll(".social-provider-menuitem");
+ is(menuProviders.length, gProviders.length, "correct number of providers listed in the menu");
+ // Find the selectedProvider's menu item
+ let el = menu.getElementsByAttribute("origin", selectedProvider.origin);
+ is(el.length, 1, "selected provider menu item exists");
+ is(el[0].getAttribute("checked"), "true", "selected provider menu item is checked");
+ }
+
+ checkProviderMenu(gProviders[0]);
+
+ // Now wait for the initial provider profile to be set
+ waitForProviderLoad(function() {
+ checkUIStateMatchesProvider(gProviders[0]);
+
+ // Now activate "provider 2"
+ observeProviderSet(function () {
+ waitForProviderLoad(function() {
+ checkUIStateMatchesProvider(gProviders[1]);
+ // disable social, click on the provider menuitem to switch providers
+ Social.enabled = false;
+ let menu = document.getElementById("social-statusarea-popup");
+ let el = menu.getElementsByAttribute("origin", gProviders[0].origin);
+ is(el.length, 1, "selected provider menu item exists");
+ el[0].click();
+ waitForProviderLoad(function() {
+ checkUIStateMatchesProvider(gProviders[0]);
+ next();
+ });
+ });
+ });
+ Social.activateFromOrigin("https://test1.example.com");
+ });
+ }
+}
+
+function checkUIStateMatchesProvider(provider) {
+ let profileData = getExpectedProfileData(provider);
+ // The toolbar
+ let loginStatus = document.getElementsByClassName("social-statusarea-loggedInStatus");
+ for (let label of loginStatus) {
+ is(label.value, profileData.userName, "username name matches provider profile");
+ }
+ // Sidebar
+ is(document.getElementById("social-sidebar-browser").getAttribute("src"), provider.sidebarURL, "side bar URL is set");
+}
+
+function getExpectedProfileData(provider) {
+ // This data is defined in social_worker.js
+ if (provider.origin == "https://test1.example.com") {
+ return {
+ displayName: "Test1 User",
+ userName: "tester"
+ };
+ }
+
+ return {
+ displayName: "Kuma Lisa",
+ userName: "trickster"
+ };
+}
+
+function observeProviderSet(cb) {
+ Services.obs.addObserver(function providerSet(subject, topic, data) {
+ Services.obs.removeObserver(providerSet, "social:provider-set");
+ info("social:provider-set observer was notified");
+ // executeSoon to let the browser UI observers run first
+ executeSoon(cb);
+ }, "social:provider-set", false);
+}
+
+function waitForProviderLoad(cb) {
+ waitForCondition(function() {
+ let sbrowser = document.getElementById("social-sidebar-browser");
+ return Social.provider.profile &&
+ Social.provider.profile.displayName &&
+ sbrowser.docShellIsActive;
+ }, cb, "waitForProviderLoad: provider profile was not set");
+}
diff --git a/browser/base/content/test/social/browser_social_perwindowPB.js b/browser/base/content/test/social/browser_social_perwindowPB.js
new file mode 100644
index 000000000..60fc28529
--- /dev/null
+++ b/browser/base/content/test/social/browser_social_perwindowPB.js
@@ -0,0 +1,82 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function openTab(win, url, callback) {
+ let newTab = win.gBrowser.addTab(url);
+ let tabBrowser = win.gBrowser.getBrowserForTab(newTab);
+ tabBrowser.addEventListener("load", function tabLoadListener() {
+ tabBrowser.removeEventListener("load", tabLoadListener, true);
+ win.gBrowser.selectedTab = newTab;
+ callback(newTab);
+ }, true)
+}
+
+// Tests for per-window private browsing.
+function openPBWindow(callback) {
+ let w = OpenBrowserWindow({private: true});
+ w.addEventListener("load", function loadListener() {
+ w.removeEventListener("load", loadListener);
+ openTab(w, "http://example.com", function() {
+ callback(w);
+ });
+ });
+}
+
+function postAndReceive(port, postTopic, receiveTopic, callback) {
+ port.onmessage = function(e) {
+ if (e.data.topic == receiveTopic)
+ callback();
+ }
+ port.postMessage({topic: postTopic});
+}
+
+function test() {
+ waitForExplicitFinish();
+
+ let manifest = { // normal provider
+ name: "provider 1",
+ origin: "https://example.com",
+ sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
+ workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
+ iconURL: "https://example.com/browser/browser/base/content/test/social/moz.png"
+ };
+ runSocialTestWithProvider(manifest, function (finishcb) {
+ openTab(window, "http://example.com", function(newTab) {
+ runSocialTests(tests, undefined, undefined, function() {
+ window.gBrowser.removeTab(newTab);
+ finishcb();
+ });
+ });
+ });
+}
+
+var tests = {
+ testPrivateBrowsing: function(next) {
+ let port = Social.provider.getWorkerPort();
+ ok(port, "provider has a port");
+ postAndReceive(port, "test-init", "test-init-done", function() {
+ // social features should all be enabled in the existing window.
+ info("checking main window ui");
+ ok(window.SocialUI.enabled, "social is enabled in normal window");
+ checkSocialUI(window);
+ // open a new private-window
+ openPBWindow(function(pbwin) {
+ // The provider should remain alive.
+ postAndReceive(port, "ping", "pong", function() {
+ // the new window should have no social features at all.
+ info("checking private window ui");
+ ok(!pbwin.SocialUI.enabled, "social is disabled in a PB window");
+ checkSocialUI(pbwin);
+ // but they should all remain enabled in the initial window
+ info("checking main window ui");
+ ok(window.SocialUI.enabled, "social is still enabled in normal window");
+ checkSocialUI(window);
+ // that's all folks...
+ pbwin.close();
+ next();
+ })
+ });
+ });
+ },
+}
diff --git a/browser/base/content/test/social/browser_social_sidebar.js b/browser/base/content/test/social/browser_social_sidebar.js
new file mode 100644
index 000000000..b9c471899
--- /dev/null
+++ b/browser/base/content/test/social/browser_social_sidebar.js
@@ -0,0 +1,105 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ let manifest = { // normal provider
+ name: "provider 1",
+ origin: "https://example.com",
+ sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
+ workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
+ iconURL: "https://example.com/browser/browser/base/content/test/moz.png"
+ };
+ runSocialTestWithProvider(manifest, doTest);
+}
+
+function doTest(finishcb) {
+ ok(SocialSidebar.canShow, "social sidebar should be able to be shown");
+ ok(SocialSidebar.opened, "social sidebar should be open by default");
+
+ let command = document.getElementById("Social:ToggleSidebar");
+ let sidebar = document.getElementById("social-sidebar-box");
+ let browser = sidebar.firstChild;
+
+ function checkShown(shouldBeShown) {
+ is(command.getAttribute("checked"), shouldBeShown ? "true" : "false",
+ "toggle command should be " + (shouldBeShown ? "checked" : "unchecked"));
+ is(sidebar.hidden, !shouldBeShown,
+ "sidebar should be " + (shouldBeShown ? "visible" : "hidden"));
+ // The sidebar.open pref only reflects the actual state of the sidebar
+ // when social is enabled.
+ if (Social.enabled)
+ is(Services.prefs.getBoolPref("social.sidebar.open"), shouldBeShown,
+ "sidebar open pref should be " + shouldBeShown);
+ if (shouldBeShown) {
+ is(browser.getAttribute('src'), Social.provider.sidebarURL, "sidebar url should be set");
+ // We don't currently check docShellIsActive as this is only set
+ // after load event fires, and the tests below explicitly wait for this
+ // anyway.
+ }
+ else {
+ ok(!browser.docShellIsActive, "sidebar should have an inactive docshell");
+ // sidebar will only be immediately unloaded (and thus set to
+ // about:blank) when canShow is false.
+ if (SocialSidebar.canShow) {
+ // should not have unloaded so will still be the provider URL.
+ is(browser.getAttribute('src'), Social.provider.sidebarURL, "sidebar url should be set");
+ } else {
+ // should have been an immediate unload.
+ is(browser.getAttribute('src'), "about:blank", "sidebar url should be blank");
+ }
+ }
+ }
+
+ // First check the the sidebar is initially visible, and loaded
+ ok(!command.hidden, "toggle command should be visible");
+ checkShown(true);
+
+ browser.addEventListener("socialFrameHide", function sidebarhide() {
+ browser.removeEventListener("socialFrameHide", sidebarhide);
+
+ checkShown(false);
+
+ browser.addEventListener("socialFrameShow", function sidebarshow() {
+ browser.removeEventListener("socialFrameShow", sidebarshow);
+
+ checkShown(true);
+
+ // Set Social.enabled = false and check everything is as expected.
+ Social.enabled = false;
+ checkShown(false);
+
+ Social.enabled = true;
+ checkShown(true);
+
+ // And an edge-case - disable social and reset the provider.
+ Social.provider = null;
+ Social.enabled = false;
+ checkShown(false);
+
+ // Finish the test
+ finishcb();
+ });
+
+ // Toggle it back on
+ info("Toggling sidebar back on");
+ Social.toggleSidebar();
+ });
+
+ // use port messaging to ensure that the sidebar is both loaded and
+ // ready before we run other tests
+ let port = Social.provider.getWorkerPort();
+ port.postMessage({topic: "test-init"});
+ port.onmessage = function (e) {
+ let topic = e.data.topic;
+ switch (topic) {
+ case "got-sidebar-message":
+ ok(true, "sidebar is loaded and ready");
+ Social.toggleSidebar();
+ }
+ };
+}
+
+// XXX test sidebar in popup
diff --git a/browser/base/content/test/social/browser_social_toolbar.js b/browser/base/content/test/social/browser_social_toolbar.js
new file mode 100644
index 000000000..2a648ade2
--- /dev/null
+++ b/browser/base/content/test/social/browser_social_toolbar.js
@@ -0,0 +1,195 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+let manifest = { // normal provider
+ name: "provider 1",
+ origin: "https://example.com",
+ workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
+ iconURL: "https://example.com/browser/browser/base/content/test/moz.png"
+};
+
+function test() {
+ waitForExplicitFinish();
+
+ runSocialTestWithProvider(manifest, function (finishcb) {
+ runSocialTests(tests, undefined, undefined, finishcb);
+ });
+}
+
+var tests = {
+ testProfileNone: function(next, useNull) {
+ let profile = useNull ? null : {};
+ Social.provider.updateUserProfile(profile);
+ // check dom values
+ let portrait = document.getElementsByClassName("social-statusarea-user-portrait")[0].getAttribute("src");
+ // this is the default image for the profile area when not logged in.
+ ok(!portrait, "portrait is empty");
+ let userDetailsBroadcaster = document.getElementById("socialBroadcaster_userDetails");
+ let notLoggedInStatusValue = userDetailsBroadcaster.getAttribute("notLoggedInLabel");
+ let userButton = document.getElementsByClassName("social-statusarea-loggedInStatus")[0];
+ ok(!userButton.hidden, "username is visible");
+ is(userButton.getAttribute("label"), notLoggedInStatusValue, "label reflects not being logged in");
+ next();
+ },
+ testProfileNull: function(next) {
+ this.testProfileNone(next, true);
+ },
+ testProfileSet: function(next) {
+ let statusIcon = document.getElementById("social-provider-button").style.listStyleImage;
+ is(statusIcon, "url(\"" + manifest.iconURL + "\")", "manifest iconURL is showing");
+ let profile = {
+ portrait: "https://example.com/portrait.jpg",
+ userName: "trickster",
+ displayName: "Kuma Lisa",
+ profileURL: "http://example.com/Kuma_Lisa",
+ iconURL: "https://example.com/browser/browser/base/content/test/social/moz.png"
+ }
+ Social.provider.updateUserProfile(profile);
+ // check dom values
+ statusIcon = document.getElementById("social-provider-button").style.listStyleImage;
+ is(statusIcon, "url(\"" + profile.iconURL + "\")", "profile iconURL is showing");
+ let portrait = document.getElementsByClassName("social-statusarea-user-portrait")[0].getAttribute("src");
+ is(profile.portrait, portrait, "portrait is set");
+ let userButton = document.getElementsByClassName("social-statusarea-loggedInStatus")[0];
+ ok(!userButton.hidden, "username is visible");
+ is(userButton.value, profile.userName, "username is set");
+ next();
+ },
+ testNoAmbientNotificationsIsNoKeyboardMenu: function(next) {
+ // The menu bar isn't as easy to instrument on Mac.
+ if (navigator.platform.contains("Mac")) {
+ info("Skipping checking the menubar on Mac OS");
+ next();
+ return;
+ }
+
+ // Test that keyboard accessible menuitem doesn't exist when no ambient icons specified.
+ let toolsPopup = document.getElementById("menu_ToolsPopup");
+ toolsPopup.addEventListener("popupshown", function ontoolspopupshownNoAmbient() {
+ toolsPopup.removeEventListener("popupshown", ontoolspopupshownNoAmbient);
+ let socialToggleMore = document.getElementById("menu_socialAmbientMenu");
+ ok(socialToggleMore, "Keyboard accessible social menu should exist");
+ is(socialToggleMore.querySelectorAll("menuitem").length, 6, "The minimum number of menuitems is two when there are no ambient notifications.");
+ is(socialToggleMore.hidden, false, "Menu should be visible since we show some non-ambient notifications in the menu.");
+ toolsPopup.hidePopup();
+ next();
+ }, false);
+ document.getElementById("menu_ToolsPopup").openPopup();
+ },
+ testAmbientNotifications: function(next) {
+ let ambience = {
+ name: "testIcon",
+ iconURL: "https://example.com/browser/browser/base/content/test/moz.png",
+ contentPanel: "about:blank",
+ counter: 42,
+ label: "Test Ambient 1 \u2046",
+ menuURL: "https://example.com/testAmbient1"
+ };
+ let ambience2 = {
+ name: "testIcon2",
+ iconURL: "https://example.com/browser/browser/base/content/test/moz.png",
+ contentPanel: "about:blank",
+ counter: 0,
+ label: "Test Ambient 2",
+ menuURL: "https://example.com/testAmbient2"
+ };
+ let ambience3 = {
+ name: "testIcon3",
+ iconURL: "https://example.com/browser/browser/base/content/test/moz.png",
+ contentPanel: "about:blank",
+ counter: 0,
+ label: "Test Ambient 3",
+ menuURL: "https://example.com/testAmbient3"
+ };
+ let ambience4 = {
+ name: "testIcon4",
+ iconURL: "https://example.com/browser/browser/base/content/test/moz.png",
+ contentPanel: "about:blank",
+ counter: 0,
+ label: "Test Ambient 4",
+ menuURL: "https://example.com/testAmbient4"
+ };
+ Social.provider.setAmbientNotification(ambience);
+
+ // for Bug 813834. Check preference whether stored data is correct.
+ is(JSON.parse(Services.prefs.getComplexValue("social.cached.ambientNotificationIcons", Ci.nsISupportsString).data).data.testIcon.label, "Test Ambient 1 \u2046", "label is stored into preference correctly");
+
+ Social.provider.setAmbientNotification(ambience2);
+ Social.provider.setAmbientNotification(ambience3);
+
+ try {
+ Social.provider.setAmbientNotification(ambience4);
+ } catch(e) {}
+ let numIcons = Object.keys(Social.provider.ambientNotificationIcons).length;
+ ok(numIcons == 3, "prevent adding more than 3 ambient notification icons");
+
+ let statusIcon = document.getElementById("social-provider-button").nextSibling;
+ waitForCondition(function() {
+ statusIcon = document.getElementById("social-provider-button").nextSibling;
+ return !!statusIcon;
+ }, function () {
+ let badge = statusIcon.getAttribute("badge");
+ is(badge, "42", "status value is correct");
+ // If there is a counter, the aria-label should reflect it.
+ is(statusIcon.getAttribute("aria-label"), "Test Ambient 1 \u2046 (42)");
+
+ ambience.counter = 0;
+ Social.provider.setAmbientNotification(ambience);
+ badge = statusIcon.getAttribute("badge");
+ is(badge, "", "status value is correct");
+ // If there is no counter, the aria-label should be the same as the label
+ is(statusIcon.getAttribute("aria-label"), "Test Ambient 1 \u2046");
+
+ // The menu bar isn't as easy to instrument on Mac.
+ if (navigator.platform.contains("Mac")) {
+ next();
+ return;
+ }
+
+ // Test that keyboard accessible menuitem was added.
+ let toolsPopup = document.getElementById("menu_ToolsPopup");
+ toolsPopup.addEventListener("popupshown", function ontoolspopupshownAmbient() {
+ toolsPopup.removeEventListener("popupshown", ontoolspopupshownAmbient);
+ let socialToggleMore = document.getElementById("menu_socialAmbientMenu");
+ ok(socialToggleMore, "Keyboard accessible social menu should exist");
+ is(socialToggleMore.querySelectorAll("menuitem").length, 9, "The number of menuitems is minimum plus three ambient notification menuitems.");
+ is(socialToggleMore.hidden, false, "Menu is visible when ambient notifications have label & menuURL");
+ let menuitem = socialToggleMore.querySelector(".ambient-menuitem");
+ is(menuitem.getAttribute("label"), "Test Ambient 1 \u2046", "Keyboard accessible ambient menuitem should have specified label");
+ toolsPopup.hidePopup();
+ next();
+ }, false);
+ document.getElementById("menu_ToolsPopup").openPopup();
+ }, "statusIcon was never found");
+ },
+ testProfileUnset: function(next) {
+ Social.provider.updateUserProfile({});
+ // check dom values
+ let ambientIcons = document.querySelectorAll("#social-toolbar-item > box");
+ for (let ambientIcon of ambientIcons) {
+ ok(ambientIcon.collapsed, "ambient icon (" + ambientIcon.id + ") is collapsed");
+ }
+
+ next();
+ },
+ testMenuitemsExist: function(next) {
+ let toggleSidebarMenuitems = document.getElementsByClassName("social-toggle-sidebar-menuitem");
+ is(toggleSidebarMenuitems.length, 2, "Toggle Sidebar menuitems exist");
+ let toggleDesktopNotificationsMenuitems = document.getElementsByClassName("social-toggle-notifications-menuitem");
+ is(toggleDesktopNotificationsMenuitems.length, 2, "Toggle notifications menuitems exist");
+ let toggleSocialMenuitems = document.getElementsByClassName("social-toggle-menuitem");
+ is(toggleSocialMenuitems.length, 2, "Toggle Social menuitems exist");
+ next();
+ },
+ testToggleNotifications: function(next) {
+ let enabled = Services.prefs.getBoolPref("social.toast-notifications.enabled");
+ let cmd = document.getElementById("Social:ToggleNotifications");
+ is(cmd.getAttribute("checked"), enabled ? "true" : "false");
+ enabled = !enabled;
+ Services.prefs.setBoolPref("social.toast-notifications.enabled", enabled);
+ is(cmd.getAttribute("checked"), enabled ? "true" : "false");
+ Services.prefs.clearUserPref("social.toast-notifications.enabled");
+ next();
+ },
+}
diff --git a/browser/base/content/test/social/browser_social_window.js b/browser/base/content/test/social/browser_social_window.js
new file mode 100644
index 000000000..f6a0afab0
--- /dev/null
+++ b/browser/base/content/test/social/browser_social_window.js
@@ -0,0 +1,145 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// Test the top-level window UI for social.
+
+// This function should "reset" Social such that the next time Social.init()
+// is called (eg, when a new window is opened), it re-performs all
+// initialization.
+function resetSocial() {
+ Social.initialized = false;
+ Social._provider = null;
+ Social.providers = [];
+ // *sob* - listeners keep getting added...
+ let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
+ SocialService._providerListeners.clear();
+}
+
+let createdWindows = [];
+
+function openWindowAndWaitForInit(callback) {
+ // this notification tells us SocialUI.init() has been run...
+ let topic = "browser-delayed-startup-finished";
+ let w = OpenBrowserWindow();
+ createdWindows.push(w);
+ Services.obs.addObserver(function providerSet(subject, topic, data) {
+ Services.obs.removeObserver(providerSet, topic);
+ info(topic + " observer was notified - continuing test");
+ // executeSoon to let the browser UI observers run first
+ executeSoon(function() {callback(w)});
+ }, topic, false);
+}
+
+function postTestCleanup(cb) {
+ for (let w of createdWindows)
+ w.close();
+ createdWindows = [];
+ Services.prefs.clearUserPref("social.enabled");
+ cb();
+}
+
+let manifest = { // normal provider
+ name: "provider 1",
+ origin: "https://example.com",
+ sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
+ workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
+ iconURL: "https://example.com/browser/browser/base/content/test/social/moz.png"
+};
+
+function test() {
+ waitForExplicitFinish();
+ runSocialTests(tests, undefined, postTestCleanup);
+}
+
+let tests = {
+ // check when social is totally disabled at startup (ie, no providers)
+ testInactiveStartup: function(cbnext) {
+ is(Social.providers.length, 0, "needs zero providers to start this test.");
+ resetSocial();
+ openWindowAndWaitForInit(function(w1) {
+ checkSocialUI(w1);
+ // Now social is (re-)initialized, open a secondary window and check that.
+ openWindowAndWaitForInit(function(w2) {
+ checkSocialUI(w2);
+ checkSocialUI(w1);
+ cbnext();
+ });
+ });
+ },
+
+ // Check when providers exist and social is turned on at startup.
+ testEnabledStartup: function(cbnext) {
+ runSocialTestWithProvider(manifest, function (finishcb) {
+ resetSocial();
+ openWindowAndWaitForInit(function(w1) {
+ ok(Social.enabled, "social is enabled");
+ checkSocialUI(w1);
+ // now init is complete, open a second window
+ openWindowAndWaitForInit(function(w2) {
+ checkSocialUI(w2);
+ checkSocialUI(w1);
+ // disable social and re-check
+ Services.prefs.setBoolPref("social.enabled", false);
+ executeSoon(function() { // let all the UI observers run...
+ ok(!Social.enabled, "social is disabled");
+ checkSocialUI(w2);
+ checkSocialUI(w1);
+ finishcb();
+ });
+ });
+ });
+ }, cbnext);
+ },
+
+ // Check when providers exist but social is turned off at startup.
+ testDisabledStartup: function(cbnext) {
+ runSocialTestWithProvider(manifest, function (finishcb) {
+ Services.prefs.setBoolPref("social.enabled", false);
+ resetSocial();
+ openWindowAndWaitForInit(function(w1) {
+ ok(!Social.enabled, "social is disabled");
+ checkSocialUI(w1);
+ // now init is complete, open a second window
+ openWindowAndWaitForInit(function(w2) {
+ checkSocialUI(w2);
+ checkSocialUI(w1);
+ // enable social and re-check
+ Services.prefs.setBoolPref("social.enabled", true);
+ executeSoon(function() { // let all the UI observers run...
+ ok(Social.enabled, "social is enabled");
+ checkSocialUI(w2);
+ checkSocialUI(w1);
+ finishcb();
+ });
+ });
+ });
+ }, cbnext);
+ },
+
+ // Check when the last provider is removed.
+ testRemoveProvider: function(cbnext) {
+ runSocialTestWithProvider(manifest, function (finishcb) {
+ openWindowAndWaitForInit(function(w1) {
+ checkSocialUI(w1);
+ // now init is complete, open a second window
+ openWindowAndWaitForInit(function(w2) {
+ checkSocialUI(w2);
+ // remove the current provider.
+ let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
+ SocialService.removeProvider(manifest.origin, function() {
+ ok(!Social.enabled, "social is disabled");
+ is(Social.providers.length, 0, "no providers");
+ checkSocialUI(w2);
+ checkSocialUI(w1);
+ // *sob* - runSocialTestWithProvider's cleanup fails when it can't
+ // remove the provider, so re-add it.
+ SocialService.addProvider(manifest, function() {
+ finishcb();
+ });
+ });
+ });
+ });
+ }, cbnext);
+ },
+}
diff --git a/browser/base/content/test/social/head.js b/browser/base/content/test/social/head.js
new file mode 100644
index 000000000..1e5d8412c
--- /dev/null
+++ b/browser/base/content/test/social/head.js
@@ -0,0 +1,517 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/commonjs/sdk/core/promise.js");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+
+function waitForCondition(condition, nextTest, errorMsg) {
+ var tries = 0;
+ var interval = setInterval(function() {
+ if (tries >= 30) {
+ ok(false, errorMsg);
+ moveOn();
+ }
+ if (condition()) {
+ moveOn();
+ }
+ tries++;
+ }, 100);
+ var moveOn = function() { clearInterval(interval); nextTest(); };
+}
+
+// Check that a specified (string) URL hasn't been "remembered" (ie, is not
+// in history, will not appear in about:newtab or auto-complete, etc.)
+function promiseSocialUrlNotRemembered(url) {
+ let deferred = Promise.defer();
+ let uri = Services.io.newURI(url, null, null);
+ PlacesUtils.asyncHistory.isURIVisited(uri, function(aURI, aIsVisited) {
+ ok(!aIsVisited, "social URL " + url + " should not be in global history");
+ deferred.resolve();
+ });
+ return deferred.promise;
+}
+
+let gURLsNotRemembered = [];
+
+
+function checkProviderPrefsEmpty(isError) {
+ let MANIFEST_PREFS = Services.prefs.getBranch("social.manifest.");
+ let prefs = MANIFEST_PREFS.getChildList("", []);
+ let c = 0;
+ for (let pref of prefs) {
+ if (MANIFEST_PREFS.prefHasUserValue(pref)) {
+ info("provider [" + pref + "] manifest left installed from previous test");
+ c++;
+ }
+ }
+ is(c, 0, "all provider prefs uninstalled from previous test");
+ is(Social.providers.length, 0, "all providers uninstalled from previous test " + Social.providers.length);
+}
+
+function defaultFinishChecks() {
+ checkProviderPrefsEmpty(true);
+ finish();
+}
+
+function runSocialTestWithProvider(manifest, callback, finishcallback) {
+ let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
+
+ let manifests = Array.isArray(manifest) ? manifest : [manifest];
+
+ // Check that none of the provider's content ends up in history.
+ function finishCleanUp() {
+ for (let i = 0; i < manifests.length; i++) {
+ let m = manifests[i];
+ for (let what of ['sidebarURL', 'workerURL', 'iconURL']) {
+ if (m[what]) {
+ yield promiseSocialUrlNotRemembered(m[what]);
+ }
+ };
+ }
+ for (let i = 0; i < gURLsNotRemembered.length; i++) {
+ yield promiseSocialUrlNotRemembered(gURLsNotRemembered[i]);
+ }
+ gURLsNotRemembered = [];
+ }
+
+ info("runSocialTestWithProvider: " + manifests.toSource());
+
+ let finishCount = 0;
+ function finishIfDone(callFinish) {
+ finishCount++;
+ if (finishCount == manifests.length)
+ Task.spawn(finishCleanUp).then(finishcallback || defaultFinishChecks);
+ }
+ function removeAddedProviders(cleanup) {
+ manifests.forEach(function (m) {
+ // If we're "cleaning up", don't call finish when done.
+ let callback = cleanup ? function () {} : finishIfDone;
+ // Similarly, if we're cleaning up, catch exceptions from removeProvider
+ let removeProvider = SocialService.removeProvider.bind(SocialService);
+ if (cleanup) {
+ removeProvider = function (origin, cb) {
+ try {
+ SocialService.removeProvider(origin, cb);
+ } catch (ex) {
+ // Ignore "provider doesn't exist" errors.
+ if (ex.message.indexOf("SocialService.removeProvider: no provider with origin") == 0)
+ return;
+ info("Failed to clean up provider " + origin + ": " + ex);
+ }
+ }
+ }
+ removeProvider(m.origin, callback);
+ });
+ }
+ function finishSocialTest(cleanup) {
+ // disable social before removing the providers to avoid providers
+ // being activated immediately before we get around to removing it.
+ Services.prefs.clearUserPref("social.enabled");
+ removeAddedProviders(cleanup);
+ }
+
+ let providersAdded = 0;
+ let firstProvider;
+
+ manifests.forEach(function (m) {
+ SocialService.addProvider(m, function(provider) {
+
+ providersAdded++;
+ info("runSocialTestWithProvider: provider added");
+
+ // we want to set the first specified provider as the UI's provider
+ if (provider.origin == manifests[0].origin) {
+ firstProvider = provider;
+ }
+
+ // If we've added all the providers we need, call the callback to start
+ // the tests (and give it a callback it can call to finish them)
+ if (providersAdded == manifests.length) {
+ // Set the UI's provider (which enables the feature)
+ Social.provider = firstProvider;
+
+ registerCleanupFunction(function () {
+ finishSocialTest(true);
+ });
+ callback(finishSocialTest);
+ }
+ });
+ });
+}
+
+function runSocialTests(tests, cbPreTest, cbPostTest, cbFinish) {
+ let testIter = Iterator(tests);
+ let providersAtStart = Social.providers.length;
+ info("runSocialTests: start test run with " + providersAtStart + " providers");
+
+ if (cbPreTest === undefined) {
+ cbPreTest = function(cb) {cb()};
+ }
+ if (cbPostTest === undefined) {
+ cbPostTest = function(cb) {cb()};
+ }
+
+ function runNextTest() {
+ let name, func;
+ try {
+ [name, func] = testIter.next();
+ } catch (err if err instanceof StopIteration) {
+ // out of items:
+ (cbFinish || defaultFinishChecks)();
+ is(providersAtStart, Social.providers.length,
+ "runSocialTests: finish test run with " + Social.providers.length + " providers");
+ return;
+ }
+ // We run on a timeout as the frameworker also makes use of timeouts, so
+ // this helps keep the debug messages sane.
+ executeSoon(function() {
+ function cleanupAndRunNextTest() {
+ info("sub-test " + name + " complete");
+ cbPostTest(runNextTest);
+ }
+ cbPreTest(function() {
+ is(providersAtStart, Social.providers.length, "pre-test: no new providers left enabled");
+ info("sub-test " + name + " starting");
+ try {
+ func.call(tests, cleanupAndRunNextTest);
+ } catch (ex) {
+ ok(false, "sub-test " + name + " failed: " + ex.toString() +"\n"+ex.stack);
+ cleanupAndRunNextTest();
+ }
+ })
+ });
+ }
+ runNextTest();
+}
+
+// A fairly large hammer which checks all aspects of the SocialUI for
+// internal consistency.
+function checkSocialUI(win) {
+ win = win || window;
+ let doc = win.document;
+ let provider = Social.provider;
+ let enabled = win.SocialUI.enabled;
+ let active = Social.providers.length > 0 && !win.SocialUI._chromeless &&
+ !PrivateBrowsingUtils.isWindowPrivate(win);
+
+ function isbool(a, b, msg) {
+ is(!!a, !!b, msg);
+ }
+ isbool(win.SocialSidebar.canShow, enabled, "social sidebar active?");
+ if (enabled)
+ isbool(win.SocialSidebar.opened, enabled, "social sidebar open?");
+ isbool(win.SocialChatBar.isAvailable, enabled && Social.haveLoggedInUser(), "chatbar available?");
+ isbool(!win.SocialChatBar.chatbar.hidden, enabled && Social.haveLoggedInUser(), "chatbar visible?");
+
+ let markVisible = enabled && provider.pageMarkInfo;
+ let canMark = markVisible && win.SocialMark.canMarkPage(win.gBrowser.currentURI);
+ isbool(!win.SocialMark.button.hidden, markVisible, "SocialMark button visible?");
+ isbool(!win.SocialMark.button.disabled, canMark, "SocialMark button enabled?");
+ isbool(!doc.getElementById("social-toolbar-item").hidden, active, "toolbar items visible?");
+ if (active) {
+ if (!enabled) {
+ ok(!win.SocialToolbar.button.style.listStyleImage, "toolbar button is default icon");
+ } else {
+ is(win.SocialToolbar.button.style.listStyleImage, 'url("' + Social.defaultProvider.iconURL + '")', "toolbar button has provider icon");
+ }
+ }
+ // the menus should always have the provider name
+ if (provider) {
+ for (let id of ["menu_socialSidebar", "menu_socialAmbientMenu"])
+ is(document.getElementById(id).getAttribute("label"), Social.provider.name, "element has the provider name");
+ }
+
+ // and for good measure, check all the social commands.
+ isbool(!doc.getElementById("Social:Toggle").hidden, active, "Social:Toggle visible?");
+ isbool(!doc.getElementById("Social:ToggleNotifications").hidden, enabled, "Social:ToggleNotifications visible?");
+ isbool(!doc.getElementById("Social:FocusChat").hidden, enabled && Social.haveLoggedInUser(), "Social:FocusChat visible?");
+ isbool(doc.getElementById("Social:FocusChat").getAttribute("disabled"), enabled ? "false" : "true", "Social:FocusChat disabled?");
+ is(doc.getElementById("Social:TogglePageMark").getAttribute("disabled"), canMark ? "false" : "true", "Social:TogglePageMark enabled?");
+
+ // broadcasters.
+ isbool(!doc.getElementById("socialActiveBroadcaster").hidden, active, "socialActiveBroadcaster hidden?");
+}
+
+// blocklist testing
+function updateBlocklist(aCallback) {
+ var blocklistNotifier = Cc["@mozilla.org/extensions/blocklist;1"]
+ .getService(Ci.nsITimerCallback);
+ var observer = function() {
+ Services.obs.removeObserver(observer, "blocklist-updated");
+ if (aCallback)
+ executeSoon(aCallback);
+ };
+ Services.obs.addObserver(observer, "blocklist-updated", false);
+ blocklistNotifier.notify(null);
+}
+
+function setAndUpdateBlocklist(aURL, aCallback) {
+ Services.prefs.setCharPref("extensions.blocklist.url", aURL);
+ updateBlocklist(aCallback);
+}
+
+function resetBlocklist(aCallback) {
+ Services.prefs.clearUserPref("extensions.blocklist.url");
+ updateBlocklist(aCallback);
+}
+
+function setManifestPref(name, manifest) {
+ let string = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ string.data = JSON.stringify(manifest);
+ Services.prefs.setComplexValue(name, Ci.nsISupportsString, string);
+}
+
+function getManifestPrefname(aManifest) {
+ // is same as the generated name in SocialServiceInternal.getManifestPrefname
+ let originUri = Services.io.newURI(aManifest.origin, null, null);
+ return "social.manifest." + originUri.hostPort.replace('.','-');
+}
+
+function setBuiltinManifestPref(name, manifest) {
+ // we set this as a default pref, it must not be a user pref
+ manifest.builtin = true;
+ let string = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ string.data = JSON.stringify(manifest);
+ Services.prefs.getDefaultBranch(null).setComplexValue(name, Ci.nsISupportsString, string);
+ // verify this is set on the default branch
+ let stored = Services.prefs.getComplexValue(name, Ci.nsISupportsString).data;
+ is(stored, string.data, "manifest '"+name+"' stored in default prefs");
+ // don't dirty our manifest, we'll need it without this flag later
+ delete manifest.builtin;
+ // verify we DO NOT have a user-level pref
+ ok(!Services.prefs.prefHasUserValue(name), "manifest '"+name+"' is not in user-prefs");
+}
+
+function resetBuiltinManifestPref(name) {
+ Services.prefs.getDefaultBranch(null).deleteBranch(name);
+ is(Services.prefs.getDefaultBranch(null).getPrefType(name),
+ Services.prefs.PREF_INVALID, "default manifest removed");
+}
+
+function addTab(url, callback) {
+ let tab = gBrowser.selectedTab = gBrowser.addTab(url, {skipAnimation: true});
+ tab.linkedBrowser.addEventListener("load", function tabLoad(event) {
+ tab.linkedBrowser.removeEventListener("load", tabLoad, true);
+ executeSoon(function() {callback(tab)});
+ }, true);
+}
+
+function selectBrowserTab(tab, callback) {
+ if (gBrowser.selectedTab == tab) {
+ executeSoon(function() {callback(tab)});
+ return;
+ }
+ gBrowser.tabContainer.addEventListener("TabSelect", function onTabSelect() {
+ gBrowser.tabContainer.removeEventListener("TabSelect", onTabSelect, false);
+ is(gBrowser.selectedTab, tab, "browser tab is selected");
+ executeSoon(function() {callback(tab)});
+ });
+ gBrowser.selectedTab = tab;
+}
+
+function loadIntoTab(tab, url, callback) {
+ tab.linkedBrowser.addEventListener("load", function tabLoad(event) {
+ tab.linkedBrowser.removeEventListener("load", tabLoad, true);
+ executeSoon(function() {callback(tab)});
+ }, true);
+ tab.linkedBrowser.loadURI(url);
+}
+
+
+// chat test help functions
+
+// And lots of helpers for the resize tests.
+function get3ChatsForCollapsing(mode, cb) {
+ // We make one chat, then measure its size. We then resize the browser to
+ // ensure a second can be created fully visible but a third can not - then
+ // create the other 2. first will will be collapsed, second fully visible
+ // and the third also visible and the "selected" one.
+ // To make our life easier we don't go via the worker and ports so we get
+ // more control over creation *and* to make the code much simpler. We
+ // assume the worker/port stuff is individually tested above.
+ let chatbar = window.SocialChatBar.chatbar;
+ let chatWidth = undefined;
+ let num = 0;
+ is(chatbar.childNodes.length, 0, "chatbar starting empty");
+ is(chatbar.menupopup.childNodes.length, 0, "popup starting empty");
+
+ makeChat(mode, "first chat", function() {
+ // got the first one.
+ checkPopup();
+ ok(chatbar.menupopup.parentNode.collapsed, "menu selection isn't visible");
+ // we kinda cheat here and get the width of the first chat, assuming
+ // that all future chats will have the same width when open.
+ chatWidth = chatbar.calcTotalWidthOf(chatbar.selectedChat);
+ let desired = chatWidth * 2.5;
+ resizeWindowToChatAreaWidth(desired, function(sizedOk) {
+ ok(sizedOk, "can't do any tests without this width");
+ checkPopup();
+ makeChat(mode, "second chat", function() {
+ is(chatbar.childNodes.length, 2, "now have 2 chats");
+ checkPopup();
+ // and create the third.
+ makeChat(mode, "third chat", function() {
+ is(chatbar.childNodes.length, 3, "now have 3 chats");
+ checkPopup();
+ // XXX - this is a hacky implementation detail around the order of
+ // the chats. Ideally things would be a little more sane wrt the
+ // other in which the children were created.
+ let second = chatbar.childNodes[2];
+ let first = chatbar.childNodes[1];
+ let third = chatbar.childNodes[0];
+ ok(first.collapsed && !second.collapsed && !third.collapsed, "collapsed state as promised");
+ is(chatbar.selectedChat, third, "third is selected as promised")
+ info("have 3 chats for collapse testing - starting actual test...");
+ cb(first, second, third);
+ }, mode);
+ }, mode);
+ });
+ }, mode);
+}
+
+function makeChat(mode, uniqueid, cb) {
+ info("making a chat window '" + uniqueid +"'");
+ const chatUrl = "https://example.com/browser/browser/base/content/test/social/social_chat.html";
+ let provider = Social.provider;
+ let isOpened = window.SocialChatBar.openChat(provider, chatUrl + "?id=" + uniqueid, function(chat) {
+ info("chat window has opened");
+ // we can't callback immediately or we might close the chat during
+ // this event which upsets the implementation - it is only 1/2 way through
+ // handling the load event.
+ chat.document.title = uniqueid;
+ executeSoon(cb);
+ }, mode);
+ if (!isOpened) {
+ ok(false, "unable to open chat window, no provider? more failures to come");
+ executeSoon(cb);
+ }
+}
+
+function checkPopup() {
+ // popup only showing if any collapsed popup children.
+ let chatbar = window.SocialChatBar.chatbar;
+ let numCollapsed = 0;
+ for (let chat of chatbar.childNodes) {
+ if (chat.collapsed) {
+ numCollapsed += 1;
+ // and it have a menuitem weakmap
+ is(chatbar.menuitemMap.get(chat).nodeName, "menuitem", "collapsed chat has a menu item");
+ } else {
+ ok(!chatbar.menuitemMap.has(chat), "open chat has no menu item");
+ }
+ }
+ is(chatbar.menupopup.parentNode.collapsed, numCollapsed == 0, "popup matches child collapsed state");
+ is(chatbar.menupopup.childNodes.length, numCollapsed, "popup has correct count of children");
+ // todo - check each individual elt is what we expect?
+}
+// Resize the main window so the chat area's boxObject is |desired| wide.
+// Does a callback passing |true| if the window is now big enough or false
+// if we couldn't resize large enough to satisfy the test requirement.
+function resizeWindowToChatAreaWidth(desired, cb, count = 0) {
+ let current = window.SocialChatBar.chatbar.getBoundingClientRect().width;
+ let delta = desired - current;
+ info(count + ": resizing window so chat area is " + desired + " wide, currently it is "
+ + current + ". Screen avail is " + window.screen.availWidth
+ + ", current outer width is " + window.outerWidth);
+
+ // WTF? Sometimes we will get fractional values due to the - err - magic
+ // of DevPointsPerCSSPixel etc, so we allow a couple of pixels difference.
+ let widthDeltaCloseEnough = function(d) {
+ return Math.abs(d) < 2;
+ }
+
+ // attempting to resize by (0,0), unsurprisingly, doesn't cause a resize
+ // event - so just callback saying all is well.
+ if (widthDeltaCloseEnough(delta)) {
+ info(count + ": skipping this as screen width is close enough");
+ executeSoon(function() {
+ cb(true);
+ });
+ return;
+ }
+ // On lo-res screens we may already be maxed out but still smaller than the
+ // requested size, so asking to resize up also will not cause a resize event.
+ // So just callback now saying the test must be skipped.
+ if (window.screen.availWidth - window.outerWidth < delta) {
+ info(count + ": skipping this as screen available width is less than necessary");
+ executeSoon(function() {
+ cb(false);
+ });
+ return;
+ }
+ function resize_handler(event) {
+ // for whatever reason, sometimes we get called twice for different event
+ // phases, only handle one of them.
+ if (event.eventPhase != event.AT_TARGET)
+ return;
+ // we did resize - but did we get far enough to be able to continue?
+ let newSize = window.SocialChatBar.chatbar.getBoundingClientRect().width;
+ let sizedOk = widthDeltaCloseEnough(newSize - desired);
+ if (!sizedOk)
+ return;
+ window.removeEventListener("resize", resize_handler);
+ info(count + ": resized window width is " + newSize);
+ executeSoon(function() {
+ cb(sizedOk);
+ });
+ }
+ // Otherwise we request resize and expect a resize event
+ window.addEventListener("resize", resize_handler);
+ window.resizeBy(delta, 0);
+}
+
+function resizeAndCheckWidths(first, second, third, checks, cb) {
+ if (checks.length == 0) {
+ cb(); // nothing more to check!
+ return;
+ }
+ let count = checks.length;
+ let [width, numExpectedVisible, why] = checks.shift();
+ info("<< Check " + count + ": " + why);
+ info(count + ": " + "resizing window to " + width + ", expect " + numExpectedVisible + " visible items");
+ resizeWindowToChatAreaWidth(width, function(sizedOk) {
+ checkPopup();
+ ok(sizedOk, count+": window resized correctly");
+ function collapsedObserver(r, m) {
+ if ([first, second, third].filter(function(item) !item.collapsed).length == numExpectedVisible) {
+ if (m) {
+ m.disconnect();
+ }
+ ok(true, count + ": " + "correct number of chats visible");
+ info(">> Check " + count);
+ resizeAndCheckWidths(first, second, third, checks, cb);
+ return true;
+ }
+ return false;
+ }
+ if (!collapsedObserver()) {
+ let m = new MutationObserver(collapsedObserver);
+ m.observe(first, {attributes: true });
+ m.observe(second, {attributes: true });
+ m.observe(third, {attributes: true });
+ }
+ }, count);
+}
+
+function getPopupWidth() {
+ let popup = window.SocialChatBar.chatbar.menupopup;
+ ok(!popup.parentNode.collapsed, "asking for popup width when it is visible");
+ let cs = document.defaultView.getComputedStyle(popup.parentNode);
+ let margins = parseInt(cs.marginLeft) + parseInt(cs.marginRight);
+ return popup.parentNode.getBoundingClientRect().width + margins;
+}
+
+function closeAllChats() {
+ let chatbar = window.SocialChatBar.chatbar;
+ chatbar.removeAll();
+}
+
diff --git a/browser/base/content/test/social/moz.build b/browser/base/content/test/social/moz.build
new file mode 100644
index 000000000..83fd82cd8
--- /dev/null
+++ b/browser/base/content/test/social/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += ['opengraph']
diff --git a/browser/base/content/test/social/moz.png b/browser/base/content/test/social/moz.png
new file mode 100644
index 000000000..769c63634
--- /dev/null
+++ b/browser/base/content/test/social/moz.png
Binary files differ
diff --git a/browser/base/content/test/social/opengraph/Makefile.in b/browser/base/content/test/social/opengraph/Makefile.in
new file mode 100644
index 000000000..d0d5a1890
--- /dev/null
+++ b/browser/base/content/test/social/opengraph/Makefile.in
@@ -0,0 +1,21 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEPTH = @DEPTH@
+topsrcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+relativesrcdir = @relativesrcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MOCHITEST_BROWSER_FILES := \
+ opengraph.html \
+ og_invalid_url.html \
+ shortlink_linkrel.html \
+ shorturl_link.html \
+ shorturl_linkrel.html \
+ $(NULL)
+
+include $(topsrcdir)/config/rules.mk
diff --git a/browser/base/content/test/social/opengraph/moz.build b/browser/base/content/test/social/opengraph/moz.build
new file mode 100644
index 000000000..89251dc39
--- /dev/null
+++ b/browser/base/content/test/social/opengraph/moz.build
@@ -0,0 +1,4 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
diff --git a/browser/base/content/test/social/opengraph/og_invalid_url.html b/browser/base/content/test/social/opengraph/og_invalid_url.html
new file mode 100644
index 000000000..ad1dae2be
--- /dev/null
+++ b/browser/base/content/test/social/opengraph/og_invalid_url.html
@@ -0,0 +1,11 @@
+<html xmlns:og="http://ogp.me/ns#">
+<head>
+ <meta property="og:url" content="chrome://browser/content/aboutDialog.xul"/>
+ <meta property="og:site_name" content="Evil chrome delivering website"/>
+ <meta property="og:description"
+ content="A test corpus file for open graph tags passing a bad url"/>
+</head>
+<body>
+ Open Graph Test Page
+</body>
+</html>
diff --git a/browser/base/content/test/social/opengraph/opengraph.html b/browser/base/content/test/social/opengraph/opengraph.html
new file mode 100644
index 000000000..50b7703b8
--- /dev/null
+++ b/browser/base/content/test/social/opengraph/opengraph.html
@@ -0,0 +1,13 @@
+<html xmlns:og="http://ogp.me/ns#">
+<head>
+ <meta property="og:title" content="&gt;This is my title&lt;"/>
+ <meta property="og:url" content="https://www.mozilla.org"/>
+ <meta property="og:image" content="https://www.mozilla.org/favicon.png"/>
+ <meta property="og:site_name" content="&#62;My simple test page&#60;"/>
+ <meta property="og:description"
+ content="A test corpus file for open graph tags we care about"/>
+</head>
+<body>
+ Open Graph Test Page
+</body>
+</html>
diff --git a/browser/base/content/test/social/opengraph/shortlink_linkrel.html b/browser/base/content/test/social/opengraph/shortlink_linkrel.html
new file mode 100644
index 000000000..54c40c376
--- /dev/null
+++ b/browser/base/content/test/social/opengraph/shortlink_linkrel.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+ <link rel="image_src" href="http://example.com/1234/56789.jpg" id="image-src" />
+ <link id="canonicalurl" rel="canonical" href="http://www.example.com/photos/56789/" />
+ <link rel="shortlink" href="http://imshort/p/abcde" />
+</head>
+<body>
+ link[rel='shortlink']
+</body>
+</html>
diff --git a/browser/base/content/test/social/opengraph/shorturl_link.html b/browser/base/content/test/social/opengraph/shorturl_link.html
new file mode 100644
index 000000000..667122cea
--- /dev/null
+++ b/browser/base/content/test/social/opengraph/shorturl_link.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+ <link rel="image_src" href="http://example.com/1234/56789.jpg" id="image-src" />
+ <link id="canonicalurl" rel="canonical" href="http://www.example.com/photos/56789/" />
+ <link id="shorturl" rev="canonical" type="text/html" href="http://imshort/p/abcde" />
+</head>
+<body>
+ link id="shorturl"
+</body>
+</html>
diff --git a/browser/base/content/test/social/opengraph/shorturl_linkrel.html b/browser/base/content/test/social/opengraph/shorturl_linkrel.html
new file mode 100644
index 000000000..36533528e
--- /dev/null
+++ b/browser/base/content/test/social/opengraph/shorturl_linkrel.html
@@ -0,0 +1,25 @@
+<html>
+<head>
+ <title>Test Image</title>
+
+ <meta name="description" content="Iron man in a tutu" />
+ <meta name="title" content="Test Image" />
+
+ <meta name="medium" content="image" />
+ <link rel="image_src" href="http://example.com/1234/56789.jpg" id="image-src" />
+ <link id="canonicalurl" rel="canonical" href="http://www.example.com/photos/56789/" />
+ <link id="shorturl" href="http://imshort/p/abcde" />
+
+ <meta property="og:title" content="TestImage" />
+ <meta property="og:type" content="photos:photo" />
+ <meta property="og:url" content="http://www.example.com/photos/56789/" />
+ <meta property="og:site_name" content="My Photo Site" />
+ <meta property="og:description" content="Iron man in a tutu" />
+ <meta property="og:image" content="http://example.com/1234/56789.jpg" />
+ <meta property="og:image:width" content="480" />
+ <meta property="og:image:height" content="640" />
+</head>
+<body>
+ link[rel='shorturl']
+</body>
+</html>
diff --git a/browser/base/content/test/social/share.html b/browser/base/content/test/social/share.html
new file mode 100644
index 000000000..6a4dc49b6
--- /dev/null
+++ b/browser/base/content/test/social/share.html
@@ -0,0 +1,18 @@
+<html>
+ <head>
+ <meta charset="utf-8">
+ <script>
+ var shareData;
+ addEventListener("OpenGraphData", function(e) {
+ shareData = JSON.parse(e.detail);
+ var port = navigator.mozSocial.getWorker().port;
+ port.postMessage({topic: "share-data-message", result: shareData});
+ // share windows self-close
+ window.close();
+ })
+ </script>
+ </head>
+ <body>
+ <p>This is a test social share window.</p>
+ </body>
+</html>
diff --git a/browser/base/content/test/social/social_activate.html b/browser/base/content/test/social/social_activate.html
new file mode 100644
index 000000000..b008aad33
--- /dev/null
+++ b/browser/base/content/test/social/social_activate.html
@@ -0,0 +1,45 @@
+<html>
+<head>
+ <title>Activation test</title>
+</head>
+<script>
+// icons from http://findicons.com/icon/158311/firefox?id=356182 by ipapun
+var data = {
+ // currently required
+ "name": "Demo Social Service",
+ "iconURL": "chrome://branding/content/icon16.png",
+ "icon32URL": "chrome://branding/content/favicon32.png",
+ "icon64URL": "chrome://branding/content/icon64.png",
+
+ // at least one of these must be defined
+ "sidebarURL": "/browser/browser/base/content/test/social/social_sidebar.html",
+ "workerURL": "/browser/browser/base/content/test/social/social_worker.js",
+
+ // should be available for display purposes
+ "description": "A short paragraph about this provider",
+ "author": "Shane Caraveo, Mozilla",
+
+ // optional
+ "version": 1
+}
+
+function activate(node) {
+ node.setAttribute("data-service", JSON.stringify(data));
+ var event = new CustomEvent("ActivateSocialFeature");
+ node.dispatchEvent(event);
+}
+
+function oldActivate(node) {
+ var event = new CustomEvent("ActivateSocialFeature");
+ document.dispatchEvent(event);
+}
+</script>
+<body>
+
+nothing to see here
+
+<button id="activation-old" onclick="oldActivate(this)">Activate The Demo Provider</button>
+<button id="activation" onclick="activate(this)">Activate The Demo Provider</button>
+
+</body>
+</html>
diff --git a/browser/base/content/test/social/social_activate_iframe.html b/browser/base/content/test/social/social_activate_iframe.html
new file mode 100644
index 000000000..f8462ec80
--- /dev/null
+++ b/browser/base/content/test/social/social_activate_iframe.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+ <title>Activation iframe test</title>
+</head>
+
+<body>
+
+<iframe src="social_activate.html"/>
+
+</body>
+</html>
diff --git a/browser/base/content/test/social/social_chat.html b/browser/base/content/test/social/social_chat.html
new file mode 100644
index 000000000..ba507592e
--- /dev/null
+++ b/browser/base/content/test/social/social_chat.html
@@ -0,0 +1,32 @@
+<html>
+ <head>
+ <meta charset="utf-8">
+ <script>
+ function pingWorker() {
+ var port = navigator.mozSocial.getWorker().port;
+ port.postMessage({topic: "chatbox-message", result: "ok"});
+ }
+ window.addEventListener("socialFrameShow", function(e) {
+ var port = navigator.mozSocial.getWorker().port;
+ port.postMessage({topic: "chatbox-visibility", result: "shown"});
+ }, false);
+ window.addEventListener("socialFrameHide", function(e) {
+ var port = navigator.mozSocial.getWorker().port;
+ port.postMessage({topic: "chatbox-visibility", result: "hidden"});
+ }, false);
+ window.addEventListener("socialTest-CloseSelf", function(e) {
+ window.close();
+ }, false);
+ </script>
+ <title>test chat window</title>
+ </head>
+ <body onload="pingWorker();">
+ <p>This is a test social chat window.</p>
+ <!-- a couple of input fields to help with focus testing -->
+ <input id="input1"/>
+ <input id="input2"/>
+
+ <!-- an iframe here so this one page generates multiple load events -->
+ <iframe id="iframe" src="data:text/plain:this is an iframe"></iframe>
+ </body>
+</html>
diff --git a/browser/base/content/test/social/social_flyout.html b/browser/base/content/test/social/social_flyout.html
new file mode 100644
index 000000000..ff426783a
--- /dev/null
+++ b/browser/base/content/test/social/social_flyout.html
@@ -0,0 +1,37 @@
+<html>
+ <head>
+ <meta charset="utf-8">
+ <script>
+ function pingWorker() {
+ var port = navigator.mozSocial.getWorker().port;
+ port.postMessage({topic: "flyout-message", result: "ok"});
+ }
+ window.addEventListener("socialFrameShow", function(e) {
+ var port = navigator.mozSocial.getWorker().port;
+ port.postMessage({topic: "flyout-visibility", result: "shown"});
+ }, false);
+ window.addEventListener("socialFrameHide", function(e) {
+ var port = navigator.mozSocial.getWorker().port;
+ port.postMessage({topic: "flyout-visibility", result: "hidden"});
+ }, false);
+ window.addEventListener("socialTest-MakeWider", function(e) {
+ document.body.setAttribute("style", "width: 500px; height: 500px; margin: 0; overflow: hidden;");
+ document.body.offsetWidth; // force a layout flush
+ var evt = document.createEvent("CustomEvent");
+ evt.initCustomEvent("SocialTest-DoneMakeWider", true, true, {});
+ document.documentElement.dispatchEvent(evt);
+ }, false);
+ window.addEventListener("socialTest-CloseSelf", function(e) {
+ window.close();
+ var evt = document.createEvent("CustomEvent");
+ evt.initCustomEvent("SocialTest-DoneCloseSelf", true, true, {});
+ document.documentElement.dispatchEvent(evt);
+ }, false);
+ </script>
+ </head>
+ <body style="width: 400px; height: 400px; margin: 0; overflow: hidden;" onload="pingWorker();">
+ <p>This is a test social flyout panel.</p>
+ <a id="traversal" href="http://mochi.test">test link</a>
+ </body>
+</html>
+
diff --git a/browser/base/content/test/social/social_mark_image.png b/browser/base/content/test/social/social_mark_image.png
new file mode 100644
index 000000000..fa1f8fb0e
--- /dev/null
+++ b/browser/base/content/test/social/social_mark_image.png
Binary files differ
diff --git a/browser/base/content/test/social/social_panel.html b/browser/base/content/test/social/social_panel.html
new file mode 100644
index 000000000..ada53d9ed
--- /dev/null
+++ b/browser/base/content/test/social/social_panel.html
@@ -0,0 +1,24 @@
+<html>
+ <head>
+ <meta charset="utf-8">
+ <script>
+ function pingWorker() {
+ var port = navigator.mozSocial.getWorker().port;
+ port.postMessage({topic: "panel-message",
+ result: "ok",
+ location: window.location.href});
+ }
+ window.addEventListener("socialFrameShow", function(e) {
+ var port = navigator.mozSocial.getWorker().port;
+ port.postMessage({topic: "status-panel-visibility", result: "shown"});
+ }, false);
+ window.addEventListener("socialFrameHide", function(e) {
+ var port = navigator.mozSocial.getWorker().port;
+ port.postMessage({topic: "status-panel-visibility", result: "hidden"});
+ }, false);
+ </script>
+ </head>
+ <body onload="pingWorker();">
+ <p>This is a test social panel.</p>
+ </body>
+</html>
diff --git a/browser/base/content/test/social/social_sidebar.html b/browser/base/content/test/social/social_sidebar.html
new file mode 100644
index 000000000..dc66b5d31
--- /dev/null
+++ b/browser/base/content/test/social/social_sidebar.html
@@ -0,0 +1,47 @@
+<html>
+ <head>
+ <meta charset="utf-8">
+ <script>
+ var testwindow;
+ function pingWorker() {
+ var port = navigator.mozSocial.getWorker().port;
+ port.onmessage = function(e) {
+ var topic = e.data.topic;
+ switch (topic) {
+ case "test-flyout-open":
+ navigator.mozSocial.openPanel("social_flyout.html");
+ break;
+ case "test-flyout-close":
+ navigator.mozSocial.closePanel();
+ break;
+ case "test-chatbox-open":
+ var url = "social_chat.html";
+ var data = e.data.data;
+ if (data && data.id) {
+ url = url + "?id="+data.id;
+ }
+ navigator.mozSocial.openChatWindow(url, function(chatwin) {
+ // Note that the following .focus() call should *not* arrange
+ // to steal focus - see browser_social_chatwindowfocus.js
+ if (data && data.stealFocus && chatwin) {
+ chatwin.focus();
+ }
+ port.postMessage({topic: "chatbox-opened",
+ result: chatwin ? "ok" : "failed"});
+ });
+ break;
+ case "test-isVisible":
+ port.postMessage({topic: "test-isVisible-response",
+ result: navigator.mozSocial.isVisible});
+ break;
+ }
+ }
+ port.postMessage({topic: "sidebar-message", result: "ok"});
+ }
+ </script>
+ </head>
+ <body onload="pingWorker();">
+ <p>This is a test social sidebar.</p>
+ <button id="chat-opener" onclick="navigator.mozSocial.openChatWindow('./social_chat.html');"/>
+ </body>
+</html>
diff --git a/browser/base/content/test/social/social_window.html b/browser/base/content/test/social/social_window.html
new file mode 100644
index 000000000..1e95ca0ee
--- /dev/null
+++ b/browser/base/content/test/social/social_window.html
@@ -0,0 +1,17 @@
+<html>
+ <head>
+ <meta charset="utf-8">
+ <script>
+ function pingWorker() {
+ var port = navigator.mozSocial.getWorker().port;
+ port.postMessage({topic: "service-window-message",
+ location: window.location.href,
+ result: "ok"
+ });
+ }
+ </script>
+ </head>
+ <body onload="pingWorker();">
+ <p>This is a test social service window.</p>
+ </body>
+</html>
diff --git a/browser/base/content/test/social/social_worker.js b/browser/base/content/test/social/social_worker.js
new file mode 100644
index 000000000..1c1fd43e2
--- /dev/null
+++ b/browser/base/content/test/social/social_worker.js
@@ -0,0 +1,149 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+let testPort, sidebarPort, apiPort;
+
+onconnect = function(e) {
+ let port = e.ports[0];
+ port.onmessage = function onMessage(event) {
+ let topic = event.data.topic;
+ switch (topic) {
+ case "test-init":
+ testPort = port;
+ port.postMessage({topic: "test-init-done"});
+ break;
+ case "ping":
+ port.postMessage({topic: "pong"});
+ break;
+ case "test-logout":
+ apiPort.postMessage({topic: "social.user-profile", data: {}});
+ break;
+ case "sidebar-message":
+ sidebarPort = port;
+ if (testPort && event.data.result == "ok")
+ testPort.postMessage({topic:"got-sidebar-message"});
+ break;
+ case "service-window-message":
+ testPort.postMessage({topic:"got-service-window-message",
+ location: event.data.location});
+ break;
+ case "service-window-closed-message":
+ testPort.postMessage({topic:"got-service-window-closed-message"});
+ break;
+ case "test-service-window":
+ sidebarPort.postMessage({topic:"test-service-window"});
+ break;
+ case "test-service-window-twice":
+ sidebarPort.postMessage({topic:"test-service-window-twice"});
+ break;
+ case "test-service-window-twice-result":
+ testPort.postMessage({topic: "test-service-window-twice-result", result: event.data.result })
+ break;
+ case "test-close-service-window":
+ sidebarPort.postMessage({topic:"test-close-service-window"});
+ break;
+ case "panel-message":
+ if (testPort && event.data.result == "ok")
+ testPort.postMessage({topic:"got-panel-message",
+ location: event.data.location
+ });
+ break;
+ case "status-panel-visibility":
+ testPort.postMessage({topic:"got-social-panel-visibility", result: event.data.result });
+ break;
+ case "test-chatbox-open":
+ sidebarPort.postMessage(event.data);
+ break;
+ case "chatbox-opened":
+ testPort.postMessage(event.data);
+ break;
+ case "chatbox-message":
+ testPort.postMessage({topic:"got-chatbox-message", result: event.data.result});
+ break;
+ case "chatbox-visibility":
+ testPort.postMessage({topic:"got-chatbox-visibility", result: event.data.result});
+ break;
+ case "test-flyout-open":
+ sidebarPort.postMessage({topic:"test-flyout-open"});
+ break;
+ case "flyout-message":
+ testPort.postMessage({topic:"got-flyout-message", result: event.data.result});
+ break;
+ case "flyout-visibility":
+ testPort.postMessage({topic:"got-flyout-visibility", result: event.data.result});
+ break;
+ case "test-flyout-close":
+ sidebarPort.postMessage({topic:"test-flyout-close"});
+ break;
+ case "test-worker-chat":
+ apiPort.postMessage({topic: "social.request-chat", data: event.data.data });
+ break;
+ case "social.initialize":
+ // This is the workerAPI port, respond and set up a notification icon.
+ // For multiprovider tests, we support acting like different providers
+ // based on the domain we load from.
+ apiPort = port;
+ let profile;
+ if (location.href.indexOf("https://test1.example.com") == 0) {
+ profile = {
+ portrait: "https://test1.example.com/portrait.jpg",
+ userName: "tester",
+ displayName: "Test1 User",
+ };
+ } else {
+ profile = {
+ portrait: "https://example.com/portrait.jpg",
+ userName: "trickster",
+ displayName: "Kuma Lisa",
+ profileURL: "http://en.wikipedia.org/wiki/Kuma_Lisa"
+ };
+ }
+ port.postMessage({topic: "social.user-profile", data: profile});
+ port.postMessage({
+ topic: "social.page-mark-config",
+ data: {
+ images: {
+ // this one is relative to test we handle relative ones.
+ marked: "/browser/browser/base/content/test/social/social_mark_image.png",
+ // absolute to check we handle them too.
+ unmarked: "https://example.com/browser/browser/base/content/test/social/social_mark_image.png"
+ },
+ messages: {
+ unmarkedTooltip: "Mark this page",
+ markedTooltip: "Unmark this page",
+ unmarkedLabel: "Mark",
+ markedLabel: "Unmark",
+ }
+ }
+ });
+ break;
+ case "test-ambient-notification":
+ let icon = {
+ name: "testIcon",
+ iconURL: "chrome://browser/skin/Info.png",
+ contentPanel: "https://example.com/browser/browser/base/content/test/social/social_panel.html",
+ counter: 1
+ };
+ apiPort.postMessage({topic: "social.ambient-notification", data: icon});
+ break;
+ case "test-isVisible":
+ sidebarPort.postMessage({topic: "test-isVisible"});
+ break;
+ case "test-isVisible-response":
+ testPort.postMessage({topic: "got-isVisible-response", result: event.data.result});
+ break;
+ case "share-data-message":
+ if (testPort)
+ testPort.postMessage({topic:"got-share-data-message", result: event.data.result});
+ break;
+ case "worker.update":
+ apiPort.postMessage({topic: 'social.manifest-get'});
+ break;
+ case "social.manifest":
+ event.data.data.version = 2;
+ apiPort.postMessage({topic: 'social.manifest-set', data: event.data.data});
+ break;
+ }
+ }
+}
diff --git a/browser/base/content/test/subtst_contextmenu.html b/browser/base/content/test/subtst_contextmenu.html
new file mode 100644
index 000000000..9f8687205
--- /dev/null
+++ b/browser/base/content/test/subtst_contextmenu.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Subtest for browser context menu</title>
+</head>
+<body>
+Browser context menu subtest.
+
+<div id="test-text">Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</div>
+<a id="test-link" href="http://mozilla.com">Click the monkey!</a>
+<a id="test-mailto" href="mailto:codemonkey@mozilla.com">Mail the monkey!</a><br>
+<input id="test-input"><br>
+<img id="test-image" src="ctxmenu-image.png">
+<canvas id="test-canvas" width="100" height="100" style="background-color: blue"></canvas>
+<video controls id="test-video-ok" src="video.ogg" width="100" height="100" style="background-color: green"></video>
+<video id="test-audio-in-video" src="audio.ogg" width="100" height="100" style="background-color: red"></video>
+<video controls id="test-video-bad" src="bogus.duh" width="100" height="100" style="background-color: orange"></video>
+<video controls id="test-video-bad2" width="100" height="100" style="background-color: yellow">
+ <source src="bogus.duh" type="video/durrrr;">
+</video>
+<iframe id="test-iframe" width="98" height="98" style="border: 1px solid black"></iframe>
+<iframe id="test-video-in-iframe" src="video.ogg" width="98" height="98" style="border: 1px solid black"></iframe>
+<iframe id="test-image-in-iframe" src="ctxmenu-image.png" width="98" height="98" style="border: 1px solid black"></iframe>
+<textarea id="test-textarea">chssseesbbbie</textarea> <!-- a weird word which generates only one suggestion -->
+<div id="test-contenteditable" contenteditable="true">chssseefsbbbie</div> <!-- a more weird word which generates no suggestions -->
+<input id="test-input-spellcheck" type="text" spellcheck="true" autofocus value="prodkjfgigrty"> <!-- this one also generates one suggestion -->
+<div id="test-contenteditable-spellcheck-false" contenteditable="true" spellcheck="false">test</div> <!-- No Check Spelling menu item -->
+<div id="test-dom-full-screen">DOM full screen FTW</div>
+<div contextmenu="myMenu">
+ <p id="test-pagemenu" hopeless="true">I've got a context menu!</p>
+ <menu id="myMenu" type="context">
+ <menuitem label="Plain item" onclick="document.getElementById('test-pagemenu').removeAttribute('hopeless');"></menuitem>
+ <menuitem label="Disabled item" disabled></menuitem>
+ <menuitem> Item w/ textContent</menuitem>
+ <menu>
+ <menuitem type="checkbox" label="Checkbox" checked></menuitem>
+ </menu>
+ <menu>
+ <menuitem type="radio" label="Radio1" checked></menuitem>
+ <menuitem type="radio" label="Radio2"></menuitem>
+ <menuitem type="radio" label="Radio3"></menuitem>
+ </menu>
+ <menu>
+ <menuitem label="Item w/ icon" icon="favicon.ico"></menuitem>
+ <menuitem label="Item w/ bad icon" icon="data://www.mozilla.org/favicon.ico"></menuitem>
+ </menu>
+ <menu label="Submenu">
+ <menuitem type="radio" label="Radio1" radiogroup="rg"></menuitem>
+ <menuitem type="radio" label="Radio2" checked radiogroup="rg"></menuitem>
+ <menuitem type="radio" label="Radio3" radiogroup="rg"></menuitem>
+ <menu>
+ <menuitem type="checkbox" label="Checkbox"></menuitem>
+ </menu>
+ </menu>
+ <menu hidden>
+ <menuitem label="Bogus item"></menuitem>
+ </menu>
+ <menu>
+ </menu>
+ <menuitem label="Hidden item" hidden></menuitem>
+ <menuitem></menuitem>
+ </menu>
+</div>
+<div id="test-select-text">Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</div>
+<div id="test-select-text-link">http://mozilla.com</div>
+<a id="test-image-link" href="#"><img src="ctxmenu-image.png"></a>
+<input id="test-select-input-text" type="text" value="input">
+<input id="test-select-input-text-type-password" type="password" value="password">
+<embed id="test-plugin" style="width: 200px; height: 200px;" type="application/x-test"></embed>
+</body>
+</html>
diff --git a/browser/base/content/test/test_bug364677.html b/browser/base/content/test/test_bug364677.html
new file mode 100644
index 000000000..67b9729d1
--- /dev/null
+++ b/browser/base/content/test/test_bug364677.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=364677
+-->
+<head>
+ <title>Test for Bug 364677</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=364677">Mozilla Bug 364677</a>
+<p id="display"><iframe id="testFrame" src="bug364677-data.xml"></iframe></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 364677 **/
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ is(SpecialPowers.wrap($("testFrame")).contentDocument.documentElement.id, "feedHandler",
+ "Feed served as text/xml without a channel/link should have been sniffed");
+});
+addLoadEvent(SimpleTest.finish);
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/browser/base/content/test/test_bug395533.html b/browser/base/content/test/test_bug395533.html
new file mode 100644
index 000000000..013c8789f
--- /dev/null
+++ b/browser/base/content/test/test_bug395533.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=395533
+-->
+<head>
+ <title>Test for Bug 395533</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=395533">Mozilla Bug 395533</a>
+<p id="display"><iframe id="testFrame" src="bug395533-data.txt"></iframe></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 395533 **/
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ // Need privs because the feed seems to have an about:feeds principal or some
+ // such. It's not same-origin with us in any case.
+ netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+ is($("testFrame").contentDocument.documentElement.id, "",
+ "Text got sniffed as a feed?");
+});
+addLoadEvent(SimpleTest.finish);
+
+
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/browser/base/content/test/test_bug435035.html b/browser/base/content/test/test_bug435035.html
new file mode 100644
index 000000000..a3d353514
--- /dev/null
+++ b/browser/base/content/test/test_bug435035.html
@@ -0,0 +1 @@
+<img src="http://example.com/browser/browser/base/content/test/moz.png">
diff --git a/browser/base/content/test/test_bug452451.html b/browser/base/content/test/test_bug452451.html
new file mode 100644
index 000000000..633bd5fa2
--- /dev/null
+++ b/browser/base/content/test/test_bug452451.html
@@ -0,0 +1,96 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=452451
+-->
+<head>
+ <title>Test for Bug 452451</title>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=452451">Mozilla Bug 452451</a>
+<p id="display"></p>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 452451 **/
+
+ netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+ const prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+
+ ok(prefs.getBoolPref("javascript.options.relimit"),
+ "relimit should be enabled by default");
+
+ /**
+ * Following tests are inspired from:
+ * js/src/tests/js1_5/extensions/regress-330569.js
+ */
+
+ var s;
+ const expected = 'InternalError: regular expression too complex';
+
+ s = '<!DOCTYPE HTML PUBLIC>' +
+ '<html>\n' +
+ '<head>\n' +
+ '<meta http-equiv="content-type" content="text/html">\n' +
+ '<title></title>\n'+
+ '</head>\n' +
+ '<body>\n' +
+ '<!-- hello -->\n' +
+ '<script language="JavaScript">\n' +
+ 'var s = document. body. innerHTML;\n' +
+ 'var d = s. replace (/<!--(.*|\n)*-->/, "");\n' +
+ '<\/script>\n' +
+ '<\/body>\n' +
+ '<\/html>\n';
+
+ try {
+ /<!--(.*|\n)*-->/.exec(s);
+ }
+ catch(ex) {
+ actual = ex;
+ }
+
+ is(actual, expected, "reg exp too complex error should have been thrown");
+
+ function testre( re, n )
+ {
+ var txt = '';
+ for (var i= 0; i <= n; ++i) {
+ txt += ',';
+ re.test(txt);
+ }
+ }
+
+ try {
+ testre( /(?:,*)*x/, 22 );
+ }
+ catch(ex) {
+ actual = ex;
+ }
+
+ is(actual, expected, "reg exp too complex error should have been thrown");
+
+ try {
+ testre( /(?:,|,)*x/, 22 );
+ }
+ catch(ex) {
+ actual = ex;
+ }
+
+ is(actual, expected, "reg exp too complex error should have been thrown");
+
+ try {
+ testre( /(?:,|,|,|,|,)*x/, 10 );
+ }
+ catch(ex) {
+ actual = ex;
+ }
+
+ is(actual, expected, "reg exp too complex error should have been thrown");
+</script>
+</pre>
+</body>
+</html>
diff --git a/browser/base/content/test/test_bug462673.html b/browser/base/content/test/test_bug462673.html
new file mode 100644
index 000000000..d864990e4
--- /dev/null
+++ b/browser/base/content/test/test_bug462673.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+<script>
+var w;
+function openIt() {
+ w = window.open("", "window2");
+}
+function closeIt() {
+ if (w) {
+ w.close();
+ w = null;
+ }
+}
+</script>
+</head>
+<body onload="openIt();" onunload="closeIt();">
+</body>
+</html>
diff --git a/browser/base/content/test/test_bug628179.html b/browser/base/content/test/test_bug628179.html
new file mode 100644
index 000000000..d35e17a7c
--- /dev/null
+++ b/browser/base/content/test/test_bug628179.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test for closing the Find bar in subdocuments</title>
+ </head>
+ <body>
+ <iframe id=iframe src="http://example.com/" width=320 height=240></iframe>
+ </body>
+</html>
+
diff --git a/browser/base/content/test/test_bug839103.html b/browser/base/content/test/test_bug839103.html
new file mode 100644
index 000000000..3639d4bda
--- /dev/null
+++ b/browser/base/content/test/test_bug839103.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Document for Bug 839103</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style></style>
+</head>
+<body>
+</body>
+</html>
diff --git a/browser/base/content/test/test_contextmenu.html b/browser/base/content/test/test_contextmenu.html
new file mode 100644
index 000000000..15555fe31
--- /dev/null
+++ b/browser/base/content/test/test_contextmenu.html
@@ -0,0 +1,1088 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Tests for browser context menu</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+Browser context menu tests.
+<p id="display"></p>
+
+<div id="content">
+</div>
+
+<pre id="test">
+<script> var perWindowPrivateBrowsing = false; </script>
+<script type="text/javascript" src="privateBrowsingMode.js"></script>
+<script class="testbody" type="text/javascript">
+
+/** Test for Login Manager: multiple login autocomplete. **/
+
+SpecialPowers.Cu.import("resource://gre/modules/InlineSpellChecker.jsm", window);
+SpecialPowers.Cu.import("resource://gre/modules/AsyncSpellCheckTestHelper.jsm", window);
+
+const Ci = SpecialPowers.Ci;
+
+function openContextMenuFor(element, shiftkey, waitForSpellCheck) {
+ // Context menu should be closed before we open it again.
+ is(SpecialPowers.wrap(contextMenu).state, "closed", "checking if popup is closed");
+
+ if (lastElement)
+ lastElement.blur();
+ element.focus();
+
+ // Some elements need time to focus and spellcheck before any tests are
+ // run on them.
+ function actuallyOpenContextMenuFor() {
+ lastElement = element;
+ var eventDetails = { type : "contextmenu", button : 2, shiftKey : shiftkey };
+ synthesizeMouse(element, 2, 2, eventDetails, element.ownerDocument.defaultView);
+ }
+
+ if (waitForSpellCheck)
+ onSpellCheck(element, actuallyOpenContextMenuFor);
+ else
+ actuallyOpenContextMenuFor();
+}
+
+function closeContextMenu() {
+ contextMenu.hidePopup();
+}
+
+function executeCopyCommand(command, expectedValue)
+{
+ // Just execute the command directly rather than simulating a context menu
+ // press to avoid having to deal with its asynchronous nature
+ SpecialPowers.wrap(subwindow).controllers.getControllerForCommand(command).doCommand(command);
+
+ // The easiest way to check the clipboard is to paste the contents into a
+ // textbox
+ input.focus();
+ input.value = "";
+ SpecialPowers.wrap(input).controllers.getControllerForCommand("cmd_paste").doCommand("cmd_paste");
+ is(input.value, expectedValue, "paste for command " + command);
+}
+
+function invokeItemAction(generatedItemId)
+{
+ var item = contextMenu.getElementsByAttribute("generateditemid",
+ generatedItemId)[0];
+ ok(item, "Got generated XUL menu item");
+ item.doCommand();
+ ok(!pagemenu.hasAttribute("hopeless"), "attribute got removed");
+}
+
+function selectText(element) {
+ // Clear any previous selections before selecting new element.
+ subwindow.getSelection().removeAllRanges();
+
+ var div = subwindow.document.createRange();
+ div.setStartBefore(element);
+ div.setEndAfter(element);
+ subwindow.getSelection().addRange(div);
+}
+
+function selectInputText(element) {
+ // Clear any previous selections before selecting new element.
+ subwindow.getSelection().removeAllRanges();
+
+ element.select();
+}
+
+function getVisibleMenuItems(aMenu, aData) {
+ var items = [];
+ var accessKeys = {};
+ for (var i = 0; i < aMenu.childNodes.length; i++) {
+ var item = aMenu.childNodes[i];
+ if (item.hidden)
+ continue;
+
+ var key = item.accessKey;
+ if (key)
+ key = key.toLowerCase();
+
+ var isGenerated = item.hasAttribute("generateditemid");
+
+ if (item.nodeName == "menuitem") {
+ var isSpellSuggestion = item.className == "spell-suggestion";
+ if (isSpellSuggestion) {
+ is(item.id, "", "child menuitem #" + i + " is a spelling suggestion");
+ } else if (isGenerated) {
+ is(item.id, "", "child menuitem #" + i + " is a generated item");
+ } else {
+ ok(item.id, "child menuitem #" + i + " has an ID");
+ }
+ var label = item.getAttribute("label");
+ ok(label.length, "menuitem " + item.id + " has a label");
+ if (isSpellSuggestion) {
+ is(key, "", "Spell suggestions shouldn't have an access key");
+ items.push("*" + label);
+ } else if (isGenerated) {
+ items.push("+" + label);
+ } else if (item.id.indexOf("spell-check-dictionary-") != 0 &&
+ item.id != "spell-no-suggestions" &&
+ item.id != "spell-add-dictionaries-main") {
+ ok(key, "menuitem " + item.id + " has an access key");
+ if (accessKeys[key])
+ ok(false, "menuitem " + item.id + " has same accesskey as " + accessKeys[key]);
+ else
+ accessKeys[key] = item.id;
+ }
+ if (!isSpellSuggestion && !isGenerated) {
+ items.push(item.id);
+ }
+ if (isGenerated) {
+ var p = {};
+ p.type = item.getAttribute("type");
+ p.icon = item.getAttribute("image");
+ p.checked = item.hasAttribute("checked");
+ p.disabled = item.hasAttribute("disabled");
+ items.push(p);
+ } else {
+ items.push(!item.disabled);
+ }
+ } else if (item.nodeName == "menuseparator") {
+ ok(true, "--- seperator id is " + item.id);
+ items.push("---");
+ items.push(null);
+ } else if (item.nodeName == "menu") {
+ if (isGenerated) {
+ item.id = "generated-submenu-" + aData.generatedSubmenuId++;
+ }
+ ok(item.id, "child menu #" + i + " has an ID");
+ if (!isGenerated) {
+ ok(key, "menu has an access key");
+ if (accessKeys[key])
+ ok(false, "menu " + item.id + " has same accesskey as " + accessKeys[key]);
+ else
+ accessKeys[key] = item.id;
+ }
+ items.push(item.id);
+ items.push(!item.disabled);
+ // Add a dummy item to that the indexes in checkMenu are the same
+ // for expectedItems and actualItems.
+ items.push([]);
+ items.push(null);
+ } else {
+ ok(false, "child #" + i + " of menu ID " + aMenu.id +
+ " has an unknown type (" + item.nodeName + ")");
+ }
+ }
+ return items;
+}
+
+function checkContextMenu(expectedItems) {
+ is(contextMenu.state, "open", "checking if popup is open");
+ var data = { generatedSubmenuId: 1 };
+ checkMenu(contextMenu, expectedItems, data);
+}
+
+/*
+ * checkMenu - checks to see if the specified <menupopup> contains the
+ * expected items and state.
+ * expectedItems is a array of (1) item IDs and (2) a boolean specifying if
+ * the item is enabled or not (or null to ignore it). Submenus can be checked
+ * by providing a nested array entry after the expected <menu> ID.
+ * For example: ["blah", true, // item enabled
+ * "submenu", null, // submenu
+ * ["sub1", true, // submenu contents
+ * "sub2", false], null, // submenu contents
+ * "lol", false] // item disabled
+ *
+ */
+function checkMenu(menu, expectedItems, data) {
+ var actualItems = getVisibleMenuItems(menu, data);
+ //ok(false, "Items are: " + actualItems);
+ for (var i = 0; i < expectedItems.length; i+=2) {
+ var actualItem = actualItems[i];
+ var actualEnabled = actualItems[i + 1];
+ var expectedItem = expectedItems[i];
+ var expectedEnabled = expectedItems[i + 1];
+ if (expectedItem instanceof Array) {
+ ok(true, "Checking submenu...");
+ var menuID = expectedItems[i - 2]; // The last item was the menu ID.
+ var submenu = menu.getElementsByAttribute("id", menuID)[0];
+ ok(submenu, "got a submenu element of id='" + menuID + "'");
+ if (submenu) {
+ is(submenu.nodeName, "menu", "submenu element of id='" + menuID +
+ "' has expected nodeName");
+ checkMenu(submenu.menupopup, expectedItem, data);
+ }
+ } else {
+ is(actualItem, expectedItem,
+ "checking item #" + i/2 + " (" + expectedItem + ") name");
+
+ if (typeof expectedEnabled == "object" && expectedEnabled != null ||
+ typeof actualEnabled == "object" && actualEnabled != null) {
+
+ ok(!(actualEnabled == null), "actualEnabled is not null");
+ ok(!(expectedEnabled == null), "expectedEnabled is not null");
+ is(typeof actualEnabled, typeof expectedEnabled, "checking types");
+
+ if (typeof actualEnabled != typeof expectedEnabled ||
+ actualEnabled == null || expectedEnabled == null)
+ continue;
+
+ is(actualEnabled.type, expectedEnabled.type,
+ "checking item #" + i/2 + " (" + expectedItem + ") type attr value");
+ var icon = actualEnabled.icon;
+ if (icon) {
+ var tmp = "";
+ var j = icon.length - 1;
+ while (j && icon[j] != "/") {
+ tmp = icon[j--] + tmp;
+ }
+ icon = tmp;
+ }
+ is(icon, expectedEnabled.icon,
+ "checking item #" + i/2 + " (" + expectedItem + ") icon attr value");
+ is(actualEnabled.checked, expectedEnabled.checked,
+ "checking item #" + i/2 + " (" + expectedItem + ") has checked attr");
+ is(actualEnabled.disabled, expectedEnabled.disabled,
+ "checking item #" + i/2 + " (" + expectedItem + ") has disabled attr");
+ } else if (expectedEnabled != null)
+ is(actualEnabled, expectedEnabled,
+ "checking item #" + i/2 + " (" + expectedItem + ") enabled state");
+ }
+ }
+ // Could find unexpected extra items at the end...
+ is(actualItems.length, expectedItems.length, "checking expected number of menu entries");
+}
+
+/*
+ * runTest
+ *
+ * Called by a popupshowing event handler. Each test checks for expected menu
+ * contents, closes the popup, and finally triggers the popup on a new element
+ * (thus kicking off another cycle).
+ *
+ */
+function runTest(testNum) {
+ // Seems we need to enable this again, or sendKeyEvent() complaints.
+ ok(true, "Starting test #" + testNum);
+
+ var inspectItems = [];
+ if (SpecialPowers.getBoolPref("devtools.inspector.enabled")) {
+ inspectItems = ["---", null,
+ "context-inspect", true];
+ }
+
+ switch (testNum) {
+ case 1:
+ // Invoke context menu for next test.
+ openContextMenuFor(text);
+ break;
+
+ case 2:
+ // Context menu for plain text
+ plainTextItems = ["context-back", false,
+ "context-forward", false,
+ "context-reload", true,
+ "---", null,
+ "context-bookmarkpage", true,
+ "context-savepage", true,
+ "---", null,
+ "context-viewbgimage", false,
+ "context-selectall", true,
+ "---", null,
+ "context-viewsource", true,
+ "context-viewinfo", true
+ ].concat(inspectItems);
+ checkContextMenu(plainTextItems);
+ closeContextMenu();
+ openContextMenuFor(link); // Invoke context menu for next test.
+ break;
+
+ case 3:
+ // Context menu for text link
+ if (perWindowPrivateBrowsing) {
+ checkContextMenu(["context-openlinkintab", true,
+ "context-openlink", true,
+ "context-openlinkprivate", true,
+ "---", null,
+ "context-bookmarklink", true,
+ "context-savelink", true,
+ "context-copylink", true
+ ].concat(inspectItems));
+ } else {
+ checkContextMenu(["context-openlinkintab", true,
+ "context-openlink", true,
+ "---", null,
+ "context-bookmarklink", true,
+ "context-savelink", true,
+ "context-copylink", true
+ ].concat(inspectItems));
+ }
+ closeContextMenu();
+ openContextMenuFor(mailto); // Invoke context menu for next test.
+ break;
+
+ case 4:
+ // Context menu for text mailto-link
+ checkContextMenu(["context-copyemail", true].concat(inspectItems));
+ closeContextMenu();
+ openContextMenuFor(input); // Invoke context menu for next test.
+ break;
+
+ case 5:
+ // Context menu for text input field
+ checkContextMenu(["context-undo", false,
+ "---", null,
+ "context-cut", false,
+ "context-copy", false,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", false,
+ "---", null,
+ "spell-check-enabled", true
+ ].concat(inspectItems));
+ closeContextMenu();
+ openContextMenuFor(img); // Invoke context menu for next test.
+ break;
+
+ case 6:
+ // Context menu for an image
+ checkContextMenu(["context-viewimage", true,
+ "context-copyimage-contents", true,
+ "context-copyimage", true,
+ "---", null,
+ "context-saveimage", true,
+ "context-sendimage", true,
+ "context-setDesktopBackground", true,
+ "context-viewimageinfo", true
+ ].concat(inspectItems));
+ closeContextMenu();
+ openContextMenuFor(canvas); // Invoke context menu for next test.
+ break;
+
+ case 7:
+ // Context menu for a canvas
+ checkContextMenu(["context-viewimage", true,
+ "context-saveimage", true,
+ "context-bookmarkpage", true,
+ "context-selectall", true
+ ].concat(inspectItems));
+ closeContextMenu();
+ openContextMenuFor(video_ok); // Invoke context menu for next test.
+ break;
+
+ case 8:
+ // Context menu for a video (with a VALID media source)
+ checkContextMenu(["context-media-play", true,
+ "context-media-mute", true,
+ "context-media-playbackrate", null,
+ ["context-media-playbackrate-050x", true,
+ "context-media-playbackrate-100x", true,
+ "context-media-playbackrate-150x", true,
+ "context-media-playbackrate-200x", true], null,
+ "context-media-hidecontrols", true,
+ "context-video-showstats", true,
+ "context-video-fullscreen", true,
+ "---", null,
+ "context-viewvideo", true,
+ "context-copyvideourl", true,
+ "---", null,
+ "context-savevideo", true,
+ "context-video-saveimage", true,
+ "context-sendvideo", true
+ ].concat(inspectItems));
+ closeContextMenu();
+ openContextMenuFor(audio_in_video); // Invoke context menu for next test.
+ break;
+
+ case 9:
+ // Context menu for a video (with an audio-only file)
+ checkContextMenu(["context-media-play", true,
+ "context-media-mute", true,
+ "context-media-playbackrate", null,
+ ["context-media-playbackrate-050x", true,
+ "context-media-playbackrate-100x", true,
+ "context-media-playbackrate-150x", true,
+ "context-media-playbackrate-200x", true], null,
+ "context-media-showcontrols", true,
+ "---", null,
+ "context-copyaudiourl", true,
+ "---", null,
+ "context-saveaudio", true,
+ "context-sendaudio", true
+ ].concat(inspectItems));
+ closeContextMenu();
+ openContextMenuFor(video_bad); // Invoke context menu for next test.
+ break;
+
+ case 10:
+ // Context menu for a video (with an INVALID media source)
+ checkContextMenu(["context-media-play", false,
+ "context-media-mute", false,
+ "context-media-playbackrate", null,
+ ["context-media-playbackrate-050x", false,
+ "context-media-playbackrate-100x", false,
+ "context-media-playbackrate-150x", false,
+ "context-media-playbackrate-200x", false], null,
+ "context-media-hidecontrols", false,
+ "context-video-showstats", false,
+ "context-video-fullscreen", false,
+ "---", null,
+ "context-viewvideo", true,
+ "context-copyvideourl", true,
+ "---", null,
+ "context-savevideo", true,
+ "context-video-saveimage", false,
+ "context-sendvideo", true
+ ].concat(inspectItems));
+ closeContextMenu();
+ openContextMenuFor(video_bad2); // Invoke context menu for next test.
+ break;
+
+ case 11:
+ // Context menu for a video (with an INVALID media source)
+ checkContextMenu(["context-media-play", false,
+ "context-media-mute", false,
+ "context-media-playbackrate", null,
+ ["context-media-playbackrate-050x", false,
+ "context-media-playbackrate-100x", false,
+ "context-media-playbackrate-150x", false,
+ "context-media-playbackrate-200x", false], null,
+ "context-media-hidecontrols", false,
+ "context-video-showstats", false,
+ "context-video-fullscreen", false,
+ "---", null,
+ "context-viewvideo", false,
+ "context-copyvideourl", false,
+ "---", null,
+ "context-savevideo", false,
+ "context-video-saveimage", false,
+ "context-sendvideo", false
+ ].concat(inspectItems));
+ closeContextMenu();
+ openContextMenuFor(iframe); // Invoke context menu for next test.
+ break;
+
+ case 12:
+ // Context menu for an iframe
+ checkContextMenu(["context-back", false,
+ "context-forward", false,
+ "context-reload", true,
+ "---", null,
+ "context-bookmarkpage", true,
+ "context-savepage", true,
+ "---", null,
+ "context-viewbgimage", false,
+ "context-selectall", true,
+ "frame", null,
+ ["context-showonlythisframe", true,
+ "context-openframeintab", true,
+ "context-openframe", true,
+ "---", null,
+ "context-reloadframe", true,
+ "---", null,
+ "context-bookmarkframe", true,
+ "context-saveframe", true,
+ "---", null,
+ "context-printframe", true,
+ "---", null,
+ "context-viewframesource", true,
+ "context-viewframeinfo", true], null,
+ "---", null,
+ "context-viewsource", true,
+ "context-viewinfo", true
+ ].concat(inspectItems));
+ closeContextMenu();
+ openContextMenuFor(video_in_iframe); // Invoke context menu for next test.
+ break;
+
+ case 13:
+ // Context menu for a video in an iframe
+ checkContextMenu(["context-media-play", true,
+ "context-media-mute", true,
+ "context-media-playbackrate", null,
+ ["context-media-playbackrate-050x", true,
+ "context-media-playbackrate-100x", true,
+ "context-media-playbackrate-150x", true,
+ "context-media-playbackrate-200x", true], null,
+ "context-media-hidecontrols", true,
+ "context-video-showstats", true,
+ "context-video-fullscreen", true,
+ "---", null,
+ "context-viewvideo", true,
+ "context-copyvideourl", true,
+ "---", null,
+ "context-savevideo", true,
+ "context-video-saveimage", true,
+ "context-sendvideo", true,
+ "frame", null,
+ ["context-showonlythisframe", true,
+ "context-openframeintab", true,
+ "context-openframe", true,
+ "---", null,
+ "context-reloadframe", true,
+ "---", null,
+ "context-bookmarkframe", true,
+ "context-saveframe", true,
+ "---", null,
+ "context-printframe", true,
+ "---", null,
+ "context-viewframeinfo", true], null].concat(inspectItems));
+ closeContextMenu();
+ openContextMenuFor(image_in_iframe); // Invoke context menu for next test.
+ break;
+
+ case 14:
+ // Context menu for an image in an iframe
+ checkContextMenu(["context-viewimage", true,
+ "context-copyimage-contents", true,
+ "context-copyimage", true,
+ "---", null,
+ "context-saveimage", true,
+ "context-sendimage", true,
+ "context-setDesktopBackground", true,
+ "context-viewimageinfo", true,
+ "frame", null,
+ ["context-showonlythisframe", true,
+ "context-openframeintab", true,
+ "context-openframe", true,
+ "---", null,
+ "context-reloadframe", true,
+ "---", null,
+ "context-bookmarkframe", true,
+ "context-saveframe", true,
+ "---", null,
+ "context-printframe", true,
+ "---", null,
+ "context-viewframeinfo", true], null].concat(inspectItems));
+ closeContextMenu();
+ openContextMenuFor(textarea, false, true); // Invoke context menu for next test, but wait for the spellcheck.
+ break;
+
+ case 15:
+ // Context menu for textarea
+ checkContextMenu(["*chubbiness", true, // spelling suggestion
+ "spell-add-to-dictionary", true,
+ "---", null,
+ "context-undo", false,
+ "---", null,
+ "context-cut", false,
+ "context-copy", false,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", true,
+ "---", null,
+ "spell-check-enabled", true,
+ "spell-dictionaries", true,
+ ["spell-check-dictionary-en-US", true,
+ "---", null,
+ "spell-add-dictionaries", true], null
+ ].concat(inspectItems));
+ contextMenu.ownerDocument.getElementById("spell-add-to-dictionary").doCommand(); // Add to dictionary
+ closeContextMenu();
+ openContextMenuFor(text); // Invoke context menu for next test.
+ break;
+
+ case 16:
+ // Re-check context menu for plain text to make sure it hasn't changed
+ checkContextMenu(plainTextItems);
+ closeContextMenu();
+ openContextMenuFor(textarea, false, true); // Invoke context menu for next test.
+ break;
+
+ case 17:
+ // Context menu for textarea after a word has been added
+ // to the dictionary
+ checkContextMenu(["spell-undo-add-to-dictionary", true,
+ "---", null,
+ "context-undo", false,
+ "---", null,
+ "context-cut", false,
+ "context-copy", false,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", true,
+ "---", null,
+ "spell-check-enabled", true,
+ "spell-dictionaries", true,
+ ["spell-check-dictionary-en-US", true,
+ "---", null,
+ "spell-add-dictionaries", true], null
+ ].concat(inspectItems));
+ contextMenu.ownerDocument.getElementById("spell-undo-add-to-dictionary").doCommand(); // Undo add to dictionary
+ closeContextMenu();
+ openContextMenuFor(contenteditable, false, true);
+ break;
+
+ case 18:
+ // Context menu for contenteditable
+ checkContextMenu(["spell-no-suggestions", false,
+ "spell-add-to-dictionary", true,
+ "---", null,
+ "context-undo", false,
+ "---", null,
+ "context-cut", false,
+ "context-copy", false,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", true,
+ "---", null,
+ "spell-check-enabled", true,
+ "spell-dictionaries", true,
+ ["spell-check-dictionary-en-US", true,
+ "---", null,
+ "spell-add-dictionaries", true], null
+ ].concat(inspectItems));
+
+ closeContextMenu();
+ openContextMenuFor(inputspell, false, true); // Invoke context menu for next test.
+ break;
+
+ case 19:
+ // Context menu for spell-check input
+ checkContextMenu(["*prodigality", true, // spelling suggestion
+ "spell-add-to-dictionary", true,
+ "---", null,
+ "context-undo", false,
+ "---", null,
+ "context-cut", false,
+ "context-copy", false,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", true,
+ "---", null,
+ "spell-check-enabled", true,
+ "spell-dictionaries", true,
+ ["spell-check-dictionary-en-US", true,
+ "---", null,
+ "spell-add-dictionaries", true], null
+ ].concat(inspectItems));
+
+ closeContextMenu();
+ openContextMenuFor(inputspellfalse, false, true); // Invoke context menu for next test.
+ break;
+
+ case 20:
+ // Context menu for text input field with spellcheck=false
+ checkContextMenu(["context-undo", false,
+ "---", null,
+ "context-cut", false,
+ "context-copy", false,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", false,
+ "---", null,
+ "context-selectall", true,
+ "---", null,
+ "spell-add-dictionaries-main", true,
+ ].concat(inspectItems));
+
+ closeContextMenu();
+ openContextMenuFor(link); // Invoke context menu for next test.
+ break;
+
+ case 21:
+ executeCopyCommand("cmd_copyLink", "http://mozilla.com/");
+ closeContextMenu();
+ openContextMenuFor(pagemenu); // Invoke context menu for next test.
+ break;
+
+ case 22:
+ // Context menu for element with assigned content context menu
+ checkContextMenu(["+Plain item", {type: "", icon: "", checked: false, disabled: false},
+ "+Disabled item", {type: "", icon: "", checked: false, disabled: true},
+ "+Item w/ textContent", {type: "", icon: "", checked: false, disabled: false},
+ "---", null,
+ "+Checkbox", {type: "checkbox", icon: "", checked: true, disabled: false},
+ "---", null,
+ "+Radio1", {type: "checkbox", icon: "", checked: true, disabled: false},
+ "+Radio2", {type: "checkbox", icon: "", checked: false, disabled: false},
+ "+Radio3", {type: "checkbox", icon: "", checked: false, disabled: false},
+ "---", null,
+ "+Item w/ icon", {type: "", icon: "favicon.ico", checked: false, disabled: false},
+ "+Item w/ bad icon", {type: "", icon: "", checked: false, disabled: false},
+ "---", null,
+ "generated-submenu-1", true,
+ ["+Radio1", {type: "checkbox", icon: "", checked: false, disabled: false},
+ "+Radio2", {type: "checkbox", icon: "", checked: true, disabled: false},
+ "+Radio3", {type: "checkbox", icon: "", checked: false, disabled: false},
+ "---", null,
+ "+Checkbox", {type: "checkbox", icon: "", checked: false, disabled: false}], null,
+ "---", null,
+ "context-back", false,
+ "context-forward", false,
+ "context-reload", true,
+ "---", null,
+ "context-bookmarkpage", true,
+ "context-savepage", true,
+ "---", null,
+ "context-viewbgimage", false,
+ "context-selectall", true,
+ "---", null,
+ "context-viewsource", true,
+ "context-viewinfo", true
+ ].concat(inspectItems));
+
+ invokeItemAction("0");
+ closeContextMenu();
+
+ // run mozRequestFullScreen on the element we're testing
+ var full_screen_element = subwindow.document.getElementById("test-dom-full-screen");
+ var openDomFullScreen = function() {
+ subwindow.removeEventListener("mozfullscreenchange", openDomFullScreen, false);
+ openContextMenuFor(dom_full_screen, true); // Invoke context menu for next test.
+ }
+ subwindow.addEventListener("mozfullscreenchange", openDomFullScreen, false);
+ SpecialPowers.setBoolPref("full-screen-api.approval-required", false);
+ SpecialPowers.setBoolPref("full-screen-api.allow-trusted-requests-only", false);
+ full_screen_element.mozRequestFullScreen();
+ break;
+
+ case 23:
+ // Context menu for DOM Fullscreen mode (NOTE: this is *NOT* on an img)
+ checkContextMenu(["context-leave-dom-fullscreen", true,
+ "---", null,
+ "context-back", false,
+ "context-forward", false,
+ "context-reload", true,
+ "---", null,
+ "context-bookmarkpage", true,
+ "context-savepage", true,
+ "---", null,
+ "context-viewbgimage", false,
+ "context-selectall", true,
+ "---", null,
+ "context-viewsource", true,
+ "context-viewinfo", true
+ ].concat(inspectItems));
+ closeContextMenu();
+ var full_screen_element = subwindow.document.getElementById("test-dom-full-screen");
+ var openPagemenu = function() {
+ subwindow.removeEventListener("mozfullscreenchange", openPagemenu, false);
+ SpecialPowers.clearUserPref("full-screen-api.approval-required");
+ SpecialPowers.clearUserPref("full-screen-api.allow-trusted-requests-only");
+ openContextMenuFor(pagemenu, true); // Invoke context menu for next test.
+ }
+ subwindow.addEventListener("mozfullscreenchange", openPagemenu, false);
+ subwindow.document.mozCancelFullScreen();
+ break;
+
+ case 24:
+ // Context menu for element with assigned content context menu
+ // The shift key should bypass content context menu processing
+ checkContextMenu(["context-back", false,
+ "context-forward", false,
+ "context-reload", true,
+ "---", null,
+ "context-bookmarkpage", true,
+ "context-savepage", true,
+ "---", null,
+ "context-viewbgimage", false,
+ "context-selectall", true,
+ "---", null,
+ "context-viewsource", true,
+ "context-viewinfo", true
+ ].concat(inspectItems));
+ closeContextMenu();
+ selectText(selecttext); // Select text prior to opening context menu.
+ openContextMenuFor(selecttext); // Invoke context menu for next test.
+ return;
+
+ case 25:
+ // Context menu for selected text
+ if (SpecialPowers.Services.appinfo.OS == "Darwin") {
+ // This test is only enabled on Mac due to bug 736399.
+ checkContextMenu(["context-copy", true,
+ "context-selectall", true,
+ "---", null,
+ "context-searchselect", true,
+ "context-viewpartialsource-selection", true
+ ].concat(inspectItems));
+ }
+ closeContextMenu();
+ selectText(selecttextlink); // Select text prior to opening context menu.
+ openContextMenuFor(selecttextlink); // Invoke context menu for next test.
+ return;
+
+ case 26:
+ // Context menu for selected text which matches valid URL pattern
+ if (SpecialPowers.Services.appinfo.OS == "Darwin") {
+ // This test is only enabled on Mac due to bug 736399.
+ if (perWindowPrivateBrowsing) {
+ checkContextMenu(["context-openlinkincurrent", true,
+ "context-openlinkintab", true,
+ "context-openlink", true,
+ "context-openlinkprivate", true,
+ "---", null,
+ "context-bookmarklink", true,
+ "context-savelink", true,
+ "context-copy", true,
+ "context-selectall", true,
+ "---", null,
+ "context-searchselect", true,
+ "context-viewpartialsource-selection", true
+ ].concat(inspectItems));
+ } else {
+ checkContextMenu(["context-openlinkincurrent", true,
+ "context-openlinkintab", true,
+ "context-openlink", true,
+ "---", null,
+ "context-bookmarklink", true,
+ "context-savelink", true,
+ "context-copy", true,
+ "context-selectall", true,
+ "---", null,
+ "context-searchselect", true,
+ "context-viewpartialsource-selection", true
+ ].concat(inspectItems));
+ }
+ }
+ closeContextMenu();
+ // clear the selection because following tests don't expect any selection
+ subwindow.getSelection().removeAllRanges();
+
+ openContextMenuFor(imagelink)
+ break;
+
+ case 27:
+ // Context menu for image link
+ if (perWindowPrivateBrowsing) {
+ checkContextMenu(["context-openlinkintab", true,
+ "context-openlink", true,
+ "context-openlinkprivate", true,
+ "---", null,
+ "context-bookmarklink", true,
+ "context-savelink", true,
+ "context-copylink", true,
+ "---", null,
+ "context-viewimage", true,
+ "context-copyimage-contents", true,
+ "context-copyimage", true,
+ "---", null,
+ "context-saveimage", true,
+ "context-sendimage", true,
+ "context-setDesktopBackground", true,
+ "context-viewimageinfo", true
+ ].concat(inspectItems));
+ } else {
+ checkContextMenu(["context-openlinkintab", true,
+ "context-openlink", true,
+ "---", null,
+ "context-bookmarklink", true,
+ "context-savelink", true,
+ "context-copylink", true,
+ "---", null,
+ "context-viewimage", true,
+ "context-copyimage-contents", true,
+ "context-copyimage", true,
+ "---", null,
+ "context-saveimage", true,
+ "context-sendimage", true,
+ "context-setDesktopBackground", true,
+ "context-viewimageinfo", true
+ ].concat(inspectItems));
+ }
+ closeContextMenu();
+ selectInputText(select_inputtext); // Select text prior to opening context menu.
+ openContextMenuFor(select_inputtext); // Invoke context menu for next test.
+ return;
+
+ case 28:
+ // Context menu for selected text in input
+ checkContextMenu(["context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", true,
+ "---", null,
+ "context-selectall", true,
+ "context-searchselect",true,
+ "---", null,
+ "spell-check-enabled", true
+ ].concat(inspectItems));
+ closeContextMenu();
+ selectInputText(select_inputtext_password); // Select text prior to opening context menu.
+ openContextMenuFor(select_inputtext_password); // Invoke context menu for next test.
+ return;
+
+ case 29:
+ // Context menu for selected text in input[type="password"]
+ checkContextMenu(["context-undo", false,
+ "---", null,
+ "context-cut", true,
+ "context-copy", true,
+ "context-paste", null, // ignore clipboard state
+ "context-delete", true,
+ "---", null,
+ "context-selectall", true,
+ "---", null,
+ "spell-check-enabled", true,
+ //spell checker is shown on input[type="password"] on this testcase
+ "spell-dictionaries", true,
+ ["spell-check-dictionary-en-US", true,
+ "---", null,
+ "spell-add-dictionaries", true], null
+ ].concat(inspectItems));
+ closeContextMenu();
+ subwindow.getSelection().removeAllRanges();
+ openContextMenuFor(plugin);
+ return;
+
+ case 30:
+ // Context menu for click-to-play blocked plugin
+ checkContextMenu(["context-ctp-play", true,
+ "context-ctp-hide", true,
+ "---", null,
+ "context-back", false,
+ "context-forward", false,
+ "context-reload", true,
+ "---", null,
+ "context-bookmarkpage", true,
+ "context-savepage", true,
+ "---", null,
+ "context-viewbgimage", false,
+ "context-selectall", true,
+ "---", null,
+ "context-viewsource", true,
+ "context-viewinfo", true
+ ].concat(inspectItems));
+ closeContextMenu();
+ SpecialPowers.clearUserPref("plugins.click_to_play");
+ var ph = SpecialPowers.Cc["@mozilla.org/plugin/host;1"]
+ .getService(SpecialPowers.Ci.nsIPluginHost);
+ var tags = ph.getPluginTags();
+ for (var tag of tags) {
+ if (tag.name == "Test Plug-in") {
+ tag.enabledState = SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED;
+ }
+ }
+
+ // finish test
+ subwindow.close();
+ SimpleTest.finish();
+ return;
+
+ /*
+ * Other things that would be nice to test:
+ * - spelling / misspelled word (in text input?)
+ * - check state of disabled items
+ * - test execution of menu items (maybe as a separate test?)
+ */
+
+ default:
+ ok(false, "Unexpected invocation of test #" + testNum);
+ subwindow.close();
+ SimpleTest.finish();
+ return;
+ }
+
+}
+
+
+var testNum = 1;
+var subwindow, chromeWin, contextMenu, lastElement;
+var text, link, mailto, input, img, canvas, video_ok, video_bad, video_bad2,
+ iframe, video_in_iframe, image_in_iframe, textarea, contenteditable,
+ inputspell, pagemenu, dom_full_screen, plainTextItems, audio_in_video,
+ selecttext, selecttextlink, imagelink, select_inputtext, select_inputtext_password,
+ plugin, inputspellfalse;
+
+function startTest() {
+ chromeWin = SpecialPowers.wrap(subwindow)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow)
+ .QueryInterface(Ci.nsIDOMChromeWindow);
+ contextMenu = chromeWin.document.getElementById("contentAreaContextMenu");
+ ok(contextMenu, "Got context menu XUL");
+
+ if (chromeWin.document.getElementById("Browser:Stop").getAttribute("disabled") != "true") {
+ todo(false, "Wait for subwindow to load... (This should usually happen once.)");
+ SimpleTest.executeSoon(startTest);
+ return;
+ }
+
+ subwindow.allowFullscreen = true;
+ lastElement = null;
+
+ text = subwindow.document.getElementById("test-text");
+ link = subwindow.document.getElementById("test-link");
+ imagelink = subwindow.document.getElementById("test-image-link");
+ mailto = subwindow.document.getElementById("test-mailto");
+ input = subwindow.document.getElementById("test-input");
+ img = subwindow.document.getElementById("test-image");
+ canvas = subwindow.document.getElementById("test-canvas");
+ video_ok = subwindow.document.getElementById("test-video-ok");
+ audio_in_video = subwindow.document.getElementById("test-audio-in-video");
+ video_bad = subwindow.document.getElementById("test-video-bad");
+ video_bad2 = subwindow.document.getElementById("test-video-bad2");
+ iframe = subwindow.document.getElementById("test-iframe");
+ video_in_iframe = subwindow.document.getElementById("test-video-in-iframe").contentDocument.getElementsByTagName("video")[0];
+ video_in_iframe.pause();
+ image_in_iframe = subwindow.document.getElementById("test-image-in-iframe").contentDocument.getElementsByTagName("img")[0];
+ textarea = subwindow.document.getElementById("test-textarea");
+ contenteditable = subwindow.document.getElementById("test-contenteditable");
+ contenteditable.focus(); // content editable needs to be focused to enable spellcheck
+ inputspell = subwindow.document.getElementById("test-input-spellcheck");
+ inputspellfalse = subwindow.document.getElementById("test-contenteditable-spellcheck-false");
+ pagemenu = subwindow.document.getElementById("test-pagemenu");
+ dom_full_screen = subwindow.document.getElementById("test-dom-full-screen");
+ selecttext = subwindow.document.getElementById("test-select-text");
+ selecttextlink = subwindow.document.getElementById("test-select-text-link");
+ select_inputtext = subwindow.document.getElementById("test-select-input-text");
+ select_inputtext_password = subwindow.document.getElementById("test-select-input-text-type-password");
+ plugin = subwindow.document.getElementById("test-plugin");
+
+ contextMenu.addEventListener("popupshown", function() { runTest(++testNum); }, false);
+ runTest(1);
+}
+
+// We open this in a separate window, because the Mochitests run inside a frame.
+// The frame causes an extra menu item, and prevents running the test
+// standalone (ie, clicking the test name in the Mochitest window) to see
+// success/failure messages.
+var painted = false, loaded = false;
+
+function waitForEvents(event)
+{
+ if (event.type == "MozAfterPaint")
+ painted = true;
+ else if (event.type == "load")
+ loaded = true;
+ if (painted && loaded) {
+ subwindow.removeEventListener("MozAfterPaint", waitForEvents, false);
+ subwindow.onload = null;
+ startTest();
+ }
+}
+
+const isOSXMtnLion = navigator.userAgent.indexOf("Mac OS X 10.8") != -1;
+
+if (isOSXMtnLion) {
+ todo(false, "Mountain Lion doesn't like this test (bug 792304)");
+} else {
+ SpecialPowers.setBoolPref("plugins.click_to_play", true);
+ var ph = SpecialPowers.Cc["@mozilla.org/plugin/host;1"]
+ .getService(SpecialPowers.Ci.nsIPluginHost);
+ var tags = ph.getPluginTags();
+ for (var tag of tags) {
+ if (tag.name == "Test Plug-in") {
+ tag.enabledState = SpecialPowers.Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+ }
+ }
+
+ var subwindow = window.open("./subtst_contextmenu.html", "contextmenu-subtext", "width=600,height=800");
+ subwindow.addEventListener("MozAfterPaint", waitForEvents, false);
+ subwindow.onload = waitForEvents;
+
+ SimpleTest.waitForExplicitFinish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/browser/base/content/test/test_feed_discovery.html b/browser/base/content/test/test_feed_discovery.html
new file mode 100644
index 000000000..31d716385
--- /dev/null
+++ b/browser/base/content/test/test_feed_discovery.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=377611
+-->
+<head>
+ <title>Test for feed discovery</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=377611">Mozilla Bug 377611</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 377611 **/
+
+var rv = { tests: null };
+var testCheckInterval = null;
+
+function startTest() {
+ var url = window.location.href.replace(/test_feed_discovery\.html/,
+ 'feed_discovery.html');
+ SpecialPowers.openDialog(window, [url, '', 'dialog=no,width=10,height=10', rv]);
+ testCheckInterval = window.setInterval(tryIfTestIsFinished, 500);
+}
+
+function tryIfTestIsFinished() {
+ if (rv.tests) {
+ window.clearInterval(testCheckInterval);
+ checkTest();
+ }
+}
+
+function checkTest() {
+ for (var i = 0; i < rv.tests.length; ++ i) {
+ var test = rv.tests[i];
+ ok(test.check, test.message);
+ }
+ SimpleTest.finish();
+}
+
+window.onload = startTest;
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/browser/base/content/test/test_offlineNotification.html b/browser/base/content/test/test_offlineNotification.html
new file mode 100644
index 000000000..8c603f150
--- /dev/null
+++ b/browser/base/content/test/test_offlineNotification.html
@@ -0,0 +1,126 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=462856
+-->
+<head>
+ <title>Test offline app notification</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="loaded()">
+<p id="display">
+<!-- Load the test frame twice from the same domain,
+ to make sure we get notifications for both -->
+<iframe name="testFrame" src="offlineChild.html"></iframe>
+<iframe name="testFrame2" src="offlineChild2.html"></iframe>
+<!-- Load from another domain to make sure we get a second allow/deny
+ notification -->
+<iframe name="testFrame3" src="http://example.com/tests/browser/base/content/test/offlineChild.html"></iframe>
+
+<iframe id="eventsTestFrame" src="offlineEvent.html"></iframe>
+
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+const Cc = SpecialPowers.Cc;
+
+var numFinished = 0;
+
+window.addEventListener("message", function(event) {
+ is(event.data, "success", "Child was successfully cached.");
+
+ if (++numFinished == 3) {
+ // Clean up after ourself
+ var pm = Cc["@mozilla.org/permissionmanager;1"].
+ getService(SpecialPowers.Ci.nsIPermissionManager);
+ var ioService = Cc["@mozilla.org/network/io-service;1"]
+ .getService(SpecialPowers.Ci.nsIIOService);
+ var uri1 = ioService.newURI(frames.testFrame.location, null, null);
+ var uri2 = ioService.newURI(frames.testFrame3.location, null, null);
+
+ var principal1 = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(SpecialPowers.Ci.nsIScriptSecurityManager)
+ .getNoAppCodebasePrincipal(uri1);
+ var principal2 = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(SpecialPowers.Ci.nsIScriptSecurityManager)
+ .getNoAppCodebasePrincipal(uri2);
+
+ pm.removeFromPrincipal(principal1, "offline-app");
+ pm.removeFromPrincipal(principal2, "offline-app");
+
+ SimpleTest.finish();
+ }
+ }, false);
+
+var count = 0;
+var expectedEvent = "";
+function eventHandler(evt) {
+ ++count;
+ is(evt.type, expectedEvent, "Wrong event!");
+}
+
+function testEventHandling() {
+ var events = [ "checking",
+ "error",
+ "noupdate",
+ "downloading",
+ "progress",
+ "updateready",
+ "cached",
+ "obsolete"];
+ var w = document.getElementById("eventsTestFrame").contentWindow;
+ var e;
+ for (var i = 0; i < events.length; ++i) {
+ count = 0;
+ expectedEvent = events[i];
+ e = w.document.createEvent("event");
+ e.initEvent(expectedEvent, true, true);
+ w.applicationCache["on" + expectedEvent] = eventHandler;
+ w.applicationCache.addEventListener(expectedEvent, eventHandler, true);
+ w.applicationCache.dispatchEvent(e);
+ is(count, 2, "Wrong number events!");
+ w.applicationCache["on" + expectedEvent] = null;
+ w.applicationCache.removeEventListener(expectedEvent, eventHandler, true);
+ w.applicationCache.dispatchEvent(e);
+ is(count, 2, "Wrong number events!");
+ }
+
+ // Test some random event.
+ count = 0;
+ expectedEvent = "foo";
+ e = w.document.createEvent("event");
+ e.initEvent(expectedEvent, true, true);
+ w.applicationCache.addEventListener(expectedEvent, eventHandler, true);
+ w.applicationCache.dispatchEvent(e);
+ is(count, 1, "Wrong number events!");
+ w.applicationCache.removeEventListener(expectedEvent, eventHandler, true);
+ w.applicationCache.dispatchEvent(e);
+ is(count, 1, "Wrong number events!");
+}
+
+function loaded() {
+ testEventHandling();
+
+ // Click the notification panel's "Allow" button. This should kick
+ // off updates, which will eventually lead to getting messages from
+ // the children.
+ var wm = SpecialPowers.Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(SpecialPowers.Ci.nsIWindowMediator);
+ var win = wm.getMostRecentWindow("navigator:browser");
+ var panel = win.PopupNotifications.panel;
+ is(panel.childElementCount, 2, "2 notifications being displayed");
+ panel.firstElementChild.button.click();
+
+ // should have dismissed one of the notifications.
+ is(panel.childElementCount, 1, "1 notification now being displayed");
+ panel.firstElementChild.button.click();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/browser/base/content/test/test_offline_gzip.html b/browser/base/content/test/test_offline_gzip.html
new file mode 100644
index 000000000..09713da92
--- /dev/null
+++ b/browser/base/content/test/test_offline_gzip.html
@@ -0,0 +1,112 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=501422
+
+When content which was transported over the network with
+Content-Type: gzip is added to the offline
+cache, it can be fetched from the cache successfully.
+-->
+<head>
+ <title>Test gzipped offline resources</title>
+ <script type="text/javascript"
+ src="/MochiKit/MochiKit.js"></script>
+ <script type="text/javascript"
+ src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="loaded()">
+<p id="display">
+<iframe name="testFrame" src="gZipOfflineChild.html"></iframe>
+
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var cacheCount = 0;
+var intervalID = 0;
+
+window.addEventListener("message", handleMessageEvents, false);
+SimpleTest.waitForExplicitFinish();
+
+function finishTest() {
+ // Clean up after ourselves.
+ var Cc = SpecialPowers.Cc;
+ var pm = Cc["@mozilla.org/permissionmanager;1"].
+ getService(SpecialPowers.Ci.nsIPermissionManager);
+
+ var uri = Cc["@mozilla.org/network/io-service;1"].getService(SpecialPowers.Ci.nsIIOService)
+ .newURI(window.frames[0].location, null, null);
+ var principal = Cc["@mozilla.org/scriptsecuritymanager;1"]
+ .getService(SpecialPowers.Ci.nsIScriptSecurityManager)
+ .getNoAppCodebasePrincipal(uri);
+
+ pm.removeFromPrincipal(principal, "offline-app");
+
+ window.removeEventListener("message", handleMessageEvents, false);
+
+ SimpleTest.finish();
+}
+
+////
+// Handle "message" events which are posted from the iframe upon
+// offline cache events.
+//
+function handleMessageEvents(event) {
+ cacheCount++;
+ switch (cacheCount) {
+ case 1:
+ // This is the initial caching off offline data.
+ is(event.data, "oncache", "Child was successfully cached.");
+ // Reload the frame; this will generate an error message
+ // in the case of bug 501422.
+ frames.testFrame.window.location.reload();
+ // Use setInterval to repeatedly call a function which
+ // checks that one of two things has occurred: either
+ // the offline cache is udpated (which means our iframe
+ // successfully reloaded), or the string "error" appears
+ // in the iframe, as in the case of bug 501422.
+ intervalID = setInterval(function() {
+ // Sometimes document.body may not exist, and trying to access
+ // it will throw an exception, so handle this case.
+ try {
+ var bodyInnerHTML = frames.testFrame.document.body.innerHTML;
+ }
+ catch (e) {
+ var bodyInnerHTML = "";
+ }
+ if (cacheCount == 2 || bodyInnerHTML.contains("error")) {
+ clearInterval(intervalID);
+ is(cacheCount, 2, "frame not reloaded successfully");
+ if (cacheCount != 2) {
+ finishTest();
+ }
+ }
+ }, 100);
+ break;
+ case 2:
+ is(event.data, "onupdate", "Child was successfully updated.");
+ finishTest();
+ break;
+ default:
+ // how'd we get here?
+ ok(false, "cacheCount not 1 or 2");
+ }
+}
+
+function loaded() {
+ // Click the notification panel's "Allow" button. This should kick
+ // off updates, which will eventually lead to getting messages from
+ // the iframe.
+ var wm = SpecialPowers.Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(SpecialPowers.Ci.nsIWindowMediator);
+ var win = wm.getMostRecentWindow("navigator:browser");
+ var panel = win.PopupNotifications.panel;
+ panel.firstElementChild.button.click();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/browser/base/content/test/test_wyciwyg_copying.html b/browser/base/content/test/test_wyciwyg_copying.html
new file mode 100644
index 000000000..3a8c3a150
--- /dev/null
+++ b/browser/base/content/test/test_wyciwyg_copying.html
@@ -0,0 +1,13 @@
+<html>
+<body>
+<script>
+ function go() {
+ var w = window.open();
+ w.document.open();
+ w.document.write("<html><body>test document</body></html>");
+ w.document.close();
+ }
+</script>
+<button id="btn" onclick="go();">test</button>
+</body>
+</html>
diff --git a/browser/base/content/test/title_test.svg b/browser/base/content/test/title_test.svg
new file mode 100644
index 000000000..6ab5b2f5c
--- /dev/null
+++ b/browser/base/content/test/title_test.svg
@@ -0,0 +1,59 @@
+<svg width="640px" height="480px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.0">
+ <title>This is a root SVG element's title</title>
+ <foreignObject>
+ <html xmlns="http://www.w3.org/1999/xhtml">
+ <body>
+ <svg xmlns="http://www.w3.org/2000/svg" id="svg1">
+ <title>This is a non-root SVG element title</title>
+ </svg>
+ </body>
+ </html>
+ </foreignObject>
+ <text id="text1" x="10px" y="32px" font-size="24px">
+ This contains only &lt;title&gt;
+ <title>
+
+
+ This is a title
+
+ </title>
+ </text>
+ <text id="text2" x="10px" y="96px" font-size="24px">
+ This contains only &lt;desc&gt;
+ <desc>This is a desc</desc>
+ </text>
+ <text id="text3" x="10px" y="128px" font-size="24px">
+ This contains nothing.
+ </text>
+ <a id="link1" xlink:href="#">
+ This link contains &lt;title&gt;
+ <title>
+ This is a title
+ </title>
+ <text id="text4" x="10px" y="192px" font-size="24px">
+ </text>
+ </a>
+ <a id="link2" xlink:href="#">
+ <text x="10px" y="192px" font-size="24px">
+ This text contains &lt;title&gt;
+ <title>
+ This is a title
+ </title>
+ </text>
+ </a>
+ <a id="link3" xlink:href="#" xlink:title="This is an xlink:title attribute">
+ <text x="10px" y="224px" font-size="24px">
+ This link contains &lt;title&gt; &amp; xlink:title attr.
+ <title>This is a title</title>
+ </text>
+ </a>
+ <a id="link4" xlink:href="#" xlink:title="This is an xlink:title attribute">
+ <text x="10px" y="256px" font-size="24px">
+ This link contains xlink:title attr.
+ </text>
+ </a>
+ <text id="text5" x="10px" y="160px" font-size="24px"
+ xlink:title="This is an xlink:title attribute but it isn't on a link" >
+ This contains nothing.
+ </text>
+</svg>
diff --git a/browser/base/content/test/video.ogg b/browser/base/content/test/video.ogg
new file mode 100644
index 000000000..ac7ece351
--- /dev/null
+++ b/browser/base/content/test/video.ogg
Binary files differ
diff --git a/browser/base/content/test/zoom_test.html b/browser/base/content/test/zoom_test.html
new file mode 100644
index 000000000..bf80490ca
--- /dev/null
+++ b/browser/base/content/test/zoom_test.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=416661
+-->
+ <head>
+ <title>Test for zoom setting</title>
+
+ </head>
+ <body>
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=416661">Bug 416661</a>
+ <p>Site specific zoom settings should not apply to image documents.</p>
+ </body>
+</html>
diff --git a/browser/base/content/urlbarBindings.xml b/browser/base/content/urlbarBindings.xml
new file mode 100644
index 000000000..1cec6d20f
--- /dev/null
+++ b/browser/base/content/urlbarBindings.xml
@@ -0,0 +1,2198 @@
+<?xml version="1.0"?>
+
+# -*- Mode: HTML -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<!DOCTYPE bindings [
+<!ENTITY % notificationDTD SYSTEM "chrome://global/locale/notification.dtd">
+%notificationDTD;
+<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
+%browserDTD;
+]>
+
+<bindings id="urlbarBindings" xmlns="http://www.mozilla.org/xbl"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xbl="http://www.mozilla.org/xbl">
+
+ <binding id="urlbar" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete">
+
+ <content sizetopopup="pref">
+ <xul:hbox anonid="textbox-container"
+ class="autocomplete-textbox-container urlbar-textbox-container"
+ flex="1" xbl:inherits="focused">
+ <children includes="image|deck|stack|box">
+ <xul:image class="autocomplete-icon" allowevents="true"/>
+ </children>
+ <xul:hbox anonid="textbox-input-box"
+ class="textbox-input-box urlbar-input-box"
+ flex="1" xbl:inherits="tooltiptext=inputtooltiptext">
+ <children/>
+ <html:input anonid="input"
+ class="autocomplete-textbox urlbar-input textbox-input uri-element-right-align"
+ allowevents="true"
+ xbl:inherits="tooltiptext=inputtooltiptext,value,type,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey"/>
+ </xul:hbox>
+ <children includes="hbox"/>
+ </xul:hbox>
+ <xul:dropmarker anonid="historydropmarker"
+ class="autocomplete-history-dropmarker urlbar-history-dropmarker"
+ allowevents="true"
+ xbl:inherits="open,enablehistory,parentfocused=focused"/>
+ <xul:popupset anonid="popupset"
+ class="autocomplete-result-popupset"/>
+ <children includes="toolbarbutton"/>
+ </content>
+
+ <implementation implements="nsIObserver, nsIDOMEventListener">
+ <constructor><![CDATA[
+ this._prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefService)
+ .getBranch("browser.urlbar.");
+
+ this._prefs.addObserver("", this, false);
+ this.clickSelectsAll = this._prefs.getBoolPref("clickSelectsAll");
+ this.doubleClickSelectsAll = this._prefs.getBoolPref("doubleClickSelectsAll");
+ this.completeDefaultIndex = this._prefs.getBoolPref("autoFill");
+ this.timeout = this._prefs.getIntPref("delay");
+ this._formattingEnabled = this._prefs.getBoolPref("formatting.enabled");
+ this._mayTrimURLs = this._prefs.getBoolPref("trimURLs");
+
+ this.inputField.controllers.insertControllerAt(0, this._copyCutController);
+ this.inputField.addEventListener("mousedown", this, false);
+ this.inputField.addEventListener("mousemove", this, false);
+ this.inputField.addEventListener("mouseout", this, false);
+ this.inputField.addEventListener("overflow", this, false);
+ this.inputField.addEventListener("underflow", this, false);
+
+ const kXULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ var textBox = document.getAnonymousElementByAttribute(this,
+ "anonid", "textbox-input-box");
+ var cxmenu = document.getAnonymousElementByAttribute(textBox,
+ "anonid", "input-box-contextmenu");
+ var pasteAndGo;
+ cxmenu.addEventListener("popupshowing", function() {
+ if (!pasteAndGo)
+ return;
+ var controller = document.commandDispatcher.getControllerForCommand("cmd_paste");
+ var enabled = controller.isCommandEnabled("cmd_paste");
+ if (enabled)
+ pasteAndGo.removeAttribute("disabled");
+ else
+ pasteAndGo.setAttribute("disabled", "true");
+ }, false);
+
+ var insertLocation = cxmenu.firstChild;
+ while (insertLocation.nextSibling &&
+ insertLocation.getAttribute("cmd") != "cmd_paste")
+ insertLocation = insertLocation.nextSibling;
+ if (insertLocation) {
+ pasteAndGo = document.createElement("menuitem");
+ let label = Services.strings.createBundle("chrome://browser/locale/browser.properties").
+ GetStringFromName("pasteAndGo.label");
+ pasteAndGo.setAttribute("label", label);
+ pasteAndGo.setAttribute("anonid", "paste-and-go");
+ pasteAndGo.setAttribute("oncommand",
+ "gURLBar.select(); goDoCommand('cmd_paste'); gURLBar.handleCommand();");
+ cxmenu.insertBefore(pasteAndGo, insertLocation.nextSibling);
+ }
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ this._prefs.removeObserver("", this);
+ this._prefs = null;
+ this.inputField.controllers.removeController(this._copyCutController);
+ this.inputField.removeEventListener("mousedown", this, false);
+ this.inputField.removeEventListener("mousemove", this, false);
+ this.inputField.removeEventListener("mouseout", this, false);
+ this.inputField.removeEventListener("overflow", this, false);
+ this.inputField.removeEventListener("underflow", this, false);
+ ]]></destructor>
+
+ <field name="_value"></field>
+
+ <!--
+ onBeforeValueGet is called by the base-binding's .value getter.
+ It can return an object with a "value" property, to override the
+ return value of the getter.
+ -->
+ <method name="onBeforeValueGet">
+ <body><![CDATA[
+ if (this.hasAttribute("actiontype"))
+ return {value: this._value};
+ return null;
+ ]]></body>
+ </method>
+
+ <!--
+ onBeforeValueSet is called by the base-binding's .value setter.
+ It should return the value that the setter should use.
+ -->
+ <method name="onBeforeValueSet">
+ <parameter name="aValue"/>
+ <body><![CDATA[
+ this._value = aValue;
+ var returnValue = aValue;
+ var action = this._parseActionUrl(aValue);
+ if (action) {
+ returnValue = action.param;
+ this.setAttribute("actiontype", action.type);
+ } else {
+ this.removeAttribute("actiontype");
+ }
+ return returnValue;
+ ]]></body>
+ </method>
+
+ <field name="_mayTrimURLs">true</field>
+ <method name="trimValue">
+ <parameter name="aURL"/>
+ <body><![CDATA[
+ // This method must not modify the given URL such that calling
+ // nsIURIFixup::createFixupURI with the result will produce a different URI.
+ return this._mayTrimURLs ? trimURL(aURL) : aURL;
+ ]]></body>
+ </method>
+
+ <field name="_formattingEnabled">true</field>
+ <method name="formatValue">
+ <body><![CDATA[
+ if (!this._formattingEnabled || this.focused)
+ return;
+
+ let controller = this.editor.selectionController;
+ let selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
+ selection.removeAllRanges();
+
+ let textNode = this.editor.rootElement.firstChild;
+ let value = textNode.textContent;
+
+ let protocol = value.match(/^[a-z\d.+\-]+:(?=[^\d])/);
+ if (protocol &&
+ ["http:", "https:", "ftp:"].indexOf(protocol[0]) == -1)
+ return;
+ let matchedURL = value.match(/^((?:[a-z]+:\/\/)?(?:[^\/]+@)?)(.+?)(?::\d+)?(?:\/|$)/);
+ if (!matchedURL)
+ return;
+
+ let [, preDomain, domain] = matchedURL;
+ let baseDomain = domain;
+ let subDomain = "";
+ // getBaseDomainFromHost doesn't recognize IPv6 literals in brackets as IPs (bug 667159)
+ if (domain[0] != "[") {
+ try {
+ baseDomain = Services.eTLD.getBaseDomainFromHost(domain);
+ if (!domain.endsWith(baseDomain)) {
+ // getBaseDomainFromHost converts its resultant to ACE.
+ let IDNService = Cc["@mozilla.org/network/idn-service;1"]
+ .getService(Ci.nsIIDNService);
+ baseDomain = IDNService.convertACEtoUTF8(baseDomain);
+ }
+ } catch (e) {}
+ }
+ if (baseDomain != domain) {
+ subDomain = domain.slice(0, -baseDomain.length);
+ }
+
+ let rangeLength = preDomain.length + subDomain.length;
+ if (rangeLength) {
+ let range = document.createRange();
+ range.setStart(textNode, 0);
+ range.setEnd(textNode, rangeLength);
+ selection.addRange(range);
+ }
+
+ let startRest = preDomain.length + domain.length;
+ if (startRest < value.length) {
+ let range = document.createRange();
+ range.setStart(textNode, startRest);
+ range.setEnd(textNode, value.length);
+ selection.addRange(range);
+ }
+ ]]></body>
+ </method>
+
+ <method name="_clearFormatting">
+ <body><![CDATA[
+ if (!this._formattingEnabled)
+ return;
+
+ let controller = this.editor.selectionController;
+ let selection = controller.getSelection(controller.SELECTION_URLSECONDARY);
+ selection.removeAllRanges();
+ ]]></body>
+ </method>
+
+ <method name="handleRevert">
+ <body><![CDATA[
+ var isScrolling = this.popupOpen;
+
+ gBrowser.userTypedValue = null;
+
+ // don't revert to last valid url unless page is NOT loading
+ // and user is NOT key-scrolling through autocomplete list
+ if (!XULBrowserWindow.isBusy && !isScrolling) {
+ URLBarSetURI();
+
+ // If the value isn't empty and the urlbar has focus, select the value.
+ if (this.value && this.hasAttribute("focused"))
+ this.select();
+ }
+
+ // tell widget to revert to last typed text only if the user
+ // was scrolling when they hit escape
+ return !isScrolling;
+ ]]></body>
+ </method>
+
+ <method name="handleCommand">
+ <parameter name="aTriggeringEvent"/>
+ <body><![CDATA[
+ if (aTriggeringEvent instanceof MouseEvent && aTriggeringEvent.button == 2)
+ return; // Do nothing for right clicks
+
+ var url = this.value;
+ var mayInheritPrincipal = false;
+ var postData = null;
+
+ var action = this._parseActionUrl(url);
+ if (action) {
+ url = action.param;
+ if (this.hasAttribute("actiontype")) {
+ if (action.type == "switchtab") {
+ this.handleRevert();
+ let prevTab = gBrowser.selectedTab;
+ if (switchToTabHavingURI(url) &&
+ isTabEmpty(prevTab))
+ gBrowser.removeTab(prevTab);
+ }
+ return;
+ }
+ }
+ else {
+ [url, postData, mayInheritPrincipal] = this._canonizeURL(aTriggeringEvent);
+ if (!url)
+ return;
+ }
+
+ this.value = url;
+ gBrowser.userTypedValue = url;
+ try {
+ addToUrlbarHistory(url);
+ } catch (ex) {
+ // Things may go wrong when adding url to session history,
+ // but don't let that interfere with the loading of the url.
+ Cu.reportError(ex);
+ }
+
+ function loadCurrent() {
+ let flags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ // Pass LOAD_FLAGS_DISALLOW_INHERIT_OWNER to prevent any loads from
+ // inheriting the currently loaded document's principal, unless this
+ // URL is marked as safe to inherit (e.g. came from a bookmark
+ // keyword).
+ if (!mayInheritPrincipal)
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER;
+ // If the value wasn't typed, we know that we decoded the value as
+ // UTF-8 (see losslessDecodeURI)
+ if (!this.valueIsTyped)
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_URI_IS_UTF8;
+ gBrowser.loadURIWithFlags(url, flags, null, null, postData);
+ }
+
+ // Focus the content area before triggering loads, since if the load
+ // occurs in a new tab, we want focus to be restored to the content
+ // area when the current tab is re-selected.
+ gBrowser.selectedBrowser.focus();
+
+ let isMouseEvent = aTriggeringEvent instanceof MouseEvent;
+ let altEnter = !isMouseEvent && aTriggeringEvent && aTriggeringEvent.altKey;
+
+ if (altEnter) {
+ // XXX This was added a long time ago, and I'm not sure why it is
+ // necessary. Alt+Enter's default action might cause a system beep,
+ // or something like that?
+ aTriggeringEvent.preventDefault();
+ aTriggeringEvent.stopPropagation();
+ }
+
+ // If the current tab is empty, ignore Alt+Enter (just reuse this tab)
+ altEnter = altEnter && !isTabEmpty(gBrowser.selectedTab);
+
+ if (isMouseEvent || altEnter) {
+ // Use the standard UI link behaviors for clicks or Alt+Enter
+ let where = "tab";
+ if (isMouseEvent)
+ where = whereToOpenLink(aTriggeringEvent, false, false);
+
+ if (where == "current") {
+ loadCurrent();
+ } else {
+ this.handleRevert();
+ let params = { allowThirdPartyFixup: true,
+ postData: postData,
+ initiatingDoc: document };
+ if (!this.valueIsTyped)
+ params.isUTF8 = true;
+ openUILinkIn(url, where, params);
+ }
+ } else {
+ loadCurrent();
+ }
+ ]]></body>
+ </method>
+
+ <method name="_canonizeURL">
+ <parameter name="aTriggeringEvent"/>
+ <body><![CDATA[
+ var url = this.value;
+ if (!url)
+ return ["", null, false];
+
+ // Only add the suffix when the URL bar value isn't already "URL-like",
+ // and only if we get a keyboard event, to match user expectations.
+ if (/^\s*[^.:\/\s]+(?:\/.*|\s*)$/i.test(url) &&
+ (aTriggeringEvent instanceof KeyEvent)) {
+#ifdef XP_MACOSX
+ let accel = aTriggeringEvent.metaKey;
+#else
+ let accel = aTriggeringEvent.ctrlKey;
+#endif
+ let shift = aTriggeringEvent.shiftKey;
+
+ let suffix = "";
+
+ switch (true) {
+ case (accel && shift):
+ suffix = ".org/";
+ break;
+ case (shift):
+ suffix = ".net/";
+ break;
+ case (accel):
+ try {
+ suffix = gPrefService.getCharPref("browser.fixup.alternate.suffix");
+ if (suffix.charAt(suffix.length - 1) != "/")
+ suffix += "/";
+ } catch(e) {
+ suffix = ".com/";
+ }
+ break;
+ }
+
+ if (suffix) {
+ // trim leading/trailing spaces (bug 233205)
+ url = url.trim();
+
+ // Tack www. and suffix on. If user has appended directories, insert
+ // suffix before them (bug 279035). Be careful not to get two slashes.
+
+ let firstSlash = url.indexOf("/");
+
+ if (firstSlash >= 0) {
+ url = url.substring(0, firstSlash) + suffix +
+ url.substring(firstSlash + 1);
+ } else {
+ url = url + suffix;
+ }
+
+ url = "http://www." + url;
+ }
+ }
+
+ var postData = {};
+ var mayInheritPrincipal = { value: false };
+ url = getShortcutOrURI(url, postData, mayInheritPrincipal);
+
+ return [url, postData.value, mayInheritPrincipal.value];
+ ]]></body>
+ </method>
+
+ <field name="_contentIsCropped">false</field>
+
+ <method name="_initURLTooltip">
+ <body><![CDATA[
+ if (this.focused || !this._contentIsCropped)
+ return;
+ this.inputField.setAttribute("tooltiptext", this.value);
+ ]]></body>
+ </method>
+
+ <method name="_hideURLTooltip">
+ <body><![CDATA[
+ this.inputField.removeAttribute("tooltiptext");
+ ]]></body>
+ </method>
+
+ <method name="onDragOver">
+ <parameter name="aEvent"/>
+ <body>
+ var types = aEvent.dataTransfer.types;
+ if (types.contains("application/x-moz-file") ||
+ types.contains("text/x-moz-url") ||
+ types.contains("text/uri-list") ||
+ types.contains("text/unicode"))
+ aEvent.preventDefault();
+ </body>
+ </method>
+
+ <method name="onDrop">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ let url = browserDragAndDrop.drop(aEvent, { })
+
+ // The URL bar automatically handles inputs with newline characters,
+ // so we can get away with treating text/x-moz-url flavours as text/plain.
+ if (url) {
+ aEvent.preventDefault();
+ this.value = url;
+ SetPageProxyState("invalid");
+ this.focus();
+ try {
+ urlSecurityCheck(url,
+ gBrowser.contentPrincipal,
+ Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
+ } catch (ex) {
+ return;
+ }
+ this.handleCommand();
+ }
+ ]]></body>
+ </method>
+
+ <method name="_getSelectedValueForClipboard">
+ <body><![CDATA[
+ // Grab the actual input field's value, not our value, which could include moz-action:
+ var inputVal = this.inputField.value;
+ var selectedVal = inputVal.substring(this.selectionStart, this.selectionEnd);
+
+ // If the selection doesn't start at the beginning or doesn't span the full domain or
+ // the URL bar is modified, nothing else to do here.
+ if (this.selectionStart > 0 || this.valueIsTyped)
+ return selectedVal;
+ // The selection doesn't span the full domain if it doesn't contain a slash and is
+ // followed by some character other than a slash.
+ if (!selectedVal.contains("/")) {
+ let remainder = inputVal.replace(selectedVal, "");
+ if (remainder != "" && remainder[0] != "/")
+ return selectedVal;
+ }
+
+ let uriFixup = Cc["@mozilla.org/docshell/urifixup;1"].getService(Ci.nsIURIFixup);
+
+ let uri;
+ try {
+ uri = uriFixup.createFixupURI(inputVal, Ci.nsIURIFixup.FIXUP_FLAG_USE_UTF8);
+ } catch (e) {}
+ if (!uri)
+ return selectedVal;
+
+ // Only copy exposable URIs
+ try {
+ uri = uriFixup.createExposableURI(uri);
+ } catch (ex) {}
+
+ // If the entire URL is selected, just use the actual loaded URI.
+ if (inputVal == selectedVal) {
+ // ... but only if isn't a javascript: or data: URI, since those
+ // are hard to read when encoded
+ if (!uri.schemeIs("javascript") && !uri.schemeIs("data")) {
+ // Parentheses are known to confuse third-party applications (bug 458565).
+ selectedVal = uri.spec.replace(/[()]/g, function (c) escape(c));
+ }
+
+ return selectedVal;
+ }
+
+ // Just the beginning of the URL is selected, check for a trimmed
+ // value
+ let spec = uri.spec;
+ let trimmedSpec = this.trimValue(spec);
+ if (spec != trimmedSpec) {
+ // Prepend the portion that trimValue removed from the beginning.
+ // This assumes trimValue will only truncate the URL at
+ // the beginning or end (or both).
+ let trimmedSegments = spec.split(trimmedSpec);
+ selectedVal = trimmedSegments[0] + selectedVal;
+ }
+
+ return selectedVal;
+ ]]></body>
+ </method>
+
+ <field name="_copyCutController"><![CDATA[
+ ({
+ urlbar: this,
+ doCommand: function(aCommand) {
+ var urlbar = this.urlbar;
+ var val = urlbar._getSelectedValueForClipboard();
+ if (!val)
+ return;
+
+ if (aCommand == "cmd_cut" && this.isCommandEnabled(aCommand)) {
+ let start = urlbar.selectionStart;
+ let end = urlbar.selectionEnd;
+ urlbar.inputField.value = urlbar.inputField.value.substring(0, start) +
+ urlbar.inputField.value.substring(end);
+ urlbar.selectionStart = urlbar.selectionEnd = start;
+ urlbar.removeAttribute("actiontype");
+ SetPageProxyState("invalid");
+ }
+
+ Cc["@mozilla.org/widget/clipboardhelper;1"]
+ .getService(Ci.nsIClipboardHelper)
+ .copyString(val, document);
+ },
+ supportsCommand: function(aCommand) {
+ switch (aCommand) {
+ case "cmd_copy":
+ case "cmd_cut":
+ return true;
+ }
+ return false;
+ },
+ isCommandEnabled: function(aCommand) {
+ return this.supportsCommand(aCommand) &&
+ (aCommand != "cmd_cut" || !this.urlbar.readOnly) &&
+ this.urlbar.selectionStart < this.urlbar.selectionEnd;
+ },
+ onEvent: function(aEventName) {}
+ })
+ ]]></field>
+
+ <method name="observe">
+ <parameter name="aSubject"/>
+ <parameter name="aTopic"/>
+ <parameter name="aData"/>
+ <body><![CDATA[
+ if (aTopic == "nsPref:changed") {
+ switch (aData) {
+ case "clickSelectsAll":
+ case "doubleClickSelectsAll":
+ this[aData] = this._prefs.getBoolPref(aData);
+ break;
+ case "autoFill":
+ this.completeDefaultIndex = this._prefs.getBoolPref(aData);
+ break;
+ case "delay":
+ this.timeout = this._prefs.getIntPref(aData);
+ break;
+ case "formatting.enabled":
+ this._formattingEnabled = this._prefs.getBoolPref(aData);
+ break;
+ case "trimURLs":
+ this._mayTrimURLs = this._prefs.getBoolPref(aData);
+ break;
+ }
+ }
+ ]]></body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ switch (aEvent.type) {
+ case "mousedown":
+ if (this.doubleClickSelectsAll &&
+ aEvent.button == 0 && aEvent.detail == 2) {
+ this.editor.selectAll();
+ aEvent.preventDefault();
+ }
+ break;
+ case "mousemove":
+ this._initURLTooltip();
+ break;
+ case "mouseout":
+ this._hideURLTooltip();
+ break;
+ case "overflow":
+ this._contentIsCropped = true;
+ break;
+ case "underflow":
+ this._contentIsCropped = false;
+ this._hideURLTooltip();
+ break;
+ }
+ ]]></body>
+ </method>
+
+ <property name="textValue"
+ onget="return this.value;">
+ <setter>
+ <![CDATA[
+ try {
+ val = losslessDecodeURI(makeURI(val));
+ } catch (ex) { }
+
+ // Trim popup selected values, but never trim results coming from
+ // autofill.
+ if (this.popup.selectedIndex == -1)
+ this._disableTrim = true;
+ this.value = val;
+ this._disableTrim = false;
+
+ // Completing a result should simulate the user typing the result, so
+ // fire an input event.
+ let evt = document.createEvent("UIEvents");
+ evt.initUIEvent("input", true, false, window, 0);
+ this.mIgnoreInput = true;
+ this.dispatchEvent(evt);
+ this.mIgnoreInput = false;
+
+ return this.value;
+ ]]>
+ </setter>
+ </property>
+
+ <method name="_parseActionUrl">
+ <parameter name="aUrl"/>
+ <body><![CDATA[
+ if (!aUrl.startsWith("moz-action:"))
+ return null;
+
+ // url is in the format moz-action:ACTION,PARAM
+ let [, action, param] = aUrl.match(/^moz-action:([^,]+),(.*)$/);
+ return {type: action, param: param};
+ ]]></body>
+ </method>
+
+ <field name="_numNoActionsKeys"><![CDATA[
+ 0
+ ]]></field>
+
+ <method name="_clearNoActions">
+ <parameter name="aURL"/>
+ <body><![CDATA[
+ this._numNoActionsKeys = 0;
+ this.popup.removeAttribute("noactions");
+ let action = this._parseActionUrl(this._value);
+ if (action)
+ this.setAttribute("actiontype", action.type);
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="keydown"><![CDATA[
+ if ((event.keyCode === KeyEvent.DOM_VK_ALT ||
+ event.keyCode === KeyEvent.DOM_VK_SHIFT) &&
+ this.popup.selectedIndex >= 0) {
+ this._numNoActionsKeys++;
+ this.popup.setAttribute("noactions", "true");
+ this.removeAttribute("actiontype");
+ }
+ ]]></handler>
+
+ <handler event="keyup"><![CDATA[
+ if ((event.keyCode === KeyEvent.DOM_VK_ALT ||
+ event.keyCode === KeyEvent.DOM_VK_SHIFT) &&
+ this._numNoActionsKeys > 0) {
+ this._numNoActionsKeys--;
+ if (this._numNoActionsKeys == 0)
+ this._clearNoActions();
+ }
+ ]]></handler>
+
+ <handler event="blur"><![CDATA[
+ this._clearNoActions();
+ this.formatValue();
+ ]]></handler>
+
+ <handler event="dragstart" phase="capturing"><![CDATA[
+ // Drag only if the gesture starts from the input field.
+ if (event.originalTarget != this.inputField)
+ return;
+
+ // Drag only if the entire value is selected and it's a valid URI.
+ var isFullSelection = this.selectionStart == 0 &&
+ this.selectionEnd == this.textLength;
+ if (!isFullSelection ||
+ this.getAttribute("pageproxystate") != "valid")
+ return;
+
+ var urlString = content.location.href;
+ var title = content.document.title || urlString;
+ var htmlString = "<a href=\"" + urlString + "\">" + urlString + "</a>";
+
+ var dt = event.dataTransfer;
+ dt.setData("text/x-moz-url", urlString + "\n" + title);
+ dt.setData("text/unicode", urlString);
+ dt.setData("text/html", htmlString);
+
+ dt.effectAllowed = "copyLink";
+ event.stopPropagation();
+ ]]></handler>
+
+ <handler event="focus" phase="capturing"><![CDATA[
+ this._hideURLTooltip();
+ this._clearFormatting();
+ ]]></handler>
+
+ <handler event="dragover" phase="capturing" action="this.onDragOver(event, this);"/>
+ <handler event="drop" phase="capturing" action="this.onDrop(event, this);"/>
+ <handler event="select"><![CDATA[
+ if (!Cc["@mozilla.org/widget/clipboard;1"]
+ .getService(Ci.nsIClipboard)
+ .supportsSelectionClipboard())
+ return;
+
+ var val = this._getSelectedValueForClipboard();
+ if (!val)
+ return;
+
+ Cc["@mozilla.org/widget/clipboardhelper;1"]
+ .getService(Ci.nsIClipboardHelper)
+ .copyStringToClipboard(val, Ci.nsIClipboard.kSelectionClipboard, document);
+ ]]></handler>
+ </handlers>
+
+ </binding>
+
+ <!-- Note: this binding is applied to the autocomplete popup used in the Search bar and in web page content -->
+ <binding id="browser-autocomplete-result-popup" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-result-popup">
+ <implementation>
+ <method name="openAutocompletePopup">
+ <parameter name="aInput"/>
+ <parameter name="aElement"/>
+ <body>
+ <![CDATA[
+ // initially the panel is hidden
+ // to avoid impacting startup / new window performance
+ aInput.popup.hidden = false;
+
+ // this method is defined on the base binding
+ this._openAutocompletePopup(aInput, aElement);
+ ]]></body>
+ </method>
+
+ <method name="onPopupClick">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ // Ignore all right-clicks
+ if (aEvent.button == 2)
+ return;
+
+ var controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController);
+
+ // Check for unmodified left-click, and use default behavior
+ if (aEvent.button == 0 && !aEvent.shiftKey && !aEvent.ctrlKey &&
+ !aEvent.altKey && !aEvent.metaKey) {
+ controller.handleEnter(true);
+ return;
+ }
+
+ // Check for middle-click or modified clicks on the search bar
+ var searchBar = BrowserSearch.searchBar;
+ if (searchBar && searchBar.textbox == this.mInput) {
+ // Handle search bar popup clicks
+ var search = controller.getValueAt(this.selectedIndex);
+
+ // close the autocomplete popup and revert the entered search term
+ this.closePopup();
+ controller.handleEscape();
+
+ // Fill in the search bar's value
+ searchBar.value = search;
+
+ // open the search results according to the clicking subtlety
+ var where = whereToOpenLink(aEvent, false, true);
+ searchBar.doSearch(search, where);
+ }
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="urlbar-rich-result-popup" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-rich-result-popup">
+ <implementation>
+ <field name="_maxResults">0</field>
+
+ <field name="_bundle" readonly="true">
+ Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService).
+ createBundle("chrome://browser/locale/places/places.properties");
+ </field>
+
+ <property name="maxResults" readonly="true">
+ <getter>
+ <![CDATA[
+ if (!this._maxResults) {
+ var prefService =
+ Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ this._maxResults = prefService.getIntPref("browser.urlbar.maxRichResults");
+ }
+ return this._maxResults;
+ ]]>
+ </getter>
+ </property>
+
+ <method name="openAutocompletePopup">
+ <parameter name="aInput"/>
+ <parameter name="aElement"/>
+ <body>
+ <![CDATA[
+ // initially the panel is hidden
+ // to avoid impacting startup / new window performance
+ aInput.popup.hidden = false;
+
+ // this method is defined on the base binding
+ this._openAutocompletePopup(aInput, aElement);
+ ]]></body>
+ </method>
+
+ <method name="onPopupClick">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ // Ignore right-clicks
+ if (aEvent.button == 2)
+ return;
+
+ var controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController);
+
+ // Check for unmodified left-click, and use default behavior
+ if (aEvent.button == 0 && !aEvent.shiftKey && !aEvent.ctrlKey &&
+ !aEvent.altKey && !aEvent.metaKey) {
+ controller.handleEnter(true);
+ return;
+ }
+
+ // Check for middle-click or modified clicks on the URL bar
+ if (gURLBar && this.mInput == gURLBar) {
+ var url = controller.getValueAt(this.selectedIndex);
+
+ // close the autocomplete popup and revert the entered address
+ this.closePopup();
+ controller.handleEscape();
+
+ // Check if this is meant to be an action
+ let action = this.mInput._parseActionUrl(url);
+ if (action) {
+ if (action.type == "switchtab")
+ url = action.param;
+ else
+ return;
+ }
+
+ // respect the usual clicking subtleties
+ openUILink(url, aEvent);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="createResultLabel">
+ <parameter name="aTitle"/>
+ <parameter name="aUrl"/>
+ <parameter name="aType"/>
+ <body>
+ <![CDATA[
+ var label = aTitle + " " + aUrl;
+ // convert aType (ex: "ac-result-type-<aType>") to text to be spoke aloud
+ // by screen readers. convert "tag" and "bookmark" to the localized versions,
+ // but don't do anything for "favicon" (the default)
+ if (aType != "favicon") {
+ label += " " + this._bundle.GetStringFromName(aType + "ResultLabel");
+ }
+ return label;
+ ]]>
+ </body>
+ </method>
+
+ </implementation>
+ </binding>
+
+ <binding id="addon-progress-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
+ <content align="start">
+ <xul:image class="popup-notification-icon"
+ xbl:inherits="popupid,src=icon"/>
+ <xul:vbox flex="1">
+ <xul:description class="popup-notification-description addon-progress-description"
+ xbl:inherits="xbl:text=label"/>
+ <xul:spacer flex="1"/>
+ <xul:hbox align="center">
+ <xul:progressmeter anonid="progressmeter" flex="1" mode="undetermined" class="popup-progress-meter"/>
+ <xul:button anonid="cancel" class="popup-progress-cancel" oncommand="document.getBindingParent(this).cancel()"/>
+ </xul:hbox>
+ <xul:label anonid="progresstext" class="popup-progress-label"/>
+ <xul:hbox class="popup-notification-button-container"
+ pack="end" align="center">
+ <xul:button anonid="button"
+ class="popup-notification-menubutton"
+ type="menu-button"
+ xbl:inherits="oncommand=buttoncommand,label=buttonlabel,accesskey=buttonaccesskey">
+ <xul:menupopup anonid="menupopup"
+ xbl:inherits="oncommand=menucommand">
+ <children/>
+ <xul:menuitem class="menuitem-iconic popup-notification-closeitem"
+ label="&closeNotificationItem.label;"
+ xbl:inherits="oncommand=closeitemcommand"/>
+ </xul:menupopup>
+ </xul:button>
+ </xul:hbox>
+ </xul:vbox>
+ <xul:vbox pack="start">
+ <xul:toolbarbutton anonid="closebutton"
+ class="messageCloseButton popup-notification-closebutton tabbable"
+ xbl:inherits="oncommand=closebuttoncommand"
+ tooltiptext="&closeNotification.tooltip;"/>
+ </xul:vbox>
+ </content>
+ <implementation>
+ <constructor><![CDATA[
+ this.cancelbtn.setAttribute("tooltiptext", gNavigatorBundle.getString("addonDownloadCancelTooltip"));
+
+ this.notification.options.installs.forEach(function(aInstall) {
+ aInstall.addListener(this);
+ }, this);
+
+ // Calling updateProgress can sometimes cause this notification to be
+ // removed in the middle of refreshing the notification panel which
+ // makes the panel get refreshed again. Just initialise to the
+ // undetermined state and then schedule a proper check at the next
+ // opportunity
+ this.setProgress(0, -1);
+ this._updateProgressTimeout = setTimeout(this.updateProgress.bind(this), 0);
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ this.destroy();
+ ]]></destructor>
+
+ <field name="progressmeter" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "progressmeter");
+ </field>
+ <field name="progresstext" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "progresstext");
+ </field>
+ <field name="cancelbtn" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "cancel");
+ </field>
+ <field name="DownloadUtils" readonly="true">
+ let utils = {};
+ Components.utils.import("resource://gre/modules/DownloadUtils.jsm", utils);
+ utils.DownloadUtils;
+ </field>
+
+ <method name="destroy">
+ <body><![CDATA[
+ this.notification.options.installs.forEach(function(aInstall) {
+ aInstall.removeListener(this);
+ }, this);
+ clearTimeout(this._updateProgressTimeout);
+ ]]></body>
+ </method>
+
+ <method name="setProgress">
+ <parameter name="aProgress"/>
+ <parameter name="aMaxProgress"/>
+ <body><![CDATA[
+ if (aMaxProgress == -1) {
+ this.progressmeter.mode = "undetermined";
+ }
+ else {
+ this.progressmeter.mode = "determined";
+ this.progressmeter.value = (aProgress * 100) / aMaxProgress;
+ }
+
+ let now = Date.now();
+
+ if (!this.notification.lastUpdate) {
+ this.notification.lastUpdate = now;
+ this.notification.lastProgress = aProgress;
+ return;
+ }
+
+ let delta = now - this.notification.lastUpdate;
+ if ((delta < 400) && (aProgress < aMaxProgress))
+ return;
+
+ delta /= 1000;
+
+ // This code is taken from nsDownloadManager.cpp
+ let speed = (aProgress - this.notification.lastProgress) / delta;
+ if (this.notification.speed)
+ speed = speed * 0.9 + this.notification.speed * 0.1;
+
+ this.notification.lastUpdate = now;
+ this.notification.lastProgress = aProgress;
+ this.notification.speed = speed;
+
+ let status = null;
+ [status, this.notification.last] = this.DownloadUtils.getDownloadStatus(aProgress, aMaxProgress, speed, this.notification.last);
+ this.progresstext.value = status;
+ ]]></body>
+ </method>
+
+ <method name="cancel">
+ <body><![CDATA[
+ // Cache these as cancelling the installs will remove this
+ // notification which will drop these references
+ let browser = this.notification.browser;
+ let contentWindow = this.notification.options.contentWindow;
+ let sourceURI = this.notification.options.sourceURI;
+
+ let installs = this.notification.options.installs;
+ installs.forEach(function(aInstall) {
+ try {
+ aInstall.cancel();
+ }
+ catch (e) {
+ // Cancel will throw if the download has already failed
+ }
+ }, this);
+
+ let anchorID = "addons-notification-icon";
+ let notificationID = "addon-install-cancelled";
+ let messageString = gNavigatorBundle.getString("addonDownloadCancelled");
+ messageString = PluralForm.get(installs.length, messageString);
+ let buttonText = gNavigatorBundle.getString("addonDownloadRestart");
+ buttonText = PluralForm.get(installs.length, buttonText);
+
+ let action = {
+ label: buttonText,
+ accessKey: gNavigatorBundle.getString("addonDownloadRestart.accessKey"),
+ callback: function() {
+ let weblistener = Cc["@mozilla.org/addons/web-install-listener;1"].
+ getService(Ci.amIWebInstallListener);
+ if (weblistener.onWebInstallRequested(contentWindow, sourceURI,
+ installs, installs.length)) {
+ installs.forEach(function(aInstall) {
+ aInstall.install();
+ });
+ }
+ }
+ };
+
+ PopupNotifications.show(browser, notificationID, messageString,
+ anchorID, action);
+ ]]></body>
+ </method>
+
+ <method name="updateProgress">
+ <body><![CDATA[
+ let downloadingCount = 0;
+ let progress = 0;
+ let maxProgress = 0;
+
+ this.notification.options.installs.forEach(function(aInstall) {
+ if (aInstall.maxProgress == -1)
+ maxProgress = -1;
+ progress += aInstall.progress;
+ if (maxProgress >= 0)
+ maxProgress += aInstall.maxProgress;
+ if (aInstall.state < AddonManager.STATE_DOWNLOADED)
+ downloadingCount++;
+ });
+
+ if (downloadingCount == 0) {
+ this.destroy();
+ PopupNotifications.remove(this.notification);
+ }
+ else {
+ this.setProgress(progress, maxProgress);
+ }
+ ]]></body>
+ </method>
+
+ <method name="onDownloadProgress">
+ <body><![CDATA[
+ this.updateProgress();
+ ]]></body>
+ </method>
+
+ <method name="onDownloadFailed">
+ <body><![CDATA[
+ this.updateProgress();
+ ]]></body>
+ </method>
+
+ <method name="onDownloadCancelled">
+ <body><![CDATA[
+ this.updateProgress();
+ ]]></body>
+ </method>
+
+ <method name="onDownloadEnded">
+ <body><![CDATA[
+ this.updateProgress();
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="identity-request-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
+ <content align="start">
+
+ <xul:image class="popup-notification-icon"
+ xbl:inherits="popupid,src=icon"/>
+
+ <xul:vbox flex="1">
+ <xul:vbox anonid="identity-deck">
+ <xul:vbox flex="1" pack="center"> <!-- 1: add an email -->
+ <html:input type="email" anonid="email" required="required" size="30"/>
+ <xul:description anonid="newidentitydesc"/>
+ <xul:spacer flex="1"/>
+ <xul:label class="text-link custom-link small-margin" anonid="chooseemail" hidden="true"/>
+ </xul:vbox>
+ <xul:vbox flex="1" hidden="true"> <!-- 2: choose an email -->
+ <xul:description anonid="chooseidentitydesc"/>
+ <xul:radiogroup anonid="identities">
+ </xul:radiogroup>
+ <xul:label class="text-link custom-link" anonid="newemail"/>
+ </xul:vbox>
+ </xul:vbox>
+ <xul:hbox class="popup-notification-button-container"
+ pack="end" align="center">
+ <xul:label anonid="tos" class="text-link" hidden="true"/>
+ <xul:label anonid="privacypolicy" class="text-link" hidden="true"/>
+ <xul:spacer flex="1"/>
+ <xul:image anonid="throbber" src="chrome://browser/skin/tabbrowser/loading.png"
+ style="visibility:hidden" width="16" height="16"/>
+ <xul:button anonid="button"
+ type="menu-button"
+ class="popup-notification-menubutton"
+ xbl:inherits="oncommand=buttoncommand,label=buttonlabel,accesskey=buttonaccesskey">
+ <xul:menupopup anonid="menupopup"
+ xbl:inherits="oncommand=menucommand">
+ <children/>
+ <xul:menuitem class="menuitem-iconic popup-notification-closeitem"
+ label="&closeNotificationItem.label;"
+ xbl:inherits="oncommand=closeitemcommand"/>
+ </xul:menupopup>
+ </xul:button>
+ </xul:hbox>
+ </xul:vbox>
+ <xul:vbox pack="start">
+ <xul:toolbarbutton anonid="closebutton"
+ class="messageCloseButton popup-notification-closebutton tabbable"
+ xbl:inherits="oncommand=closebuttoncommand"
+ tooltiptext="&closeNotification.tooltip;"/>
+ </xul:vbox>
+ </content>
+ <implementation>
+ <constructor><![CDATA[
+ // this.notification.options.identity is used to pass identity-specific info to the binding
+ let origin = this.identity.origin
+
+ // Populate text
+ this.emailField.placeholder = gNavigatorBundle.
+ getString("identity.newIdentity.email.placeholder");
+ this.newIdentityDesc.textContent = gNavigatorBundle.getFormattedString(
+ "identity.newIdentity.description", [origin]);
+ this.chooseIdentityDesc.textContent = gNavigatorBundle.getFormattedString(
+ "identity.chooseIdentity.description", [origin]);
+
+ // Show optional terms of service and privacy policy links
+ this._populateLink(this.identity.termsOfService, "tos", "identity.termsOfService");
+ this._populateLink(this.identity.privacyPolicy, "privacypolicy", "identity.privacyPolicy");
+
+ // Populate the list of identities to choose from. The origin is used to provide
+ // better suggestions.
+ let identities = this.SignInToWebsiteUX.getIdentitiesForSite(origin);
+
+ this._populateIdentityList(identities);
+
+ if (typeof this.step == "undefined") {
+ // First opening of this notification
+ // Show the add email pane (0) if there are no existing identities otherwise show the list
+ this.step = "result" in identities && identities.result.length ? 1 : 0;
+ } else {
+ // Already opened so restore previous state
+ if (this.identity.typedEmail) {
+ this.emailField.value = this.identity.typedEmail;
+ }
+ if (this.identity.selected) {
+ // If the user already chose an identity then update the UI to reflect that
+ this.onIdentitySelected();
+ }
+ // Update the view for the step
+ this.step = this.step;
+ }
+
+ // Fire notification with the chosen identity when main button is clicked
+ this.button.addEventListener("command", this._onButtonCommand.bind(this), true);
+
+ // Do the same if enter is pressed in the email field
+ this.emailField.addEventListener("keypress", function emailFieldKeypress(aEvent) {
+ if (aEvent.keyCode != aEvent.DOM_VK_RETURN)
+ return;
+ this._onButtonCommand(aEvent);
+ }.bind(this));
+
+ this.addEmailLink.value = gNavigatorBundle.getString("identity.newIdentity.label");
+ this.addEmailLink.accessKey = gNavigatorBundle.getString("identity.newIdentity.accessKey");
+ this.addEmailLink.addEventListener("click", function addEmailClick(evt) {
+ this.step = 0;
+ }.bind(this));
+
+ this.chooseEmailLink.value = gNavigatorBundle.getString("identity.chooseIdentity.label");
+ this.chooseEmailLink.hidden = !("result" in identities && identities.result.length);
+ this.chooseEmailLink.addEventListener("click", function chooseEmailClick(evt) {
+ this.step = 1;
+ }.bind(this));
+
+ this.emailField.addEventListener("blur", function onEmailBlur() {
+ this.identity.typedEmail = this.emailField.value;
+ }.bind(this));
+ ]]></constructor>
+
+ <field name="SignInToWebsiteUX" readonly="true">
+ let sitw = {};
+ Components.utils.import("resource:///modules/SignInToWebsite.jsm", sitw);
+ sitw.SignInToWebsiteUX;
+ </field>
+
+ <field name="newIdentityDesc" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "newidentitydesc");
+ </field>
+
+ <field name="chooseIdentityDesc" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "chooseidentitydesc");
+ </field>
+
+ <field name="identityList" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "identities");
+ </field>
+
+ <field name="emailField" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "email");
+ </field>
+
+ <field name="addEmailLink" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "newemail");
+ </field>
+
+ <field name="chooseEmailLink" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "chooseemail");
+ </field>
+
+ <field name="throbber" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "throbber");
+ </field>
+
+ <field name="identity" readonly="true">
+ this.notification.options.identity;
+ </field>
+
+ <!-- persist the state on the identity object so we can re-create the
+ notification state upon re-opening -->
+ <property name="step">
+ <getter>
+ return this.identity.step;
+ </getter>
+ <setter><![CDATA[
+ let deck = document.getAnonymousElementByAttribute(this, "anonid", "identity-deck");
+ for (let i = 0; i < deck.children.length; i++) {
+ deck.children[i].hidden = (val != i);
+ }
+ this.identity.step = val;
+ switch (val) {
+ case 0:
+ this.emailField.focus();
+ break;
+ }]]>
+ </setter>
+ </property>
+
+ <method name="onIdentitySelected">
+ <body><![CDATA[
+ this.throbber.style.visibility = "visible";
+ this.button.disabled = true;
+ this.emailField.value = this.identity.selected
+ this.emailField.disabled = true;
+ this.identityList.disabled = true;
+ ]]></body>
+ </method>
+
+ <method name="_populateLink">
+ <parameter name="aURL"/>
+ <parameter name="aLinkId"/>
+ <parameter name="aStringId"/>
+ <body><![CDATA[
+ if (aURL) {
+ // Show optional link to aURL
+ let link = document.getAnonymousElementByAttribute(this, "anonid", aLinkId);
+ link.value = gNavigatorBundle.getString(aStringId);
+ link.href = aURL;
+ link.hidden = false;
+ }
+ ]]></body>
+ </method>
+
+ <method name="_populateIdentityList">
+ <parameter name="aIdentities"/>
+ <body><![CDATA[
+ let foundLastUsed = false;
+ let lastUsed = this.identity.selected || aIdentities.lastUsed;
+ for (let id in aIdentities.result) {
+ let label = aIdentities.result[id];
+ let opt = this.identityList.appendItem(label);
+ if (label == lastUsed) {
+ this.identityList.selectedItem = opt;
+ foundLastUsed = true;
+ }
+ }
+ if (!foundLastUsed) {
+ this.identityList.selectedIndex = -1;
+ }
+ ]]></body>
+ </method>
+
+ <method name="_onButtonCommand">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ if (aEvent.target != aEvent.currentTarget)
+ return;
+ let chosenId;
+ switch (this.step) {
+ case 0:
+ aEvent.stopPropagation();
+ if (!this.emailField.validity.valid) {
+ this.emailField.focus();
+ return;
+ }
+ chosenId = this.emailField.value;
+ break;
+ case 1:
+ aEvent.stopPropagation();
+ let selectedItem = this.identityList.selectedItem
+ chosenId = selectedItem ? selectedItem.label : null;
+ if (!chosenId)
+ return;
+ break;
+ default:
+ throw new Error("Unknown case");
+ return;
+ }
+ // Actually select the identity
+ this.SignInToWebsiteUX.selectIdentity(this.identity.rpId, chosenId);
+ this.identity.selected = chosenId;
+ this.onIdentitySelected();
+ ]]></body>
+ </method>
+
+ </implementation>
+ </binding>
+
+ <binding id="plugin-popupnotification-center-item">
+ <content align="center">
+ <xul:vbox pack="center" anonid="itemBox" class="itemBox">
+ <xul:description anonid="center-item-label" class="center-item-label" />
+ <xul:hbox flex="1" pack="start" align="center" anonid="center-item-warning">
+ <xul:image anonid="center-item-warning-icon" class="center-item-warning-icon"/>
+ <xul:label anonid="center-item-warning-label"/>
+ <xul:label anonid="center-item-link" value="&checkForUpdates;" class="text-link"/>
+ </xul:hbox>
+ </xul:vbox>
+ <xul:vbox pack="center">
+ <xul:menulist class="center-item-menulist"
+ anonid="center-item-menulist">
+ <xul:menupopup>
+ <xul:menuitem anonid="allownow" value="allownow"
+ label="&pluginActivateNow.label;" />
+ <xul:menuitem anonid="allowalways" value="allowalways"
+ label="&pluginActivateAlways.label;" />
+ <xul:menuitem anonid="block" value="block"
+ label="&pluginBlockNow.label;" />
+ </xul:menupopup>
+ </xul:menulist>
+ </xul:vbox>
+ </content>
+ <resources>
+ <stylesheet src="chrome://global/skin/notification.css"/>
+ </resources>
+ <implementation>
+ <constructor><![CDATA[
+ document.getAnonymousElementByAttribute(this, "anonid", "center-item-label").value = this.action.pluginName;
+
+ let curState = "block";
+ if (this.action.fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE) {
+ if (this.action.pluginPermissionType == Ci.nsIPermissionManager.EXPIRE_SESSION) {
+ curState = "allownow";
+ }
+ else {
+ curState = "allowalways";
+ }
+ }
+ document.getAnonymousElementByAttribute(this, "anonid", "center-item-menulist").value = curState;
+
+ let warningString = "";
+ let linkString = "";
+
+ let link = document.getAnonymousElementByAttribute(this, "anonid", "center-item-link");
+
+ let url;
+ let linkHandler;
+
+ if (this.action.pluginTag.enabledState == Ci.nsIPluginTag.STATE_DISABLED) {
+ document.getAnonymousElementByAttribute(this, "anonid", "center-item-menulist").hidden = true;
+ warningString = gNavigatorBundle.getString("pluginActivateDisabled.label");
+ linkString = gNavigatorBundle.getString("pluginActivateDisabled.manage");
+ linkHandler = function(event) {
+ event.preventDefault();
+ gPluginHandler.managePlugins();
+ };
+ document.getAnonymousElementByAttribute(this, "anonid", "center-item-warning-icon").hidden = true;
+ }
+ else {
+ url = this.action.detailsLink;
+
+ switch (this.action.blocklistState) {
+ case Ci.nsIBlocklistService.STATE_NOT_BLOCKED:
+ document.getAnonymousElementByAttribute(this, "anonid", "center-item-warning").hidden = true;
+ break;
+ case Ci.nsIBlocklistService.STATE_BLOCKED:
+ document.getAnonymousElementByAttribute(this, "anonid", "center-item-menulist").hidden = true;
+ warningString = gNavigatorBundle.getString("pluginActivateBlocked.label");
+ linkString = gNavigatorBundle.getString("pluginActivate.learnMore");
+ break;
+ case Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE:
+ warningString = gNavigatorBundle.getString("pluginActivateOutdated.label");
+ linkString = gNavigatorBundle.getString("pluginActivate.updateLabel");
+ break;
+ case Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE:
+ warningString = gNavigatorBundle.getString("pluginActivateVulnerable.label");
+ linkString = gNavigatorBundle.getString("pluginActivate.riskLabel");
+ break;
+ }
+ }
+ document.getAnonymousElementByAttribute(this, "anonid", "center-item-warning-label").value = warningString;
+
+ if (url || linkHandler) {
+ link.value = linkString;
+ if (url) {
+ link.href = url;
+ }
+ if (linkHandler) {
+ link.addEventListener("click", linkHandler, false);
+ }
+ }
+ else {
+ link.hidden = true;
+ }
+ ]]></constructor>
+ <property name="value">
+ <getter>
+ return document.getAnonymousElementByAttribute(this, "anonid",
+ "center-item-menulist").value;
+ </getter>
+ <setter><!-- This should be used only in automated tests -->
+ document.getAnonymousElementByAttribute(this, "anonid",
+ "center-item-menulist").value = val;
+ </setter>
+ </property>
+ </implementation>
+ </binding>
+
+ <binding id="click-to-play-plugins-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
+ <content align="start" class="click-to-play-plugins-notification-content">
+ <xul:vbox flex="1" align="stretch" class="popup-notification-main-box"
+ xbl:inherits="popupid">
+ <xul:hbox class="click-to-play-plugins-notification-description-box" flex="1" align="start">
+ <xul:description class="click-to-play-plugins-outer-description" flex="1">
+ <html:span anonid="click-to-play-plugins-notification-description" />
+ <xul:label class="text-link click-to-play-plugins-notification-link" anonid="click-to-play-plugins-notification-link" />
+ </xul:description>
+ <xul:toolbarbutton anonid="closebutton"
+ class="messageCloseButton popup-notification-closebutton tabbable"
+ xbl:inherits="oncommand=closebuttoncommand"
+ tooltiptext="&closeNotification.tooltip;"/>
+ </xul:hbox>
+ <xul:grid anonid="click-to-play-plugins-notification-center-box"
+ class="click-to-play-plugins-notification-center-box">
+ <xul:columns>
+ <xul:column flex="1"/>
+ <xul:column/>
+ </xul:columns>
+ <xul:rows>
+ <children includes="row"/>
+ <xul:hbox pack="start" anonid="plugin-notification-showbox">
+ <xul:button label="&pluginNotification.showAll.label;"
+ accesskey="&pluginNotification.showAll.accesskey;"
+ class="plugin-notification-showbutton"
+ oncommand="document.getBindingParent(this)._setState(2)"/>
+ </xul:hbox>
+ </xul:rows>
+ </xul:grid>
+ <xul:hbox anonid="button-container"
+ class="click-to-play-plugins-notification-button-container"
+ pack="center" align="center">
+ <xul:button anonid="primarybutton"
+ class="click-to-play-popup-button primary-button"
+ oncommand="document.getBindingParent(this)._onButton(this)"
+ flex="1"/>
+ <xul:button anonid="secondarybutton"
+ class="click-to-play-popup-button"
+ oncommand="document.getBindingParent(this)._onButton(this);"
+ flex="1"/>
+ </xul:hbox>
+ <xul:box hidden="true">
+ <children/>
+ </xul:box>
+ </xul:vbox>
+ </content>
+ <resources>
+ <stylesheet src="chrome://global/skin/notification.css"/>
+ </resources>
+ <implementation>
+ <field name="_states">
+ ({SINGLE: 0, MULTI_COLLAPSED: 1, MULTI_EXPANDED: 2})
+ </field>
+ <field name="_primaryButton">
+ document.getAnonymousElementByAttribute(this, "anonid", "primarybutton");
+ </field>
+ <field name="_secondaryButton">
+ document.getAnonymousElementByAttribute(this, "anonid", "secondarybutton")
+ </field>
+ <field name="_buttonContainer">
+ document.getAnonymousElementByAttribute(this, "anonid", "button-container")
+ </field>
+ <field name="_brandShortName">
+ document.getElementById("bundle_brand").getString("brandShortName")
+ </field>
+ <field name="_items">[]</field>
+ <constructor><![CDATA[
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ for (let action of this.notification.options.centerActions) {
+ let item = document.createElementNS(XUL_NS, "row");
+ item.setAttribute("class", "plugin-popupnotification-centeritem");
+ item.action = action;
+ this.appendChild(item);
+ this._items.push(item);
+ }
+ switch (this.notification.options.centerActions.length) {
+ case 0:
+ PopupNotifications._dismiss();
+ break;
+ case 1:
+ this._setState(this._states.SINGLE);
+ break;
+ default:
+ if (this.notification.options.primaryPlugin) {
+ this._setState(this._states.MULTI_COLLAPSED);
+ } else {
+ this._setState(this._states.MULTI_EXPANDED);
+ }
+ }
+ ]]></constructor>
+ <method name="_setState">
+ <parameter name="state" />
+ <body><![CDATA[
+ var grid = document.getAnonymousElementByAttribute(this, "anonid", "click-to-play-plugins-notification-center-box");
+
+ if (this._states.SINGLE == state) {
+ grid.hidden = true;
+ this._setupSingleState();
+ return;
+ }
+
+ let host = gPluginHandler._getHostFromPrincipal(this.notification.browser.contentWindow.document.nodePrincipal);
+ this._setupDescription("pluginActivateMultiple.message", null, host);
+
+ var showBox = document.getAnonymousElementByAttribute(this, "anonid", "plugin-notification-showbox");
+
+ var dialogStrings = Services.strings.createBundle("chrome://global/locale/dialog.properties");
+ this._primaryButton.label = dialogStrings.GetStringFromName("button-accept");
+ this._primaryButton.setAttribute("default", "true");
+
+ this._secondaryButton.label = dialogStrings.GetStringFromName("button-cancel");
+ this._primaryButton.setAttribute("action", "_multiAccept");
+ this._secondaryButton.setAttribute("action", "_cancel");
+
+ grid.hidden = false;
+
+ if (this._states.MULTI_COLLAPSED == state) {
+ for (let child of this.childNodes) {
+ if (child.tagName != "row") {
+ continue;
+ }
+ child.hidden = this.notification.options.primaryPlugin !=
+ child.action.permissionString;
+ }
+ showBox.hidden = false;
+ }
+ else {
+ for (let child of this.childNodes) {
+ if (child.tagName != "row") {
+ continue;
+ }
+ child.hidden = false;
+ }
+ showBox.hidden = true;
+ }
+ this._setupLink(null);
+ ]]></body>
+ </method>
+ <method name="_setupSingleState">
+ <body><![CDATA[
+ var action = this.notification.options.centerActions[0];
+ var host = action.pluginPermissionHost;
+
+ let label, linkLabel, linkUrl, button1, button2;
+
+ if (action.fallbackType == Ci.nsIObjectLoadingContent.PLUGIN_ACTIVE) {
+ button1 = {
+ label: "pluginBlockNow.label",
+ accesskey: "pluginBlockNow.accesskey",
+ action: "_singleBlock"
+ };
+ button2 = {
+ label: "pluginContinue.label",
+ accesskey: "pluginContinue.accesskey",
+ action: "_singleContinue",
+ default: true
+ };
+ switch (action.blocklistState) {
+ case Ci.nsIBlocklistService.STATE_NOT_BLOCKED:
+ label = "pluginEnabled.message";
+ linkLabel = "pluginActivate.learnMore";
+ break;
+
+ case Ci.nsIBlocklistService.STATE_BLOCKED:
+ Cu.reportError(Error("Cannot happen!"));
+ break;
+
+ case Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE:
+ label = "pluginEnabledOutdated.message";
+ linkLabel = "pluginActivate.updateLabel";
+ break;
+
+ case Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE:
+ label = "pluginEnabledVulnerable.message";
+ linkLabel = "pluginActivate.riskLabel"
+ break;
+
+ default:
+ Cu.reportError(Error("Unexpected blocklist state"));
+ }
+ }
+ else if (action.pluginTag.enabledState == Ci.nsIPluginTag.STATE_DISABLED) {
+ let linkElement =
+ document.getAnonymousElementByAttribute(
+ this, "anonid", "click-to-play-plugins-notification-link");
+ linkElement.textContent = gNavigatorBundle.getString("pluginActivateDisabled.manage");
+ linkElement.setAttribute("onclick", "gPluginHandler.managePlugins()");
+
+ let descElement = document.getAnonymousElementByAttribute(this, "anonid", "click-to-play-plugins-notification-description");
+ descElement.textContent = gNavigatorBundle.getFormattedString(
+ "pluginActivateDisabled.message", [action.pluginName, this._brandShortName]) + " ";
+ this._buttonContainer.hidden = true;
+ return;
+ }
+ else if (action.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) {
+ let descElement = document.getAnonymousElementByAttribute(this, "anonid", "click-to-play-plugins-notification-description");
+ descElement.textContent = gNavigatorBundle.getFormattedString(
+ "pluginActivateBlocked.message", [action.pluginName, this._brandShortName]) + " ";
+ this._setupLink("pluginActivate.learnMore", action.detailsLink);
+ this._buttonContainer.hidden = true;
+ return;
+ }
+ else {
+ button1 = {
+ label: "pluginActivateNow.label",
+ accesskey: "pluginActivateNow.accesskey",
+ action: "_singleActivateNow"
+ };
+ button2 = {
+ label: "pluginActivateAlways.label",
+ accesskey: "pluginActivateAlways.accesskey",
+ action: "_singleActivateAlways"
+ };
+ switch (action.blocklistState) {
+ case Ci.nsIBlocklistService.STATE_NOT_BLOCKED:
+ label = "pluginActivateNew.message";
+ linkLabel = "pluginActivate.learnMore";
+ button2.default = true;
+ break;
+
+ case Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE:
+ label = "pluginActivateOutdated.message";
+ linkLabel = "pluginActivate.updateLabel";
+ button1.default = true;
+ break;
+
+ case Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE:
+ label = "pluginActivateVulnerable.message";
+ linkLabel = "pluginActivate.riskLabel"
+ button1.default = true;
+ break;
+
+ default:
+ Cu.reportError(Error("Unexpected blocklist state"));
+ }
+ }
+ this._setupDescription(label, action.pluginName, host);
+ this._setupLink(linkLabel, action.detailsLink);
+
+ this._primaryButton.label = gNavigatorBundle.getString(button1.label);
+ this._primaryButton.accesskey = gNavigatorBundle.getString(button1.accesskey);
+ this._primaryButton.setAttribute("action", button1.action);
+
+ this._secondaryButton.label = gNavigatorBundle.getString(button2.label);
+ this._secondaryButton.accesskey = gNavigatorBundle.getString(button2.accesskey);
+ this._secondaryButton.setAttribute("action", button2.action);
+ if (button1.default) {
+ this._primaryButton.setAttribute("default", "true");
+ }
+ else if (button2.default) {
+ this._secondaryButton.setAttribute("default", "true");
+ }
+ ]]></body>
+ </method>
+ <method name="_setupDescription">
+ <parameter name="baseString" />
+ <parameter name="pluginName" /> <!-- null for the multiple-plugin case -->
+ <parameter name="host" />
+ <body><![CDATA[
+ var bsn = this._brandShortName;
+ var span = document.getAnonymousElementByAttribute(this, "anonid", "click-to-play-plugins-notification-description");
+ while (span.lastChild) {
+ span.removeChild(span.lastChild);
+ }
+
+ var args = ["__host__", this._brandShortName];
+ if (pluginName) {
+ args.unshift(pluginName);
+ }
+ var bases = gNavigatorBundle.getFormattedString(baseString, args).
+ split("__host__", 2);
+
+ span.appendChild(document.createTextNode(bases[0]));
+ var hostSpan = document.createElementNS("http://www.w3.org/1999/xhtml", "em");
+ hostSpan.appendChild(document.createTextNode(host));
+ span.appendChild(hostSpan);
+ span.appendChild(document.createTextNode(bases[1] + " "));
+ ]]></body>
+ </method>
+ <method name="_setupLink">
+ <parameter name="linkString"/>
+ <parameter name="linkUrl" />
+ <body><![CDATA[
+ var link = document.getAnonymousElementByAttribute(this, "anonid", "click-to-play-plugins-notification-link");
+ if (!linkString || !linkUrl) {
+ link.hidden = true;
+ return;
+ }
+
+ link.hidden = false;
+ link.textContent = gNavigatorBundle.getString(linkString);
+ link.href = linkUrl;
+ ]]></body>
+ </method>
+ <method name="_onButton">
+ <parameter name="aButton" />
+ <body><![CDATA[
+ let methodName = aButton.getAttribute("action");
+ this[methodName]();
+ ]]></body>
+ </method>
+ <method name="_singleActivateNow">
+ <body><![CDATA[
+ gPluginHandler._updatePluginPermission(this.notification,
+ this.notification.options.centerActions[0],
+ "allownow");
+ this._cancel();
+ ]]></body>
+ </method>
+ <method name="_singleBlock">
+ <body><![CDATA[
+ gPluginHandler._updatePluginPermission(this.notification,
+ this.notification.options.centerActions[0],
+ "block");
+ this._cancel();
+ ]]></body>
+ </method>
+ <method name="_singleActivateAlways">
+ <body><![CDATA[
+ gPluginHandler._updatePluginPermission(this.notification,
+ this.notification.options.centerActions[0],
+ "allowalways");
+ this._cancel();
+ ]]></body>
+ </method>
+ <method name="_singleContinue">
+ <body><![CDATA[
+ gPluginHandler._updatePluginPermission(this.notification,
+ this.notification.options.centerActions[0],
+ "continue");
+ this._cancel();
+ ]]></body>
+ </method>
+ <method name="_multiAccept">
+ <body><![CDATA[
+ for (let item of this._items) {
+ let action = item.action;
+ if (action.pluginTag.enabledState == Ci.nsIPluginTag.STATE_DISABLED ||
+ action.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) {
+ continue;
+ }
+ gPluginHandler._updatePluginPermission(this.notification,
+ item.action, item.value);
+ }
+ this._cancel();
+ ]]></body>
+ </method>
+ <method name="_cancel">
+ <body><![CDATA[
+ PopupNotifications._dismiss();
+ ]]></body>
+ </method>
+ <method name="_accept">
+ <parameter name="aEvent" />
+ <body><![CDATA[
+ if (aEvent.defaultPrevented)
+ return;
+ aEvent.preventDefault();
+ if (this._primaryButton.getAttribute("default") == "true") {
+ this._primaryButton.click();
+ }
+ else if (this._secondaryButton.getAttribute("default") == "true") {
+ this._secondaryButton.click();
+ }
+ ]]></body>
+ </method>
+ </implementation>
+ <handlers>
+ <!-- The _accept method checks for .defaultPrevented so that if focus is in a button,
+ enter activates the button and not this default action -->
+ <handler event="keypress" keycode="VK_ENTER" group="system" action="this._accept(event);"/>
+ <handler event="keypress" keycode="VK_RETURN" group="system" action="this._accept(event);"/>
+ </handlers>
+ </binding>
+
+ <binding id="splitmenu">
+ <content>
+ <xul:hbox anonid="menuitem" flex="1"
+ class="splitmenu-menuitem"
+ xbl:inherits="iconic,label,disabled,onclick=oncommand,_moz-menuactive=active"/>
+ <xul:menu anonid="menu" class="splitmenu-menu"
+ xbl:inherits="disabled,_moz-menuactive=active"
+ oncommand="event.stopPropagation();">
+ <children includes="menupopup"/>
+ </xul:menu>
+ </content>
+
+ <implementation implements="nsIDOMEventListener">
+ <constructor><![CDATA[
+ this._parentMenupopup.addEventListener("DOMMenuItemActive", this, false);
+ this._parentMenupopup.addEventListener("popuphidden", this, false);
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ this._parentMenupopup.removeEventListener("DOMMenuItemActive", this, false);
+ this._parentMenupopup.removeEventListener("popuphidden", this, false);
+ ]]></destructor>
+
+ <field name="menuitem" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "menuitem");
+ </field>
+ <field name="menu" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "menu");
+ </field>
+
+ <field name="_menuDelay">600</field>
+
+ <field name="_parentMenupopup"><![CDATA[
+ this._getParentMenupopup(this);
+ ]]></field>
+
+ <method name="_getParentMenupopup">
+ <parameter name="aNode"/>
+ <body><![CDATA[
+ let node = aNode.parentNode;
+ while (node) {
+ if (node.localName == "menupopup")
+ break;
+ node = node.parentNode;
+ }
+ return node;
+ ]]></body>
+ </method>
+
+ <method name="handleEvent">
+ <parameter name="event"/>
+ <body><![CDATA[
+ switch (event.type) {
+ case "DOMMenuItemActive":
+ if (this.getAttribute("active") == "true" &&
+ event.target != this &&
+ this._getParentMenupopup(event.target) == this._parentMenupopup)
+ this.removeAttribute("active");
+ break;
+ case "popuphidden":
+ if (event.target == this._parentMenupopup)
+ this.removeAttribute("active");
+ break;
+ }
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="mouseover"><![CDATA[
+ if (this.getAttribute("active") != "true") {
+ this.setAttribute("active", "true");
+
+ let event = document.createEvent("Events");
+ event.initEvent("DOMMenuItemActive", true, false);
+ this.dispatchEvent(event);
+
+ if (this.getAttribute("disabled") != "true") {
+ let self = this;
+ setTimeout(function () {
+ if (self.getAttribute("active") == "true")
+ self.menu.open = true;
+ }, this._menuDelay);
+ }
+ }
+ ]]></handler>
+
+ <handler event="popupshowing"><![CDATA[
+ if (event.target == this.firstChild &&
+ this._parentMenupopup._currentPopup)
+ this._parentMenupopup._currentPopup.hidePopup();
+ ]]></handler>
+
+ <handler event="click" phase="capturing"><![CDATA[
+ if (this.getAttribute("disabled") == "true") {
+ // Prevent the command from being carried out
+ event.stopPropagation();
+ return;
+ }
+
+ let node = event.originalTarget;
+ while (true) {
+ if (node == this.menuitem)
+ break;
+ if (node == this)
+ return;
+ node = node.parentNode;
+ }
+
+ this._parentMenupopup.hidePopup();
+ ]]></handler>
+ </handlers>
+ </binding>
+
+ <binding id="menuitem-tooltip" extends="chrome://global/content/bindings/menu.xml#menuitem">
+ <implementation>
+ <constructor><![CDATA[
+ this.setAttribute("tooltiptext", this.getAttribute("acceltext"));
+ // TODO: Simplify this to this.setAttribute("acceltext", "") once bug
+ // 592424 is fixed
+ document.getAnonymousElementByAttribute(this, "anonid", "accel").firstChild.setAttribute("value", "");
+ ]]></constructor>
+ </implementation>
+ </binding>
+
+ <binding id="menuitem-iconic-tooltip" extends="chrome://global/content/bindings/menu.xml#menuitem-iconic">
+ <implementation>
+ <constructor><![CDATA[
+ this.setAttribute("tooltiptext", this.getAttribute("acceltext"));
+ // TODO: Simplify this to this.setAttribute("acceltext", "") once bug
+ // 592424 is fixed
+ document.getAnonymousElementByAttribute(this, "anonid", "accel").firstChild.setAttribute("value", "");
+ ]]></constructor>
+ </implementation>
+ </binding>
+
+ <binding id="promobox">
+ <content>
+ <xul:hbox class="panel-promo-box" align="start" flex="1">
+ <xul:hbox align="center" flex="1">
+ <xul:image class="panel-promo-icon"/>
+ <xul:description anonid="promo-message" class="panel-promo-message" flex="1">
+ <xul:description anonid="promo-link"
+ class="plain text-link inline-link"
+ onclick="document.getBindingParent(this).onLinkClick();"/>
+ </xul:description>
+ </xul:hbox>
+ <xul:toolbarbutton class="panel-promo-closebutton"
+ oncommand="document.getBindingParent(this).onCloseButtonCommand();"
+ tooltiptext="&closeNotification.tooltip;"/>
+ </xul:hbox>
+ </content>
+
+ <implementation implements="nsIDOMEventListener">
+ <constructor><![CDATA[
+ this._panel.addEventListener("popupshowing", this, false);
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ this._panel.removeEventListener("popupshowing", this, false);
+ ]]></destructor>
+
+ <field name="_panel" readonly="true"><![CDATA[
+ let node = this.parentNode;
+ while(node && node.localName != "panel") {
+ node = node.parentNode;
+ }
+ node;
+ ]]></field>
+ <field name="_promomessage" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "promo-message");
+ </field>
+ <field name="_promolink" readonly="true">
+ document.getAnonymousElementByAttribute(this, "anonid", "promo-link");
+ </field>
+ <field name="_brandBundle" readonly="true">
+ Services.strings.createBundle("chrome://branding/locale/brand.properties");
+ </field>
+ <property name="_viewsLeftMap">
+ <getter><![CDATA[
+ let viewsLeftMap = {};
+ try {
+ viewsLeftMap = JSON.parse(Services.prefs.getCharPref("browser.syncPromoViewsLeftMap"));
+ } catch (ex) {
+ // If the old preference exists, migrate it to the new one.
+ try {
+ let oldPref = Services.prefs.getIntPref("browser.syncPromoViewsLeft");
+ Services.prefs.clearUserPref("browser.syncPromoViewsLeft");
+ viewsLeftMap.bookmarks = oldPref;
+ viewsLeftMap.passwords = oldPref;
+ Services.prefs.setCharPref("browser.syncPromoViewsLeftMap",
+ JSON.stringify(viewsLeftMap));
+ } catch (ex2) {}
+ }
+ return viewsLeftMap;
+ ]]></getter>
+ </property>
+ <property name="_viewsLeft">
+ <getter><![CDATA[
+ let views = 5;
+ let map = this._viewsLeftMap;
+ if (this._notificationType in map) {
+ views = map[this._notificationType];
+ }
+ return views;
+ ]]></getter>
+ <setter><![CDATA[
+ let map = this._viewsLeftMap;
+ map[this._notificationType] = val;
+ Services.prefs.setCharPref("browser.syncPromoViewsLeftMap",
+ JSON.stringify(map));
+ return val;
+ ]]></setter>
+ </property>
+ <property name="_notificationType">
+ <getter><![CDATA[
+ // Use the popupid attribute to identify the notification type,
+ // otherwise just rely on the panel id for common arrowpanels.
+ let type = this._panel.firstChild.getAttribute("popupid") ||
+ this._panel.id;
+ if (type.startsWith("password-"))
+ return "passwords";
+ if (type == "editBookmarkPanel")
+ return "bookmarks";
+ if (type == "addon-install-complete") {
+ if (!Services.prefs.prefHasUserValue("services.sync.username"))
+ return "addons";
+ if (!Services.prefs.getBoolPref("services.sync.engine.addons"))
+ return "addons-sync-disabled";
+ }
+ return null;
+ ]]></getter>
+ </property>
+ <property name="_notificationMessage">
+ <getter><![CDATA[
+ return gNavigatorBundle.getFormattedString(
+ "syncPromoNotification." + this._notificationType + ".description",
+ [this._brandBundle.GetStringFromName("syncBrandShortName")]
+ );
+ ]]></getter>
+ </property>
+ <property name="_notificationLink">
+ <getter><![CDATA[
+ if (this._notificationType == "addons-sync-disabled") {
+ return "https://support.mozilla.org/kb/how-do-i-enable-add-sync";
+ }
+ return "https://services.mozilla.com/sync/";
+ ]]></getter>
+ </property>
+ <method name="onCloseButtonCommand">
+ <body><![CDATA[
+ this._viewsLeft = 0;
+ this.hidden = true;
+ ]]></body>
+ </method>
+ <method name="onLinkClick">
+ <body><![CDATA[
+ // Open a new selected tab and close the current panel.
+ gBrowser.loadOneTab(this._promolink.getAttribute("href"),
+ { inBackground: false });
+ this._panel.hidePopup();
+ ]]></body>
+ </method>
+ <method name="handleEvent">
+ <parameter name="event"/>
+ <body><![CDATA[
+ if (event.type != "popupshowing" || event.target != this._panel)
+ return;
+
+ // A previous notification may have unhidden this.
+ this.hidden = true;
+
+ // Only handle supported notification panels.
+ if (!this._notificationType) {
+ return;
+ }
+
+ let viewsLeft = this._viewsLeft;
+ if (viewsLeft) {
+ if (Services.prefs.prefHasUserValue("services.sync.username") &&
+ this._notificationType != "addons-sync-disabled") {
+ // If the user has already setup Sync, don't show the notification.
+ this._viewsLeft = 0;
+ // Be sure to hide the panel, in case it was visible and the user
+ // decided to setup Sync after noticing it.
+ viewsLeft = 0;
+ // The panel is still hidden, just bail out.
+ return;
+ }
+ else {
+ this._viewsLeft = viewsLeft - 1;
+ }
+
+ this._promolink.setAttribute("href", this._notificationLink);
+ this._promolink.value = gNavigatorBundle.getString("syncPromoNotification.learnMoreLinkText");
+
+ this.hidden = false;
+
+ // HACK: The description element doesn't wrap correctly in panels,
+ // thus set a width on it, based on the available space, before
+ // setting its textContent. Then set its height as well, to
+ // fix wrong height calculation on Linux (bug 659578).
+ this._panel.addEventListener("popupshown", function panelShown() {
+ this._panel.removeEventListener("popupshown", panelShown, true);
+ // Previous popupShown events may close the panel or change
+ // its contents, so ensure this is still valid.
+ if (this._panel.state != "open" || !this._notificationType)
+ return;
+ this._promomessage.width = this._promomessage.getBoundingClientRect().width;
+ this._promomessage.firstChild.textContent = this._notificationMessage;
+ this._promomessage.height = this._promomessage.getBoundingClientRect().height;
+ }.bind(this), true);
+ }
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+
+ <binding id="toolbarbutton-badged" display="xul:button"
+ extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
+ <content>
+ <children includes="observes|template|menupopup|panel|tooltip"/>
+ <xul:hbox class="toolbarbutton-badge-container" align="start" pack="end" flex="1">
+ <xul:hbox class="toolbarbutton-badge" xbl:inherits="badge"/>
+ <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label"/>
+ </xul:hbox>
+ <xul:label class="toolbarbutton-text" crop="right" flex="1"
+ xbl:inherits="value=label,accesskey,crop"/>
+ </content>
+ </binding>
+
+</bindings>
diff --git a/browser/base/content/utilityOverlay.js b/browser/base/content/utilityOverlay.js
new file mode 100644
index 000000000..2271d9793
--- /dev/null
+++ b/browser/base/content/utilityOverlay.js
@@ -0,0 +1,686 @@
+# -*- Mode: javascript; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// Services = object with smart getters for common XPCOM services
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+Components.utils.import("resource:///modules/RecentWindow.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "BROWSER_NEW_TAB_URL", function () {
+ const PREF = "browser.newtab.url";
+
+ function getNewTabPageURL() {
+ if (!Services.prefs.prefHasUserValue(PREF)) {
+ if (PrivateBrowsingUtils.isWindowPrivate(window) &&
+ !PrivateBrowsingUtils.permanentPrivateBrowsing)
+ return "about:privatebrowsing";
+ }
+ return Services.prefs.getCharPref(PREF) || "about:blank";
+ }
+
+ function update() {
+ BROWSER_NEW_TAB_URL = getNewTabPageURL();
+ }
+
+ Services.prefs.addObserver(PREF, update, false);
+
+ addEventListener("unload", function onUnload() {
+ removeEventListener("unload", onUnload);
+ Services.prefs.removeObserver(PREF, update);
+ });
+
+ return getNewTabPageURL();
+});
+
+var TAB_DROP_TYPE = "application/x-moz-tabbrowser-tab";
+
+var gBidiUI = false;
+
+/**
+ * Determines whether the given url is considered a special URL for new tabs.
+ */
+function isBlankPageURL(aURL) {
+ // Pale Moon: Only make "about:blank" or "about:newtab" be a "blank page" to fix focus issues.
+ // Original code: return aURL == "about:blank" || aURL == BROWSER_NEW_TAB_URL;
+ return aURL == "about:blank" || aURL == "about:newtab";
+}
+
+function getBrowserURL()
+{
+ return "chrome://browser/content/browser.xul";
+}
+
+function getTopWin(skipPopups) {
+ // If this is called in a browser window, use that window regardless of
+ // whether it's the frontmost window, since commands can be executed in
+ // background windows (bug 626148).
+ if (top.document.documentElement.getAttribute("windowtype") == "navigator:browser" &&
+ (!skipPopups || top.toolbar.visible))
+ return top;
+
+ let isPrivate = PrivateBrowsingUtils.isWindowPrivate(window);
+ return RecentWindow.getMostRecentBrowserWindow({private: isPrivate,
+ allowPopups: !skipPopups});
+}
+
+function openTopWin(url) {
+ /* deprecated */
+ openUILinkIn(url, "current");
+}
+
+function getBoolPref(prefname, def)
+{
+ try {
+ return Services.prefs.getBoolPref(prefname);
+ }
+ catch(er) {
+ return def;
+ }
+}
+
+/* openUILink handles clicks on UI elements that cause URLs to load.
+ *
+ * As the third argument, you may pass an object with the same properties as
+ * accepted by openUILinkIn, plus "ignoreButton" and "ignoreAlt".
+ */
+function openUILink(url, event, aIgnoreButton, aIgnoreAlt, aAllowThirdPartyFixup,
+ aPostData, aReferrerURI) {
+ let params;
+
+ if (aIgnoreButton && typeof aIgnoreButton == "object") {
+ params = aIgnoreButton;
+
+ // don't forward "ignoreButton" and "ignoreAlt" to openUILinkIn
+ aIgnoreButton = params.ignoreButton;
+ aIgnoreAlt = params.ignoreAlt;
+ delete params.ignoreButton;
+ delete params.ignoreAlt;
+ } else {
+ params = {
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ postData: aPostData,
+ referrerURI: aReferrerURI,
+ initiatingDoc: event ? event.target.ownerDocument : null
+ };
+ }
+
+ let where = whereToOpenLink(event, aIgnoreButton, aIgnoreAlt);
+ openUILinkIn(url, where, params);
+}
+
+
+/* whereToOpenLink() looks at an event to decide where to open a link.
+ *
+ * The event may be a mouse event (click, double-click, middle-click) or keypress event (enter).
+ *
+ * On Windows, the modifiers are:
+ * Ctrl new tab, selected
+ * Shift new window
+ * Ctrl+Shift new tab, in background
+ * Alt save
+ *
+ * Middle-clicking is the same as Ctrl+clicking (it opens a new tab).
+ *
+ * Exceptions:
+ * - Alt is ignored for menu items selected using the keyboard so you don't accidentally save stuff.
+ * (Currently, the Alt isn't sent here at all for menu items, but that will change in bug 126189.)
+ * - Alt is hard to use in context menus, because pressing Alt closes the menu.
+ * - Alt can't be used on the bookmarks toolbar because Alt is used for "treat this as something draggable".
+ * - The button is ignored for the middle-click-paste-URL feature, since it's always a middle-click.
+ */
+function whereToOpenLink( e, ignoreButton, ignoreAlt )
+{
+ // This method must treat a null event like a left click without modifier keys (i.e.
+ // e = { shiftKey:false, ctrlKey:false, metaKey:false, altKey:false, button:0 })
+ // for compatibility purposes.
+ if (!e)
+ return "current";
+
+ var shift = e.shiftKey;
+ var ctrl = e.ctrlKey;
+ var meta = e.metaKey;
+ var alt = e.altKey && !ignoreAlt;
+
+ // ignoreButton allows "middle-click paste" to use function without always opening in a new window.
+ var middle = !ignoreButton && e.button == 1;
+ var middleUsesTabs = getBoolPref("browser.tabs.opentabfor.middleclick", true);
+
+ // Don't do anything special with right-mouse clicks. They're probably clicks on context menu items.
+
+#ifdef XP_MACOSX
+ if (meta || (middle && middleUsesTabs))
+#else
+ if (ctrl || (middle && middleUsesTabs))
+#endif
+ return shift ? "tabshifted" : "tab";
+
+ if (alt && getBoolPref("browser.altClickSave", false))
+ return "save";
+
+ if (shift || (middle && !middleUsesTabs))
+ return "window";
+
+ return "current";
+}
+
+/* openUILinkIn opens a URL in a place specified by the parameter |where|.
+ *
+ * |where| can be:
+ * "current" current tab (if there aren't any browser windows, then in a new window instead)
+ * "tab" new tab (if there aren't any browser windows, then in a new window instead)
+ * "tabshifted" same as "tab" but in background if default is to select new tabs, and vice versa
+ * "window" new window
+ * "save" save to disk (with no filename hint!)
+ *
+ * aAllowThirdPartyFixup controls whether third party services such as Google's
+ * I Feel Lucky are allowed to interpret this URL. This parameter may be
+ * undefined, which is treated as false.
+ *
+ * Instead of aAllowThirdPartyFixup, you may also pass an object with any of
+ * these properties:
+ * allowThirdPartyFixup (boolean)
+ * postData (nsIInputStream)
+ * referrerURI (nsIURI)
+ * relatedToCurrent (boolean)
+ */
+function openUILinkIn(url, where, aAllowThirdPartyFixup, aPostData, aReferrerURI) {
+ var params;
+
+ if (arguments.length == 3 && typeof arguments[2] == "object") {
+ params = aAllowThirdPartyFixup;
+ } else {
+ params = {
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ postData: aPostData,
+ referrerURI: aReferrerURI
+ };
+ }
+
+ params.fromChrome = true;
+
+ openLinkIn(url, where, params);
+}
+
+function openLinkIn(url, where, params) {
+ if (!where || !url)
+ return;
+
+ var aFromChrome = params.fromChrome;
+ var aAllowThirdPartyFixup = params.allowThirdPartyFixup;
+ var aPostData = params.postData;
+ var aCharset = params.charset;
+ var aReferrerURI = params.referrerURI;
+ var aRelatedToCurrent = params.relatedToCurrent;
+ var aInBackground = params.inBackground;
+ var aDisallowInheritPrincipal = params.disallowInheritPrincipal;
+ // Currently, this parameter works only for where=="tab" or "current"
+ var aIsUTF8 = params.isUTF8;
+ var aInitiatingDoc = params.initiatingDoc;
+ var aIsPrivate = params.private;
+
+ if (where == "save") {
+ if (!aInitiatingDoc) {
+ Components.utils.reportError("openUILink/openLinkIn was called with " +
+ "where == 'save' but without initiatingDoc. See bug 814264.");
+ return;
+ }
+ saveURL(url, null, null, true, null, aReferrerURI, aInitiatingDoc);
+ return;
+ }
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+
+ var w = getTopWin();
+ if ((where == "tab" || where == "tabshifted") &&
+ w && !w.toolbar.visible) {
+ w = getTopWin(true);
+ aRelatedToCurrent = false;
+ }
+
+ if (!w || where == "window") {
+ var sa = Cc["@mozilla.org/supports-array;1"].
+ createInstance(Ci.nsISupportsArray);
+
+ var wuri = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ wuri.data = url;
+
+ let charset = null;
+ if (aCharset) {
+ charset = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(Ci.nsISupportsString);
+ charset.data = "charset=" + aCharset;
+ }
+
+ var allowThirdPartyFixupSupports = Cc["@mozilla.org/supports-PRBool;1"].
+ createInstance(Ci.nsISupportsPRBool);
+ allowThirdPartyFixupSupports.data = aAllowThirdPartyFixup;
+
+ sa.AppendElement(wuri);
+ sa.AppendElement(charset);
+ sa.AppendElement(aReferrerURI);
+ sa.AppendElement(aPostData);
+ sa.AppendElement(allowThirdPartyFixupSupports);
+
+ let features = "chrome,dialog=no,all";
+ if (aIsPrivate) {
+ features += ",private";
+ }
+
+ Services.ww.openWindow(w || window, getBrowserURL(), null, features, sa);
+ return;
+ }
+
+ let loadInBackground = where == "current" ? false : aInBackground;
+ if (loadInBackground == null) {
+ loadInBackground = aFromChrome ?
+ false :
+ getBoolPref("browser.tabs.loadInBackground");
+ }
+
+ if (where == "current" && w.gBrowser.selectedTab.pinned) {
+ try {
+ let uriObj = Services.io.newURI(url, null, null);
+ if (!uriObj.schemeIs("javascript") &&
+ w.gBrowser.currentURI.host != uriObj.host) {
+ where = "tab";
+ loadInBackground = false;
+ }
+ } catch (err) {
+ where = "tab";
+ loadInBackground = false;
+ }
+ }
+
+ // Raise the target window before loading the URI, since loading it may
+ // result in a new frontmost window (e.g. "javascript:window.open('');").
+ w.focus();
+
+ switch (where) {
+ case "current":
+ let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
+ if (aAllowThirdPartyFixup)
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ if (aDisallowInheritPrincipal)
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_OWNER;
+ if (aIsUTF8)
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_URI_IS_UTF8;
+ w.gBrowser.loadURIWithFlags(url, flags, aReferrerURI, null, aPostData);
+ break;
+ case "tabshifted":
+ loadInBackground = !loadInBackground;
+ // fall through
+ case "tab":
+ let browser = w.gBrowser;
+ browser.loadOneTab(url, {
+ referrerURI: aReferrerURI,
+ charset: aCharset,
+ postData: aPostData,
+ inBackground: loadInBackground,
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ relatedToCurrent: aRelatedToCurrent,
+ isUTF8: aIsUTF8});
+ break;
+ }
+
+ w.gBrowser.selectedBrowser.focus();
+
+ if (!loadInBackground && w.isBlankPageURL(url))
+ w.focusAndSelectUrlBar();
+}
+
+// Used as an onclick handler for UI elements with link-like behavior.
+// e.g. onclick="checkForMiddleClick(this, event);"
+function checkForMiddleClick(node, event) {
+ // We should be using the disabled property here instead of the attribute,
+ // but some elements that this function is used with don't support it (e.g.
+ // menuitem).
+ if (node.getAttribute("disabled") == "true")
+ return; // Do nothing
+
+ if (event.button == 1) {
+ /* Execute the node's oncommand or command.
+ *
+ * XXX: we should use node.oncommand(event) once bug 246720 is fixed.
+ */
+ var target = node.hasAttribute("oncommand") ? node :
+ node.ownerDocument.getElementById(node.getAttribute("command"));
+ var fn = new Function("event", target.getAttribute("oncommand"));
+ fn.call(target, event);
+
+ // If the middle-click was on part of a menu, close the menu.
+ // (Menus close automatically with left-click but not with middle-click.)
+ closeMenus(event.target);
+ }
+}
+
+// Closes all popups that are ancestors of the node.
+function closeMenus(node)
+{
+ if ("tagName" in node) {
+ if (node.namespaceURI == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ && (node.tagName == "menupopup" || node.tagName == "popup"))
+ node.hidePopup();
+
+ closeMenus(node.parentNode);
+ }
+}
+
+// Gather all descendent text under given document node.
+function gatherTextUnder ( root )
+{
+ var text = "";
+ var node = root.firstChild;
+ var depth = 1;
+ while ( node && depth > 0 ) {
+ // See if this node is text.
+ if ( node.nodeType == Node.TEXT_NODE ) {
+ // Add this text to our collection.
+ text += " " + node.data;
+ } else if ( node instanceof HTMLImageElement) {
+ // If it has an alt= attribute, use that.
+ var altText = node.getAttribute( "alt" );
+ if ( altText && altText != "" ) {
+ text = altText;
+ break;
+ }
+ }
+ // Find next node to test.
+ // First, see if this node has children.
+ if ( node.hasChildNodes() ) {
+ // Go to first child.
+ node = node.firstChild;
+ depth++;
+ } else {
+ // No children, try next sibling (or parent next sibling).
+ while ( depth > 0 && !node.nextSibling ) {
+ node = node.parentNode;
+ depth--;
+ }
+ if ( node.nextSibling ) {
+ node = node.nextSibling;
+ }
+ }
+ }
+ // Strip leading whitespace.
+ text = text.replace( /^\s+/, "" );
+ // Strip trailing whitespace.
+ text = text.replace( /\s+$/, "" );
+ // Compress remaining whitespace.
+ text = text.replace( /\s+/g, " " );
+ return text;
+}
+
+function getShellService()
+{
+ var shell = null;
+ try {
+ shell = Components.classes["@mozilla.org/browser/shell-service;1"]
+ .getService(Components.interfaces.nsIShellService);
+ } catch (e) {
+ }
+ return shell;
+}
+
+function isBidiEnabled() {
+ // first check the pref.
+ if (getBoolPref("bidi.browser.ui", false))
+ return true;
+
+ // if the pref isn't set, check for an RTL locale and force the pref to true
+ // if we find one.
+ var rv = false;
+
+ try {
+ var localeService = Components.classes["@mozilla.org/intl/nslocaleservice;1"]
+ .getService(Components.interfaces.nsILocaleService);
+ var systemLocale = localeService.getSystemLocale().getCategory("NSILOCALE_CTYPE").substr(0,3);
+
+ switch (systemLocale) {
+ case "ar-":
+ case "he-":
+ case "fa-":
+ case "ur-":
+ case "syr":
+ rv = true;
+ Services.prefs.setBoolPref("bidi.browser.ui", true);
+ }
+ } catch (e) {}
+
+ return rv;
+}
+
+function openAboutDialog() {
+ var enumerator = Services.wm.getEnumerator("Browser:About");
+ while (enumerator.hasMoreElements()) {
+ // Only open one about window (Bug 599573)
+ let win = enumerator.getNext();
+ win.focus();
+ return;
+ }
+
+#ifdef XP_WIN
+ var features = "chrome,centerscreen,dependent";
+#elifdef XP_MACOSX
+ var features = "chrome,resizable=no,minimizable=no";
+#else
+ var features = "chrome,centerscreen,dependent,dialog=no";
+#endif
+ window.openDialog("chrome://browser/content/aboutDialog.xul", "", features);
+}
+
+function openPreferences(paneID, extraArgs)
+{
+ if (Services.prefs.getBoolPref("browser.preferences.inContent")) {
+ openUILinkIn("about:preferences", "tab");
+ } else {
+ var instantApply = getBoolPref("browser.preferences.instantApply", false);
+ var features = "chrome,titlebar,toolbar,centerscreen" + (instantApply ? ",dialog=no" : ",modal");
+
+ var win = Services.wm.getMostRecentWindow("Browser:Preferences");
+ if (win) {
+ win.focus();
+ if (paneID) {
+ var pane = win.document.getElementById(paneID);
+ win.document.documentElement.showPane(pane);
+ }
+
+ if (extraArgs && extraArgs["advancedTab"]) {
+ var advancedPaneTabs = win.document.getElementById("advancedPrefs");
+ advancedPaneTabs.selectedTab = win.document.getElementById(extraArgs["advancedTab"]);
+ }
+
+ return;
+ }
+
+ openDialog("chrome://browser/content/preferences/preferences.xul",
+ "Preferences", features, paneID, extraArgs);
+ }
+}
+
+function openAdvancedPreferences(tabID)
+{
+ openPreferences("paneAdvanced", { "advancedTab" : tabID });
+}
+
+/**
+ * Opens the troubleshooting information (about:support) page for this version
+ * of the application.
+ */
+function openTroubleshootingPage()
+{
+ openUILinkIn("about:support", "tab");
+}
+
+#ifdef MOZ_SERVICES_HEALTHREPORT
+/**
+ * Opens the troubleshooting information (about:support) page for this version
+ * of the application.
+ */
+function openHealthReport()
+{
+ openUILinkIn("about:healthreport", "tab");
+}
+#endif
+
+/**
+ * Opens the feedback page for this version of the application.
+ */
+function openFeedbackPage()
+{
+ openUILinkIn("http://forum.palemoon.org", "tab");
+}
+
+function buildHelpMenu()
+{
+ // Enable/disable the "Report Web Forgery" menu item.
+ if (typeof gSafeBrowsing != "undefined")
+ gSafeBrowsing.setReportPhishingMenu();
+}
+
+function isElementVisible(aElement)
+{
+ if (!aElement)
+ return false;
+
+ // If aElement or a direct or indirect parent is hidden or collapsed,
+ // height, width or both will be 0.
+ var bo = aElement.boxObject;
+ return (bo.height > 0 && bo.width > 0);
+}
+
+function makeURLAbsolute(aBase, aUrl)
+{
+ // Note: makeURI() will throw if aUri is not a valid URI
+ return makeURI(aUrl, null, makeURI(aBase)).spec;
+}
+
+
+/**
+ * openNewTabWith: opens a new tab with the given URL.
+ *
+ * @param aURL
+ * The URL to open (as a string).
+ * @param aDocument
+ * The document from which the URL came, or null. This is used to set the
+ * referrer header and to do a security check of whether the document is
+ * allowed to reference the URL. If null, there will be no referrer
+ * header and no security check.
+ * @param aPostData
+ * Form POST data, or null.
+ * @param aEvent
+ * The triggering event (for the purpose of determining whether to open
+ * in the background), or null.
+ * @param aAllowThirdPartyFixup
+ * If true, then we allow the URL text to be sent to third party services
+ * (e.g., Google's I Feel Lucky) for interpretation. This parameter may
+ * be undefined in which case it is treated as false.
+ * @param [optional] aReferrer
+ * If aDocument is null, then this will be used as the referrer.
+ * There will be no security check.
+ */
+function openNewTabWith(aURL, aDocument, aPostData, aEvent,
+ aAllowThirdPartyFixup, aReferrer) {
+ if (aDocument)
+ urlSecurityCheck(aURL, aDocument.nodePrincipal);
+
+ // As in openNewWindowWith(), we want to pass the charset of the
+ // current document over to a new tab.
+ var originCharset = aDocument && aDocument.characterSet;
+ if (!originCharset &&
+ document.documentElement.getAttribute("windowtype") == "navigator:browser")
+ originCharset = window.content.document.characterSet;
+
+ openLinkIn(aURL, aEvent && aEvent.shiftKey ? "tabshifted" : "tab",
+ { charset: originCharset,
+ postData: aPostData,
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ referrerURI: aDocument ? aDocument.documentURIObject : aReferrer });
+}
+
+function openNewWindowWith(aURL, aDocument, aPostData, aAllowThirdPartyFixup, aReferrer) {
+ if (aDocument)
+ urlSecurityCheck(aURL, aDocument.nodePrincipal);
+
+ // if and only if the current window is a browser window and it has a
+ // document with a character set, then extract the current charset menu
+ // setting from the current document and use it to initialize the new browser
+ // window...
+ var originCharset = aDocument && aDocument.characterSet;
+ if (!originCharset &&
+ document.documentElement.getAttribute("windowtype") == "navigator:browser")
+ originCharset = window.content.document.characterSet;
+
+ openLinkIn(aURL, "window",
+ { charset: originCharset,
+ postData: aPostData,
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ referrerURI: aDocument ? aDocument.documentURIObject : aReferrer });
+}
+
+/**
+ * isValidFeed: checks whether the given data represents a valid feed.
+ *
+ * @param aLink
+ * An object representing a feed with title, href and type.
+ * @param aPrincipal
+ * The principal of the document, used for security check.
+ * @param aIsFeed
+ * Whether this is already a known feed or not, if true only a security
+ * check will be performed.
+ */
+function isValidFeed(aLink, aPrincipal, aIsFeed)
+{
+ if (!aLink || !aPrincipal)
+ return false;
+
+ var type = aLink.type.toLowerCase().replace(/^\s+|\s*(?:;.*)?$/g, "");
+ if (!aIsFeed) {
+ aIsFeed = (type == "application/rss+xml" ||
+ type == "application/atom+xml");
+ }
+
+ if (aIsFeed) {
+ try {
+ urlSecurityCheck(aLink.href, aPrincipal,
+ Components.interfaces.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
+ return type || "application/rss+xml";
+ }
+ catch(ex) {
+ }
+ }
+
+ return null;
+}
+
+// aCalledFromModal is optional
+function openHelpLink(aHelpTopic, aCalledFromModal) {
+ var url = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
+ .getService(Components.interfaces.nsIURLFormatter)
+ .formatURLPref("app.support.baseURL");
+ url += aHelpTopic;
+
+ var where = aCalledFromModal ? "window" : "tab";
+ openUILinkIn(url, where);
+}
+
+function openPrefsHelp() {
+ // non-instant apply prefwindows are usually modal, so we can't open in the topmost window,
+ // since its probably behind the window.
+ var instantApply = getBoolPref("browser.preferences.instantApply");
+
+ var helpTopic = document.getElementsByTagName("prefwindow")[0].currentPane.helpTopic;
+ openHelpLink(helpTopic, !instantApply);
+}
+
+function trimURL(aURL) {
+ // This function must not modify the given URL such that calling
+ // nsIURIFixup::createFixupURI with the result will produce a different URI.
+ return aURL /* remove single trailing slash for http/https/ftp URLs */
+ .replace(/^((?:http|https|ftp):\/\/[^/]+)\/$/, "$1")
+ /* remove http:// unless the host starts with "ftp\d*\." or contains "@" */
+ .replace(/^http:\/\/((?!ftp\d*\.)[^\/@]+(?:\/|$))/, "$1");
+}
diff --git a/browser/base/content/viewSourceOverlay.xul b/browser/base/content/viewSourceOverlay.xul
new file mode 100644
index 000000000..8b40ddfd2
--- /dev/null
+++ b/browser/base/content/viewSourceOverlay.xul
@@ -0,0 +1,26 @@
+<?xml version="1.0"?>
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xul-overlay href="chrome://browser/content/baseMenuOverlay.xul"?>
+
+<overlay id="viewSourceOverlay"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<window id="viewSource">
+ <commandset id="baseMenuCommandSet"/>
+ <keyset id="baseMenuKeyset"/>
+ <stringbundleset id="stringbundleset"/>
+</window>
+
+<menubar id="viewSource-main-menubar">
+#ifdef XP_MACOSX
+ <menu id="windowMenu"/>
+ <menupopup id="menu_ToolsPopup"/>
+#endif
+ <menu id="helpMenu"/>
+</menubar>
+
+</overlay>
diff --git a/browser/base/content/web-panels.js b/browser/base/content/web-panels.js
new file mode 100644
index 000000000..b784d0a1e
--- /dev/null
+++ b/browser/base/content/web-panels.js
@@ -0,0 +1,103 @@
+/* -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const NS_ERROR_MODULE_NETWORK = 2152398848;
+const NS_NET_STATUS_READ_FROM = NS_ERROR_MODULE_NETWORK + 8;
+const NS_NET_STATUS_WROTE_TO = NS_ERROR_MODULE_NETWORK + 9;
+
+function getPanelBrowser()
+{
+ return document.getElementById("web-panels-browser");
+}
+
+var panelProgressListener = {
+ onProgressChange : function (aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress) {
+ },
+
+ onStateChange : function(aWebProgress, aRequest, aStateFlags, aStatus)
+ {
+ if (!aRequest)
+ return;
+
+ //ignore local/resource:/chrome: files
+ if (aStatus == NS_NET_STATUS_READ_FROM || aStatus == NS_NET_STATUS_WROTE_TO)
+ return;
+
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_START &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
+ window.parent.document.getElementById('sidebar-throbber').setAttribute("loading", "true");
+ }
+ else if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
+ window.parent.document.getElementById('sidebar-throbber').removeAttribute("loading");
+ }
+ }
+ ,
+
+ onLocationChange : function(aWebProgress, aRequest, aLocation, aFlags) {
+ UpdateBackForwardCommands(getPanelBrowser().webNavigation);
+ },
+
+ onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage) {
+ },
+
+ onSecurityChange : function(aWebProgress, aRequest, aState) {
+ },
+
+ QueryInterface : function(aIID)
+ {
+ if (aIID.equals(Ci.nsIWebProgressListener) ||
+ aIID.equals(Ci.nsISupportsWeakReference) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_NOINTERFACE;
+ }
+};
+
+var gLoadFired = false;
+function loadWebPanel(aURI) {
+ var panelBrowser = getPanelBrowser();
+ if (gLoadFired) {
+ panelBrowser.webNavigation
+ .loadURI(aURI, nsIWebNavigation.LOAD_FLAGS_NONE,
+ null, null, null);
+ }
+ panelBrowser.setAttribute("cachedurl", aURI);
+}
+
+function load()
+{
+ var panelBrowser = getPanelBrowser();
+ panelBrowser.webProgress.addProgressListener(panelProgressListener,
+ Ci.nsIWebProgress.NOTIFY_ALL);
+ var cachedurl = panelBrowser.getAttribute("cachedurl")
+ if (cachedurl) {
+ panelBrowser.webNavigation
+ .loadURI(cachedurl, nsIWebNavigation.LOAD_FLAGS_NONE, null,
+ null, null);
+ }
+
+ gLoadFired = true;
+}
+
+function unload()
+{
+ getPanelBrowser().webProgress.removeProgressListener(panelProgressListener);
+}
+
+function PanelBrowserStop()
+{
+ getPanelBrowser().webNavigation.stop(nsIWebNavigation.STOP_ALL)
+}
+
+function PanelBrowserReload()
+{
+ getPanelBrowser().webNavigation
+ .sessionHistory
+ .QueryInterface(nsIWebNavigation)
+ .reload(nsIWebNavigation.LOAD_FLAGS_NONE);
+}
diff --git a/browser/base/content/web-panels.xul b/browser/base/content/web-panels.xul
new file mode 100644
index 000000000..8be808cca
--- /dev/null
+++ b/browser/base/content/web-panels.xul
@@ -0,0 +1,71 @@
+<?xml version="1.0"?>
+
+# -*- Mode: Java; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+
+<!DOCTYPE page [
+<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
+%browserDTD;
+<!ENTITY % palemoonDTD SYSTEM "chrome://browser/locale/palemoon.dtd" >
+%palemoonDTD;
+<!ENTITY % textcontextDTD SYSTEM "chrome://global/locale/textcontext.dtd">
+%textcontextDTD;
+]>
+
+<page id="webpanels-window"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="load()" onunload="unload()">
+ <script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
+ <script type="application/javascript" src="chrome://browser/content/browser.js"/>
+ <script type="application/javascript" src="chrome://global/content/inlineSpellCheckUI.js"/>
+ <script type="application/javascript" src="chrome://browser/content/nsContextMenu.js"/>
+ <script type="application/javascript" src="chrome://browser/content/web-panels.js"/>
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_browser" src="chrome://browser/locale/browser.properties"/>
+ </stringbundleset>
+
+ <broadcasterset id="mainBroadcasterSet">
+ <broadcaster id="isFrameImage"/>
+ </broadcasterset>
+
+ <commandset id="mainCommandset">
+ <command id="Browser:Back"
+ oncommand="getPanelBrowser().webNavigation.goBack();"
+ disabled="true"/>
+ <command id="Browser:Forward"
+ oncommand="getPanelBrowser().webNavigation.goForward();"
+ disabled="true"/>
+ <command id="Browser:Stop" oncommand="PanelBrowserStop();"/>
+ <command id="Browser:Reload" oncommand="PanelBrowserReload();"/>
+ </commandset>
+
+ <popupset id="mainPopupSet">
+ <tooltip id="aHTMLTooltip" page="true"/>
+ <menupopup id="contentAreaContextMenu" pagemenu="start"
+ onpopupshowing="if (event.target != this)
+ return true;
+ gContextMenu = new nsContextMenu(this, event.shiftKey);
+ if (gContextMenu.shouldDisplay)
+ document.popupNode = this.triggerNode;
+ return gContextMenu.shouldDisplay;"
+ onpopuphiding="if (event.target != this)
+ return;
+ gContextMenu.hiding();
+ gContextMenu = null;">
+#include browser-context.inc
+ </menupopup>
+ </popupset>
+
+ <commandset id="editMenuCommands"/>
+ <browser id="web-panels-browser" persist="cachedurl" type="content" flex="1"
+ context="contentAreaContextMenu" tooltip="aHTMLTooltip"
+ onclick="window.parent.contentAreaClick(event, true);"/>
+</page>
diff --git a/browser/base/content/win6BrowserOverlay.xul b/browser/base/content/win6BrowserOverlay.xul
new file mode 100644
index 000000000..a69e3f6bd
--- /dev/null
+++ b/browser/base/content/win6BrowserOverlay.xul
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+
+<!-- -*- Mode: HTML -*- -->
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<overlay id="win6-browser-overlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <toolbar id="toolbar-menubar"
+ autohide="true"/>
+</overlay>
diff --git a/browser/base/jar.mn b/browser/base/jar.mn
new file mode 100644
index 000000000..f178a74c2
--- /dev/null
+++ b/browser/base/jar.mn
@@ -0,0 +1,143 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+browser.jar:
+% content browser %content/browser/ contentaccessible=yes
+#ifdef XP_MACOSX
+% overlay chrome://mozapps/content/downloads/downloads.xul chrome://browser/content/downloadManagerOverlay.xul
+% overlay chrome://global/content/console.xul chrome://browser/content/jsConsoleOverlay.xul
+% overlay chrome://mozapps/content/update/updates.xul chrome://browser/content/softwareUpdateOverlay.xul
+#endif
+#ifdef XP_WIN
+% overlay chrome://browser/content/browser.xul chrome://browser/content/win6BrowserOverlay.xul os=WINNT osversion>=6
+#endif
+% overlay chrome://global/content/viewSource.xul chrome://browser/content/viewSourceOverlay.xul
+% overlay chrome://global/content/viewPartialSource.xul chrome://browser/content/viewSourceOverlay.xul
+% style chrome://global/content/customizeToolbar.xul chrome://browser/content/browser.css
+% style chrome://global/content/customizeToolbar.xul chrome://browser/skin/
+* content/browser/aboutDialog.xul (content/aboutDialog.xul)
+* content/browser/aboutDialog.js (content/aboutDialog.js)
+ content/browser/aboutDialog.css (content/aboutDialog.css)
+ content/browser/aboutRobots.xhtml (content/aboutRobots.xhtml)
+ content/browser/abouthome/aboutHome.xhtml (content/abouthome/aboutHome.xhtml)
+ content/browser/abouthome/aboutHome.js (content/abouthome/aboutHome.js)
+* content/browser/abouthome/aboutHome.css (content/abouthome/aboutHome.css)
+ content/browser/abouthome/noise.png (content/abouthome/noise.png)
+ content/browser/abouthome/snippet1.png (content/abouthome/snippet1.png)
+ content/browser/abouthome/snippet2.png (content/abouthome/snippet2.png)
+ content/browser/abouthome/downloads.png (content/abouthome/downloads.png)
+ content/browser/abouthome/bookmarks.png (content/abouthome/bookmarks.png)
+ content/browser/abouthome/history.png (content/abouthome/history.png)
+ content/browser/abouthome/apps.png (content/abouthome/apps.png)
+ content/browser/abouthome/addons.png (content/abouthome/addons.png)
+ content/browser/abouthome/sync.png (content/abouthome/sync.png)
+ content/browser/abouthome/settings.png (content/abouthome/settings.png)
+ content/browser/abouthome/restore.png (content/abouthome/restore.png)
+ content/browser/abouthome/restore-large.png (content/abouthome/restore-large.png)
+ content/browser/abouthome/mozilla.png (content/abouthome/mozilla.png)
+ content/browser/abouthome/snippet1@2x.png (content/abouthome/snippet1@2x.png)
+ content/browser/abouthome/snippet2@2x.png (content/abouthome/snippet2@2x.png)
+ content/browser/abouthome/downloads@2x.png (content/abouthome/downloads@2x.png)
+ content/browser/abouthome/bookmarks@2x.png (content/abouthome/bookmarks@2x.png)
+ content/browser/abouthome/history@2x.png (content/abouthome/history@2x.png)
+ content/browser/abouthome/apps@2x.png (content/abouthome/apps@2x.png)
+ content/browser/abouthome/addons@2x.png (content/abouthome/addons@2x.png)
+ content/browser/abouthome/sync@2x.png (content/abouthome/sync@2x.png)
+ content/browser/abouthome/settings@2x.png (content/abouthome/settings@2x.png)
+ content/browser/abouthome/restore@2x.png (content/abouthome/restore@2x.png)
+ content/browser/abouthome/restore-large@2x.png (content/abouthome/restore-large@2x.png)
+ content/browser/abouthome/mozilla@2x.png (content/abouthome/mozilla@2x.png)
+#ifdef MOZ_SERVICES_HEALTHREPORT
+ content/browser/abouthealthreport/abouthealth.xhtml (content/abouthealthreport/abouthealth.xhtml)
+ content/browser/abouthealthreport/abouthealth.js (content/abouthealthreport/abouthealth.js)
+ content/browser/abouthealthreport/abouthealth.css (content/abouthealthreport/abouthealth.css)
+#endif
+ content/browser/aboutRobots-icon.png (content/aboutRobots-icon.png)
+ content/browser/aboutRobots-widget-left.png (content/aboutRobots-widget-left.png)
+ content/browser/aboutSocialError.xhtml (content/aboutSocialError.xhtml)
+* content/browser/browser.css (content/browser.css)
+* content/browser/browser-title.css (content/browser-title.css)
+* content/browser/browser.js (content/browser.js)
+* content/browser/browser.xul (content/browser.xul)
+* content/browser/browser-tabPreviews.xml (content/browser-tabPreviews.xml)
+* content/browser/chatWindow.xul (content/chatWindow.xul)
+ content/browser/content.js (content/content.js)
+ content/browser/imagedocument.png (content/imagedocument.png)
+* content/browser/padlock.xul (content/padlock.xul)
+* content/browser/padlock.js (content/padlock.js)
+* content/browser/padlock.css (content/padlock.css)
+ content/browser/padlock_mod_ev.png (content/padlock_mod_ev.png)
+ content/browser/padlock_mod_https.png (content/padlock_mod_https.png)
+ content/browser/padlock_mod_low.png (content/padlock_mod_low.png)
+ content/browser/padlock_mod_broken.png (content/padlock_mod_broken.png)
+ content/browser/padlock_classic_ev.png (content/padlock_classic_ev.png)
+ content/browser/padlock_classic_https.png (content/padlock_classic_https.png)
+ content/browser/padlock_classic_broken.png (content/padlock_classic_broken.png)
+ content/browser/newtab/newTab.xul (content/newtab/newTab.xul)
+* content/browser/newtab/newTab.js (content/newtab/newTab.js)
+ content/browser/newtab/newTab.css (content/newtab/newTab.css)
+* content/browser/pageinfo/pageInfo.xul (content/pageinfo/pageInfo.xul)
+ content/browser/pageinfo/pageInfo.js (content/pageinfo/pageInfo.js)
+ content/browser/pageinfo/pageInfo.css (content/pageinfo/pageInfo.css)
+ content/browser/pageinfo/pageInfo.xml (content/pageinfo/pageInfo.xml)
+ content/browser/pageinfo/feeds.js (content/pageinfo/feeds.js)
+ content/browser/pageinfo/feeds.xml (content/pageinfo/feeds.xml)
+ content/browser/pageinfo/permissions.js (content/pageinfo/permissions.js)
+ content/browser/pageinfo/security.js (content/pageinfo/security.js)
+#ifdef MOZ_SERVICES_SYNC
+ content/browser/sync/aboutSyncTabs.xul (content/sync/aboutSyncTabs.xul)
+ content/browser/sync/aboutSyncTabs.js (content/sync/aboutSyncTabs.js)
+ content/browser/sync/aboutSyncTabs.css (content/sync/aboutSyncTabs.css)
+ content/browser/sync/aboutSyncTabs-bindings.xml (content/sync/aboutSyncTabs-bindings.xml)
+* content/browser/sync/setup.xul (content/sync/setup.xul)
+ content/browser/sync/addDevice.js (content/sync/addDevice.js)
+ content/browser/sync/addDevice.xul (content/sync/addDevice.xul)
+* content/browser/sync/setup.js (content/sync/setup.js)
+ content/browser/sync/genericChange.xul (content/sync/genericChange.xul)
+ content/browser/sync/genericChange.js (content/sync/genericChange.js)
+ content/browser/sync/key.xhtml (content/sync/key.xhtml)
+ content/browser/sync/notification.xml (content/sync/notification.xml)
+ content/browser/sync/quota.xul (content/sync/quota.xul)
+ content/browser/sync/quota.js (content/sync/quota.js)
+ content/browser/sync/utils.js (content/sync/utils.js)
+ content/browser/sync/progress.js (content/sync/progress.js)
+ content/browser/sync/progress.xhtml (content/sync/progress.xhtml)
+#endif
+ content/browser/openLocation.js (content/openLocation.js)
+ content/browser/openLocation.xul (content/openLocation.xul)
+* content/browser/safeMode.css (content/safeMode.css)
+* content/browser/safeMode.js (content/safeMode.js)
+* content/browser/safeMode.xul (content/safeMode.xul)
+* content/browser/sanitize.js (content/sanitize.js)
+* content/browser/sanitize.xul (content/sanitize.xul)
+* content/browser/sanitizeDialog.js (content/sanitizeDialog.js)
+ content/browser/sanitizeDialog.css (content/sanitizeDialog.css)
+ content/browser/tabbrowser.css (content/tabbrowser.css)
+* content/browser/tabbrowser.xml (content/tabbrowser.xml)
+* content/browser/urlbarBindings.xml (content/urlbarBindings.xml)
+* content/browser/utilityOverlay.js (content/utilityOverlay.js)
+ content/browser/web-panels.js (content/web-panels.js)
+* content/browser/web-panels.xul (content/web-panels.xul)
+* content/browser/baseMenuOverlay.xul (content/baseMenuOverlay.xul)
+* content/browser/nsContextMenu.js (content/nsContextMenu.js)
+# XXX: We should exclude this one as well (bug 71895)
+* content/browser/hiddenWindow.xul (content/hiddenWindow.xul)
+#ifdef XP_MACOSX
+* content/browser/macBrowserOverlay.xul (content/macBrowserOverlay.xul)
+* content/browser/downloadManagerOverlay.xul (content/downloadManagerOverlay.xul)
+* content/browser/jsConsoleOverlay.xul (content/jsConsoleOverlay.xul)
+* content/browser/softwareUpdateOverlay.xul (content/softwareUpdateOverlay.xul)
+#endif
+* content/browser/viewSourceOverlay.xul (content/viewSourceOverlay.xul)
+#ifdef XP_WIN
+ content/browser/win6BrowserOverlay.xul (content/win6BrowserOverlay.xul)
+#endif
+ content/browser/socialchat.xml (content/socialchat.xml)
+# the following files are browser-specific overrides
+* content/browser/license.html (/toolkit/content/license.html)
+% override chrome://global/content/license.html chrome://browser/content/license.html
+#ifdef MOZ_SAFE_BROWSING
+ content/browser/report-phishing-overlay.xul (content/report-phishing-overlay.xul)
+ content/browser/blockedSite.xhtml (content/blockedSite.xhtml)
+% overlay chrome://browser/content/browser.xul chrome://browser/content/report-phishing-overlay.xul
+#endif
diff --git a/browser/base/moz.build b/browser/base/moz.build
new file mode 100644
index 000000000..d13541370
--- /dev/null
+++ b/browser/base/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+TEST_DIRS += ['content/test']