summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThomas Groman <tgroman@nuegia.net>2019-12-16 19:48:42 -0800
committerThomas Groman <tgroman@nuegia.net>2019-12-16 19:48:42 -0800
commit4492b5f8e774bf3b4f21e4e468fc052cbcbb468a (patch)
tree37970571a7dcbeb6b58c991ce718ce7001ac97d6
downloadwebbrowser-4492b5f8e774bf3b4f21e4e468fc052cbcbb468a.tar
webbrowser-4492b5f8e774bf3b4f21e4e468fc052cbcbb468a.tar.gz
webbrowser-4492b5f8e774bf3b4f21e4e468fc052cbcbb468a.tar.lz
webbrowser-4492b5f8e774bf3b4f21e4e468fc052cbcbb468a.tar.xz
webbrowser-4492b5f8e774bf3b4f21e4e468fc052cbcbb468a.zip
initial commit
-rw-r--r--LICENSE7
-rw-r--r--Makefile.in13
-rw-r--r--app-rules.mk1
-rw-r--r--app.mozbuild17
-rw-r--r--app/Makefile.in108
-rw-r--r--app/application.ini50
-rw-r--r--app/blocklist.xml3909
-rw-r--r--app/macbuild/Contents/CodeResources1
-rw-r--r--app/macbuild/Contents/Info.plist.in227
-rw-r--r--app/macbuild/Contents/MacOS-files.in10
-rw-r--r--app/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in5
-rw-r--r--app/macbuild/Contents/_CodeSignature/CodeResources71
-rw-r--r--app/macversion.py44
-rw-r--r--app/module.ver8
-rw-r--r--app/moz.build64
-rw-r--r--app/nsBrowserApp.cpp393
-rw-r--r--app/palemoon.exe.manifest48
-rw-r--r--app/permissions14
-rw-r--r--app/profile/channel-prefs.js6
-rw-r--r--app/profile/extensions/moz.build7
-rw-r--r--app/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/Makefile.in10
-rw-r--r--app/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/install.rdf.in40
-rw-r--r--app/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/moz.build8
-rw-r--r--app/profile/pagethemes.rdf7
-rw-r--r--app/profile/palemoon.js1205
-rw-r--r--app/profile/prefs.js13
-rw-r--r--app/splash.rc21
-rw-r--r--base/content/aboutDialog.css74
-rw-r--r--base/content/aboutDialog.js62
-rw-r--r--base/content/aboutDialog.xul86
-rw-r--r--base/content/autocomplete.css17
-rw-r--r--base/content/autocomplete.xml2128
-rw-r--r--base/content/autorecovery.js60
-rw-r--r--base/content/autorecovery.xul12
-rw-r--r--base/content/baseMenuOverlay.xul114
-rw-r--r--base/content/browser-addons.js537
-rw-r--r--base/content/browser-appmenu.inc381
-rw-r--r--base/content/browser-charsetmenu.inc62
-rw-r--r--base/content/browser-context.inc384
-rw-r--r--base/content/browser-devtools-theme.js91
-rw-r--r--base/content/browser-doctype.inc19
-rw-r--r--base/content/browser-feeds.js224
-rw-r--r--base/content/browser-fullScreen.js462
-rw-r--r--base/content/browser-fullZoom.js526
-rw-r--r--base/content/browser-gestureSupport.js1059
-rw-r--r--base/content/browser-menubar.inc564
-rw-r--r--base/content/browser-menudragging.js340
-rw-r--r--base/content/browser-menudragging.xul13
-rw-r--r--base/content/browser-places.js1316
-rw-r--r--base/content/browser-plugins.js781
-rw-r--r--base/content/browser-sets.inc357
-rw-r--r--base/content/browser-syncui.js472
-rw-r--r--base/content/browser-tabPreviews.js1058
-rw-r--r--base/content/browser-tabPreviews.xml78
-rw-r--r--base/content/browser-thumbnails.js203
-rw-r--r--base/content/browser-title.css204
-rw-r--r--base/content/browser-uacompat.js45
-rw-r--r--base/content/browser-webrtcUI.js55
-rw-r--r--base/content/browser.css759
-rw-r--r--base/content/browser.js7440
-rw-r--r--base/content/browser.xul1046
-rw-r--r--base/content/browserMountPoints.inc12
-rw-r--r--base/content/content.js177
-rw-r--r--base/content/downloadManagerOverlay.xul32
-rw-r--r--base/content/global-devtools-theme-scripts.inc6
-rw-r--r--base/content/global-scripts.inc13
-rw-r--r--base/content/hiddenWindow.xul19
-rw-r--r--base/content/highlighter.css105
-rw-r--r--base/content/jsConsoleOverlay.xul18
-rw-r--r--base/content/macBrowserOverlay.xul67
-rw-r--r--base/content/nsContextMenu.js1603
-rw-r--r--base/content/openLocation.js150
-rw-r--r--base/content/openLocation.xul57
-rw-r--r--base/content/overrides/app-license.html6
-rw-r--r--base/content/padlock.css203
-rw-r--r--base/content/padlock.js234
-rw-r--r--base/content/padlock.xul63
-rw-r--r--base/content/padlock_classic_broken.pngbin0 -> 726 bytes
-rw-r--r--base/content/padlock_classic_ev.pngbin0 -> 566 bytes
-rw-r--r--base/content/padlock_classic_https.pngbin0 -> 589 bytes
-rw-r--r--base/content/padlock_classic_low.pngbin0 -> 682 bytes
-rw-r--r--base/content/padlock_mod_broken.pngbin0 -> 728 bytes
-rw-r--r--base/content/padlock_mod_ev.pngbin0 -> 290 bytes
-rw-r--r--base/content/padlock_mod_https.pngbin0 -> 338 bytes
-rw-r--r--base/content/padlock_mod_low.pngbin0 -> 386 bytes
-rw-r--r--base/content/palemoon.xhtml66
-rw-r--r--base/content/popup-notifications.inc104
-rw-r--r--base/content/safeMode.css8
-rw-r--r--base/content/safeMode.js128
-rw-r--r--base/content/safeMode.xul55
-rw-r--r--base/content/sanitize.js534
-rw-r--r--base/content/sanitize.xul190
-rw-r--r--base/content/sanitizeDialog.css23
-rw-r--r--base/content/sanitizeDialog.js910
-rw-r--r--base/content/softwareUpdateOverlay.xul18
-rw-r--r--base/content/tabbrowser.css77
-rw-r--r--base/content/tabbrowser.xml5403
-rw-r--r--base/content/test/general/audio.oggbin0 -> 47411 bytes
-rw-r--r--base/content/urlbarBindings.xml1800
-rw-r--r--base/content/utilityOverlay.js901
-rw-r--r--base/content/viewSourceOverlay.xul26
-rw-r--r--base/content/web-panels.js102
-rw-r--r--base/content/web-panels.xul84
-rw-r--r--base/content/win6BrowserOverlay.xul12
-rw-r--r--base/jar.mn79
-rw-r--r--base/moz.build20
-rw-r--r--branding/official/LICENSE4
-rw-r--r--branding/official/VisualElements_150.pngbin0 -> 27921 bytes
-rw-r--r--branding/official/VisualElements_70.pngbin0 -> 7785 bytes
-rw-r--r--branding/official/appname.bmpbin0 -> 35690 bytes
-rw-r--r--branding/official/branding.nsi16
-rw-r--r--branding/official/configure.sh6
-rw-r--r--branding/official/content/about-background.jpgbin0 -> 111893 bytes
-rw-r--r--branding/official/content/about-logo.pngbin0 -> 42128 bytes
-rw-r--r--branding/official/content/about-logo@2x.pngbin0 -> 166292 bytes
-rw-r--r--branding/official/content/about-wordmark.pngbin0 -> 10178 bytes
-rw-r--r--branding/official/content/about-wordmark.svg133
-rw-r--r--branding/official/content/about.pngbin0 -> 47380 bytes
-rw-r--r--branding/official/content/aboutDialog.css53
-rw-r--r--branding/official/content/icon48.pngbin0 -> 3885 bytes
-rw-r--r--branding/official/content/icon64.pngbin0 -> 6176 bytes
-rw-r--r--branding/official/content/jar.mn16
-rw-r--r--branding/official/content/moz.build7
-rw-r--r--branding/official/default16.pngbin0 -> 811 bytes
-rw-r--r--branding/official/default22.pngbin0 -> 1377 bytes
-rw-r--r--branding/official/default24.pngbin0 -> 1534 bytes
-rw-r--r--branding/official/default256.pngbin0 -> 70279 bytes
-rw-r--r--branding/official/default32.pngbin0 -> 2273 bytes
-rw-r--r--branding/official/default48.pngbin0 -> 3885 bytes
-rw-r--r--branding/official/disk.icnsbin0 -> 164632 bytes
-rw-r--r--branding/official/document.icnsbin0 -> 111772 bytes
-rw-r--r--branding/official/document.icobin0 -> 54261 bytes
-rw-r--r--branding/official/dsstorebin0 -> 12292 bytes
-rw-r--r--branding/official/firefox.icnsbin0 -> 253858 bytes
-rw-r--r--branding/official/firefox.icobin0 -> 94683 bytes
-rw-r--r--branding/official/locales/en-US/brand.dtd4
-rw-r--r--branding/official/locales/en-US/brand.properties5
-rw-r--r--branding/official/locales/jar.mn11
-rw-r--r--branding/official/locales/moz.build7
-rw-r--r--branding/official/moz.build13
-rw-r--r--branding/official/mozicon128.pngbin0 -> 20601 bytes
-rw-r--r--branding/official/palemoon.VisualElementsManifest.xml8
-rw-r--r--branding/official/palemoon.desktop353
-rw-r--r--branding/official/pref/palemoon-branding.js35
-rw-r--r--branding/official/wizHeader.bmpbin0 -> 25818 bytes
-rw-r--r--branding/official/wizHeaderRTL.bmpbin0 -> 25818 bytes
-rw-r--r--branding/official/wizWatermark.bmpbin0 -> 154542 bytes
-rw-r--r--branding/shared/background.pngbin0 -> 115 bytes
-rw-r--r--branding/shared/branding.mozbuild58
-rw-r--r--branding/shared/locales/browserconfig.properties2
-rw-r--r--branding/shared/newtab.icobin0 -> 1150 bytes
-rw-r--r--branding/shared/newwindow.icobin0 -> 1150 bytes
-rw-r--r--branding/shared/pbmode.icobin0 -> 1150 bytes
-rw-r--r--branding/shared/pref/preferences.inc107
-rw-r--r--branding/shared/pref/uaoverrides.inc83
-rw-r--r--branding/unofficial/VisualElements_150.pngbin0 -> 19030 bytes
-rw-r--r--branding/unofficial/VisualElements_70.pngbin0 -> 5861 bytes
-rw-r--r--branding/unofficial/appname.bmpbin0 -> 11158 bytes
-rw-r--r--branding/unofficial/branding.nsi12
-rw-r--r--branding/unofficial/configure.sh5
-rw-r--r--branding/unofficial/content/about-background.pngbin0 -> 95145 bytes
-rw-r--r--branding/unofficial/content/about-logo.pngbin0 -> 15708 bytes
-rw-r--r--branding/unofficial/content/about-logo@2x.pngbin0 -> 49779 bytes
-rw-r--r--branding/unofficial/content/about.pngbin0 -> 13027 bytes
-rw-r--r--branding/unofficial/content/aboutDialog.css19
-rw-r--r--branding/unofficial/content/icon48.pngbin0 -> 2145 bytes
-rw-r--r--branding/unofficial/content/icon64.pngbin0 -> 3025 bytes
-rw-r--r--branding/unofficial/content/jar.mn15
-rw-r--r--branding/unofficial/content/moz.build7
-rw-r--r--branding/unofficial/default16.pngbin0 -> 693 bytes
-rw-r--r--branding/unofficial/default32.pngbin0 -> 1624 bytes
-rw-r--r--branding/unofficial/default48.pngbin0 -> 2771 bytes
-rw-r--r--branding/unofficial/disk.icnsbin0 -> 39250 bytes
-rw-r--r--branding/unofficial/document.icnsbin0 -> 12451 bytes
-rw-r--r--branding/unofficial/document.icobin0 -> 22486 bytes
-rw-r--r--branding/unofficial/dsstorebin0 -> 6148 bytes
-rw-r--r--branding/unofficial/firefox.icnsbin0 -> 12079 bytes
-rw-r--r--branding/unofficial/firefox.icobin0 -> 22486 bytes
-rw-r--r--branding/unofficial/locales/browserconfig.properties0
-rw-r--r--branding/unofficial/locales/en-US/brand.dtd4
-rw-r--r--branding/unofficial/locales/en-US/brand.properties5
-rw-r--r--branding/unofficial/locales/jar.mn12
-rw-r--r--branding/unofficial/locales/moz.build3
-rw-r--r--branding/unofficial/moz.build13
-rw-r--r--branding/unofficial/mozicon128.pngbin0 -> 11024 bytes
-rw-r--r--branding/unofficial/pref/palemoon-branding.js9
-rw-r--r--branding/unofficial/webbrowser.VisualElementsManifest.xml8
-rw-r--r--branding/unofficial/webbrowser.desktop353
-rw-r--r--branding/unofficial/wizHeader.bmpbin0 -> 25818 bytes
-rw-r--r--branding/unofficial/wizHeaderRTL.bmpbin0 -> 25818 bytes
-rw-r--r--branding/unofficial/wizWatermark.bmpbin0 -> 154542 bytes
-rw-r--r--branding/unstable/VisualElements_150.pngbin0 -> 34257 bytes
-rw-r--r--branding/unstable/VisualElements_70.pngbin0 -> 9508 bytes
-rw-r--r--branding/unstable/appname.bmpbin0 -> 11158 bytes
-rw-r--r--branding/unstable/branding.nsi16
-rw-r--r--branding/unstable/configure.sh6
-rw-r--r--branding/unstable/content/about-background.jpgbin0 -> 115006 bytes
-rw-r--r--branding/unstable/content/about-logo.pngbin0 -> 42470 bytes
-rw-r--r--branding/unstable/content/about-logo@2x.pngbin0 -> 120348 bytes
-rw-r--r--branding/unstable/content/about-wordmark.pngbin0 -> 11708 bytes
-rw-r--r--branding/unstable/content/about.pngbin0 -> 55384 bytes
-rw-r--r--branding/unstable/content/aboutDialog.css53
-rw-r--r--branding/unstable/content/icon48.pngbin0 -> 4855 bytes
-rw-r--r--branding/unstable/content/icon64.pngbin0 -> 6531 bytes
-rw-r--r--branding/unstable/content/jar.mn16
-rw-r--r--branding/unstable/content/moz.build7
-rw-r--r--branding/unstable/default16.pngbin0 -> 872 bytes
-rw-r--r--branding/unstable/default32.pngbin0 -> 2499 bytes
-rw-r--r--branding/unstable/default48.pngbin0 -> 4855 bytes
-rw-r--r--branding/unstable/disk.icnsbin0 -> 39250 bytes
-rw-r--r--branding/unstable/document.icnsbin0 -> 12451 bytes
-rw-r--r--branding/unstable/document.icobin0 -> 19790 bytes
-rw-r--r--branding/unstable/dsstorebin0 -> 6148 bytes
-rw-r--r--branding/unstable/firefox.icnsbin0 -> 59809 bytes
-rw-r--r--branding/unstable/firefox.icobin0 -> 100371 bytes
-rw-r--r--branding/unstable/locales/en-US/brand.dtd4
-rw-r--r--branding/unstable/locales/en-US/brand.properties5
-rw-r--r--branding/unstable/locales/jar.mn12
-rw-r--r--branding/unstable/locales/moz.build9
-rw-r--r--branding/unstable/moz.build13
-rw-r--r--branding/unstable/mozicon128.pngbin0 -> 24891 bytes
-rw-r--r--branding/unstable/palemoon.VisualElementsManifest.xml8
-rw-r--r--branding/unstable/pref/palemoon-branding.js46
-rw-r--r--branding/unstable/wizHeader.bmpbin0 -> 25818 bytes
-rw-r--r--branding/unstable/wizHeaderRTL.bmpbin0 -> 25818 bytes
-rw-r--r--branding/unstable/wizWatermark.bmpbin0 -> 154542 bytes
-rw-r--r--build.mk57
-rw-r--r--components/BrowserComponents.manifest64
-rw-r--r--components/abouthome/aboutHome.css343
-rw-r--r--components/abouthome/aboutHome.js227
-rw-r--r--components/abouthome/aboutHome.xhtml62
-rw-r--r--components/abouthome/addons.pngbin0 -> 1444 bytes
-rw-r--r--components/abouthome/addons@2x.pngbin0 -> 3783 bytes
-rw-r--r--components/abouthome/bookmarks.pngbin0 -> 1276 bytes
-rw-r--r--components/abouthome/bookmarks@2x.pngbin0 -> 2946 bytes
-rw-r--r--components/abouthome/downloads.pngbin0 -> 898 bytes
-rw-r--r--components/abouthome/downloads@2x.pngbin0 -> 2018 bytes
-rw-r--r--components/abouthome/history.pngbin0 -> 1654 bytes
-rw-r--r--components/abouthome/history@2x.pngbin0 -> 4629 bytes
-rw-r--r--components/abouthome/jar.mn33
-rw-r--r--components/abouthome/moz.build8
-rw-r--r--components/abouthome/noise.pngbin0 -> 4025 bytes
-rw-r--r--components/abouthome/restore-large.pngbin0 -> 2841 bytes
-rw-r--r--components/abouthome/restore-large@2x.pngbin0 -> 7267 bytes
-rw-r--r--components/abouthome/restore.pngbin0 -> 1796 bytes
-rw-r--r--components/abouthome/restore@2x.pngbin0 -> 4810 bytes
-rw-r--r--components/abouthome/settings.pngbin0 -> 1557 bytes
-rw-r--r--components/abouthome/settings@2x.pngbin0 -> 3836 bytes
-rw-r--r--components/abouthome/snippet1.pngbin0 -> 1470 bytes
-rw-r--r--components/abouthome/snippet1@2x.pngbin0 -> 3243 bytes
-rw-r--r--components/abouthome/snippet2.pngbin0 -> 3287 bytes
-rw-r--r--components/abouthome/snippet2@2x.pngbin0 -> 11027 bytes
-rw-r--r--components/abouthome/sync.pngbin0 -> 1879 bytes
-rw-r--r--components/abouthome/sync@2x.pngbin0 -> 4615 bytes
-rw-r--r--components/build/Makefile.in8
-rw-r--r--components/build/moz.build36
-rw-r--r--components/build/nsBrowserCompsCID.h31
-rw-r--r--components/build/nsModule.cpp91
-rw-r--r--components/certerror/content/aboutCertError.css17
-rw-r--r--components/certerror/content/aboutCertError.xhtml247
-rw-r--r--components/certerror/jar.mn7
-rw-r--r--components/certerror/moz.build7
-rw-r--r--components/dirprovider/DirectoryProvider.cpp268
-rw-r--r--components/dirprovider/DirectoryProvider.h51
-rw-r--r--components/dirprovider/moz.build13
-rw-r--r--components/distribution.js345
-rw-r--r--components/downloads/BrowserDownloads.manifest4
-rw-r--r--components/downloads/DownloadsCommon.jsm1920
-rw-r--r--components/downloads/DownloadsLogger.jsm76
-rw-r--r--components/downloads/DownloadsStartup.js278
-rw-r--r--components/downloads/DownloadsTaskbar.jsm177
-rw-r--r--components/downloads/DownloadsUI.js151
-rw-r--r--components/downloads/DownloadsViewUI.jsm250
-rw-r--r--components/downloads/content/allDownloadsViewOverlay.css56
-rw-r--r--components/downloads/content/allDownloadsViewOverlay.js1399
-rw-r--r--components/downloads/content/allDownloadsViewOverlay.xul119
-rw-r--r--components/downloads/content/contentAreaDownloadsView.css11
-rw-r--r--components/downloads/content/contentAreaDownloadsView.js15
-rw-r--r--components/downloads/content/contentAreaDownloadsView.xul45
-rw-r--r--components/downloads/content/download.css45
-rw-r--r--components/downloads/content/download.xml188
-rw-r--r--components/downloads/content/downloads.css132
-rw-r--r--components/downloads/content/downloads.js1614
-rw-r--r--components/downloads/content/downloadsOverlay.xul142
-rw-r--r--components/downloads/content/indicator.js609
-rw-r--r--components/downloads/content/indicatorOverlay.xul60
-rw-r--r--components/downloads/jar.mn18
-rw-r--r--components/downloads/moz.build23
-rw-r--r--components/feeds/BrowserFeeds.manifest28
-rw-r--r--components/feeds/FeedConverter.js591
-rw-r--r--components/feeds/FeedWriter.js1397
-rw-r--r--components/feeds/WebContentConverter.js927
-rw-r--r--components/feeds/content/subscribe.css7
-rw-r--r--components/feeds/content/subscribe.js23
-rw-r--r--components/feeds/content/subscribe.xhtml65
-rw-r--r--components/feeds/content/subscribe.xml40
-rw-r--r--components/feeds/jar.mn9
-rw-r--r--components/feeds/moz.build33
-rw-r--r--components/feeds/nsFeedSniffer.cpp363
-rw-r--r--components/feeds/nsFeedSniffer.h37
-rw-r--r--components/feeds/nsIFeedResultService.idl66
-rw-r--r--components/feeds/nsIWebContentConverterRegistrar.idl117
-rw-r--r--components/fuel/fuelApplication.js822
-rw-r--r--components/fuel/fuelApplication.manifest3
-rw-r--r--components/fuel/fuelIApplication.idl347
-rw-r--r--components/fuel/moz.build14
-rw-r--r--components/moz.build47
-rw-r--r--components/newtab/cells.js126
-rw-r--r--components/newtab/drag.js151
-rw-r--r--components/newtab/dragDataHelper.js22
-rw-r--r--components/newtab/drop.js150
-rw-r--r--components/newtab/dropPreview.js222
-rw-r--r--components/newtab/dropTargetShim.js232
-rw-r--r--components/newtab/grid.js175
-rw-r--r--components/newtab/jar.mn8
-rw-r--r--components/newtab/moz.build8
-rw-r--r--components/newtab/newTab.css349
-rw-r--r--components/newtab/newTab.js69
-rw-r--r--components/newtab/newTab.xhtml61
-rw-r--r--components/newtab/page.js244
-rw-r--r--components/newtab/search.js134
-rw-r--r--components/newtab/sites.js353
-rw-r--r--components/newtab/transformations.js270
-rw-r--r--components/newtab/undo.js116
-rw-r--r--components/newtab/updater.js177
-rw-r--r--components/nsAboutRedirector.js114
-rw-r--r--components/nsBrowserContentHandler.js803
-rw-r--r--components/nsBrowserGlue.js2055
-rw-r--r--components/nsIBrowserGlue.idl47
-rw-r--r--components/nsIBrowserHandler.idl20
-rw-r--r--components/pageinfo/feeds.js59
-rw-r--r--components/pageinfo/feeds.xml40
-rw-r--r--components/pageinfo/jar.mn13
-rw-r--r--components/pageinfo/moz.build8
-rw-r--r--components/pageinfo/pageInfo.css26
-rw-r--r--components/pageinfo/pageInfo.js1286
-rw-r--r--components/pageinfo/pageInfo.xml29
-rw-r--r--components/pageinfo/pageInfo.xul507
-rw-r--r--components/pageinfo/permissions.js341
-rw-r--r--components/pageinfo/security.js378
-rw-r--r--components/permissions/aboutPermissions.css11
-rw-r--r--components/permissions/aboutPermissions.js1335
-rw-r--r--components/permissions/aboutPermissions.xml113
-rw-r--r--components/permissions/aboutPermissions.xul313
-rw-r--r--components/permissions/jar.mn9
-rw-r--r--components/permissions/moz.build7
-rw-r--r--components/places/PlacesUIUtils.jsm1373
-rw-r--r--components/places/content/bookmarkProperties.js675
-rw-r--r--components/places/content/bookmarkProperties.xul43
-rw-r--r--components/places/content/bookmarksPanel.js25
-rw-r--r--components/places/content/bookmarksPanel.xul55
-rw-r--r--components/places/content/browserPlacesViews.js1754
-rw-r--r--components/places/content/controller.js1895
-rw-r--r--components/places/content/downloadsViewOverlay.xul47
-rw-r--r--components/places/content/editBookmarkOverlay.js1063
-rw-r--r--components/places/content/editBookmarkOverlay.xul228
-rw-r--r--components/places/content/history-panel.js91
-rw-r--r--components/places/content/history-panel.xul95
-rw-r--r--components/places/content/menu.xml488
-rw-r--r--components/places/content/moveBookmarks.js54
-rw-r--r--components/places/content/moveBookmarks.xul53
-rw-r--r--components/places/content/organizer.css7
-rw-r--r--components/places/content/places.css16
-rw-r--r--components/places/content/places.js1553
-rw-r--r--components/places/content/places.xul471
-rw-r--r--components/places/content/placesOverlay.xul247
-rw-r--r--components/places/content/sidebarUtils.js108
-rw-r--r--components/places/content/tree.xml789
-rw-r--r--components/places/content/treeView.js1770
-rw-r--r--components/places/jar.mn34
-rw-r--r--components/places/moz.build9
-rw-r--r--components/preferences/advanced.js755
-rw-r--r--components/preferences/advanced.xul465
-rw-r--r--components/preferences/applicationManager.js102
-rw-r--r--components/preferences/applicationManager.xul59
-rw-r--r--components/preferences/applications.js1890
-rw-r--r--components/preferences/applications.xul99
-rw-r--r--components/preferences/colors.xul102
-rw-r--r--components/preferences/connection.js200
-rw-r--r--components/preferences/connection.xul164
-rw-r--r--components/preferences/content.js187
-rw-r--r--components/preferences/content.xul171
-rw-r--r--components/preferences/cookies.js948
-rw-r--r--components/preferences/cookies.xul106
-rw-r--r--components/preferences/fonts.js144
-rw-r--r--components/preferences/fonts.xul279
-rw-r--r--components/preferences/handlers.css25
-rw-r--r--components/preferences/handlers.xml81
-rw-r--r--components/preferences/jar.mn44
-rw-r--r--components/preferences/languages.js304
-rw-r--r--components/preferences/languages.xul98
-rw-r--r--components/preferences/main.js473
-rw-r--r--components/preferences/main.xul188
-rw-r--r--components/preferences/moz.build14
-rw-r--r--components/preferences/newtaburl.js102
-rw-r--r--components/preferences/permissions.js463
-rw-r--r--components/preferences/permissions.xul85
-rw-r--r--components/preferences/preferences.xul92
-rw-r--r--components/preferences/privacy.js485
-rw-r--r--components/preferences/privacy.xul273
-rw-r--r--components/preferences/sanitize.js12
-rw-r--r--components/preferences/sanitize.xul108
-rw-r--r--components/preferences/security.js263
-rw-r--r--components/preferences/security.xul184
-rw-r--r--components/preferences/selectBookmark.js83
-rw-r--r--components/preferences/selectBookmark.xul44
-rw-r--r--components/preferences/sync.js192
-rw-r--r--components/preferences/sync.xul178
-rw-r--r--components/preferences/tabs.js90
-rw-r--r--components/preferences/tabs.xul102
-rw-r--r--components/privatebrowsing/content/aboutPrivateBrowsing.xhtml156
-rw-r--r--components/privatebrowsing/jar.mn6
-rw-r--r--components/privatebrowsing/moz.build7
-rw-r--r--components/search/content/engineManager.js492
-rw-r--r--components/search/content/engineManager.xul93
-rw-r--r--components/search/content/search.xml837
-rw-r--r--components/search/content/searchbarBindings.css13
-rw-r--r--components/search/jar.mn9
-rw-r--r--components/search/moz.build7
-rw-r--r--components/sessionstore/DocumentUtils.jsm230
-rw-r--r--components/sessionstore/SessionStorage.jsm165
-rw-r--r--components/sessionstore/SessionStore.jsm4786
-rw-r--r--components/sessionstore/XPathGenerator.jsm97
-rw-r--r--components/sessionstore/_SessionFile.jsm314
-rw-r--r--components/sessionstore/content/aboutSessionRestore.js320
-rw-r--r--components/sessionstore/content/aboutSessionRestore.xhtml94
-rw-r--r--components/sessionstore/content/content-sessionStore.js40
-rw-r--r--components/sessionstore/jar.mn8
-rw-r--r--components/sessionstore/moz.build29
-rw-r--r--components/sessionstore/nsISessionStartup.idl59
-rw-r--r--components/sessionstore/nsISessionStore.idl206
-rw-r--r--components/sessionstore/nsSessionStartup.js296
-rw-r--r--components/sessionstore/nsSessionStore.js37
-rw-r--r--components/sessionstore/nsSessionStore.manifest18
-rw-r--r--components/shell/ShellService.jsm114
-rw-r--r--components/shell/content/setDesktopBackground.js214
-rw-r--r--components/shell/content/setDesktopBackground.xul84
-rw-r--r--components/shell/jar.mn7
-rw-r--r--components/shell/moz.build40
-rw-r--r--components/shell/nsGNOMEShellService.cpp637
-rw-r--r--components/shell/nsGNOMEShellService.h36
-rw-r--r--components/shell/nsIGNOMEShellService.idl19
-rw-r--r--components/shell/nsIMacShellService.idl15
-rw-r--r--components/shell/nsIShellService.idl95
-rw-r--r--components/shell/nsIWindowsShellService.idl17
-rw-r--r--components/shell/nsMacShellService.cpp434
-rw-r--r--components/shell/nsMacShellService.h32
-rw-r--r--components/shell/nsSetDefaultBrowser.js30
-rw-r--r--components/shell/nsSetDefaultBrowser.manifest3
-rw-r--r--components/shell/nsShellService.h12
-rw-r--r--components/shell/nsWindowsShellService.cpp1277
-rw-r--r--components/shell/nsWindowsShellService.h37
-rw-r--r--components/statusbar/Downloads.jsm674
-rw-r--r--components/statusbar/Progress.jsm183
-rw-r--r--components/statusbar/Status.jsm456
-rw-r--r--components/statusbar/Status4Evar.jsm312
-rw-r--r--components/statusbar/Toolbars.jsm221
-rw-r--r--components/statusbar/content-thunk.js23
-rw-r--r--components/statusbar/content/overlay.css14
-rw-r--r--components/statusbar/content/overlay.js16
-rw-r--r--components/statusbar/content/overlay.xul82
-rw-r--r--components/statusbar/content/prefs.css10
-rw-r--r--components/statusbar/content/prefs.js274
-rw-r--r--components/statusbar/content/prefs.xml704
-rw-r--r--components/statusbar/content/prefs.xul297
-rw-r--r--components/statusbar/content/tabbrowser.xml218
-rw-r--r--components/statusbar/jar.mn15
-rw-r--r--components/statusbar/moz.build25
-rw-r--r--components/statusbar/status4evar.idl57
-rw-r--r--components/statusbar/status4evar.js695
-rw-r--r--components/statusbar/status4evar.manifest3
-rw-r--r--components/sync/aboutSyncTabs-bindings.xml46
-rw-r--r--components/sync/aboutSyncTabs.css11
-rw-r--r--components/sync/aboutSyncTabs.js313
-rw-r--r--components/sync/aboutSyncTabs.xul68
-rw-r--r--components/sync/addDevice.js157
-rw-r--r--components/sync/addDevice.xul129
-rw-r--r--components/sync/genericChange.js234
-rw-r--r--components/sync/genericChange.xul123
-rw-r--r--components/sync/jar.mn22
-rw-r--r--components/sync/key.xhtml54
-rw-r--r--components/sync/moz.build8
-rw-r--r--components/sync/notification.xml129
-rw-r--r--components/sync/progress.js71
-rw-r--r--components/sync/progress.xhtml55
-rw-r--r--components/sync/quota.js247
-rw-r--r--components/sync/quota.xul65
-rw-r--r--components/sync/setup.js1071
-rw-r--r--components/sync/setup.xul491
-rw-r--r--components/sync/utils.js218
-rw-r--r--config/mozconfig9
-rw-r--r--config/mozconfigs/common7
-rw-r--r--config/mozconfigs/linux32/beta7
-rw-r--r--config/mozconfigs/linux32/common-opt19
-rw-r--r--config/mozconfigs/linux32/debug22
-rw-r--r--config/mozconfigs/linux32/debug-asan20
-rw-r--r--config/mozconfigs/linux32/l10n-mozconfig12
-rw-r--r--config/mozconfigs/linux32/release13
-rw-r--r--config/mozconfigs/linux32/valgrind11
-rw-r--r--config/mozconfigs/linux64/beta7
-rw-r--r--config/mozconfigs/linux64/common-opt19
-rw-r--r--config/mozconfigs/linux64/debug22
-rw-r--r--config/mozconfigs/linux64/debug-asan20
-rw-r--r--config/mozconfigs/linux64/debug-static-analysis-clang15
-rw-r--r--config/mozconfigs/linux64/l10n-mozconfig12
-rw-r--r--config/mozconfigs/linux64/release13
-rw-r--r--config/mozconfigs/linux64/valgrind11
-rw-r--r--config/mozconfigs/macosx-universal/beta5
-rw-r--r--config/mozconfigs/macosx-universal/common-opt19
-rw-r--r--config/mozconfigs/macosx-universal/l10n-mozconfig11
-rw-r--r--config/mozconfigs/macosx-universal/release11
-rw-r--r--config/mozconfigs/macosx64/debug19
-rw-r--r--config/mozconfigs/macosx64/debug-asan16
-rw-r--r--config/mozconfigs/macosx64/l10n-mozconfig8
-rw-r--r--config/mozconfigs/win32/beta7
-rw-r--r--config/mozconfigs/win32/common-opt33
-rw-r--r--config/mozconfigs/win32/debug26
-rw-r--r--config/mozconfigs/win32/l10n-mozconfig16
-rw-r--r--config/mozconfigs/win32/release13
-rw-r--r--config/mozconfigs/win64/debug22
-rw-r--r--config/mozconfigs/win64/nightly26
-rw-r--r--config/tooltool-manifests/linux32/clang.manifest17
-rw-r--r--config/tooltool-manifests/linux32/releng.manifest1
-rw-r--r--config/tooltool-manifests/linux64/clang.manifest17
-rw-r--r--config/tooltool-manifests/linux64/releng.manifest1
-rw-r--r--config/tooltool-manifests/macosx64/releng.manifest17
-rw-r--r--config/version.txt1
-rw-r--r--configure.in37
-rw-r--r--confvars.sh104
-rw-r--r--defs.mk1
-rw-r--r--fonts/README.txt9
-rw-r--r--fonts/TwemojiMozilla.ttfbin0 -> 1158828 bytes
-rw-r--r--fonts/moz.build9
-rw-r--r--installer/Makefile.in189
-rw-r--r--installer/moz.build6
-rw-r--r--installer/package-manifest.in337
-rw-r--r--installer/removed-files.in116
-rw-r--r--installer/windows/Makefile.in69
-rw-r--r--installer/windows/app.tag4
-rw-r--r--installer/windows/moz.build15
-rw-r--r--installer/windows/nsis/defines.nsi.in65
-rw-r--r--installer/windows/nsis/installer.nsi1162
-rw-r--r--installer/windows/nsis/shared.nsh1306
-rw-r--r--installer/windows/nsis/uninstaller.nsi557
-rw-r--r--installer/windows/nsis/updater_append.ini12
-rw-r--r--locales/Makefile.in222
-rw-r--r--locales/all-locales97
-rw-r--r--locales/en-US/chrome/browser-region/region.properties38
-rw-r--r--locales/en-US/chrome/browser/aboutCertError.dtd40
-rw-r--r--locales/en-US/chrome/browser/aboutDialog.dtd91
-rw-r--r--locales/en-US/chrome/browser/aboutHome.dtd26
-rw-r--r--locales/en-US/chrome/browser/aboutPrivateBrowsing.dtd21
-rw-r--r--locales/en-US/chrome/browser/aboutSessionRestore.dtd23
-rw-r--r--locales/en-US/chrome/browser/aboutSyncTabs.dtd21
-rw-r--r--locales/en-US/chrome/browser/baseMenuOverlay.dtd45
-rw-r--r--locales/en-US/chrome/browser/browser.dtd608
-rw-r--r--locales/en-US/chrome/browser/browser.properties420
-rw-r--r--locales/en-US/chrome/browser/charsetMenu.dtd18
-rw-r--r--locales/en-US/chrome/browser/charsetMenu.properties103
-rw-r--r--locales/en-US/chrome/browser/charsetOverlay.dtd23
-rw-r--r--locales/en-US/chrome/browser/downloads/downloads.dtd96
-rw-r--r--locales/en-US/chrome/browser/downloads/downloads.properties78
-rw-r--r--locales/en-US/chrome/browser/engineManager.dtd29
-rw-r--r--locales/en-US/chrome/browser/engineManager.properties9
-rw-r--r--locales/en-US/chrome/browser/feeds/subscribe.dtd10
-rw-r--r--locales/en-US/chrome/browser/feeds/subscribe.properties53
-rw-r--r--locales/en-US/chrome/browser/newTab.dtd11
-rw-r--r--locales/en-US/chrome/browser/newTab.properties7
-rw-r--r--locales/en-US/chrome/browser/openLocation.dtd14
-rw-r--r--locales/en-US/chrome/browser/openLocation.properties5
-rw-r--r--locales/en-US/chrome/browser/pageInfo.dtd92
-rw-r--r--locales/en-US/chrome/browser/pageInfo.properties56
-rw-r--r--locales/en-US/chrome/browser/palemoon.dtd15
-rw-r--r--locales/en-US/chrome/browser/permissions/aboutPermissions.dtd50
-rw-r--r--locales/en-US/chrome/browser/permissions/aboutPermissions.properties14
-rw-r--r--locales/en-US/chrome/browser/places/bookmarkProperties.properties19
-rw-r--r--locales/en-US/chrome/browser/places/editBookmarkOverlay.dtd28
-rw-r--r--locales/en-US/chrome/browser/places/moveBookmarks.dtd9
-rw-r--r--locales/en-US/chrome/browser/places/places.dtd140
-rw-r--r--locales/en-US/chrome/browser/places/places.properties95
-rw-r--r--locales/en-US/chrome/browser/preferences/advanced.dtd151
-rw-r--r--locales/en-US/chrome/browser/preferences/applicationManager.dtd8
-rw-r--r--locales/en-US/chrome/browser/preferences/applicationManager.properties14
-rw-r--r--locales/en-US/chrome/browser/preferences/applications.dtd14
-rw-r--r--locales/en-US/chrome/browser/preferences/colors.dtd30
-rw-r--r--locales/en-US/chrome/browser/preferences/connection.dtd49
-rw-r--r--locales/en-US/chrome/browser/preferences/content.dtd41
-rw-r--r--locales/en-US/chrome/browser/preferences/cookies.dtd27
-rw-r--r--locales/en-US/chrome/browser/preferences/fonts.dtd107
-rw-r--r--locales/en-US/chrome/browser/preferences/languages.dtd19
-rw-r--r--locales/en-US/chrome/browser/preferences/main.dtd44
-rw-r--r--locales/en-US/chrome/browser/preferences/permissions.dtd28
-rw-r--r--locales/en-US/chrome/browser/preferences/preferences.dtd23
-rw-r--r--locales/en-US/chrome/browser/preferences/preferences.properties153
-rw-r--r--locales/en-US/chrome/browser/preferences/privacy.dtd83
-rw-r--r--locales/en-US/chrome/browser/preferences/security.dtd49
-rw-r--r--locales/en-US/chrome/browser/preferences/selectBookmark.dtd9
-rw-r--r--locales/en-US/chrome/browser/preferences/sync.dtd47
-rw-r--r--locales/en-US/chrome/browser/preferences/tabs.dtd36
-rw-r--r--locales/en-US/chrome/browser/quitDialog.properties13
-rw-r--r--locales/en-US/chrome/browser/safeMode.dtd27
-rw-r--r--locales/en-US/chrome/browser/sanitize.dtd64
-rw-r--r--locales/en-US/chrome/browser/search.properties18
-rw-r--r--locales/en-US/chrome/browser/searchbar.dtd6
-rw-r--r--locales/en-US/chrome/browser/setDesktopBackground.dtd15
-rw-r--r--locales/en-US/chrome/browser/shellservice.properties13
-rw-r--r--locales/en-US/chrome/browser/statusbar/meta.properties9
-rw-r--r--locales/en-US/chrome/browser/statusbar/overlay.properties17
-rw-r--r--locales/en-US/chrome/browser/statusbar/prefs.properties4
-rw-r--r--locales/en-US/chrome/browser/statusbar/statusbar-overlay.dtd10
-rw-r--r--locales/en-US/chrome/browser/statusbar/statusbar-prefs.dtd99
-rw-r--r--locales/en-US/chrome/browser/syncBrand.dtd6
-rw-r--r--locales/en-US/chrome/browser/syncGenericChange.properties37
-rw-r--r--locales/en-US/chrome/browser/syncKey.dtd18
-rw-r--r--locales/en-US/chrome/browser/syncProgress.dtd15
-rw-r--r--locales/en-US/chrome/browser/syncQuota.dtd8
-rw-r--r--locales/en-US/chrome/browser/syncQuota.properties42
-rw-r--r--locales/en-US/chrome/browser/syncSetup.dtd116
-rw-r--r--locales/en-US/chrome/browser/syncSetup.properties51
-rw-r--r--locales/en-US/chrome/browser/tabbrowser.dtd6
-rw-r--r--locales/en-US/chrome/browser/tabbrowser.properties30
-rw-r--r--locales/en-US/chrome/browser/taskbar.properties12
-rw-r--r--locales/en-US/chrome/overrides/appstrings.properties37
-rw-r--r--locales/en-US/chrome/overrides/netError.dtd254
-rw-r--r--locales/en-US/chrome/overrides/settingsChange.dtd7
-rw-r--r--locales/en-US/crashreporter/crashreporter-override.ini9
-rw-r--r--locales/en-US/defines.inc12
-rw-r--r--locales/en-US/installer/custom.properties85
-rw-r--r--locales/en-US/installer/mui.properties61
-rw-r--r--locales/en-US/installer/override.properties86
-rw-r--r--locales/en-US/palemoon-l10n.js7
-rw-r--r--locales/en-US/profile/bookmarks.inc40
-rw-r--r--locales/en-US/profile/chrome/userChrome-example.css50
-rw-r--r--locales/en-US/profile/chrome/userContent-example.css32
-rw-r--r--locales/en-US/searchplugins/amazondotcom.xml15
-rw-r--r--locales/en-US/searchplugins/answers.xml16
-rw-r--r--locales/en-US/searchplugins/bing.xml18
-rw-r--r--locales/en-US/searchplugins/creativecommons.xml14
-rw-r--r--locales/en-US/searchplugins/duckduckgo-palemoon.xml16
-rw-r--r--locales/en-US/searchplugins/eBay.xml19
-rw-r--r--locales/en-US/searchplugins/ecosia.xml12
-rw-r--r--locales/en-US/searchplugins/list.txt6
-rw-r--r--locales/en-US/searchplugins/twitter.xml15
-rw-r--r--locales/en-US/searchplugins/wikipedia.xml18
-rw-r--r--locales/en-US/searchplugins/yahoo.xml17
-rw-r--r--locales/en-US/updater/updater.ini10
-rw-r--r--locales/filter.py37
-rw-r--r--locales/generic/extract-bookmarks.py62
-rw-r--r--locales/generic/profile/bookmarks.html.in19
-rw-r--r--locales/generic/profile/localstore.rdf9
-rw-r--r--locales/generic/profile/mimeTypes.rdf17
-rw-r--r--locales/jar.mn97
-rw-r--r--locales/l10n.ini22
-rw-r--r--locales/moz.build7
-rw-r--r--locales/shipped-locales90
-rw-r--r--modules/AboutHomeUtils.jsm67
-rw-r--r--modules/AutoCompletePopup.jsm293
-rw-r--r--modules/BrowserNewTabPreloader.jsm436
-rw-r--r--modules/CharsetMenu.jsm160
-rw-r--r--modules/FormSubmitObserver.jsm235
-rw-r--r--modules/FormValidationHandler.jsm157
-rw-r--r--modules/NetworkPrioritizer.jsm179
-rw-r--r--modules/PageMenu.jsm238
-rw-r--r--modules/PopupNotifications.jsm994
-rw-r--r--modules/QuotaManager.jsm51
-rw-r--r--modules/RecentWindow.jsm68
-rw-r--r--modules/SharedFrame.jsm221
-rw-r--r--modules/Windows8WindowFrameColor.jsm53
-rw-r--r--modules/WindowsJumpLists.jsm581
-rw-r--r--modules/WindowsPreviewPerTab.jsm861
-rw-r--r--modules/moz.build42
-rw-r--r--modules/offlineAppCache.jsm20
-rw-r--r--modules/openLocationLastURL.jsm85
-rw-r--r--modules/webrtcUI.jsm292
-rw-r--r--moz.build24
-rw-r--r--moz.configure7
-rw-r--r--mozconfig.example42
-rw-r--r--themes/LICENSE2
-rw-r--r--themes/linux/Geolocation-16.pngbin0 -> 606 bytes
-rw-r--r--themes/linux/Geolocation-64.pngbin0 -> 8056 bytes
-rw-r--r--themes/linux/Go-arrow.pngbin0 -> 573 bytes
-rw-r--r--themes/linux/Info.pngbin0 -> 767 bytes
-rw-r--r--themes/linux/KUI-close.pngbin0 -> 393 bytes
-rw-r--r--themes/linux/Makefile.in8
-rw-r--r--themes/linux/Privacy-16.pngbin0 -> 822 bytes
-rw-r--r--themes/linux/Privacy-32.pngbin0 -> 2085 bytes
-rw-r--r--themes/linux/Privacy-48.pngbin0 -> 3422 bytes
-rw-r--r--themes/linux/Privacy-64.pngbin0 -> 4828 bytes
-rw-r--r--themes/linux/Secure.pngbin0 -> 865 bytes
-rw-r--r--themes/linux/Security-broken.pngbin0 -> 928 bytes
-rw-r--r--themes/linux/Toolbar-small.pngbin0 -> 5429 bytes
-rw-r--r--themes/linux/Toolbar.pngbin0 -> 8925 bytes
-rw-r--r--themes/linux/aboutCertError.css73
-rw-r--r--themes/linux/aboutCertError_sectionCollapsed-rtl.pngbin0 -> 791 bytes
-rw-r--r--themes/linux/aboutCertError_sectionCollapsed.pngbin0 -> 776 bytes
-rw-r--r--themes/linux/aboutCertError_sectionExpanded.pngbin0 -> 767 bytes
-rw-r--r--themes/linux/aboutPrivateBrowsing.css47
-rw-r--r--themes/linux/aboutSessionRestore-window-icon.pngbin0 -> 405 bytes
-rw-r--r--themes/linux/aboutSessionRestore.css90
-rw-r--r--themes/linux/aboutSyncTabs.css101
-rw-r--r--themes/linux/actionicon-tab.pngbin0 -> 236 bytes
-rw-r--r--themes/linux/autocomplete.css210
-rw-r--r--themes/linux/browser.css2210
-rw-r--r--themes/linux/click-to-play-warning-stripes.pngbin0 -> 1563 bytes
-rw-r--r--themes/linux/communicator/communicator.css6
-rw-r--r--themes/linux/communicator/jar.mn7
-rw-r--r--themes/linux/communicator/moz.build7
-rw-r--r--themes/linux/downloads/allDownloadsViewOverlay.css125
-rw-r--r--themes/linux/downloads/buttons.pngbin0 -> 5091 bytes
-rw-r--r--themes/linux/downloads/contentAreaDownloadsView.css11
-rw-r--r--themes/linux/downloads/download-glow-small.pngbin0 -> 556 bytes
-rw-r--r--themes/linux/downloads/download-glow.pngbin0 -> 723 bytes
-rw-r--r--themes/linux/downloads/download-notification-finish.pngbin0 -> 3626 bytes
-rw-r--r--themes/linux/downloads/download-notification-start.pngbin0 -> 3166 bytes
-rw-r--r--themes/linux/downloads/download-summary.pngbin0 -> 691 bytes
-rw-r--r--themes/linux/downloads/downloads.css376
-rw-r--r--themes/linux/engineManager.css16
-rw-r--r--themes/linux/feeds/feedIcon.pngbin0 -> 1794 bytes
-rw-r--r--themes/linux/feeds/feedIcon16.pngbin0 -> 799 bytes
-rw-r--r--themes/linux/feeds/subscribe-ui.css29
-rw-r--r--themes/linux/feeds/subscribe.css163
-rw-r--r--themes/linux/icon.pngbin0 -> 2185 bytes
-rw-r--r--themes/linux/identity-icons-generic.pngbin0 -> 965 bytes
-rw-r--r--themes/linux/identity-icons-https-ev.pngbin0 -> 708 bytes
-rw-r--r--themes/linux/identity-icons-https-mixed-active.pngbin0 -> 984 bytes
-rw-r--r--themes/linux/identity-icons-https.pngbin0 -> 672 bytes
-rw-r--r--themes/linux/identity.pngbin0 -> 9690 bytes
-rw-r--r--themes/linux/imagedocument.pngbin0 -> 2185 bytes
-rw-r--r--themes/linux/jar.mn142
-rw-r--r--themes/linux/mixed-content-blocked-16.pngbin0 -> 346 bytes
-rw-r--r--themes/linux/mixed-content-blocked-64.pngbin0 -> 2063 bytes
-rw-r--r--themes/linux/monitor.pngbin0 -> 6217 bytes
-rw-r--r--themes/linux/monitor_16-10.pngbin0 -> 6787 bytes
-rw-r--r--themes/linux/moz.build9
-rw-r--r--themes/linux/newtab/newTab.css27
-rw-r--r--themes/linux/page-livemarks.pngbin0 -> 799 bytes
-rw-r--r--themes/linux/pageInfo.css276
-rw-r--r--themes/linux/pageInfo.pngbin0 -> 8849 bytes
-rw-r--r--themes/linux/permissions/aboutPermissions.css149
-rw-r--r--themes/linux/places/bookmarksMenu.pngbin0 -> 461 bytes
-rw-r--r--themes/linux/places/bookmarksToolbar.pngbin0 -> 508 bytes
-rw-r--r--themes/linux/places/calendar.pngbin0 -> 670 bytes
-rw-r--r--themes/linux/places/downloads.pngbin0 -> 599 bytes
-rw-r--r--themes/linux/places/editBookmarkOverlay.css71
-rw-r--r--themes/linux/places/livemark-item.pngbin0 -> 863 bytes
-rw-r--r--themes/linux/places/organizer.css107
-rw-r--r--themes/linux/places/organizer.xml21
-rw-r--r--themes/linux/places/pageStarred.pngbin0 -> 767 bytes
-rw-r--r--themes/linux/places/places.css221
-rw-r--r--themes/linux/places/query.pngbin0 -> 678 bytes
-rw-r--r--themes/linux/places/star-icons.pngbin0 -> 1106 bytes
-rw-r--r--themes/linux/places/starPage.pngbin0 -> 723 bytes
-rw-r--r--themes/linux/places/starred48.pngbin0 -> 2658 bytes
-rw-r--r--themes/linux/places/tag.pngbin0 -> 877 bytes
-rw-r--r--themes/linux/places/toolbarDropMarker.pngbin0 -> 583 bytes
-rw-r--r--themes/linux/places/unsortedBookmarks.pngbin0 -> 748 bytes
-rw-r--r--themes/linux/places/unstarred48.pngbin0 -> 2255 bytes
-rw-r--r--themes/linux/pointerLock-16.pngbin0 -> 249 bytes
-rw-r--r--themes/linux/pointerLock-64.pngbin0 -> 1119 bytes
-rw-r--r--themes/linux/preferences/Options-sync.pngbin0 -> 3585 bytes
-rw-r--r--themes/linux/preferences/Options.pngbin0 -> 12680 bytes
-rw-r--r--themes/linux/preferences/alwaysAsk.pngbin0 -> 575 bytes
-rw-r--r--themes/linux/preferences/applications.css66
-rw-r--r--themes/linux/preferences/mail.pngbin0 -> 548 bytes
-rw-r--r--themes/linux/preferences/preferences.css156
-rw-r--r--themes/linux/privatebrowsing-mask.pngbin0 -> 1355 bytes
-rw-r--r--themes/linux/sanitizeDialog.css107
-rw-r--r--themes/linux/searchbar.css72
-rw-r--r--themes/linux/setDesktopBackground.css18
-rw-r--r--themes/linux/slowStartup-16.pngbin0 -> 478 bytes
-rw-r--r--themes/linux/statusbar/overlay.css114
-rw-r--r--themes/linux/statusbar/prefs.css8
-rw-r--r--themes/linux/sync-128.pngbin0 -> 20229 bytes
-rw-r--r--themes/linux/sync-16-throbber.pngbin0 -> 10365 bytes
-rw-r--r--themes/linux/sync-16.pngbin0 -> 1847 bytes
-rw-r--r--themes/linux/sync-24-throbber.pngbin0 -> 15774 bytes
-rw-r--r--themes/linux/sync-32.pngbin0 -> 3384 bytes
-rw-r--r--themes/linux/sync-bg.pngbin0 -> 21309 bytes
-rw-r--r--themes/linux/sync-desktopIcon.pngbin0 -> 291 bytes
-rw-r--r--themes/linux/sync-mobileIcon.pngbin0 -> 352 bytes
-rw-r--r--themes/linux/syncCommon.css49
-rw-r--r--themes/linux/syncProgress.css46
-rw-r--r--themes/linux/syncQuota.css26
-rw-r--r--themes/linux/syncSetup.css127
-rw-r--r--themes/linux/tabbrowser/alltabs.pngbin0 -> 192 bytes
-rw-r--r--themes/linux/tabbrowser/connecting.pngbin0 -> 8540 bytes
-rw-r--r--themes/linux/tabbrowser/loading.pngbin0 -> 12184 bytes
-rw-r--r--themes/linux/tabbrowser/tab-overflow-border.pngbin0 -> 193 bytes
-rw-r--r--themes/linux/tabbrowser/tab.pngbin0 -> 353 bytes
-rw-r--r--themes/linux/tabbrowser/tabDragIndicator.pngbin0 -> 450 bytes
-rw-r--r--themes/linux/urlbar-arrow.pngbin0 -> 305 bytes
-rw-r--r--themes/linux/web-notifications-icon.svg15
-rw-r--r--themes/linux/web-notifications-tray.svg23
-rw-r--r--themes/linux/webRTC-shareDevice-16.pngbin0 -> 224 bytes
-rw-r--r--themes/linux/webRTC-shareDevice-64.pngbin0 -> 1097 bytes
-rw-r--r--themes/linux/webRTC-sharingDevice-16.pngbin0 -> 404 bytes
-rw-r--r--themes/moz.build14
-rw-r--r--themes/osx/Geolocation-16.pngbin0 -> 704 bytes
-rw-r--r--themes/osx/Geolocation-64.pngbin0 -> 8424 bytes
-rw-r--r--themes/osx/Info.pngbin0 -> 641 bytes
-rw-r--r--themes/osx/KUI-background.pngbin0 -> 222 bytes
-rw-r--r--themes/osx/KUI-close.pngbin0 -> 393 bytes
-rw-r--r--themes/osx/Makefile.in7
-rw-r--r--themes/osx/Privacy-16.pngbin0 -> 800 bytes
-rw-r--r--themes/osx/Privacy-32.pngbin0 -> 1995 bytes
-rw-r--r--themes/osx/Privacy-48.pngbin0 -> 3884 bytes
-rw-r--r--themes/osx/Privacy-64.pngbin0 -> 8140 bytes
-rw-r--r--themes/osx/Search-glass.pngbin0 -> 1448 bytes
-rw-r--r--themes/osx/Secure24.pngbin0 -> 1098 bytes
-rw-r--r--themes/osx/Toolbar-glass.pngbin0 -> 18355 bytes
-rw-r--r--themes/osx/Toolbar-inverted.pngbin0 -> 4653 bytes
-rw-r--r--themes/osx/Toolbar.pngbin0 -> 15505 bytes
-rw-r--r--themes/osx/aboutCertError.css73
-rw-r--r--themes/osx/aboutCertError_sectionCollapsed-rtl.pngbin0 -> 791 bytes
-rw-r--r--themes/osx/aboutCertError_sectionCollapsed.pngbin0 -> 776 bytes
-rw-r--r--themes/osx/aboutCertError_sectionExpanded.pngbin0 -> 767 bytes
-rw-r--r--themes/osx/aboutPrivateBrowsing.css47
-rw-r--r--themes/osx/aboutSessionRestore.css73
-rw-r--r--themes/osx/aboutSyncTabs.css101
-rw-r--r--themes/osx/actionicon-tab.pngbin0 -> 425 bytes
-rw-r--r--themes/osx/appmenu-dropmarker.pngbin0 -> 262 bytes
-rw-r--r--themes/osx/appmenu-icons.pngbin0 -> 2115 bytes
-rw-r--r--themes/osx/autocomplete.css198
-rw-r--r--themes/osx/browser.css2802
-rw-r--r--themes/osx/click-to-play-warning-stripes.pngbin0 -> 1563 bytes
-rw-r--r--themes/osx/communicator/communicator.css6
-rw-r--r--themes/osx/communicator/jar.mn7
-rw-r--r--themes/osx/communicator/moz.build7
-rw-r--r--themes/osx/downloads/allDownloadsViewOverlay.css146
-rw-r--r--themes/osx/downloads/buttons.pngbin0 -> 6881 bytes
-rw-r--r--themes/osx/downloads/contentAreaDownloadsView.css22
-rw-r--r--themes/osx/downloads/download-glow.pngbin0 -> 546 bytes
-rw-r--r--themes/osx/downloads/download-notification-finish.pngbin0 -> 3755 bytes
-rw-r--r--themes/osx/downloads/download-notification-start.pngbin0 -> 3166 bytes
-rw-r--r--themes/osx/downloads/download-summary.pngbin0 -> 741 bytes
-rw-r--r--themes/osx/downloads/downloads.css394
-rw-r--r--themes/osx/engineManager.css16
-rw-r--r--themes/osx/feeds/feed-icons-16.pngbin0 -> 2187 bytes
-rw-r--r--themes/osx/feeds/feedIcon.pngbin0 -> 1833 bytes
-rw-r--r--themes/osx/feeds/feedIcon16.pngbin0 -> 791 bytes
-rw-r--r--themes/osx/feeds/subscribe-ui.css29
-rw-r--r--themes/osx/feeds/subscribe.css159
-rw-r--r--themes/osx/icon.pngbin0 -> 2185 bytes
-rw-r--r--themes/osx/identity-icons-generic.pngbin0 -> 965 bytes
-rw-r--r--themes/osx/identity-icons-https-ev.pngbin0 -> 708 bytes
-rw-r--r--themes/osx/identity-icons-https-mixed-active.pngbin0 -> 984 bytes
-rw-r--r--themes/osx/identity-icons-https.pngbin0 -> 672 bytes
-rw-r--r--themes/osx/identity.pngbin0 -> 10508 bytes
-rw-r--r--themes/osx/imagedocument.pngbin0 -> 2185 bytes
-rw-r--r--themes/osx/jar.mn181
-rw-r--r--themes/osx/keyhole-forward-mask.svg15
-rw-r--r--themes/osx/livemark-folder.pngbin0 -> 680 bytes
-rw-r--r--themes/osx/menu-back.pngbin0 -> 341 bytes
-rw-r--r--themes/osx/menu-forward.pngbin0 -> 343 bytes
-rw-r--r--themes/osx/mixed-content-blocked-16.pngbin0 -> 346 bytes
-rw-r--r--themes/osx/mixed-content-blocked-64.pngbin0 -> 2063 bytes
-rw-r--r--themes/osx/monitor.pngbin0 -> 6217 bytes
-rw-r--r--themes/osx/monitor_16-10.pngbin0 -> 6787 bytes
-rw-r--r--themes/osx/moz.build9
-rw-r--r--themes/osx/newtab/newTab.css29
-rw-r--r--themes/osx/page-livemarks.pngbin0 -> 683 bytes
-rw-r--r--themes/osx/page-livemarks@2x.pngbin0 -> 1167 bytes
-rw-r--r--themes/osx/pageInfo.css258
-rw-r--r--themes/osx/pageInfo.pngbin0 -> 8118 bytes
-rw-r--r--themes/osx/panel-expander-closed.pngbin0 -> 155 bytes
-rw-r--r--themes/osx/panel-expander-closed@2x.pngbin0 -> 362 bytes
-rw-r--r--themes/osx/panel-expander-open.pngbin0 -> 155 bytes
-rw-r--r--themes/osx/panel-expander-open@2x.pngbin0 -> 356 bytes
-rw-r--r--themes/osx/panel-plus-sign.pngbin0 -> 212 bytes
-rw-r--r--themes/osx/permissions/aboutPermissions.css153
-rw-r--r--themes/osx/places/allBookmarks.pngbin0 -> 673 bytes
-rw-r--r--themes/osx/places/bookmark.pngbin0 -> 1779 bytes
-rw-r--r--themes/osx/places/bookmarksMenu.pngbin0 -> 353 bytes
-rw-r--r--themes/osx/places/bookmarksToolbar.pngbin0 -> 524 bytes
-rw-r--r--themes/osx/places/bookmarksToolbar@2x.pngbin0 -> 1179 bytes
-rw-r--r--themes/osx/places/calendar.pngbin0 -> 614 bytes
-rw-r--r--themes/osx/places/downloads.pngbin0 -> 678 bytes
-rw-r--r--themes/osx/places/editBookmark.pngbin0 -> 1642 bytes
-rw-r--r--themes/osx/places/editBookmarkOverlay.css105
-rw-r--r--themes/osx/places/expander-closed-active.pngbin0 -> 1329 bytes
-rw-r--r--themes/osx/places/expander-closed.pngbin0 -> 837 bytes
-rw-r--r--themes/osx/places/expander-open-active.pngbin0 -> 1329 bytes
-rw-r--r--themes/osx/places/expander-open.pngbin0 -> 818 bytes
-rw-r--r--themes/osx/places/folderDropArrow.pngbin0 -> 201 bytes
-rw-r--r--themes/osx/places/folderDropArrow@2x.pngbin0 -> 443 bytes
-rw-r--r--themes/osx/places/history.pngbin0 -> 843 bytes
-rw-r--r--themes/osx/places/history@2x.pngbin0 -> 1872 bytes
-rw-r--r--themes/osx/places/libraryToolbar.pngbin0 -> 2217 bytes
-rw-r--r--themes/osx/places/livemark-item.pngbin0 -> 863 bytes
-rw-r--r--themes/osx/places/organizer.css134
-rw-r--r--themes/osx/places/places.css146
-rw-r--r--themes/osx/places/query.pngbin0 -> 549 bytes
-rw-r--r--themes/osx/places/query@2x.pngbin0 -> 1055 bytes
-rw-r--r--themes/osx/places/starred48.pngbin0 -> 1785 bytes
-rw-r--r--themes/osx/places/tag.pngbin0 -> 789 bytes
-rw-r--r--themes/osx/places/tag@2x.pngbin0 -> 1593 bytes
-rw-r--r--themes/osx/places/toolbarDropMarker.pngbin0 -> 302 bytes
-rw-r--r--themes/osx/places/unfiledBookmarks.pngbin0 -> 586 bytes
-rw-r--r--themes/osx/places/unfiledBookmarks@2x.pngbin0 -> 1289 bytes
-rw-r--r--themes/osx/places/unsortedBookmarks.pngbin0 -> 780 bytes
-rw-r--r--themes/osx/places/unstarred48.pngbin0 -> 818 bytes
-rw-r--r--themes/osx/pointerLock-16.pngbin0 -> 249 bytes
-rw-r--r--themes/osx/pointerLock-64.pngbin0 -> 1119 bytes
-rw-r--r--themes/osx/preferences/Options-sync.pngbin0 -> 3585 bytes
-rw-r--r--themes/osx/preferences/Options.pngbin0 -> 9077 bytes
-rw-r--r--themes/osx/preferences/alwaysAsk.pngbin0 -> 446 bytes
-rw-r--r--themes/osx/preferences/application.pngbin0 -> 441 bytes
-rw-r--r--themes/osx/preferences/applications.css64
-rw-r--r--themes/osx/preferences/mail.pngbin0 -> 630 bytes
-rw-r--r--themes/osx/preferences/preferences.css142
-rw-r--r--themes/osx/preferences/saveFile.pngbin0 -> 791 bytes
-rw-r--r--themes/osx/privatebrowsing-dark.pngbin0 -> 1355 bytes
-rw-r--r--themes/osx/privatebrowsing-light.pngbin0 -> 696 bytes
-rw-r--r--themes/osx/privatebrowsing-mask.pngbin0 -> 1074 bytes
-rw-r--r--themes/osx/privatebrowsing-mask@2x.pngbin0 -> 2639 bytes
-rw-r--r--themes/osx/reload-stop-go.pngbin0 -> 1945 bytes
-rw-r--r--themes/osx/sanitizeDialog.css93
-rw-r--r--themes/osx/searchbar-dropdown-arrow.pngbin0 -> 509 bytes
-rw-r--r--themes/osx/searchbar.css79
-rw-r--r--themes/osx/setDesktopBackground.css18
-rw-r--r--themes/osx/shared.inc6
-rw-r--r--themes/osx/slowStartup-16.pngbin0 -> 512 bytes
-rw-r--r--themes/osx/statusbar/overlay.css108
-rw-r--r--themes/osx/statusbar/prefs.css13
-rw-r--r--themes/osx/sync-128.pngbin0 -> 20229 bytes
-rw-r--r--themes/osx/sync-16.pngbin0 -> 1847 bytes
-rw-r--r--themes/osx/sync-32.pngbin0 -> 3384 bytes
-rw-r--r--themes/osx/sync-bg.pngbin0 -> 21309 bytes
-rw-r--r--themes/osx/sync-desktopIcon.pngbin0 -> 291 bytes
-rw-r--r--themes/osx/sync-mobileIcon.pngbin0 -> 352 bytes
-rw-r--r--themes/osx/sync-throbber.pngbin0 -> 10362 bytes
-rw-r--r--themes/osx/syncCommon.css49
-rw-r--r--themes/osx/syncProgress.css46
-rw-r--r--themes/osx/syncQuota.css26
-rw-r--r--themes/osx/syncSetup.css132
-rw-r--r--themes/osx/tabbrowser/alltabs-inverted.pngbin0 -> 469 bytes
-rw-r--r--themes/osx/tabbrowser/alltabs.pngbin0 -> 584 bytes
-rw-r--r--themes/osx/tabbrowser/connecting.pngbin0 -> 8540 bytes
-rw-r--r--themes/osx/tabbrowser/loading.pngbin0 -> 10727 bytes
-rw-r--r--themes/osx/tabbrowser/newtab-glass.pngbin0 -> 398 bytes
-rw-r--r--themes/osx/tabbrowser/newtab-inverted.pngbin0 -> 247 bytes
-rw-r--r--themes/osx/tabbrowser/newtab.pngbin0 -> 237 bytes
-rw-r--r--themes/osx/tabbrowser/tab-arrow-left-glass.pngbin0 -> 331 bytes
-rw-r--r--themes/osx/tabbrowser/tab-arrow-left-inverted.pngbin0 -> 250 bytes
-rw-r--r--themes/osx/tabbrowser/tab-arrow-left.pngbin0 -> 368 bytes
-rw-r--r--themes/osx/tabbrowser/tab-overflow-border.pngbin0 -> 193 bytes
-rw-r--r--themes/osx/tabbrowser/tabDragIndicator.pngbin0 -> 3117 bytes
-rw-r--r--themes/osx/toolbarbutton-dropdown-arrow-inverted.pngbin0 -> 221 bytes
-rw-r--r--themes/osx/toolbarbutton-dropdown-arrow.pngbin0 -> 287 bytes
-rw-r--r--themes/osx/urlbar-arrow.pngbin0 -> 305 bytes
-rw-r--r--themes/osx/urlbar-history-dropmarker.pngbin0 -> 480 bytes
-rw-r--r--themes/osx/urlbar-popup-blocked.pngbin0 -> 745 bytes
-rw-r--r--themes/osx/web-notifications-icon.svg15
-rw-r--r--themes/osx/web-notifications-tray.svg23
-rw-r--r--themes/osx/webRTC-shareDevice-16.pngbin0 -> 233 bytes
-rw-r--r--themes/osx/webRTC-shareDevice-64.pngbin0 -> 1097 bytes
-rw-r--r--themes/osx/webRTC-sharingDevice-16.pngbin0 -> 404 bytes
-rw-r--r--themes/shared/browser.inc8
-rw-r--r--themes/shared/newtab/controls.pngbin0 -> 4180 bytes
-rw-r--r--themes/shared/newtab/newTab.css.inc203
-rw-r--r--themes/shared/newtab/noise.pngbin0 -> 2118 bytes
-rw-r--r--themes/shared/newtab/pinned.pngbin0 -> 307 bytes
-rw-r--r--themes/shared/plugin-doorhanger.inc.css53
-rw-r--r--themes/shared/plugins/notification-pluginAlert.pngbin0 -> 648 bytes
-rw-r--r--themes/shared/plugins/notification-pluginAlert@2x.pngbin0 -> 1189 bytes
-rw-r--r--themes/shared/plugins/notification-pluginBlocked.pngbin0 -> 968 bytes
-rw-r--r--themes/shared/plugins/notification-pluginBlocked@2x.pngbin0 -> 2067 bytes
-rw-r--r--themes/shared/plugins/notification-pluginNormal.pngbin0 -> 340 bytes
-rw-r--r--themes/shared/plugins/notification-pluginNormal@2x.pngbin0 -> 469 bytes
-rw-r--r--themes/shared/statusbar/dynamic.css25
-rw-r--r--themes/shared/statusbar/overlay.css169
-rw-r--r--themes/shared/statusbar/pms16.pngbin0 -> 604 bytes
-rw-r--r--themes/shared/statusbar/pms24.pngbin0 -> 774 bytes
-rw-r--r--themes/shared/statusbar/prefs.css38
-rw-r--r--themes/shared/statusbar/pulse.pngbin0 -> 2775 bytes
-rw-r--r--themes/shared/statusbar/throbber-idle.pngbin0 -> 713 bytes
-rw-r--r--themes/shared/statusbar/throbberStatic.pngbin0 -> 1736 bytes
-rw-r--r--themes/shared/tabbrowser/tab-audio-small.svg58
-rw-r--r--themes/shared/tabbrowser/tab-audio.svg18
-rw-r--r--themes/windows/Geolocation-16.pngbin0 -> 704 bytes
-rw-r--r--themes/windows/Geolocation-64.pngbin0 -> 8424 bytes
-rw-r--r--themes/windows/Info.pngbin0 -> 615 bytes
-rw-r--r--themes/windows/KUI-background.pngbin0 -> 222 bytes
-rw-r--r--themes/windows/KUI-close.pngbin0 -> 393 bytes
-rw-r--r--themes/windows/Makefile.in7
-rw-r--r--themes/windows/Privacy-16.pngbin0 -> 798 bytes
-rw-r--r--themes/windows/Privacy-32.pngbin0 -> 1997 bytes
-rw-r--r--themes/windows/Privacy-48.pngbin0 -> 3912 bytes
-rw-r--r--themes/windows/Privacy-64.pngbin0 -> 8172 bytes
-rw-r--r--themes/windows/Push-16.pngbin0 -> 704 bytes
-rw-r--r--themes/windows/Push-64.pngbin0 -> 8388 bytes
-rw-r--r--themes/windows/Secure24.pngbin0 -> 1117 bytes
-rw-r--r--themes/windows/Toolbar-glass.pngbin0 -> 18803 bytes
-rw-r--r--themes/windows/Toolbar-glass.svg3218
-rw-r--r--themes/windows/Toolbar-inverted.pngbin0 -> 7101 bytes
-rw-r--r--themes/windows/Toolbar-inverted.svg302
-rw-r--r--themes/windows/Toolbar.pngbin0 -> 16015 bytes
-rw-r--r--themes/windows/Toolbar.svg1356
-rw-r--r--themes/windows/aboutCertError.css73
-rw-r--r--themes/windows/aboutCertError_sectionCollapsed-rtl.pngbin0 -> 791 bytes
-rw-r--r--themes/windows/aboutCertError_sectionCollapsed.pngbin0 -> 776 bytes
-rw-r--r--themes/windows/aboutCertError_sectionExpanded.pngbin0 -> 767 bytes
-rw-r--r--themes/windows/aboutPrivateBrowsing.css47
-rw-r--r--themes/windows/aboutSessionRestore-window-icon.pngbin0 -> 352 bytes
-rw-r--r--themes/windows/aboutSessionRestore.css73
-rw-r--r--themes/windows/aboutSyncTabs.css101
-rw-r--r--themes/windows/actionicon-tab.pngbin0 -> 425 bytes
-rw-r--r--themes/windows/appmenu-dropmarker.pngbin0 -> 262 bytes
-rw-r--r--themes/windows/appmenu-icons.pngbin0 -> 2115 bytes
-rw-r--r--themes/windows/autocomplete.css238
-rw-r--r--themes/windows/browser.css3856
-rw-r--r--themes/windows/caption-buttons.svg121
-rw-r--r--themes/windows/click-to-play-warning-stripes.pngbin0 -> 1563 bytes
-rw-r--r--themes/windows/communicator/communicator.css6
-rw-r--r--themes/windows/communicator/jar.mn7
-rw-r--r--themes/windows/communicator/moz.build7
-rw-r--r--themes/windows/downloads/allDownloadsViewOverlay.css178
-rw-r--r--themes/windows/downloads/buttons.pngbin0 -> 6881 bytes
-rw-r--r--themes/windows/downloads/contentAreaDownloadsView.css22
-rw-r--r--themes/windows/downloads/download-notification-finish.pngbin0 -> 3755 bytes
-rw-r--r--themes/windows/downloads/download-notification-start.pngbin0 -> 3166 bytes
-rw-r--r--themes/windows/downloads/download-summary.pngbin0 -> 741 bytes
-rw-r--r--themes/windows/downloads/downloads.css487
-rw-r--r--themes/windows/engineManager.css16
-rw-r--r--themes/windows/feeds/feed-icons-16.pngbin0 -> 2138 bytes
-rw-r--r--themes/windows/feeds/feedIcon.pngbin0 -> 1885 bytes
-rw-r--r--themes/windows/feeds/feedIcon16.pngbin0 -> 787 bytes
-rw-r--r--themes/windows/feeds/subscribe-ui.css29
-rw-r--r--themes/windows/feeds/subscribe.css163
-rw-r--r--themes/windows/icon.pngbin0 -> 2185 bytes
-rw-r--r--themes/windows/identity-icons-generic.pngbin0 -> 965 bytes
-rw-r--r--themes/windows/identity-icons-https-ev.pngbin0 -> 708 bytes
-rw-r--r--themes/windows/identity-icons-https-mixed-active.pngbin0 -> 984 bytes
-rw-r--r--themes/windows/identity-icons-https.pngbin0 -> 672 bytes
-rw-r--r--themes/windows/identity.pngbin0 -> 11844 bytes
-rw-r--r--themes/windows/imagedocument.pngbin0 -> 2185 bytes
-rw-r--r--themes/windows/jar.mn168
-rw-r--r--themes/windows/keyhole-forward-mask.svg15
-rw-r--r--themes/windows/livemark-folder.pngbin0 -> 626 bytes
-rw-r--r--themes/windows/menu-back.pngbin0 -> 435 bytes
-rw-r--r--themes/windows/menu-forward.pngbin0 -> 434 bytes
-rw-r--r--themes/windows/mixed-content-blocked-16.pngbin0 -> 346 bytes
-rw-r--r--themes/windows/mixed-content-blocked-64.pngbin0 -> 2063 bytes
-rw-r--r--themes/windows/monitor.pngbin0 -> 6217 bytes
-rw-r--r--themes/windows/monitor_16-10.pngbin0 -> 6787 bytes
-rw-r--r--themes/windows/moz.build9
-rw-r--r--themes/windows/newtab/newTab.css29
-rw-r--r--themes/windows/pageInfo.css268
-rw-r--r--themes/windows/pageInfo.pngbin0 -> 8432 bytes
-rw-r--r--themes/windows/permissions/aboutPermissions.css153
-rw-r--r--themes/windows/places/allBookmarks.pngbin0 -> 612 bytes
-rw-r--r--themes/windows/places/bookmark.pngbin0 -> 1779 bytes
-rw-r--r--themes/windows/places/bookmarksMenu.pngbin0 -> 346 bytes
-rw-r--r--themes/windows/places/bookmarksToolbar.pngbin0 -> 380 bytes
-rw-r--r--themes/windows/places/calendar.pngbin0 -> 637 bytes
-rw-r--r--themes/windows/places/downloads.pngbin0 -> 674 bytes
-rw-r--r--themes/windows/places/editBookmark.pngbin0 -> 1642 bytes
-rw-r--r--themes/windows/places/editBookmarkOverlay.css80
-rw-r--r--themes/windows/places/history.pngbin0 -> 871 bytes
-rw-r--r--themes/windows/places/libraryToolbar.pngbin0 -> 1331 bytes
-rw-r--r--themes/windows/places/livemark-item.pngbin0 -> 863 bytes
-rw-r--r--themes/windows/places/organizer.css253
-rw-r--r--themes/windows/places/places.css172
-rw-r--r--themes/windows/places/query.pngbin0 -> 602 bytes
-rw-r--r--themes/windows/places/starred48.pngbin0 -> 1911 bytes
-rw-r--r--themes/windows/places/tag.pngbin0 -> 676 bytes
-rw-r--r--themes/windows/places/toolbarDropMarker.pngbin0 -> 223 bytes
-rw-r--r--themes/windows/places/unsortedBookmarks.pngbin0 -> 762 bytes
-rw-r--r--themes/windows/places/unstarred48.pngbin0 -> 818 bytes
-rw-r--r--themes/windows/pointerLock-16.pngbin0 -> 249 bytes
-rw-r--r--themes/windows/pointerLock-64.pngbin0 -> 1119 bytes
-rw-r--r--themes/windows/preferences/Options-sync.pngbin0 -> 3585 bytes
-rw-r--r--themes/windows/preferences/Options.pngbin0 -> 8899 bytes
-rw-r--r--themes/windows/preferences/alwaysAsk.pngbin0 -> 439 bytes
-rw-r--r--themes/windows/preferences/application.pngbin0 -> 419 bytes
-rw-r--r--themes/windows/preferences/applications.css64
-rw-r--r--themes/windows/preferences/mail.pngbin0 -> 549 bytes
-rw-r--r--themes/windows/preferences/preferences.css146
-rw-r--r--themes/windows/preferences/saveFile.pngbin0 -> 767 bytes
-rw-r--r--themes/windows/privatebrowsing-dark.pngbin0 -> 1355 bytes
-rw-r--r--themes/windows/privatebrowsing-light.pngbin0 -> 696 bytes
-rw-r--r--themes/windows/reload-stop-go.pngbin0 -> 1945 bytes
-rw-r--r--themes/windows/sanitize.pngbin0 -> 779 bytes
-rw-r--r--themes/windows/sanitizeDialog.css93
-rw-r--r--themes/windows/searchbar-dropdown-arrow.pngbin0 -> 517 bytes
-rw-r--r--themes/windows/searchbar.css81
-rw-r--r--themes/windows/setDesktopBackground.css18
-rw-r--r--themes/windows/slowStartup-16.pngbin0 -> 512 bytes
-rw-r--r--themes/windows/statusbar/overlay.css104
-rw-r--r--themes/windows/statusbar/prefs.css7
-rw-r--r--themes/windows/sync-128.pngbin0 -> 20229 bytes
-rw-r--r--themes/windows/sync-16.pngbin0 -> 1847 bytes
-rw-r--r--themes/windows/sync-32.pngbin0 -> 3384 bytes
-rw-r--r--themes/windows/sync-bg.pngbin0 -> 21309 bytes
-rw-r--r--themes/windows/sync-desktopIcon.pngbin0 -> 291 bytes
-rw-r--r--themes/windows/sync-mobileIcon.pngbin0 -> 352 bytes
-rw-r--r--themes/windows/sync-throbber.pngbin0 -> 10362 bytes
-rw-r--r--themes/windows/syncCommon.css49
-rw-r--r--themes/windows/syncProgress.css46
-rw-r--r--themes/windows/syncQuota.css26
-rw-r--r--themes/windows/syncSetup.css132
-rw-r--r--themes/windows/tabbrowser/alltabs-inverted.pngbin0 -> 469 bytes
-rw-r--r--themes/windows/tabbrowser/alltabs.pngbin0 -> 584 bytes
-rw-r--r--themes/windows/tabbrowser/connecting.pngbin0 -> 8540 bytes
-rw-r--r--themes/windows/tabbrowser/loading.pngbin0 -> 10727 bytes
-rw-r--r--themes/windows/tabbrowser/newtab-glass.pngbin0 -> 398 bytes
-rw-r--r--themes/windows/tabbrowser/newtab-inverted.pngbin0 -> 247 bytes
-rw-r--r--themes/windows/tabbrowser/newtab.pngbin0 -> 237 bytes
-rw-r--r--themes/windows/tabbrowser/tab-arrow-left-glass.pngbin0 -> 331 bytes
-rw-r--r--themes/windows/tabbrowser/tab-arrow-left-inverted.pngbin0 -> 250 bytes
-rw-r--r--themes/windows/tabbrowser/tab-arrow-left.pngbin0 -> 368 bytes
-rw-r--r--themes/windows/tabbrowser/tab-overflow-border.pngbin0 -> 193 bytes
-rw-r--r--themes/windows/tabbrowser/tabDragIndicator.pngbin0 -> 3117 bytes
-rw-r--r--themes/windows/toolbarbutton-dropdown-arrow-inverted.pngbin0 -> 221 bytes
-rw-r--r--themes/windows/toolbarbutton-dropdown-arrow.pngbin0 -> 287 bytes
-rw-r--r--themes/windows/urlbar-arrow.pngbin0 -> 305 bytes
-rw-r--r--themes/windows/urlbar-history-dropmarker.pngbin0 -> 480 bytes
-rw-r--r--themes/windows/urlbar-popup-blocked.pngbin0 -> 745 bytes
-rw-r--r--themes/windows/web-notifications-icon.svg15
-rw-r--r--themes/windows/web-notifications-tray.svg23
-rw-r--r--themes/windows/webRTC-shareDevice-16.pngbin0 -> 233 bytes
-rw-r--r--themes/windows/webRTC-shareDevice-64.pngbin0 -> 1097 bytes
-rw-r--r--themes/windows/webRTC-sharingDevice-16.pngbin0 -> 404 bytes
1123 files changed, 147741 insertions, 0 deletions
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..e3b7160
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,7 @@
+Please see the file ../toolkit/content/license.html for the copyright
+licensing conditions attached to this codebase, including copies of the
+licenses concerned.
+
+You are not granted rights or licenses to the trademarks of the
+Mozilla Foundation, Moonchild Productions or any party, including without
+limitation the Pale Moon name or logo.
diff --git a/Makefile.in b/Makefile.in
new file mode 100644
index 0000000..92527ea
--- /dev/null
+++ b/Makefile.in
@@ -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/.
+
+include $(topsrcdir)/config/rules.mk
+
+ifdef MAKENSISU
+
+# For Windows build the uninstaller during the application build since the
+# uninstaller is included with the application for mar file generation.
+libs::
+ $(MAKE) -C installer/windows uninstaller
+endif
diff --git a/app-rules.mk b/app-rules.mk
new file mode 100644
index 0000000..2c31653
--- /dev/null
+++ b/app-rules.mk
@@ -0,0 +1 @@
+PURGECACHES_DIRS = $(DIST)/bin/browser
diff --git a/app.mozbuild b/app.mozbuild
new file mode 100644
index 0000000..5f1c0b9
--- /dev/null
+++ b/app.mozbuild
@@ -0,0 +1,17 @@
+# 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/.
+
+if not CONFIG['LIBXUL_SDK']:
+ include('/toolkit/toolkit.mozbuild')
+
+if CONFIG['MOZ_EXTENSIONS']:
+ DIRS += ['/extensions']
+
+DIRS += ['/%s' % CONFIG['MOZ_BRANDING_DIRECTORY']]
+
+# Never add tier dirs after browser because they apparently won't get
+# packaged properly on Mac.
+DIRS += ['/application/webbrowser']
+
diff --git a/app/Makefile.in b/app/Makefile.in
new file mode 100644
index 0000000..d008010
--- /dev/null
+++ b/app/Makefile.in
@@ -0,0 +1,108 @@
+# 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/.
+
+dist_dest = $(DIST)/$(MOZ_MACBUNDLE_NAME)
+
+# hardcode en-US for the moment
+AB_CD = en-US
+
+DEFINES += \
+ -DAB_CD=$(AB_CD) \
+ -DAPP_VERSION="$(MOZ_APP_VERSION)" \
+ -DFIREFOX_ICO=\"$(DIST)/branding/firefox.ico\" \
+ -DDOCUMENT_ICO=\"$(DIST)/branding/document.ico\" \
+ -DNEWWINDOW_ICO=\"$(DIST)/branding/newwindow.ico\" \
+ -DNEWTAB_ICO=\"$(DIST)/branding/newtab.ico\" \
+ -DPBMODE_ICO=\"$(DIST)/branding/pbmode.ico\" \
+ $(NULL)
+
+# Build a binary bootstrapping with XRE_main
+
+ifndef MOZ_WINCONSOLE
+ifdef MOZ_DEBUG
+MOZ_WINCONSOLE = 1
+else
+MOZ_WINCONSOLE = 0
+endif
+endif
+
+# This switches $(INSTALL) to copy mode, like $(SYSINSTALL), so things that
+# shouldn't get 755 perms need $(IFLAGS1) for either way of calling nsinstall.
+NSDISTMODE = copy
+
+include $(topsrcdir)/config/config.mk
+
+ifeq ($(OS_ARCH),WINNT)
+# Rebuild firefox.exe if the manifest changes - it's included by splash.rc.
+# (this dependency should really be just for firefox.exe, not other targets)
+EXTRA_DEPS += $(PROGRAM).manifest
+endif
+
+PROGRAMS_DEST = $(DIST)/bin
+
+include $(topsrcdir)/config/rules.mk
+
+ifneq (,$(filter-out WINNT,$(OS_ARCH)))
+
+ifdef COMPILE_ENVIRONMENT
+libs::
+ cp -p $(MOZ_APP_NAME)$(BIN_SUFFIX) $(DIST)/bin/$(MOZ_APP_NAME)-bin$(BIN_SUFFIX)
+endif
+
+GARBAGE += $(addprefix $(FINAL_TARGET)/defaults/pref/, palemoon.js)
+
+endif
+
+ifndef LIBXUL_SDK
+# channel-prefs.js is handled separate from other prefs due to bug 756325
+libs:: $(srcdir)/profile/channel-prefs.js
+ $(NSINSTALL) -D $(DIST)/bin/defaults/pref
+ $(call py_action,preprocessor,-Fsubstitution $(PREF_PPFLAGS) $(ACDEFINES) $^ -o $(DIST)/bin/defaults/pref/channel-prefs.js)
+endif
+
+ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
+
+MAC_APP_NAME = $(MOZ_APP_DISPLAYNAME)
+
+ifdef MOZ_DEBUG
+MAC_APP_NAME := $(MAC_APP_NAME)Debug
+endif
+
+AB_CD = $(MOZ_UI_LOCALE)
+
+AB := $(firstword $(subst -, ,$(AB_CD)))
+
+clean clobber repackage::
+ $(RM) -r $(dist_dest)
+
+MAC_BUNDLE_VERSION = $(shell $(PYTHON) $(srcdir)/macversion.py --version=$(MOZ_APP_VERSION) --buildid=$(DEPTH)/buildid.h)
+
+.PHONY: repackage
+tools repackage:: $(PROGRAM)
+ $(MKDIR) -p '$(dist_dest)/Contents/MacOS'
+ $(MKDIR) -p '$(dist_dest)/Contents/Resources/$(AB).lproj'
+ rsync -a --exclude '*.in' $(srcdir)/macbuild/Contents '$(dist_dest)' --exclude English.lproj
+ rsync -a --exclude '*.in' $(srcdir)/macbuild/Contents/Resources/English.lproj/ '$(dist_dest)/Contents/Resources/$(AB).lproj'
+ sed -e 's/%APP_VERSION%/$(MOZ_APP_VERSION)/' -e 's/%MAC_APP_NAME%/$(MAC_APP_NAME)/' -e 's/%MOZ_MACBUNDLE_ID%/$(MOZ_MACBUNDLE_ID)/' -e 's/%MAC_BUNDLE_VERSION%/$(MAC_BUNDLE_VERSION)/' $(srcdir)/macbuild/Contents/Info.plist.in > '$(dist_dest)/Contents/Info.plist'
+ sed -e 's/%MAC_APP_NAME%/$(MAC_APP_NAME)/' $(srcdir)/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in | iconv -f UTF-8 -t UTF-16 > '$(dist_dest)/Contents/Resources/$(AB).lproj/InfoPlist.strings'
+ rsync -a --exclude-from='$(srcdir)/macbuild/Contents/MacOS-files.in' $(DIST)/bin/ '$(dist_dest)/Contents/Resources'
+ rsync -a --include-from='$(srcdir)/macbuild/Contents/MacOS-files.in' --exclude '*' $(DIST)/bin/ '$(dist_dest)/Contents/MacOS'
+ $(RM) '$(dist_dest)/Contents/MacOS/$(PROGRAM)'
+ rsync -aL $(PROGRAM) '$(dist_dest)/Contents/MacOS'
+ cp -RL $(DIST)/branding/firefox.icns '$(dist_dest)/Contents/Resources/firefox.icns'
+ cp -RL $(DIST)/branding/document.icns '$(dist_dest)/Contents/Resources/document.icns'
+ printf APPLMOZB > '$(dist_dest)/Contents/PkgInfo'
+endif
+
+ifdef LIBXUL_SDK #{
+ifndef SKIP_COPY_XULRUNNER #{
+libs::
+ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT)) #{
+ rsync -a --copy-unsafe-links $(LIBXUL_DIST)/XUL.framework '$(dist_dest)/Contents/Frameworks'
+else
+ $(NSINSTALL) -D $(DIST)/bin/xulrunner
+ (cd $(LIBXUL_SDK)/bin && tar $(TAR_CREATE_FLAGS) - .) | (cd $(DIST)/bin/xulrunner && tar -xf -)
+endif #} cocoa
+endif #} SKIP_COPY_XULRUNNER
+endif #} LIBXUL_SDK
diff --git a/app/application.ini b/app/application.ini
new file mode 100644
index 0000000..c64ed90
--- /dev/null
+++ b/app/application.ini
@@ -0,0 +1,50 @@
+#if MOZ_APP_STATIC_INI
+#ifdef MOZ_BUILD_APP_IS_BROWSER
+; This file is not used. If you modify it and want the application to use
+; your modifications, move it under the browser/ subdirectory and start with
+; the "-app /path/to/browser/application.ini" argument.
+#else
+; This file is not used. If you modify it and want the application to use
+; your modifications, start with the "-app /path/to/application.ini"
+; argument.
+#endif
+#endif
+#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
+#filter substitution
+#include @TOPOBJDIR@/buildid.h
+#include @TOPOBJDIR@/source-repo.h
+
+[App]
+# Vendor=@MOZ_APP_VENDOR@
+Vendor=Moonchild Productions
+# Name=@MOZ_APP_BASENAME@
+Name=Pale Moon
+RemotingName=@MOZ_APP_REMOTINGNAME@
+#ifdef MOZ_APP_DISPLAYNAME
+CodeName=@MOZ_APP_DISPLAYNAME@
+#endif
+Version=@MOZ_APP_VERSION@
+#ifdef MOZ_APP_PROFILE
+Profile=@MOZ_APP_PROFILE@
+#endif
+BuildID=@MOZ_BUILDID@
+#ifdef MOZ_SOURCE_REPO
+SourceRepository=@MOZ_SOURCE_REPO@
+#endif
+#ifdef MOZ_SOURCE_STAMP
+SourceStamp=@MOZ_SOURCE_STAMP@
+#endif
+ID=@MOZ_APP_ID@
+
+[Gecko]
+MinVersion=@GRE_MILESTONE@
+MaxVersion=@GRE_MILESTONE@
+
+[XRE]
+#ifdef MOZ_PROFILE_MIGRATOR
+EnableProfileMigrator=1
+#endif \ No newline at end of file
diff --git a/app/blocklist.xml b/app/blocklist.xml
new file mode 100644
index 0000000..4bc4be1
--- /dev/null
+++ b/app/blocklist.xml
@@ -0,0 +1,3909 @@
+<?xml version='1.0' encoding='utf-8'?>
+<blocklist lastupdate="1554898538000"
+xmlns="http://www.mozilla.org/2006/addons-blocklist">
+ <emItems>
+ <emItem blockID="i545" id="superlrcs@svenyor.net">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i167"
+ id="{b64982b1-d112-42b5-b1e4-d3867c4533f8}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i306"
+ id="{ADFA33FD-16F5-4355-8504-DF4D664CFE10}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i515"
+ id="/^({bf9194c2-b86d-4ebc-9b53-1c08b6ff779e}|{61a83e16-7198-49c6-8874-3e4e8faeb4f3}|{f0af464e-5167-45cf-9cf0-66b396d1918c}|{5d9968c3-101c-4944-ba71-72d77393322d}|{01e86e69-a2f8-48a0-b068-83869bdba3d0})$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i354"
+ id="{c0c2693d-2ee8-47b4-9df7-b67a0ee31988}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i529"
+ id="/^(torntv@torntv\.com|trtv3@trtv\.com|torntv2@torntv\.com|e2fd07a6-e282-4f2e-8965-85565fcb6384@b69158e6-3c3b-476c-9d98-ae5838c5b707\.com)$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i808"
+ id="{c96d1ae6-c4cf-4984-b110-f5f561b33b5a}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i304"
+ id="{f0e59437-6148-4a98-b0a6-60d557ef57f4}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i479" id="mbrsepone@facebook.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1424"
+ id="/^(jid0-S9kkzfTvEmC985BVmf8ZOzA5nLM@jetpack|jid1-qps14pkDB6UDvA@jetpack|jid1-Tsr09YnAqIWL0Q@jetpack|shole@ats.ext|{38a64ef0-7181-11e3-981f-0800200c9a66}|eochoa@ualberta.ca)$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i314" id="crossriderapp8812@crossrider.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i59" id="ghostviewer@youtube2.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i790" id="JMLv@njMaHh.org">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i162"
+ id="{EB7508CA-C7B2-46E0-8C04-3E94A035BD49}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1492" id="googlotim@gmail.com">
+ <prefs />
+ <versionRange minVersion="1.3.2" maxVersion="1.3.2"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i53"
+ id="{a3a5c777-f583-4fef-9380-ab4add1bc2a8}">
+ <prefs />
+ <versionRange minVersion="2.0.3" maxVersion="2.0.3"
+ severity="3" />
+ <versionRange minVersion="4.2" maxVersion="4.2"
+ severity="3" />
+ </emItem>
+ <emItem blockID="i800"
+ id="{424b0d11-e7fe-4a04-b7df-8f2c77f58aaf}">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i566"
+ id="{77BEC163-D389-42c1-91A4-C758846296A5}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i434" id="afurladvisor@anchorfree.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i66" id="youtubeer@youtuber.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i55" id="youtube@youtube7.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i82"
+ id="{8f42fb8b-b6f6-45de-81c0-d6d39f54f971}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i836" id="hansin@topvest.id">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i926"
+ id="{B1FC07E1-E05B-4567-8891-E63FBE545BA8}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange maxVersion="*" minVersion="39.0a1" />
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i83" id="flash@adobee.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i507"
+ id="4zffxtbr-bs@VideoDownloadConverter_4z.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="5.75.3.25126"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i784"
+ id="{41e5ef7a-171d-4ab5-8351-951c65a29908}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i256" id="/^[0-9a-f]+@[0-9a-f]+\.info/">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i882" id="69ffxtbr@PackageTracer_69.com">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i734" id="profsites@pr.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i6"
+ id="{3f963a5b-e555-4543-90e2-c3908898db71}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="8.5" severity="1" />
+ </emItem>
+ <emItem blockID="i238" id="/^pink@.*\.info$/">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange maxVersion="*" minVersion="18.0" />
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i78"
+ id="socialnetworktools@mozilla.doslash.org">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i696"
+ id="/^({fa95f577-07cb-4470-ac90-e843f5f83c52}|ffxtlbr@speedial\.com)$/">
+
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i712"
+ id="{a2bfe612-4cf5-48ea-907c-f3fb25bc9d6b}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i54" id="applebeegifts@mozilla.doslash.org">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i518"
+ id="/^({d6e79525-4524-4707-9b97-1d70df8e7e59}|{ddb4644d-1a37-4e6d-8b6e-8e35e2a8ea6c}|{e55007f4-80c5-418e-ac33-10c4d60db01e}|{e77d8ca6-3a60-4ae9-8461-53b22fa3125b}|{e89a62b7-248e-492f-9715-43bf8c507a2f}|{5ce3e0cb-aa83-45cb-a7da-a2684f05b8f3})$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i67" id="youtube2@youtube2.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1425"
+ id="/^(pdftoword@addingapps.com|jid0-EYTXLS0GyfQME5irGbnD4HksnbQ@jetpack|jid1-ZjJ7t75BAcbGCX@jetpack)$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i678"
+ id="{C4A4F5A0-4B89-4392-AFAC-D58010E349AF}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i478"
+ id="{7e8a1050-cf67-4575-92df-dcc60e7d952d}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i402"
+ id="{99079a25-328f-4bd4-be04-00955acaa0a7}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i51" id="admin@youtubeplayer.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i642"
+ id="{bee6eb20-01e0-ebd1-da83-080329fb9a3a}">
+ <prefs />
+ <versionRange minVersion="40.10.1" maxVersion="44.10.1"
+ severity="3" />
+ </emItem>
+ <emItem blockID="i485" id="/^brasilescape.*\@facebook\.com$//">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i127" id="plugin@youtubeplayer.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i63" id="youtube@youtuber.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i495" id="kallow@facebook.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i524"
+ id="/^({4e988b08-8c51-45c1-8d74-73e0c8724579}|{93ec97bf-fe43-4bca-a735-5c5d6a0a40c4}|{aed63b38-7428-4003-a052-ca6834d8bad3}|{0b5130a9-cc50-4ced-99d5-cda8cc12ae48}|{C4CFC0DE-134F-4466-B2A2-FF7C59A8BFAD})$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i884" id="detgdp@gmail.com">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i700"
+ id="2bbadf1f-a5af-499f-9642-9942fcdb7c76@f05a14cc-8842-4eee-be17-744677a917ed.com">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i838"
+ id="{87b5a11e-3b54-42d2-9102-0a7cb1f79ebf}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i68" id="flashupdate@adobe.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1264" id="suchpony@suchpony.de">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="1.6.7"
+ severity="3" />
+ </emItem>
+ <emItem blockID="i346"
+ id="{a6e67e6f-8615-4fe0-a599-34a73fc3fba5}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i42"
+ id="{D19CA586-DD6C-4a0a-96F8-14644F340D60}">
+ <prefs />
+ <versionRange minVersion="0.1" maxVersion="14.4.0"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i578" id="jid1-XLjasWL55iEE1Q@jetpack">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i812"
+ id="{1e4ea5fc-09e5-4f45-a43b-c048304899fc}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i43" id="supportaccessplugin@gmail.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i282"
+ id="{33e0daa6-3af3-d8b5-6752-10e949c61516}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="1.1.999"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i246" id="support@vide1flash2.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i648" id="firefoxaddon@youtubeenhancer.com">
+ <prefs />
+ <versionRange minVersion="199.7.0" maxVersion="199.7.0"
+ severity="3" />
+ <versionRange minVersion="208.7.0" maxVersion="208.7.0"
+ severity="3" />
+ <versionRange minVersion="199.7.0" maxVersion="208.7.0"
+ severity="3" />
+ </emItem>
+ <emItem blockID="i1523"
+ id="{a0d7ccb3-214d-498b-b4aa-0e8fda9a7bf7}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="20170120"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i1227"
+ id="{A34CAF42-A3E3-11E5-945F-18C31D5D46B0}">
+ <prefs>
+ <pref>security.csp.enable</pref>
+ <pref>security.fileuri.strict_origin_policy</pref>
+ <pref>security.mixed_content.block_active_content</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i220" id="pricepeep@getpricepeep.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="2.1.0.19.99"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i358" id="lfind@nijadsoft.net">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i382"
+ id="{6926c7f7-6006-42d1-b046-eba1b3010315}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i1012" id="wxtui502n2xce9j@no14">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1137"
+ id="/^({d50bfa5f-291d-48a8-909c-5f1a77b31948}|{d54bc985-6e7b-46cd-ad72-a4a266ad879e}|{d89e5de3-5543-4363-b320-a98cf150f86a}|{f3465017-6f51-4980-84a5-7bee2f961eba}|{fae25f38-ff55-46ea-888f-03b49aaf8812})$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1022"
+ id="g99hiaoekjoasiijdkoleabsy278djasi@jetpack">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i16"
+ id="{27182e60-b5f3-411c-b545-b44205977502}">
+ <prefs />
+ <versionRange minVersion="1.0" maxVersion="1.0"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i484" id="plugin@getwebcake.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i99" id="pfzPXmnzQRXX6@2iABkVe.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1233" id="cloudmask@cloudmask.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="2.0.788"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i588" id="quick_start@gmail.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i498" id="hoverst@facebook.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i914"
+ id="{0153E448-190B-4987-BDE1-F256CADA672F}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange maxVersion="*" minVersion="39.0a1" />
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i1228" id="unblocker30__web@unblocker.yt">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i620"
+ id="{21EAF666-26B3-4A3C-ABD0-CA2F5A326744}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1279" id="dodatek@flash2.pl">
+ <prefs />
+ <versionRange minVersion="1.3" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1214" id="firefoxdav@icloud.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="1.4.22"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i338"
+ id="{1FD91A9C-410C-4090-BBCC-55D3450EF433}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i468"
+ id="05dd836e-2cbd-4204-9ff3-2f8a8665967d@a8876730-fb0c-4057-a2fc-f9c09d438e81.com">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1522"
+ id="/^(ciscowebexstart1@cisco\.com|ciscowebexstart_test@cisco\.com|ciscowebexstart@cisco\.com|ciscowebexgpc@cisco\.com)$/">
+
+ <prefs />
+ <versionRange minVersion="1.0.0" maxVersion="1.0.1"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i998"
+ id="meOYKQEbBBjH5Ml91z0p9Aosgus8P55bjTa4KPfl@jetpack">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1232" id="nosquint@urandom.ca">
+ <prefs />
+ <versionRange minVersion="0"
+ maxVersion="2.1.9.1-signed.1-signed" severity="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange maxVersion="*" minVersion="47" />
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i378"
+ id="{a7aae4f0-bc2e-a0dd-fb8d-68ce32c9261f}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1016" id="jid1-uabu5A9hduqzCw@jetpack">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i326"
+ id="/^((support2_en@adobe14\.com)|(XN4Xgjw7n4@yUWgc\.com)|(C7yFVpIP@WeolS3acxgS\.com)|(Kbeu4h0z@yNb7QAz7jrYKiiTQ3\.com)|(aWQzX@a6z4gWdPu8FF\.com)|(CBSoqAJLYpCbjTP90@JoV0VMywCjsm75Y0toAd\.com)|(zZ2jWZ1H22Jb5NdELHS@o0jQVWZkY1gx1\.com))$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i474"
+ id="{906000a4-88d9-4d52-b209-7a772970d91f}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1024"
+ id="{458fb825-2370-4973-bf66-9d7142141847}">
+ <prefs>
+ <pref>app.update.auto</pref>
+ <pref>app.update.enabled</pref>
+ <pref>app.update.interval</pref>
+ <pref>app.update.url</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i1036"
+ id="HxLVJK1ioigz9WEWo8QgCs3evE7uW6LEExAniBGG@jetpack">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i888" id="istart_ffnt@gmail.com">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i501" id="xivars@aol.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1128" id="youtubeunblocker@unblocker.yt">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i686"
+ id="{a7f2cb14-0472-42a1-915a-8adca2280a2c}">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i98" id="youtubeeing@youtuberie.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i539" id="ScorpionSaver@jetpack">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i582" id="discoverypro@discoverypro.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i541"
+ id="/^({988919ff-0cd8-4d0c-bc7e-60d55a49eb64}|{494b9726-9084-415c-a499-68c07e187244}|{55b95864-3251-45e9-bb30-1a82589aaff1}|{eef3855c-fc2d-41e6-8d91-d368f51b3055}|{90a1b331-c2b4-4933-9f63-ba7b84d60d58}|{d2cf9842-af95-48cd-b873-bfbb48cd7f5e})$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1077" id="helper@vidscrab.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i348"
+ id="{13c9f1f9-2322-4d5c-81df-6d4bf8476ba4}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i772"
+ id="{72b98dbc-939a-4e0e-b5a9-9fdbf75963ef}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i814" id="liiros@facebook.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1278"
+ id="/^(ff\-)?dodate(kKKK|XkKKK|k|kk|kkx|kR)@(firefox|flash(1)?)\.pl|dode(ee)?k@firefoxnet\.pl|(addon|1)@upsolutions\.pl$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i17"
+ id="{3252b9ae-c69a-4eaf-9502-dc9c1f6c009e}">
+ <prefs />
+ <versionRange minVersion="2.2" maxVersion="2.2"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i972"
+ id="831778-poidjao88DASfsAnindsd@jetpack">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i852" id="6lIy@T.edu">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i76" id="crossriderapp3924@crossrider.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i742"
+ id="{f894a29a-f065-40c3-bb19-da6057778493}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i549"
+ id="/^firefox@(albrechto|swiftbrowse|springsmart|storimbo|squirrelweb|betterbrowse|lizardlink|rolimno|browsebeyond|clingclang|weblayers|kasimos|higher-aurum|xaven|bomlabio)\.(com?|net|org|info|biz)$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i435" id="pluggets@gmail.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i471" id="firefox@luckyleap.net">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i780"
+ id="{b6ef1336-69bb-45b6-8cba-e578fc0e4433}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i406"
+ id="{bf7380fa-e3b4-4db2-af3e-9d8783a45bfc}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i788"
+ id="{729c9605-0626-4792-9584-4cbe65b243e6}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i970"
+ id="hha8771ui3-Fo9j9h7aH98jsdfa8sda@jetpack">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1266" id="@stopad">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="0.0.4"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i465" id="trtv3@trtv.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i1032"
+ id="KSqOiTeSJEDZtTGuvc18PdPmYodROmYzfpoyiCr2@jetpack">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i718" id="G4Ce4@w.net">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i1210" id="auto-plugin-checker@jetpack">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i988"
+ id="{b12785f5-d8d0-4530-a3ea-5c4263b85bef}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i447"
+ id="{B18B1E5C-4D81-11E1-9C00-AFEB4824019B}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i546" id="firefox@browsefox.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i344" id="lrcsTube@hansanddeta.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i628" id="ffxtlbr@iminent.com">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i740" id="ascsurfingprotection@iobit.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i818" id="contentarget@maildrop.cc">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i482" id="brasilescapeeight@facebook.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i531"
+ id="/^(4cb61367-efbf-4aa1-8e3a-7f776c9d5763@cdece6e9-b2ef-40a9-b178-291da9870c59\.com|0efc9c38-1ec7-49ed-8915-53a48b6b7600@e7f17679-2a42-4659-83c5-7ba961fdf75a\.com|6be3335b-ef79-4b0b-a0ba-b87afbc6f4ad@6bbb4d2e-e33e-4fa5-9b37-934f4fb50182\.com)$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i886" id="searchengine@gmail.com">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i1223" id="tmbepff@trendmicro.com">
+ <prefs />
+ <versionRange minVersion="9.2" maxVersion="9.2.0.1023"
+ severity="1" />
+ <versionRange minVersion="0" maxVersion="9.1.0.1035"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i774" id="x77IjS@xU.net">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i136" id="Adobe@flash.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i1056"
+ id="{82AF8DCA-6DE9-405D-BD5E-43525BDAD38A}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="7.5.0.9082"
+ severity="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange maxVersion="*" minVersion="43.0a1" />
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i494"
+ id="/^({e9df9360-97f8-4690-afe6-996c80790da4}|{687578b9-7132-4a7a-80e4-30ee31099e03}|{46a3135d-3683-48cf-b94c-82655cbc0e8a}|{49c795c2-604a-4d18-aeb1-b3eba27e5ea2}|{7473b6bd-4691-4744-a82b-7854eb3d70b6}|{96f454ea-9d38-474f-b504-56193e00c1a5})$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i564"
+ id="/^(firefox@vebergreat\.net|EFGLQA@78ETGYN-0W7FN789T87\.COM)$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i698"
+ id="{6b2a75c8-6e2e-4267-b955-43e25b54e575}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i380"
+ id="{cc8f597b-0765-404e-a575-82aefbd81daf}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i850" id="P2@D.edu">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i57" id="youtube@youtube3.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i537" id="rally_toolbar_ff@bulletmedia.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i980" id="wHO@W9.net">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i540"
+ id="/^(ffxtlbr@mixidj\.com|{c0c2693d-2ee8-47b4-9df7-b67a0ee31988}|{67097627-fd8e-4f6b-af4b-ecb65e50112e}|{f6f0f973-a4a3-48cf-9a7a-b7a69c30d71a}|{a3d0e35f-f1da-4ccb-ae77-e9d27777e68d}|{1122b43d-30ee-403f-9bfa-3cc99b0caddd})$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i560" id="adsremoval@adsremoval.net">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i722"
+ id="{9802047e-5a84-4da3-b103-c55995d147d1}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i766"
+ id="/^[a-z0-9]+@foxysecure[a-z0-9]*\.com$/">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i586" id="jid1-0xtMKhXFEs4jIg@jetpack">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i690"
+ id="{55dce8ba-9dec-4013-937e-adbf9317d990">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i618" id="toolbar@ask.com">
+ <prefs />
+ <versionRange minVersion="3.15.5" maxVersion="3.15.5.*"
+ severity="1" />
+ <versionRange minVersion="3.15.31" maxVersion="3.15.31.*"
+ severity="1" />
+ <versionRange minVersion="3.15.22" maxVersion="3.15.22.*"
+ severity="1" />
+ <versionRange minVersion="3.15.8" maxVersion="3.15.8.*"
+ severity="1" />
+ <versionRange minVersion="3.15.10" maxVersion="3.15.11.*"
+ severity="1" />
+ <versionRange minVersion="3.15.13" maxVersion="3.15.13.*"
+ severity="1" />
+ <versionRange minVersion="3.15.24" maxVersion="3.15.24.*"
+ severity="1" />
+ <versionRange minVersion="3.15.18" maxVersion="3.15.20.*"
+ severity="1" />
+ <versionRange minVersion="3.15.26" maxVersion="3.15.26.*"
+ severity="1" />
+ <versionRange minVersion="3.15.28" maxVersion="3.15.28.*"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i3" id="langpack-vi-VN@firefox.mozilla.org">
+ <prefs />
+ <versionRange minVersion="2.0" maxVersion="2.0"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i666" id="wecarereminder@bryan">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i668"
+ id="/^(matchersite(pro(srcs?)?)?\@matchersite(pro(srcs?)?)?\.com)|((pro)?sitematcher(_srcs?|pro|site|sitesrc|-generic)?\@(pro)?sitematcher(_srcs?|pro|site|sitesrc|-generic)?\.com)$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i996" id="9598582LLKmjasieijkaslesae@jetpack">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i322"
+ id="jid0-Y6TVIzs0r7r4xkOogmJPNAGFGBw@jetpack">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i590"
+ id="{94cd2cc3-083f-49ba-a218-4cda4b4829fd}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i356"
+ id="{341f4dac-1966-47ff-aacf-0ce175f1498a}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i509" id="contato@facefollow.net">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i1212" id="unblocker20@unblocker.yt">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="2.0.0"
+ severity="3" />
+ </emItem>
+ <emItem blockID="i100"
+ id="{394DCBA4-1F92-4f8e-8EC9-8D2CB90CB69B}">
+ <prefs />
+ <versionRange minVersion="2.5.0" maxVersion="2.5.0"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i522"
+ id="/^({976cd962-e0ca-4337-aea7-d93fae63a79c}|{525ba996-1ce4-4677-91c5-9fc4ead2d245}|{91659dab-9117-42d1-a09f-13ec28037717}|{c1211069-1163-4ba8-b8b3-32fc724766be})$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i487"
+ id="{df6bb2ec-333b-4267-8c4f-3f27dc8c6e07}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i730" id="25p@9eAkaLq.net">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i8"
+ id="{B13721C7-F507-4982-B2E5-502A71474FED}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i505" id="extacylife@a.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1126"
+ id="{bbea93c6-64a3-4a5a-854a-9cc61c8d309e}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i62"
+ id="jid0-EcdqvFOgWLKHNJPuqAnawlykCGZ@jetpack">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i324"
+ id="/^((34qEOefiyYtRJT@IM5Munavn\.com)|(Mro5Fm1Qgrmq7B@ByrE69VQfZvZdeg\.com)|(KtoY3KGxrCe5ie@yITPUzbBtsHWeCdPmGe\.com)|(9NgIdLK5Dq4ZMwmRo6zk@FNt2GCCLGyUuOD\.com)|(NNux7bWWW@RBWyXdnl6VGls3WAwi\.com)|(E3wI2n@PEHTuuNVu\.com)|(2d3VuWrG6JHBXbQdbr@3BmSnQL\.com))$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i488" id="jid1-4P0kohSJxU1qGg@jetpack">
+ <prefs />
+ <versionRange minVersion="1.2.50" maxVersion="1.2.50"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i1414"
+ id="/^new@kuot\.pro|{13ec6687-0b15-4f01-a5a0-7a891c18e4ee}|rebeccahoppkins(ty(tr)?)?@gmail\.com|{501815af-725e-45be-b0f2-8f36f5617afc}|{9bdb5f1f-b1e1-4a75-be31-bdcaace20a99}|{e9d93e1d-792f-4f95-b738-7adb0e853b7b}|dojadewaskurwa@gmail\.com$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i71" id="youtube@2youtube.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i676" id="SpecialSavings@SpecialSavings.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i91" id="crossriderapp4926@crossrider.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="0.81.43"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i441"
+ id="{49c53dce-afa0-49a1-a08b-2eb8e8444128}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i842"
+ id="{746505DC-0E21-4667-97F8-72EA6BCF5EEF}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i45"
+ id="{22119944-ED35-4ab1-910B-E619EA06A115}">
+ <prefs />
+ <versionRange minVersion="0.1" maxVersion="7.6.1"
+ severity="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange maxVersion="*" minVersion="8.0a1" />
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i514"
+ id="/^(67314b39-24e6-4f05-99f3-3f88c7cddd17@6c5fa560-13a3-4d42-8e90-53d9930111f9\.com|ffxtlbr@visualbee\.com|{7aeae561-714b-45f6-ace3-4a8aed6e227b}|{7093ee04-f2e4-4637-a667-0f730797b3a0}|{53c4024f-5a2e-4f2a-b33e-e8784d730938})$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i20"
+ id="{AB2CE124-6272-4b12-94A9-7303C7397BD1}">
+ <prefs />
+ <versionRange minVersion="0.1" maxVersion="5.2.0.7164"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i706" id="thefoxonlybetter@quicksaver">
+ <prefs />
+ <versionRange minVersion="1.10" maxVersion="*"
+ severity="3" />
+ <versionRange minVersion="0" maxVersion="0.*" severity="3" />
+ <versionRange minVersion="1.6.160" maxVersion="1.6.160"
+ severity="3" />
+ </emItem>
+ <emItem blockID="i804"
+ id="{ad7ce998-a77b-4062-9ffb-1d0b7cb23183}">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i360" id="ytd@mybrowserbar.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i598"
+ id="{29b136c9-938d-4d3d-8df8-d649d9b74d02}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i470" id="extension@FastFreeConverter.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i222" id="dealcabby@jetpack">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i84" id="pink@rosaplugin.info">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i764" id="prositez@prz.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i858" id="fftoolbar2014@etech.com">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i56" id="flash@adobe.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i469" id="OKitSpace@OKitSpace.es">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i163" id="info@allpremiumplay.info">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1230" id="addon@gemaoff">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i748"
+ id="{32da2f20-827d-40aa-a3b4-2fc4a294352e}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i664" id="123456789@offeringmedia.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i491"
+ id="{515b2424-5911-40bd-8a2c-bdb20286d8f5}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i527"
+ id="/^({bfec236d-e122-4102-864f-f5f19d897f5e}|{3f842035-47f4-4f10-846b-6199b07f09b8}|{92ed4bbd-83f2-4c70-bb4e-f8d3716143fe})$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i73" id="a1g0a9g219d@a1.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i736"
+ id="{c5e48979-bd7f-4cf7-9b73-2482a67a4f37}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i489" id="astrovia@facebook.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i492"
+ id="{af95cc15-3b9b-45ae-8d9b-98d08eda3111}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i496"
+ id="{ACAA314B-EEBA-48e4-AD47-84E31C44796C}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i350" id="sqlmoz@facebook.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i92" id="play5@vide04flash.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i870" id="M1uwW0@47z8gRpK8sULXXLivB.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i533" id="extension@Fast_Free_Converter.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i79" id="GifBlock@facebook.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i370" id="happylyrics@hpyproductions.net">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i93"
+ id="{68b8676b-99a5-46d1-b390-22411d8bcd61}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i216" id="fdm_ffext@freedownloadmanager.org">
+ <prefs />
+ <versionRange minVersion="1.5.7.5" maxVersion="1.5.7.5"
+ severity="1" />
+ <versionRange minVersion="1.0" maxVersion="1.3.1"
+ severity="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange maxVersion="*" minVersion="3.0a1" />
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i376"
+ id="{9e09ac65-43c0-4b9d-970f-11e2e9616c55}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i872" id="search-snacks@search-snacks.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i694"
+ id="59D317DB041748fdB89B47E6F96058F3@jetpack">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i477" id="mbrnovone@facebook.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i473"
+ id="{81b13b5d-fba1-49fd-9a6b-189483ac548a}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i14"
+ id="mozilla_cc@internetdownloadmanager.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="6.9.8" severity="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange maxVersion="*" minVersion="3.7a1pre" />
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i525"
+ id="/^({65f9f6b7-2dae-46fc-bfaf-f88e4af1beca}|{9ed31f84-c8b3-4926-b950-dff74047ff79}|{0134af61-7a0c-4649-aeca-90d776060cb3}|{02edb56b-9b33-435b-b7df-b2843273a694}|{da51d4f6-3e7e-4ef8-b400-9198e0874606}|{b24577db-155e-4077-bb37-3fdd3c302bb5})$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i65" id="activity@facebook.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i172" id="info@bflix.info">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i726"
+ id="{d87d56b2-1379-49f4-b081-af2850c79d8e}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i724"
+ id="{1cdbda58-45f8-4d91-b566-8edce18f8d0a}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i24"
+ id="{6E19037A-12E3-4295-8915-ED48BC341614}">
+ <prefs />
+ <versionRange minVersion="0.1" maxVersion="1.3.328.4"
+ severity="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange maxVersion="*" minVersion="3.7a1pre" />
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i510"
+ id="{3c9a72a0-b849-40f3-8c84-219109c27554}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i918"
+ id="{C3949AC2-4B17-43ee-B4F1-D26B9D42404D}" os="WINNT">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange maxVersion="*" minVersion="39.0a1" />
+ </targetApplication>
+ </versionRange>
+ <versionRange minVersion="0" maxVersion="15.0.5"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i174" id="info@thebflix.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i426" id="addlyrics@addlyrics.net">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i77"
+ id="{fa277cfc-1d75-4949-a1f9-4ac8e41b2dfd}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i466" id="afext@anchorfree.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i1262" id="my7thfakeid@gmail.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i994" id="addonhack@mozilla.kewis.ch">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i660" id="youplayer@addons.mozilla.org">
+ <prefs />
+ <versionRange minVersion="79.9.8" maxVersion="208.0.1"
+ severity="3" />
+ </emItem>
+ <emItem blockID="i446"
+ id="{E90FA778-C2B7-41D0-9FA9-3FEC1CA54D66}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i820"
+ id="{aab02ab1-33cf-4dfa-8a9f-f4e60e976d27}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i768"
+ id="{f2548724-373f-45fe-be6a-3a85e87b7711}">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1231" id="youtube@downloader.yt">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i394"
+ id="{7D4F1959-3F72-49d5-8E59-F02F8AA6815D}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i460"
+ id="{845cab51-d8d2-472f-8bd9-2b44642d97c2}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i483" id="brasilescapefive@facebook.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i47" id="youtube@youtube2.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1058" id="amo-validator-bypass@example.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i554" id="lightningnewtab@gmail.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i438"
+ id="{02edb56b-9b33-435b-b7df-b2843273a694}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i104" id="yasd@youasdr3.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i770"
+ id="{8dc5c42e-9204-2a64-8b97-fa94ff8a241f}">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i634" id="jid1-4vUehhSALFNqCw@jetpack">
+ <prefs />
+ <versionRange minVersion="100.7" maxVersion="100.7"
+ severity="3" />
+ <versionRange minVersion="99.7" maxVersion="99.7"
+ severity="3" />
+ </emItem>
+ <emItem blockID="i543"
+ id="{badea1ae-72ed-4f6a-8c37-4db9a4ac7bc9}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i5" id="support@daemon-tools.cc">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="1.0.0.5"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i450"
+ id="{dff137ae-1ffd-11e3-8277-b8ac6f996f26}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i453" id="/^brasilescape.*\@facebook\.com$/">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i720" id="FXqG@xeeR.net">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i746"
+ id="{58d2a791-6199-482f-a9aa-9b725ec61362}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i352" id="vpyekkifgv@vpyekkifgv.org">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i640"
+ id="jid0-l9BxpNUhx1UUgRfKigWzSfrZqAc@jetpack">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i449" id="gystqfr@ylgga.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i108"
+ id="{28bfb930-7620-11e1-b0c4-0800200c9a66}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i476" id="mbroctone@facebook.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i778"
+ id="{f2456568-e603-43db-8838-ffa7c4a685c7}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i916"
+ id="{97E22097-9A2F-45b1-8DAF-36AD648C7EF4}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange maxVersion="*" minVersion="39.0a1" />
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i86"
+ id="{45147e67-4020-47e2-8f7a-55464fb535aa}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i822"
+ id="{6af08a71-380e-42dd-9312-0111d2bc0630}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i516"
+ id="/^({3f3cddf8-f74d-430c-bd19-d2c9147aed3d}|{515b2424-5911-40bd-8a2c-bdb20286d8f5}|{17464f93-137e-4646-a0c6-0dc13faf0113}|{d1b5aad5-d1ae-4b20-88b1-feeaeb4c1ebc}|{aad50c91-b136-49d9-8b30-0e8d3ead63d0})$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i656" id="hdv@vovcacik.addons.mozilla.org">
+ <prefs />
+ <versionRange minVersion="102.0" maxVersion="102.0"
+ severity="3" />
+ </emItem>
+ <emItem blockID="i400"
+ id="{dd6b651f-dfb9-4142-b0bd-09912ad22674}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i732"
+ id="{e935dd68-f90d-46a6-b89e-c4657534b353}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i140" id="mozillahmpg@mozilla.org">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i521"
+ id="/^({66b103a7-d772-4fcd-ace4-16f79a9056e0}|{6926c7f7-6006-42d1-b046-eba1b3010315}|{72cabc40-64b2-46ed-8648-26d831761150}|{73ee2cf2-7b76-4c49-b659-c3d8cf30825d}|{ca6446a5-73d5-4c35-8aa1-c71dc1024a18}|{5373a31d-9410-45e2-b299-4f61428f0be4})$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i688" id="firefox-extension@mozilla.org">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i342" id="lbmsrvfvxcblvpane@lpaezhjez.org">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i226"
+ id="{462be121-2b54-4218-bf00-b9bf8135b23f}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i862"
+ id="{CA8C84C6-3918-41b1-BE77-049B2BDD887C}">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i752" id="savingsslider@mybrowserbar.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i18" id="msntoolbar@msn.com">
+ <prefs />
+ <versionRange minVersion=" 0" maxVersion="6.*"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i750"
+ id="{46eddf51-a4f6-4476-8d6c-31c5187b2a2f}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i472" id="linksicle@linksicle.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i626"
+ id="{20AD702C-661E-4534-8CE9-BA4EC9AD6ECC}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1018" id="grjkntbhr@hgergerherg.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i526"
+ id="/^({83a8ce1b-683c-4784-b86d-9eb601b59f38}|{ef1feedd-d8da-4930-96f1-0a1a598375c6}|{79ff1aae-701f-4ca5-aea3-74b3eac6f01b}|{8a184644-a171-4b05-bc9a-28d75ffc9505}|{bc09c55d-0375-4dcc-836e-0e3c8addfbda}|{cef81415-2059-4dd5-9829-1aef3cf27f4f})$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i866" id="faststartff@gmail.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i318" id="ffxtlbr@incredibar.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i88" id="anttoolbar@ant.com">
+ <prefs />
+ <versionRange minVersion="2.4.6.4" maxVersion="2.4.6.4"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i1524" id="ext@alibonus.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="1.20.9"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i670"
+ id="/^({ad9a41d2-9a49-4fa6-a79e-71a0785364c8})|(ffxtlbr@mysearchdial\.com)$/">
+
+ <prefs>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i1211" id="flvto@hotger.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i75" id="firebug@software.joehewitt.com"
+ os="Darwin,Linux">
+ <prefs />
+ <versionRange minVersion="1.9.0" maxVersion="1.9.0"
+ severity="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange maxVersion="9.*" minVersion="9.0a1" />
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i12" id="masterfiler@gmail.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i519"
+ id="703db0db-5fe9-44b6-9f53-c6a91a0ad5bd@7314bc82-969e-4d2a-921b-e5edd0b02cf1.com">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i1136"
+ id="/^({1f43c8af-e9e4-4e5a-b77a-f51c7a916324}|{3a3bd700-322e-440a-8a6a-37243d5c7f92}|{6a5b9fc2-733a-4964-a96a-958dd3f3878e}|{7b5d6334-8bc7-4bca-a13e-ff218d5a3f17}|{b87bca5b-2b5d-4ae8-ad53-997aa2e238d4}|{bf8e032b-150f-4656-8f2d-6b5c4a646e0d})$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i874" id="/^toolbar[0-9]*@findwide\.com$/">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i708"
+ id="{849ded12-59e9-4dae-8f86-918b70d213dc}">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i810"
+ id="{41339ee8-61ed-489d-b049-01e41fd5d7e0}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i228" id="crossriderapp5060@crossrider.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i523"
+ id="/^({7e8a1050-cf67-4575-92df-dcc60e7d952d}|{b3420a9c-a397-4409-b90d-bcf22da1a08a}|{eca6641f-2176-42ba-bdbe-f3e327f8e0af}|{707dca12-3f99-4d94-afea-06dcc0ae0108}|{aea20431-87fc-40be-bc5b-18066fe2819c}|{30ee6676-1ba6-455a-a7e8-298fa863a546})$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i1423"
+ id="/^(@pluginscribens_firefox|extension@vidscrab.com|firefox@jjj.ee|firefox@shop-reward.de|FxExtPasteNGoHtk@github.lostdj|himanshudotrai@gmail.com|jid0-bigoD0uivzAMmt07zrf3OHqa418@jetpack|jid0-iXbAR01tjT2BsbApyS6XWnjDhy8@jetpack)$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i364"
+ id="{FE1DEEEA-DB6D-44b8-83F0-34FC0F9D1052}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i336" id="CortonExt@ext.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i682"
+ id="f6682b47-e12f-400b-9bc0-43b3ccae69d1@39d6f481-b198-4349-9ebe-9a93a86f9267.com">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i262"
+ id="{167d9323-f7cc-48f5-948a-6f012831a69f}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i48" id="admin@youtubespeedup.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i4"
+ id="{4B3803EA-5230-4DC3-A7FC-33638F3D3542}">
+ <prefs />
+ <versionRange minVersion="1.2" maxVersion="1.2" severity="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange maxVersion="*" minVersion="3.0a1" />
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i103" id="kdrgun@gmail.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i11" id="yslow@yahoo-inc.com">
+ <prefs />
+ <versionRange minVersion="2.0.5" maxVersion="2.0.5"
+ severity="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange maxVersion="*" minVersion="3.5.7" />
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i1129"
+ id="youtubeunblocker__web@unblocker.yt">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i334"
+ id="{0F827075-B026-42F3-885D-98981EE7B1AE}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i538"
+ id="{354dbb0a-71d5-4e9f-9c02-6c88b9d387ba}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i19"
+ id="{46551EC9-40F0-4e47-8E18-8E5CF550CFB8}">
+ <prefs />
+ <versionRange minVersion="1.1b1" maxVersion="1.1b1"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i646"
+ id="{e1aaa9f8-4500-47f1-9a0a-b02bd60e4076}">
+ <prefs />
+ <versionRange minVersion="178.7.0" maxVersion="178.7.0"
+ severity="3" />
+ </emItem>
+ <emItem blockID="i864"
+ id="{0A92F062-6AC6-8180-5881-B6E0C0DC2CC5}">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i451"
+ id="{e44a1809-4d10-4ab8-b343-3326b64c7cdd}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i436"
+ id="/(\{7aeae561-714b-45f6-ace3-4a8aed6e227b\})|(\{01e86e69-a2f8-48a0-b068-83869bdba3d0\})|(\{77f5fe49-12e3-4cf5-abb4-d993a0164d9e\})/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i461"
+ id="{8E9E3331-D360-4f87-8803-52DE43566502}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i115"
+ id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i493" id="12x3q@3244516.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i105"
+ id="{95ff02bc-ffc6-45f0-a5c8-619b8226a9de}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1119"
+ id="/^(test3@test.org|test2@test.org|test@test.org|support@mozilla.org)$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i101"
+ id="{3a12052a-66ef-49db-8c39-e5b0bd5c83fa}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1078"
+ id="/^(jid1-W4CLFIRExukJIFW@jetpack|jid1-W4CLFIRExukJIFW@jetpack_1|jid1-W3CLwrP[a-z]+@jetpack)$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i692"
+ id="/^(j003-lqgrmgpcekslhg|SupraSavings|j003-dkqonnnthqjnkq|j003-kaggrpmirxjpzh)@jetpack$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i312" id="extension21804@extension21804.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i650" id="jid1-qj0w91o64N7Eeg@jetpack">
+ <prefs />
+ <versionRange minVersion="39.5.1" maxVersion="47.0.4"
+ severity="3" />
+ </emItem>
+ <emItem blockID="i433"
+ id="{c95a4e8e-816d-4655-8c79-d736da1adb6d}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i286"
+ id="{58bd07eb-0ee0-4df0-8121-dc9b693373df}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i506" id="/^ext@bettersurfplus/">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i196" id="info@wxdownloadmanager.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i816" id="noOpus@outlook.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i46"
+ id="{841468a1-d7f4-4bd3-84e6-bb0f13a06c64}">
+ <prefs />
+ <versionRange minVersion="0.1" maxVersion="*" severity="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange maxVersion="9.0" minVersion="9.0a1" />
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i432" id="lugcla21@gmail.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i658" id="low_quality_flash@pie2k.com">
+ <prefs />
+ <versionRange minVersion="46.2" maxVersion="47.1"
+ severity="3" />
+ </emItem>
+ <emItem blockID="i710"
+ id="{e0352044-1439-48ba-99b6-b05ed1a4d2de}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i486" id="xz123@ya456.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i109"
+ id="{392e123b-b691-4a5e-b52f-c4c1027e749c}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i7"
+ id="{2224e955-00e9-4613-a844-ce69fccaae91}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i374" id="update@firefox.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i802"
+ id="{18d5a8fe-5428-485b-968f-b97b05a92b54}">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i547"
+ id="{87934c42-161d-45bc-8cef-ef18abe2a30c}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="3.7.9999999999"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i23" id="firefox@bandoo.com">
+ <prefs />
+ <versionRange minVersion="5.0" maxVersion="5.0" severity="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange maxVersion="*" minVersion="3.7a1pre" />
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i499"
+ id="{babb9931-ad56-444c-b935-38bffe18ad26}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i924"
+ id="{DAC3F861-B30D-40dd-9166-F4E75327FAC7}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange maxVersion="*" minVersion="39.0a1" />
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i840"
+ id="{4889ddce-7a83-45e6-afc9-1e4f1149fff4}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i562" id="iobitapps@mybrowserbar.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i792"
+ id="{8f894ed3-0bf2-498e-a103-27ef6e88899f}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i430" id="1chtw@facebook.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i570" id="jid1-vW9nopuIAJiRHw@jetpack">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i548"
+ id="/^firefox@(jumpflip|webconnect|browsesmart|mybuzzsearch|outobox|greygray|lemurleap|divapton|secretsauce|batbrowse|whilokii|linkswift|qualitink|browsefox|kozaka|diamondata|glindorus|saltarsmart|bizzybolt|websparkle)\.(com?|net|org|info|biz)$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i504" id="aytac@abc.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i392"
+ id="{EEE6C361-6118-11DC-9C72-001320C79847}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i846"
+ id="PDVDZDW52397720@XDDWJXW57740856.com">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i550" id="colmer@yopmail.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1000"
+ id="jufa098j-LKooapd9jasJ9jliJsd@jetpack">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i762"
+ id="/^({2d7886a0-85bb-4bf2-b684-ba92b4b21d23}|{2fab2e94-d6f9-42de-8839-3510cef6424b}|{c02397f7-75b0-446e-a8fa-6ef70cfbf12b}|{8b337819-d1e8-48d3-8178-168ae8c99c36}|firefox@neurowise.info|firefox@allgenius.info)$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i1263" id="axtara__web@axtara.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="1.1.1"
+ severity="3" />
+ </emItem>
+ <emItem blockID="i444" id="fplayer@adobe.flash">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1229"
+ id="/^(.*@(unblocker\.yt|sparpilot\.com))|(axtara@axtara\.com)$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i535"
+ id="/^ext@WebexpEnhancedV1alpha[0-9]+\.net$/">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i224"
+ id="{336D0C35-8A85-403a-B9D2-65C292C39087}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i424"
+ id="{C7AE725D-FA5C-4027-BB4C-787EF9F8248A}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="1.0.0.2"
+ severity="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange maxVersion="*" minVersion="23.0a1" />
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i38"
+ id="{B7082FAA-CB62-4872-9106-E42DD88EDE45}">
+ <prefs />
+ <versionRange minVersion="0.1" maxVersion="3.3.0.*"
+ severity="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange maxVersion="*" minVersion="3.7a1" />
+ </targetApplication>
+ </versionRange>
+ <versionRange minVersion="3.3.1" maxVersion="*" severity="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange maxVersion="*" minVersion="5.0a1" />
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i786"
+ id="{63eb5ed4-e1b3-47ec-a253-f8462f205350}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i60" id="youtb3@youtb3.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1050"
+ id="87aukfkausiopoawjsuifhasefgased278djasi@jetpack">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i584"
+ id="{52b0f3db-f988-4788-b9dc-861d016f4487}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="0.1.9999999"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i437"
+ id="{4933189D-C7F7-4C6E-834B-A29F087BFD23}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i806"
+ id="{d9284e50-81fc-11da-a72b-0800200c9a66}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="7.7.34"
+ severity="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange maxVersion="*" minVersion="34.0a1" />
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i536"
+ id="{25D77636-38B1-1260-887C-2D4AFA92D6A4}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i52" id="ff-ext@youtube">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i320" id="torntv@torntv.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i568" id="thunder@xunlei.com" os="Darwin">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="2.0.6"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i448"
+ id="{0134af61-7a0c-4649-aeca-90d776060cb3}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i854" id="/^(7tG@zEb\.net|ru@gfK0J\.edu)$/">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1030" id="support@todoist.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="3.9" severity="1" />
+ </emItem>
+ <emItem blockID="i90" id="videoplugin@player.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i532"
+ id="249911bc-d1bd-4d66-8c17-df533609e6d8@c76f3de9-939e-4922-b73c-5d7a3139375d.com">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i452"
+ id="{77beece6-3997-403a-92fa-0055bfcf88e5}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i684"
+ id="{9edd0ea8-2819-47c2-8320-b007d5996f8a}">
+ <prefs>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i674" id="crossriderapp12555@crossrider.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i340" id="chiang@programmer.net">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i44" id="sigma@labs.mozilla">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i1042" id="gjhrjenrengoe@jfdnkwelfwkm.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1493"
+ id="{de71f09a-3342-48c5-95c1-4b0f17567554}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="1.3.9"
+ severity="3" />
+ </emItem>
+ <emItem blockID="i1245"
+ id="{4ED1F68A-5463-4931-9384-8FFF5ED91D92}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="3.9.9"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i168" id="flashX@adobe.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i576" id="newmoz@facebook.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i630" id="webbooster@iminent.com">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i544"
+ id="/^(93abedcf-8e3a-4d02-b761-d1441e437c09@243f129d-aee2-42c2-bcd1-48858e1c22fd\.com|9acfc440-ac2d-417a-a64c-f6f14653b712@09f9a966-9258-4b12-af32-da29bdcc28c5\.com|58ad0086-1cfb-48bb-8ad2-33a8905572bc@5715d2be-69b9-4930-8f7e-64bdeb961cfd\.com)$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i429"
+ id="{B40794A0-7477-4335-95C5-8CB9BBC5C4A5}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i528"
+ id="008abed2-b43a-46c9-9a5b-a771c87b82da@1ad61d53-2bdc-4484-a26b-b888ecae1906.com">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i580"
+ id="{51c77233-c0ad-4220-8388-47c11c18b355}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="0.1.9999999"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i744"
+ id="{84a93d51-b7a9-431e-8ff8-d60e5d7f5df1}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i372" id="5nc3QHFgcb@r06Ws9gvNNVRfH.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i503"
+ id="{9CE11043-9A15-4207-A565-0C94C42D590D}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i64" id="royal@facebook.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i922"
+ id="{34712C68-7391-4c47-94F3-8F88D49AD632}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange maxVersion="*" minVersion="39.0a1" />
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i455"
+ id="7d51fb17-b199-4d8f-894e-decaff4fc36a@a298838b-7f50-4c7c-9277-df6abbd42a0c.com">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1079"
+ id="/^(@9338379C-DD5C-4A45-9A36-9733DC806FAE|9338379C-DD5C-4A45-9A36-9733DC806FAE|@EBC7B466-8A28-4061-81B5-10ACC05FFE53|@bd6a97c0-4b18-40ed-bce7-3b7d3309e3c4222|@bd6a97c0-4b18-40ed-bce7-3b7d3309e3c4|@b2d6a97c0-4b18-40ed-bce7-3b7d3309e3c4222)$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1213" id="unblocker20__web@unblocker.yt">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i490" id="now.msn.com@services.mozilla.org">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i716"
+ id="{cc6cc772-f121-49e0-b1f0-c26583cb0c5e}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i97" id="support3_en@adobe122.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i308"
+ id="9518042e-7ad6-4dac-b377-056e28d00c8f@f1cc0a13-4df1-4d66-938f-088db8838882.com">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i362" id="addon@defaulttab.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="1.4.4"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i21" id="support@update-firefox.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i218" id="ffxtlbr@claro.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i672"
+ id="/^(saamazon@mybrowserbar\.com)|(saebay@mybrowserbar\.com)$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i756"
+ id="{5eeb83d0-96ea-4249-942c-beead6847053}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i10"
+ id="{8CE11043-9A15-4207-A565-0C94C42D590D}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i15" id="personas@christopher.beard">
+ <prefs />
+ <versionRange minVersion="1.6" maxVersion="1.6" severity="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange maxVersion="3.6.*" minVersion="3.6" />
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i520"
+ id="/^({7316e43a-3ebd-4bb4-95c1-9caf6756c97f}|{0cc09160-108c-4759-bab1-5c12c216e005}|{ef03e721-f564-4333-a331-d4062cee6f2b}|{465fcfbb-47a4-4866-a5d5-d12f9a77da00}|{7557724b-30a9-42a4-98eb-77fcb0fd1be3}|{b7c7d4b0-7a84-4b73-a7ef-48ef59a52c3b})$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i107"
+ id="{ABDE892B-13A8-4d1b-88E6-365A6E755758}" os="WINNT">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="15.0.5"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i856"
+ id="/^({94d62e35-4b43-494c-bf52-ba5935df36ef}|firefox@advanceelite\.com|{bb7b7a60-f574-47c2-8a0b-4c56f2da9802})$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i440"
+ id="{2d069a16-fca1-4e81-81ea-5d5086dcbd0c}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i714"
+ id="{25dd52dc-89a8-469d-9e8f-8d483095d1e8}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i652" id="garg_sms@yahoo.in">
+ <prefs />
+ <versionRange minVersion="67.9" maxVersion="67.9"
+ severity="3" />
+ </emItem>
+ <emItem blockID="i552"
+ id="jid0-O6MIff3eO5dIGf5Tcv8RsJDKxrs@jetpack">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i508" id="advance@windowsclient.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i165"
+ id="{EEF73632-A085-4fd3-A778-ECD82C8CB297}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i728" id="l@AdLJ7uz.net">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i439"
+ id="{d2cf9842-af95-48cd-b873-bfbb48cd7f5e}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i517"
+ id="/^({16e193c8-1706-40bf-b6f3-91403a9a22be}|{284fed43-2e13-4afe-8aeb-50827d510e20}|{5e3cc5d8-ed11-4bed-bc47-35b4c4bc1033}|{7429e64a-1fd4-4112-a186-2b5630816b91}|{8c9980d7-0f09-4459-9197-99b3e559660c}|{8f1d9545-0bb9-4583-bb3c-5e1ac1e2920c})$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i497"
+ id="{872b5b88-9db5-4310-bdd0-ac189557e5f5}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i39"
+ id="{c2d64ff7-0ab8-4263-89c9-ea3b0f8f050c}">
+ <prefs />
+ <versionRange minVersion="0.1" maxVersion="4.3.1.00"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i396"
+ id="/@(ft|putlocker|clickmovie|m2k|sharerepo|smarter-?)downloader\.com$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i398"
+ id="{377e5d4d-77e5-476a-8716-7e70a9272da0}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i920"
+ id="{FCE04E1F-9378-4f39-96F6-5689A9159E45}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange maxVersion="*" minVersion="39.0a1" />
+ </targetApplication>
+ </versionRange>
+ </emItem>
+ <emItem blockID="i13"
+ id="{E8E88AB0-7182-11DF-904E-6045E0D72085}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i624"
+ id="/^({b95faac1-a3d7-4d69-8943-ddd5a487d966}|{ecce0073-a837-45a2-95b9-600420505f7e}|{2713b394-286f-4d7c-89ea-4174eeab9f5a}|{da7a20cf-bef4-4342-ad78-0240fdf87055})$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i782" id="safebrowse@safebrowse.co">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i776" id="g@uzcERQ6ko.net">
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i96" id="youtubeee@youtuber3.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i69"
+ id="{977f3b97-5461-4346-92c8-a14c749b77c9}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i70" id="psid-vhvxQHMZBOzUZA@jetpack">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i258" id="helperbar@helperbar.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="1.0" severity="1" />
+ </emItem>
+ <emItem blockID="i754"
+ id="{bb7b7a60-f574-47c2-8a0b-4c56f2da9802}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i22" id="ShopperReports@ShopperReports.com">
+ <prefs />
+ <versionRange minVersion="3.1.22.0" maxVersion="3.1.22.0"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i966"
+ id="{5C655500-E712-41e7-9349-CE462F844B19}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="1.0.1-signed"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i596"
+ id="{b99c8534-7800-48fa-bd71-519a46cdc7e1}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i662" id="imbaty@taringamp3.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i868"
+ id="{6e7f6f9f-8ce6-4611-add2-05f0f7049ee6}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i982" id="odtffplugin@ibm.com">
+ <prefs />
+ <versionRange minVersion="9.0.1.1" maxVersion="9.0.1.100"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i680" id="jid1-bKSXgRwy1UQeRA@jetpack">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i1038"
+ id="344141-fasf9jas08hasoiesj9ia8ws@jetpack">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i500"
+ id="{2aab351c-ad56-444c-b935-38bffe18ad26}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i58" id="webmaster@buzzzzvideos.info">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i1034"
+ id="a88a77ahjjfjakckmmabsy278djasi@jetpack">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i530"
+ id="{739df940-c5ee-4bab-9d7e-270894ae687a}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i848" id="bcVX5@nQm9l.org">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i844"
+ id="e9d197d59f2f45f382b1aa5c14d82@8706aaed9b904554b5cb7984e9.com">
+
+ <prefs>
+ <pref>browser.startup.homepage</pref>
+ <pref>browser.search.defaultenginename</pref>
+ </prefs>
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i968"
+ id="{184AA5E6-741D-464a-820E-94B3ABC2F3B4}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i117"
+ id="{ce7e73df-6a44-4028-8079-5927a588c948}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="1.0.8"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i1040" id="frhegnejkgner@grhjgewfewf.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i404"
+ id="{a9bb9fa0-4122-4c75-bd9a-bc27db3f9155}">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i442" id="pennerdu@faceobooks.ws">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i431" id="chinaescapeone@facebook.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i542"
+ id="/^({bf67a47c-ea97-4caf-a5e3-feeba5331231}|{24a0cfe1-f479-4b19-b627-a96bf1ea3a56})$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="3" />
+ </emItem>
+ <emItem blockID="i40"
+ id="{28387537-e3f9-4ed7-860c-11e69af4a8a0}">
+ <prefs />
+ <versionRange minVersion="0.1" maxVersion="4.3.1.00"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i1265" id="@video_downloader_pro">
+ <prefs />
+ <versionRange minVersion="1.2.1" maxVersion="1.2.5"
+ severity="1" />
+ </emItem>
+ <emItem blockID="i467" id="plugin@analytic-s.com">
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i622"
+ id="/^({ebd898f8-fcf6-4694-bc3b-eabc7271eeb1}|{46008e0d-47ac-4daa-a02a-5eb69044431a}|{213c8ed6-1d78-4d8f-8729-25006aa86a76}|{fa23121f-ee7c-4bd8-8c06-123d087282c5}|{19803860-b306-423c-bbb5-f60a7d82cde5})$/">
+
+ <prefs />
+ <versionRange minVersion="0" maxVersion="*" severity="1" />
+ </emItem>
+ <emItem blockID="i638"
+ id="{7b1bf0b6-a1b9-42b0-b75d-252036438bdc}">
+ <prefs />
+ <versionRange minVersion="27.8" maxVersion="27.9"
+ severity="3" />
+ </emItem>
+ <emItem blockID="i1261" id="support@lastpass.com">
+ <prefs />
+ <versionRange minVersion="4.0.0a" maxVersion="4.1.20a"
+ severity="1" />
+ </emItem>
+ <emItem blockID="2447476f-043b-4d0b-9d3c-8e859c97d950" id="{44e4b2cf-77ba-4f76-aca7-f3fcbc2dda2f}">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="f58729ec-f93c-41d9-870d-dd9c9fd811b6" id="/^(addon@fasterweb\.com|\{5f398d3f-25db-47f5-b422-aa2364ff6c0b\}|addon@fasterp\.com|addon@calculator)$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="3d55fab0-ec1a-4bca-84c9-3b74f5d01509" id="/^.*extension.*@asdf\.pl$/">
+ <prefs/>
+ <versionRange minVersion="0" maxVersion="*" severity="3"/>
+ </emItem>
+ <emItem blockID="pm100" id="{d10d0bf8-f5b5-c8b4-a8b2-2b9879e08c5d}">
+ <versionRange minVersion="0" maxVersion="*" severity="1">
+ </versionRange>
+ <prefs></prefs>
+ </emItem>
+ <emItem blockID="pm101" id="{ebdb9835-ca92-4f3b-9376-3763d1ad1978}">
+ <versionRange minVersion="0" maxVersion="*" severity="3">
+ </versionRange>
+ <prefs></prefs>
+ </emItem>
+ <emItem blockID="pm102" id="status4evar@caligonstudios.com">
+ <versionRange minVersion="0" maxVersion="*" severity="3">
+ </versionRange>
+ <prefs></prefs>
+ </emItem>
+ <emItem blockID="pm103" id="statusbar@palemoon.org">
+ <versionRange minVersion="0" maxVersion="*" severity="3">
+ </versionRange>
+ <prefs></prefs>
+ </emItem>
+ <emItem blockID="pm104" id="ext-promises@palemoon.org">
+ <versionRange minVersion="0" maxVersion="*" severity="3">
+ </versionRange>
+ <prefs></prefs>
+ </emItem>
+ <emItem blockID="pm105" id="ClassicThemeRestorer@ArisT2Noia4dev">
+ <versionRange minVersion="0" maxVersion="*" severity="3">
+ </versionRange>
+ <prefs></prefs>
+ </emItem>
+ <emItem blockID="pm106" id="tabutils@angrydroid.com">
+ <versionRange minVersion="0" maxVersion="1.9" severity="3">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+ <versionRange minVersion="27.0.0a1" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ <prefs></prefs>
+ </emItem>
+ <emItem blockID="pm108" id="html5@encoding">
+ <versionRange minVersion="0" maxVersion="*" severity="3">
+ </versionRange>
+ <prefs></prefs>
+ </emItem>
+ <emItem blockID="pm109" id="{05b2e82d-b047-4d00-a9dd-9ddae0602172}">
+ <versionRange minVersion="0" maxVersion="1.2.3.1" severity="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+ <versionRange minVersion="27.0.0a1" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ <prefs></prefs>
+ </emItem>
+ <emItem blockID="pm111" id="customtoolbarsplus@srazzano.com">
+ <versionRange minVersion="0" maxVersion="52.4.2" severity="3">
+ </versionRange>
+ <prefs></prefs>
+ </emItem>
+ <emItem blockID="pm112" id="{73a6fe31-595d-460b-a920-fcc0f8843232}">
+ <versionRange minVersion="0" maxVersion="*" severity="1">
+ </versionRange>
+ <prefs></prefs>
+ </emItem>
+ <emItem blockID="pm113" id="addonsmanagerfix@sonco.com">
+ <versionRange minVersion="0" maxVersion="*" severity="3">
+ </versionRange>
+ <prefs></prefs>
+ </emItem>
+ <emItem blockID="pm114" id="jid1-KKzOGWgsW3Ao4Q@jetpack">
+ <versionRange minVersion="2.9.9" maxVersion="2.9.9" severity="3">
+ </versionRange>
+ <prefs></prefs>
+ </emItem>
+ </emItems>
+ <pluginItems>
+ <pluginItem blockID="p26">
+ <match name="name" exp="^Yahoo Application State Plugin$" />
+ <match name="description"
+ exp="^Yahoo Application State Plugin$" />
+ <match name="filename" exp="npYState.dll" />
+ <versionRange>
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="3.0a1" maxVersion="3.*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p27">
+ <match name="name" exp="QuickTime Plug-in 7[.]1[.]" />
+ <match name="filename" exp="npqtplugin.?[.]dll" />
+ <versionRange>
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="3.0a1" maxVersion="3.*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p28">
+ <match name="filename" exp="NPFFAddOn.dll" />
+ </pluginItem>
+ <pluginItem blockID="p31">
+ <match name="filename" exp="NPMySrch.dll" />
+ </pluginItem>
+ <pluginItem blockID="p32">
+ <match name="filename" exp="npViewpoint.dll" />
+ <versionRange>
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="3.0" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p33">
+ <match name="name" exp="[0-6]\.0\.[01]\d{2}\.\d+" />
+ <match name="filename" exp="npdeploytk.dll" />
+ <versionRange severity="1"></versionRange>
+ </pluginItem>
+ <pluginItem blockID="p34">
+ <match name="filename"
+ exp="[Nn][Pp][Jj][Pp][Ii]1[56]0_[0-9]+\.[Dd][Ll][Ll]" />
+ <versionRange>
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="3.6a1pre" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p80">
+ <match name="name" exp="\(TM\)" />
+ <match name="description"
+ exp="[^\d\._]((0(\.\d+(\.\d+([_\.]\d+)?)?)?)|(1\.(([0-5](\.\d+([_\.]\d+)?)?)|(6(\.0([_\.](0?\d|1\d|2\d|30))?)?)|(7(\.0([_\.][0-2])?)?))))([^\d\._]|$)" />
+ <match name="filename" exp="(npjp2\.dll)|(libnpjp2\.so)" />
+ <versionRange severity="1"></versionRange>
+ </pluginItem>
+ <pluginItem blockID="p85">
+ <match name="filename" exp="JavaPlugin2_NPAPI\.plugin" />
+ <versionRange minVersion="0" maxVersion="13.6.0"
+ severity="1"></versionRange>
+ </pluginItem>
+ <pluginItem os="Darwin" blockID="p89">
+ <match name="filename" exp="AdobePDFViewerNPAPI\.plugin" />
+ <versionRange minVersion="0" maxVersion="10.1.3"
+ severity="1"></versionRange>
+ </pluginItem>
+ <pluginItem blockID="p94">
+ <match name="filename" exp="Flash\ Player\.plugin" />
+ <versionRange minVersion="0" maxVersion="10.2.159.1"
+ severity="0">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="0.1" maxVersion="17.0.1" />
+ </targetApplication>
+ </versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p102">
+ <match name="filename" exp="npmozax\.dll" />
+ <versionRange minVersion="0" maxVersion="*"></versionRange>
+ </pluginItem>
+ <pluginItem blockID="p113">
+ <match name="filename" exp="npuplaypc\.dll" />
+ <versionRange minVersion="0" maxVersion="1.0.0.0"
+ severity="1"></versionRange>
+ </pluginItem>
+ <pluginItem blockID="p119">
+ <match name="name"
+ exp="Java\(TM\) Plug-in 1\.(6\.0_(\d|[0-2]\d?|3[0-2])|7\.0(_0?([1-4]))?)([^\d\._]|$)" />
+ <match name="filename" exp="libnpjp2\.so" />
+ <versionRange severity="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="0.1" maxVersion="17.*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p123">
+ <match name="filename" exp="JavaPlugin2_NPAPI\.plugin" />
+ <versionRange minVersion="0" maxVersion="14.2.0"
+ severity="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="0.1" maxVersion="17.*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p125">
+ <match name="name"
+ exp="Java\(TM\) Platform SE ((6( U(\d|([0-2]\d)|3[0-2]))?)|(7(\sU[0-4])?))(\s[^\d\._U]|$)" />
+ <match name="filename" exp="npjp2\.dll" />
+ <versionRange severity="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="0.1" maxVersion="17.*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p129">
+ <match name="filename" exp="Silverlight\.plugin" />
+ <versionRange minVersion="0" maxVersion="5.0.99999"
+ severity="1"></versionRange>
+ </pluginItem>
+ <pluginItem blockID="p132">
+ <match name="name"
+ exp="Java\(TM\) Plug-in 1\.7\.0(_0?([5-6]))?([^\d\._]|$)" />
+ <match name="filename" exp="libnpjp2\.so" />
+ <versionRange severity="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="0.1" maxVersion="17.*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p134">
+ <match name="name"
+ exp="Java\(TM\) Platform SE 7 U[5-6](\s[^\d\._U]|$)" />
+ <match name="filename" exp="npjp2\.dll" />
+ <versionRange severity="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="0.1" maxVersion="17.*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p138">
+ <match name="filename" exp="JavaAppletPlugin\.plugin" />
+ <versionRange minVersion="Java 7 Update 01"
+ maxVersion="Java 7 Update 06" severity="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="0.1" maxVersion="17.*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p152">
+ <match name="filename" exp="npctrl\.dll" />
+ <versionRange minVersion="0" maxVersion="4.1.10328.0"
+ severity="0" vulnerabilitystatus="1"></versionRange>
+ </pluginItem>
+ <pluginItem blockID="p154">
+ <match name="filename" exp="npctrl\.dll" />
+ <versionRange minVersion="5.0" maxVersion="5.1.20124.9999"
+ severity="0" vulnerabilitystatus="1"></versionRange>
+ </pluginItem>
+ <pluginItem blockID="p156">
+ <match name="filename" exp="nppdf32\.dll" />
+ <versionRange minVersion="0" maxVersion="9.5.1" severity="0"
+ vulnerabilitystatus="1"></versionRange>
+ </pluginItem>
+ <pluginItem blockID="p158">
+ <match name="filename" exp="nppdf32\.dll" />
+ <versionRange minVersion="10.0" maxVersion="10.1.5.9999"
+ severity="0" vulnerabilitystatus="1"></versionRange>
+ </pluginItem>
+ <pluginItem blockID="p160">
+ <match name="filename" exp="NPSWF32\.dll" />
+ <versionRange minVersion="0" maxVersion="10.2.9999"
+ severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="4.0" maxVersion="16.*" />
+ </targetApplication>
+ </versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p176">
+ <match name="filename"
+ exp="(NPSWF32\.dll)|(Flash\ Player\.plugin)" />
+ <versionRange minVersion="10.3" maxVersion="10.3.183.18.999"
+ severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="19.0a1" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p176">
+ <match name="filename"
+ exp="(NPSWF32\.dll)|(Flash\ Player\.plugin)" />
+ <versionRange minVersion="10.3" maxVersion="10.3.183.18.999"
+ severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="17.0.4" maxVersion="17.0.*" />
+ </targetApplication>
+ </versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p178">
+ <match name="filename"
+ exp="(NPSWF[0-9_]*\.dll)|(Flash\ Player\.plugin)" />
+ <versionRange minVersion="11.0" maxVersion="11.7.700.169"
+ severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="19.0a1" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p178">
+ <match name="filename"
+ exp="(NPSWF[0-9_]*\.dll)|(Flash\ Player\.plugin)" />
+ <versionRange minVersion="11.0" maxVersion="11.7.700.169"
+ severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="17.0.4" maxVersion="17.0.*" />
+ </targetApplication>
+ </versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p180">
+ <match name="filename" exp="JavaAppletPlugin\.plugin" />
+ <versionRange minVersion="Java 7 Update 0"
+ maxVersion="Java 7 Update 11" severity="0"
+ vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="17.0" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p182">
+ <match name="name"
+ exp="Java\(TM\) Platform SE 7 U([0-9]|(1[0-1]))(\s[^\d\._U]|$)" />
+ <match name="filename" exp="npjp2\.dll" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="17.0" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p184">
+ <match name="name"
+ exp="Java\(TM\) Plug-in 1\.7\.0(_0?([0-9]|(1[0-1]))?)?([^\d\._]|$)" />
+ <match name="filename" exp="libnpjp2\.so" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="17.0" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p186">
+ <match name="name"
+ exp="Java\(TM\) Platform SE 6 U3[1-8](\s[^\d\._U]|$)" />
+ <match name="filename" exp="npjp2\.dll" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="17.0" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p188">
+ <match name="filename" exp="JavaAppletPlugin\.plugin" />
+ <versionRange minVersion="Java 6 Update 0"
+ maxVersion="Java 6 Update 38" severity="0"
+ vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="17.0" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p190">
+ <match name="name"
+ exp="Java\(TM\) Plug-in 1\.6\.0_3[1-8]([^\d\._]|$)" />
+ <match name="filename" exp="libnpjp2\.so" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="17.0" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p210">
+ <match name="name"
+ exp="Java\(TM\) Plug-in 1\.7\.0(_0?7)?([^\d\._]|$)" />
+ <match name="filename" exp="libnpjp2\.so" />
+ <versionRange severity="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="0.1" maxVersion="17.*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p212">
+ <match name="filename" exp="JavaAppletPlugin\.plugin" />
+ <versionRange minVersion="Java 7 Update 07"
+ maxVersion="Java 7 Update 07" severity="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="0.1" maxVersion="17.*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p214">
+ <match name="name"
+ exp="Java\(TM\) Platform SE 7 U7(\s[^\d\._U]|$)" />
+ <match name="filename" exp="npjp2\.dll" />
+ <versionRange severity="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="0.1" maxVersion="17.*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p240">
+ <match name="filename" exp="DivXBrowserPlugin\.plugin" />
+ <versionRange minVersion="0" maxVersion="1.4" severity="1">
+ </versionRange>
+ </pluginItem>
+ <pluginItem os="Darwin" blockID="p242">
+ <match name="description" exp="Flip4Mac" />
+ <versionRange minVersion="0" maxVersion="2.4.3.999"
+ severity="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="18.0a1" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p248">
+ <match name="filename" exp="Scorch\.plugin" />
+ <versionRange minVersion="0" maxVersion="6.2.0b88"
+ severity="1"></versionRange>
+ </pluginItem>
+ <pluginItem blockID="p250">
+ <match name="filename" exp="npFoxitReaderPlugin\.dll" />
+ <versionRange minVersion="0" maxVersion="2.2.1.530"
+ severity="0" vulnerabilitystatus="2"></versionRange>
+ </pluginItem>
+ <pluginItem os="Darwin" blockID="p252">
+ <match name="filename" exp="AdobePDFViewerNPAPI\.plugin" />
+ <versionRange minVersion="11.0.0" maxVersion="11.0.01"
+ severity="1"></versionRange>
+ </pluginItem>
+ <pluginItem blockID="p254">
+ <match name="filename" exp="PDF Browser Plugin\.plugin" />
+ <versionRange minVersion="0" maxVersion="2.4.2" severity="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="18.0a1" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p260">
+ <match name="filename"
+ exp="(NPSWF32\.dll)|(Flash\ Player\.plugin)" />
+ <versionRange minVersion="0" maxVersion="10.2.9999"
+ severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="18.0a1" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p260">
+ <match name="filename"
+ exp="(NPSWF32\.dll)|(Flash\ Player\.plugin)" />
+ <versionRange minVersion="0" maxVersion="10.2.9999"
+ severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="17.0.4" maxVersion="17.0.*" />
+ </targetApplication>
+ </versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p290">
+ <match name="filename"
+ exp="(NPSWF32\.dll)|(Flash\ Player\.plugin)" />
+ <versionRange minVersion="10.3.183.19"
+ maxVersion="10.3.183.66" severity="0"
+ vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="19.0a1" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p290">
+ <match name="filename"
+ exp="(NPSWF32\.dll)|(Flash\ Player\.plugin)" />
+ <versionRange minVersion="10.3.183.19"
+ maxVersion="10.3.183.66" severity="0"
+ vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="17.0.4" maxVersion="17.0.*" />
+ </targetApplication>
+ </versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p292">
+ <match name="filename" exp="JavaAppletPlugin\.plugin" />
+ <versionRange minVersion="Java 7 Update 12"
+ maxVersion="Java 7 Update 15" severity="0"
+ vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="17.0" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p294">
+ <match name="name"
+ exp="Java\(TM\) Platform SE 7 U1[2-5](\s[^\d\._U]|$)" />
+ <match name="filename" exp="npjp2\.dll" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="17.0" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p296">
+ <match name="name"
+ exp="Java\(TM\) Plug-in 1\.7\.0_1[2-5]([^\d\._]|$)" />
+ <match name="filename" exp="libnpjp2\.so" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="17.0" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p298">
+ <match name="filename" exp="JavaAppletPlugin\.plugin" />
+ <versionRange minVersion="Java 6 Update 39"
+ maxVersion="Java 6 Update 41" severity="0"
+ vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="17.0" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p300">
+ <match name="name"
+ exp="Java\(TM\) Platform SE 6 U(39|40|41)(\s[^\d\._U]|$)" />
+ <match name="filename" exp="npjp2\.dll" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="17.0" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p302">
+ <match name="name"
+ exp="Java\(TM\) Plug-in 1\.6\.0_(39|40|41)([^\d\._]|$)" />
+ <match name="filename" exp="libnpjp2\.so" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="17.0" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p328">
+ <match name="filename" exp="Silverlight\.plugin" />
+ <versionRange minVersion="5.1" maxVersion="5.1.20124.9999"
+ severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="19.0a1" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p328">
+ <match name="filename" exp="Silverlight\.plugin" />
+ <versionRange minVersion="5.1" maxVersion="5.1.20124.9999"
+ severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="17.0.4" maxVersion="17.0.*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p330">
+ <match name="description"
+ exp="^Shockwave Flash (([1-9]\.[0-9]+)|(10\.([0-2]|(3 r(([0-9][0-9]?)|1(([0-7][0-9])|8[0-2]))))))( |$)" />
+ <match name="filename" exp="libflashplayer\.so" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="19.0a1" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p330">
+ <match name="description"
+ exp="^Shockwave Flash (([1-9]\.[0-9]+)|(10\.([0-2]|(3 r(([0-9][0-9]?)|1(([0-7][0-9])|8[0-2]))))))( |$)" />
+ <match name="filename" exp="libflashplayer\.so" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="17.0.4" maxVersion="17.0.*" />
+ </targetApplication>
+ </versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p332">
+ <match name="description"
+ exp="^Shockwave Flash 11.(0|1) r[0-9]{1,3}$" />
+ <match name="filename" exp="libflashplayer\.so" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="19.0a1" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p332">
+ <match name="description"
+ exp="^Shockwave Flash 11.(0|1) r[0-9]{1,3}$" />
+ <match name="filename" exp="libflashplayer\.so" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="17.0.4" maxVersion="17.0.*" />
+ </targetApplication>
+ </versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p366">
+ <match name="filename" exp="Scorch\.plugin" />
+ <versionRange minVersion="6.2.0" maxVersion="6.2.0"
+ severity="1"></versionRange>
+ </pluginItem>
+ <pluginItem blockID="p408">
+ <match name="filename" exp="QuickTime Plugin\.plugin" />
+ <versionRange minVersion="0" maxVersion="7.6.5" severity="0"
+ vulnerabilitystatus="1"></versionRange>
+ </pluginItem>
+ <pluginItem blockID="p410">
+ <match name="filename" exp="npqtplugin\.dll" />
+ <versionRange minVersion="0" maxVersion="7.7.3" severity="0"
+ vulnerabilitystatus="1"></versionRange>
+ </pluginItem>
+ <pluginItem blockID="p412">
+ <match name="name"
+ exp="Java\(TM\) Plug-in 1\.6\.0_4[2-5]([^\d\._]|$)" />
+ <match name="filename" exp="libnpjp2\.so" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="17.0" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p414">
+ <match name="name"
+ exp="Java\(TM\) Platform SE 6 U4[2-5](\s[^\d\._U]|$)" />
+ <match name="filename" exp="npjp2\.dll" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="17.0" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p416">
+ <match name="filename" exp="JavaAppletPlugin\.plugin" />
+ <versionRange minVersion="Java 6 Update 42"
+ maxVersion="Java 6 Update 45" severity="0"
+ vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="17.0" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p418">
+ <match name="name"
+ exp="Java\(TM\) Plug-in 1\.7\.0_(1[6-9]|2[0-4])([^\d\._]|$)" />
+ <match name="filename" exp="libnpjp2\.so" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="17.0" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p420">
+ <match name="name"
+ exp="Java\(TM\) Platform SE 7 U(1[6-9]|2[0-4])(\s[^\d\._U]|$)" />
+ <match name="filename" exp="npjp2\.dll" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="17.0" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p422">
+ <match name="filename" exp="JavaAppletPlugin\.plugin" />
+ <versionRange minVersion="Java 7 Update 16"
+ maxVersion="Java 7 Update 24" severity="0"
+ vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="17.0" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p428">
+ <match name="filename" exp="np[dD]eployJava1\.dll" />
+ <versionRange severity="0" vulnerabilitystatus="2">
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p456">
+ <match name="filename" exp="npvlc\.dll" />
+ <versionRange minVersion="0" maxVersion="2.0.5" severity="0"
+ vulnerabilitystatus="1"></versionRange>
+ </pluginItem>
+ <pluginItem blockID="p457">
+ <match name="name"
+ exp="Java(\(TM\))? Plug-in ((1\.7\.0_(2[5-9]|3\d|4[0-4]))|(10\.4[0-4](\.[0-9]+)?))([^\d\._]|$)" />
+ <match name="filename" exp="libnpjp2\.so" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="17.0" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p458">
+ <match name="name"
+ exp="Java\(TM\) Platform SE 7 U(2[5-9]|3\d|4[0-4])(\s[^\d\._U]|$)" />
+ <match name="filename" exp="npjp2\.dll" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="17.0" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p459">
+ <match name="filename" exp="JavaAppletPlugin\.plugin" />
+ <versionRange minVersion="Java 7 Update 25"
+ maxVersion="Java 7 Update 44" severity="0"
+ vulnerabilitystatus="1">
+ <targetApplication id="{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}">
+
+ <versionRange minVersion="17.0" maxVersion="*" />
+ </targetApplication>
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p556">
+ <match name="filename" exp="npUnity3D32\.dll" />
+ <versionRange minVersion="0" maxVersion="4.6.6f1"
+ severity="0" vulnerabilitystatus="1"></versionRange>
+ </pluginItem>
+ <pluginItem blockID="p558">
+ <match name="description"
+ exp="^($|Unity Web Player version ([0-3]|(4\.([0-5]|6(\.([0-5]|6f1)))?[^0-9.])))" />
+ <match name="filename" exp="Unity Web Player\.plugin" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p572">
+ <match name="filename" exp="npdjvu\.dll" />
+ <versionRange minVersion="0" maxVersion="6.1.4.27993"
+ severity="0" vulnerabilitystatus="1"></versionRange>
+ </pluginItem>
+ <pluginItem blockID="p574">
+ <match name="filename" exp="NPDjVu\.plugin" />
+ <versionRange minVersion="0" maxVersion="6.1.1" severity="0"
+ vulnerabilitystatus="1"></versionRange>
+ </pluginItem>
+ <pluginItem blockID="p592">
+ <match name="filename" exp="CiscoWebCommunicator\.plugin" />
+ <versionRange minVersion="0"
+ maxVersion="3.0.5.99999999999999" severity="0"
+ vulnerabilitystatus="1"></versionRange>
+ </pluginItem>
+ <pluginItem blockID="p594">
+ <match name="filename" exp="npCiscoWebCommunicator\.dll" />
+ <versionRange minVersion="0"
+ maxVersion="3.0.5.99999999999999" severity="0"
+ vulnerabilitystatus="1"></versionRange>
+ </pluginItem>
+ <pluginItem blockID="p794">
+ <match name="filename"
+ exp="(NPSWF32.*\.dll)|(Flash\ Player\.plugin)" />
+ <versionRange minVersion="10.3.183.66"
+ maxVersion="13.0.0.258" severity="0" vulnerabilitystatus="1">
+ </versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem os="Linux" blockID="p796">
+ <match name="filename" exp="libflashplayer\.so" />
+ <versionRange minVersion="0" maxVersion="11.2.202.424"
+ severity="0" vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p798">
+ <match name="filename"
+ exp="(NPSWF32.*\.dll)|(Flash\ Player\.plugin)" />
+ <versionRange minVersion="14.0" maxVersion="15.0.0.242"
+ severity="0" vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p824">
+ <match name="filename"
+ exp="(NPSWF32.*\.dll)|(Flash\ Player\.plugin)" />
+ <versionRange minVersion="13.0.0.259" maxVersion="13.0.0.263"
+ severity="0" vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem os="Linux" blockID="p826">
+ <match name="filename" exp="libflashplayer\.so" />
+ <versionRange minVersion="11.2.202.425"
+ maxVersion="11.2.202.439" severity="0"
+ vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p828">
+ <match name="filename"
+ exp="(NPSWF32.*\.dll)|(Flash\ Player\.plugin)" />
+ <versionRange minVersion="15.0.0.243" maxVersion="16.0.0.287"
+ severity="0" vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem os="Linux" blockID="p830">
+ <match name="filename" exp="libflashplayer\.so" />
+ <versionRange minVersion="11.2.202.439"
+ maxVersion="11.2.202.441" severity="0"
+ vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p832">
+ <match name="filename"
+ exp="(NPSWF32.*\.dll)|(Flash\ Player\.plugin)" />
+ <versionRange minVersion="16.0.0.295" maxVersion="16.0.0.304"
+ severity="0" vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p834">
+ <match name="filename"
+ exp="(NPSWF32.*\.dll)|(Flash\ Player\.plugin)" />
+ <versionRange minVersion="13.0.0.263" maxVersion="13.0.0.268"
+ severity="0" vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p902">
+ <match name="filename" exp="JavaAppletPlugin\.plugin" />
+ <versionRange minVersion="Java 7 Update 45"
+ maxVersion="Java 7 Update 78" severity="0"
+ vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://java.com/</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p904">
+ <match name="filename" exp="JavaAppletPlugin\.plugin" />
+ <versionRange minVersion="Java 8"
+ maxVersion="Java 8 Update 44" severity="0"
+ vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://java.com/</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p906">
+ <match name="name"
+ exp="Java\(TM\) Platform SE 7 U(4[5-9]|(5|6)\d|7[0-8])(\s[^\d\._U]|$)" />
+ <match name="filename" exp="npjp2\.dll" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ </versionRange>
+ <infoURL>https://java.com/</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p908">
+ <match name="name"
+ exp="Java\(TM\) Platform SE 8( U([1-3]?\d|4[0-4]))?(\s[^\d\._U]|$)" />
+ <match name="filename" exp="npjp2\.dll" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ </versionRange>
+ <infoURL>https://java.com/</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p910">
+ <match name="name"
+ exp="Java(\(TM\))? Plug-in 10\.(4[5-9]|(5|6)\d|7[0-8])(\.[0-9]+)?([^\d\._]|$)" />
+ <match name="filename" exp="libnpjp2\.so" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ </versionRange>
+ <infoURL>https://java.com/</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p912">
+ <match name="name"
+ exp="Java(\(TM\))? Plug-in 11\.(\d|[1-3]\d|4[0-4])(\.[0-9]+)?([^\d\._]|$)" />
+ <match name="filename" exp="libnpjp2\.so" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ </versionRange>
+ <infoURL>https://java.com/</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p928">
+ <match name="filename"
+ exp="(NPSWF32.*\.dll)|(NPSWF64.*\.dll)|(Flash\ Player\.plugin)" />
+ <versionRange minVersion="13.0.0.269" maxVersion="13.0.0.295"
+ severity="0" vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p930">
+ <match name="filename"
+ exp="(NPSWF32.*\.dll)|(NPSWF64.*\.dll)|(Flash\ Player\.plugin)" />
+ <versionRange minVersion="16.0.0.305" maxVersion="18.0.0.193"
+ severity="0" vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem os="Linux" blockID="p932">
+ <match name="filename" exp="libflashplayer\.so" />
+ <versionRange minVersion="11.2.202.442"
+ maxVersion="11.2.202.467" severity="0"
+ vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem os="Linux" blockID="p936">
+ <match name="filename" exp="libflashplayer\.so" />
+ <versionRange minVersion="11.2.202.468"
+ maxVersion="11.2.202.480" severity="0"
+ vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p938">
+ <match name="filename"
+ exp="(NPSWF32.*\.dll)|(NPSWF64.*\.dll)|(Flash\ Player\.plugin)" />
+ <versionRange minVersion="18.0.0.194" maxVersion="18.0.0.202"
+ severity="0" vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p940">
+ <match name="filename"
+ exp="(NPSWF32.*\.dll)|(NPSWF64.*\.dll)|(Flash\ Player\.plugin)" />
+ <versionRange minVersion="13.0.0.296" maxVersion="13.0.0.301"
+ severity="0" vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem os="Linux" blockID="p948">
+ <match name="filename" exp="libflashplayer\.so" />
+ <versionRange minVersion="11.2.202.481"
+ maxVersion="11.2.202.481" severity="0"
+ vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p954">
+ <match name="filename" exp="JavaAppletPlugin\.plugin" />
+ <versionRange minVersion="Java 7 Update 79"
+ maxVersion="Java 7 Update 80" severity="0"
+ vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://java.com/</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p956">
+ <match name="filename" exp="JavaAppletPlugin\.plugin" />
+ <versionRange minVersion="Java 8 Update 45"
+ maxVersion="Java 8 Update 45" severity="0"
+ vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://java.com/</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p958">
+ <match name="name"
+ exp="Java\(TM\) Platform SE 7 U(79|80)(\s[^\d\._U]|$)" />
+ <match name="filename" exp="npjp2\.dll" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ </versionRange>
+ <infoURL>https://java.com/</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p960">
+ <match name="name"
+ exp="Java\(TM\) Platform SE 8 U45(\s[^\d\._U]|$)" />
+ <match name="filename" exp="npjp2\.dll" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ </versionRange>
+ <infoURL>https://java.com/</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p962">
+ <match name="name"
+ exp="Java(\(TM\))? Plug-in 10\.(79|80)(\.[0-9]+)?([^\d\._]|$)" />
+ <match name="filename" exp="libnpjp2\.so" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ </versionRange>
+ <infoURL>https://java.com/</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p964">
+ <match name="name"
+ exp="Java(\(TM\))? Plug-in 11\.45(\.[0-9]+)?([^\d\._]|$)" />
+ <match name="filename" exp="libnpjp2\.so" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ </versionRange>
+ <infoURL>https://java.com/</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p1002">
+ <match name="filename" exp="npUnity3D32\.dll" />
+ <versionRange minVersion="5.0" maxVersion="5.0.3f1"
+ severity="0" vulnerabilitystatus="1"></versionRange>
+ </pluginItem>
+ <pluginItem blockID="p1004">
+ <match name="description"
+ exp="^($|Unity Web Player version 5.0(\.([0-2]|3f1))?[^0-9.])" />
+ <match name="filename" exp="Unity Web Player\.plugin" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ </versionRange>
+ </pluginItem>
+ <pluginItem blockID="p1020">
+ <match name="filename"
+ exp="(NPSWF32.*\.dll)|(NPSWF64.*\.dll)|(Flash\ Player\.plugin)" />
+ <versionRange minVersion="13.0" maxVersion="13.0.0.308"
+ severity="0" vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p1026">
+ <match name="filename"
+ exp="(NPSWF32.*\.dll)|(NPSWF64.*\.dll)|(Flash\ Player\.plugin)" />
+ <versionRange minVersion="18.0.0.204" maxVersion="18.0.0.232"
+ severity="0" vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem os="Linux" blockID="p1028">
+ <match name="filename" exp="libflashplayer\.so" />
+ <versionRange minVersion="11.2.202.482"
+ maxVersion="11.2.202.508" severity="0"
+ vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem os="Linux" blockID="p1044">
+ <match name="filename" exp="libflashplayer\.so" />
+ <versionRange minVersion="11.2.202.509"
+ maxVersion="11.2.202.539" severity="0"
+ vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p1046">
+ <match name="filename"
+ exp="(NPSWF32.*\.dll)|(NPSWF64.*\.dll)|(Flash\ Player\.plugin)" />
+ <versionRange minVersion="18.0.0.233" maxVersion="18.0.0.254"
+ severity="0" vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p1048">
+ <match name="filename"
+ exp="(NPSWF32.*\.dll)|(NPSWF64.*\.dll)|(Flash\ Player\.plugin)" />
+ <versionRange minVersion="19.0" maxVersion="19.0.0.225"
+ severity="0" vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p1053">
+ <match name="filename" exp="nprpplugin\.dll" />
+ <versionRange minVersion="0" maxVersion="17.0.10.7"
+ severity="0" vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://real.com/</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p1054">
+ <match name="filename" exp="np32dsw_[0-9]+\.dll" />
+ <versionRange minVersion="0" maxVersion="12.2.0.162"
+ severity="0" vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://get.adobe.com/shockwave/</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p1055">
+ <match name="filename" exp="DirectorShockwave\.plugin" />
+ <versionRange minVersion="0" maxVersion="12.2.0.162"
+ severity="0" vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://get.adobe.com/shockwave/</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p1059">
+ <match name="filename" exp="JavaAppletPlugin\.plugin" />
+ <versionRange minVersion="Java 7 Update 81"
+ maxVersion="Java 7 Update 90" severity="0"
+ vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://java.com/</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p1060">
+ <match name="filename" exp="JavaAppletPlugin\.plugin" />
+ <versionRange minVersion="Java 8 Update 46"
+ maxVersion="Java 8 Update 64" severity="0"
+ vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://java.com/</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p1061">
+ <match name="name"
+ exp="Java\(TM\) Platform SE 7 U(8[1-9]|90)(\s[^\d\._U]|$)" />
+ <match name="filename" exp="npjp2\.dll" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ </versionRange>
+ <infoURL>https://java.com/</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p1062">
+ <match name="name"
+ exp="Java\(TM\) Platform SE 8 U(4[6-9]|5\d|6[0-4])(\s[^\d\._U]|$)" />
+ <match name="filename" exp="npjp2\.dll" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ </versionRange>
+ <infoURL>https://java.com/</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p1063">
+ <match name="name"
+ exp="Java(\(TM\))? Plug-in 10\.(8[1-9]|90)(\.[0-9]+)?([^\d\._]|$)" />
+ <match name="filename" exp="libnpjp2\.so" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ </versionRange>
+ <infoURL>https://java.com/</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p1064">
+ <match name="name"
+ exp="Java(\(TM\))? Plug-in 11\.(4[6-9]|5\d|6[0-4])(\.[0-9]+)?([^\d\._]|$)" />
+ <match name="filename" exp="libnpjp2\.so" />
+ <versionRange severity="0" vulnerabilitystatus="1">
+ </versionRange>
+ <infoURL>https://java.com/</infoURL>
+ </pluginItem>
+ <pluginItem os="Linux" blockID="p1065">
+ <match name="filename" exp="libflashplayer\.so" />
+ <versionRange minVersion="11.2.202.540"
+ maxVersion="11.2.202.548" severity="0"
+ vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p1066">
+ <match name="filename"
+ exp="(NPSWF32.*\.dll)|(NPSWF64.*\.dll)|(Flash\ Player\.plugin)" />
+ <versionRange minVersion="18.0.0.255" maxVersion="18.0.0.261"
+ severity="0" vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ <pluginItem blockID="p1067">
+ <match name="filename"
+ exp="(NPSWF32.*\.dll)|(NPSWF64.*\.dll)|(Flash\ Player\.plugin)" />
+ <versionRange minVersion="19.0.0.226" maxVersion="19.0.0.245"
+ severity="0" vulnerabilitystatus="1"></versionRange>
+ <infoURL>https://www.adobe.com/products/flashplayer/distribution3.html</infoURL>
+ </pluginItem>
+ </pluginItems>
+ <gfxItems>
+ <gfxBlacklistEntry blockID="g35">
+ <os>WINNT 6.1</os>
+ <vendor>0x10de</vendor>
+ <devices>
+ <device>0x0a6c</device>
+ </devices>
+ <feature>DIRECT2D</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>8.17.12.5896</driverVersion>
+ <driverVersionComparator>
+ LESS_THAN_OR_EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g36">
+ <os>WINNT 6.1</os>
+ <vendor>0x10de</vendor>
+ <devices>
+ <device>0x0a6c</device>
+ </devices>
+ <feature>DIRECT3D_9_LAYERS</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>8.17.12.5896</driverVersion>
+ <driverVersionComparator>
+ LESS_THAN_OR_EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g37">
+ <os>WINNT 5.1</os>
+ <vendor>0x10de</vendor>
+ <feature>DIRECT3D_9_LAYERS</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>7.0.0.0</driverVersion>
+ <driverVersionComparator>
+ GREATER_THAN_OR_EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g144">
+ <os>All</os>
+ <vendor>0x1002</vendor>
+ <feature>DIRECT2D</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>8.982.0.0</driverVersion>
+ <driverVersionComparator>EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g146">
+ <os>All</os>
+ <vendor>0x1022</vendor>
+ <feature>DIRECT2D</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>8.982.0.0</driverVersion>
+ <driverVersionComparator>EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g148">
+ <os>All</os>
+ <vendor>0x1022</vendor>
+ <feature>DIRECT3D_9_LAYERS</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>8.982.0.0</driverVersion>
+ <driverVersionComparator>EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g150">
+ <os>All</os>
+ <vendor>0x1002</vendor>
+ <feature>DIRECT3D_9_LAYERS</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>8.982.0.0</driverVersion>
+ <driverVersionComparator>EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g192">
+ <os>WINNT 6.2</os>
+ <vendor>0x1002</vendor>
+ <feature>DIRECT2D</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>9.10.8.0</driverVersion>
+ <driverVersionComparator>
+ LESS_THAN_OR_EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g194">
+ <os>WINNT 6.2</os>
+ <vendor>0x1022</vendor>
+ <feature>DIRECT2D</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>9.10.8.0</driverVersion>
+ <driverVersionComparator>
+ LESS_THAN_OR_EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g198">
+ <os>Darwin 10</os>
+ <vendor>0x10de</vendor>
+ <feature>WEBGL_MSAA</feature>
+ <featureStatus>BLOCKED_DEVICE</featureStatus>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g200">
+ <os>Darwin 11</os>
+ <vendor>0x10de</vendor>
+ <feature>WEBGL_MSAA</feature>
+ <featureStatus>BLOCKED_DEVICE</featureStatus>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g202">
+ <os>Darwin 12</os>
+ <vendor>0x10de</vendor>
+ <feature>WEBGL_MSAA</feature>
+ <featureStatus>BLOCKED_DEVICE</featureStatus>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g204">
+ <os>Darwin 10</os>
+ <vendor>0x8086</vendor>
+ <feature>WEBGL_MSAA</feature>
+ <featureStatus>BLOCKED_DEVICE</featureStatus>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g206">
+ <os>Darwin 11</os>
+ <vendor>0x8086</vendor>
+ <feature>WEBGL_MSAA</feature>
+ <featureStatus>BLOCKED_DEVICE</featureStatus>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g208">
+ <os>Darwin 12</os>
+ <vendor>0x8086</vendor>
+ <feature>WEBGL_MSAA</feature>
+ <featureStatus>BLOCKED_DEVICE</featureStatus>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g230">
+ <os>Darwin 10</os>
+ <vendor>0x1002</vendor>
+ <feature>WEBGL_MSAA</feature>
+ <featureStatus>BLOCKED_DEVICE</featureStatus>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g232">
+ <os>Darwin 11</os>
+ <vendor>0x1002</vendor>
+ <feature>WEBGL_MSAA</feature>
+ <featureStatus>BLOCKED_DEVICE</featureStatus>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g234">
+ <os>Darwin 12</os>
+ <vendor>0x1002</vendor>
+ <feature>WEBGL_MSAA</feature>
+ <featureStatus>BLOCKED_DEVICE</featureStatus>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g278">
+ <os>WINNT 6.1</os>
+ <vendor>0x1002</vendor>
+ <devices>
+ <device>0x68e1</device>
+ <device>0x68e4</device>
+ <device>0x68e5</device>
+ <device>0x68f9</device>
+ <device>0x9802</device>
+ <device>0x9803</device>
+ <device>0x9803</device>
+ <device>0x9804</device>
+ <device>0x9805</device>
+ <device>0x9806</device>
+ <device>0x9807</device>
+ </devices>
+ <feature>DIRECT2D</feature>
+ <featureStatus>BLOCKED_DEVICE</featureStatus>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g280">
+ <os>WINNT 6.1</os>
+ <vendor>0x1002</vendor>
+ <devices>
+ <device>0x9802</device>
+ <device>0x9803</device>
+ <device>0x9803</device>
+ <device>0x9804</device>
+ <device>0x9805</device>
+ <device>0x9806</device>
+ <device>0x9807</device>
+ </devices>
+ <feature>DIRECT3D_9_LAYERS</feature>
+ <featureStatus>BLOCKED_DEVICE</featureStatus>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g974">
+ <os>WINNT 10.0</os>
+ <vendor>0x1002</vendor>
+ <devices>
+ <device>0x6920</device>
+ <device>0x6921</device>
+ <device>0x6928</device>
+ <device>0x6929</device>
+ <device>0x692b</device>
+ <device>0x692f</device>
+ <device>0x6930</device>
+ <device>0x6938</device>
+ <device>0x6939</device>
+ <device>0x6900</device>
+ <device>0x6901</device>
+ <device>0x6902</device>
+ <device>0x6903</device>
+ <device>0x6907</device>
+ <device>0x7300</device>
+ <device>0x9870</device>
+ <device>0x9874</device>
+ <device>0x9875</device>
+ <device>0x9876</device>
+ <device>0x9877</device>
+ </devices>
+ <feature>DIRECT2D</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>15.201.1151.0</driverVersion>
+ <driverVersionComparator>LESS_THAN</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g984">
+ <os>All</os>
+ <vendor>0x8086</vendor>
+ <feature>DIRECT2D</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>8.15.10.2413</driverVersion>
+ <driverVersionComparator>
+ LESS_THAN_OR_EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g992">
+ <os>WINNT 8.1</os>
+ <vendor>0x1002</vendor>
+ <devices>
+ <device>0x6920</device>
+ <device>0x6921</device>
+ <device>0x6928</device>
+ <device>0x6929</device>
+ <device>0x692b</device>
+ <device>0x692f</device>
+ <device>0x6930</device>
+ <device>0x6938</device>
+ <device>0x6939</device>
+ <device>0x6900</device>
+ <device>0x6901</device>
+ <device>0x6902</device>
+ <device>0x6903</device>
+ <device>0x6907</device>
+ <device>0x7300</device>
+ <device>0x9870</device>
+ <device>0x9874</device>
+ <device>0x9875</device>
+ <device>0x9876</device>
+ <device>0x9877</device>
+ </devices>
+ <feature>DIRECT2D</feature>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>15.201.1151.0</driverVersion>
+ <driverVersionComparator>LESS_THAN</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g1068">
+ <vendor>0x8086</vendor>
+ <devices>
+ <device>0x2a42</device>
+ <device>0x2e22</device>
+ <device>0x2e12</device>
+ <device>0x2e32</device>
+ <device>0x0046</device>
+ </devices>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>8.15.10.1851</driverVersion>
+ <driverVersionComparator>EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g1069">
+ <vendor>0x8086</vendor>
+ <devices>
+ <device>0x2a42</device>
+ <device>0x2e22</device>
+ <device>0x2e12</device>
+ <device>0x2e32</device>
+ <device>0x0046</device>
+ </devices>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>8.15.10.1855</driverVersion>
+ <driverVersionComparator>EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g1070">
+ <vendor>0x8086</vendor>
+ <devices>
+ <device>0x2a42</device>
+ <device>0x2e22</device>
+ <device>0x2e12</device>
+ <device>0x2e32</device>
+ <device>0x0046</device>
+ </devices>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>8.15.10.1872</driverVersion>
+ <driverVersionComparator>EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g1071">
+ <vendor>0x8086</vendor>
+ <devices>
+ <device>0x2a42</device>
+ <device>0x2e22</device>
+ <device>0x2e12</device>
+ <device>0x2e32</device>
+ <device>0x0046</device>
+ </devices>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>8.15.10.1883</driverVersion>
+ <driverVersionComparator>EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g1072">
+ <vendor>0x8086</vendor>
+ <devices>
+ <device>0x2a42</device>
+ <device>0x2e22</device>
+ <device>0x2e12</device>
+ <device>0x2e32</device>
+ <device>0x0046</device>
+ </devices>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>8.15.10.1892</driverVersion>
+ <driverVersionComparator>EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ <gfxBlacklistEntry blockID="g1073">
+ <vendor>0x8086</vendor>
+ <devices>
+ <device>0x2a42</device>
+ <device>0x2e22</device>
+ <device>0x2e12</device>
+ <device>0x2e32</device>
+ <device>0x0046</device>
+ </devices>
+ <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>
+ <driverVersion>8.15.10.1994</driverVersion>
+ <driverVersionComparator>EQUAL</driverVersionComparator>
+ </gfxBlacklistEntry>
+ </gfxItems>
+</blocklist>
diff --git a/app/macbuild/Contents/CodeResources b/app/macbuild/Contents/CodeResources
new file mode 100644
index 0000000..1a65e20
--- /dev/null
+++ b/app/macbuild/Contents/CodeResources
@@ -0,0 +1 @@
+_CodeSignature/CodeResources \ No newline at end of file
diff --git a/app/macbuild/Contents/Info.plist.in b/app/macbuild/Contents/Info.plist.in
new file mode 100644
index 0000000..b224064
--- /dev/null
+++ b/app/macbuild/Contents/Info.plist.in
@@ -0,0 +1,227 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleDocumentTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>html</string>
+ <string>htm</string>
+ <string>shtml</string>
+ <string>xht</string>
+ <string>xhtml</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>document.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>HTML Document</string>
+ <key>CFBundleTypeOSTypes</key>
+ <array>
+ <string>HTML</string>
+ </array>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>svg</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>document.icns</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>image/svg+xml</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>SVG document</string>
+ <key>CFBundleTypeOSTypes</key>
+ <array>
+ <string>TEXT</string>
+ </array>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ <key>NSDocumentClass</key>
+ <string>BrowserDocument</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>text</string>
+ <string>txt</string>
+ <string>js</string>
+ <string>log</string>
+ <string>css</string>
+ <string>xul</string>
+ <string>rdf</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>document.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>Text Document</string>
+ <key>CFBundleTypeOSTypes</key>
+ <array>
+ <string>TEXT</string>
+ <string>utxt</string>
+ </array>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>jpeg</string>
+ <string>jpg</string>
+ <string>png</string>
+ <string>gif</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>fileBookmark.icns</string>
+ <key>CFBundleTypeName</key>
+ <string>document.icns</string>
+ <key>CFBundleTypeOSTypes</key>
+ <array>
+ <string>GIFf</string>
+ <string>JPEG</string>
+ <string>PNGf</string>
+ </array>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>oga</string>
+ <string>ogg</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>document.icns</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>audio/ogg</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>HTML5 Audio (Ogg)</string>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>ogv</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>document.icns</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>video/ogg</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>HTML5 Video (Ogg)</string>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ </dict>
+ <dict>
+ <key>CFBundleTypeExtensions</key>
+ <array>
+ <string>webm</string>
+ </array>
+ <key>CFBundleTypeIconFile</key>
+ <string>document.icns</string>
+ <key>CFBundleTypeMIMETypes</key>
+ <array>
+ <string>video/webm</string>
+ </array>
+ <key>CFBundleTypeName</key>
+ <string>HTML5 Video (WebM)</string>
+ <key>CFBundleTypeRole</key>
+ <string>Viewer</string>
+ </dict>
+ </array>
+ <key>CFBundleExecutable</key>
+ <string>palemoon</string>
+ <key>CFBundleGetInfoString</key>
+ <string>%MAC_APP_NAME% %APP_VERSION%</string>
+ <key>CFBundleIconFile</key>
+ <string>firefox</string>
+ <key>CFBundleIdentifier</key>
+ <string>%MOZ_MACBUNDLE_ID%</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>%MAC_APP_NAME%</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>%APP_VERSION%</string>
+ <key>CFBundleSignature</key>
+ <string>MOZB</string>
+ <key>CFBundleURLTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleURLIconFile</key>
+ <string>document.icns</string>
+ <key>CFBundleURLName</key>
+ <string>http URL</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>http</string>
+ </array>
+ </dict>
+ <dict>
+ <key>CFBundleURLIconFile</key>
+ <string>document.icns</string>
+ <key>CFBundleURLName</key>
+ <string>https URL</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>https</string>
+ </array>
+ </dict>
+ <dict>
+ <key>CFBundleURLName</key>
+ <string>ftp URL</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>ftp</string>
+ </array>
+ </dict>
+ <dict>
+ <key>CFBundleURLName</key>
+ <string>file URL</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>file</string>
+ </array>
+ </dict>
+ </array>
+ <key>CFBundleVersion</key>
+ <string>%MAC_BUNDLE_VERSION%</string>
+ <key>NSAppleScriptEnabled</key>
+ <true/>
+ <key>LSApplicationCategoryType</key>
+ <string>public.app-category.productivity</string>
+ <key>LSEnvironment</key>
+ <dict>
+ <key>MallocNanoZone</key>
+ <string>0</string>
+ </dict>
+ <key>LSMinimumSystemVersion</key>
+ <string>10.6</string>
+ <key>LSMinimumSystemVersionByArchitecture</key>
+ <dict>
+ <key>i386</key>
+ <string>10.6.0</string>
+ <key>x86_64</key>
+ <string>10.6.0</string>
+ </dict>
+ <key>NSSupportsAutomaticGraphicsSwitching</key>
+ <true/>
+ <key>NSPrincipalClass</key>
+ <string>GoannaNSApplication</string>
+</dict>
+</plist>
diff --git a/app/macbuild/Contents/MacOS-files.in b/app/macbuild/Contents/MacOS-files.in
new file mode 100644
index 0000000..561366d
--- /dev/null
+++ b/app/macbuild/Contents/MacOS-files.in
@@ -0,0 +1,10 @@
+/*.app/***
+/*.dylib
+/certutil
+/firefox-bin
+/gtest/***
+/pk12util
+/ssltunnel
+/xpcshell
+/XUL
+
diff --git a/app/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in b/app/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in
new file mode 100644
index 0000000..74d192c
--- /dev/null
+++ b/app/macbuild/Contents/Resources/English.lproj/InfoPlist.strings.in
@@ -0,0 +1,5 @@
+/* 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/. */
+
+CFBundleName = "%MAC_APP_NAME%";
diff --git a/app/macbuild/Contents/_CodeSignature/CodeResources b/app/macbuild/Contents/_CodeSignature/CodeResources
new file mode 100644
index 0000000..6f6e20e
--- /dev/null
+++ b/app/macbuild/Contents/_CodeSignature/CodeResources
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+ <dict>
+ <key>rules</key>
+ <dict>
+ <key>^Info.plist$</key>
+ <true/>
+ <key>^PkgInfo$</key>
+ <true/>
+ <key>^MacOS/</key>
+ <true/>
+ <key>^Resources/</key>
+ <true/>
+ <key>^MacOS/distribution/.*</key><dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>10</real>
+ </dict>
+ <key>^MacOS/override.ini</key><dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>10</real>
+ </dict>
+ <key>^MacOS/updates/.*</key><dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>10</real>
+ </dict>
+ <key>^MacOS/active-update.xml$</key><dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>10</real>
+ </dict>
+ <key>^MacOS/defaults/.*</key><dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>10</real>
+ </dict>
+ <key>^MacOS/removed-files$</key><dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>10</real>
+ </dict>
+ <key>^MacOS/updates.xml$</key><dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>10</real>
+ </dict>
+ <key>^Updated.app/.*</key><dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>10</real>
+ </dict>
+ <key>^updating/.*</key><dict>
+ <key>omit</key>
+ <true/>
+ <key>weight</key>
+ <real>10</real>
+ </dict>
+ </dict>
+ </dict>
+</plist>
diff --git a/app/macversion.py b/app/macversion.py
new file mode 100644
index 0000000..839aac1
--- /dev/null
+++ b/app/macversion.py
@@ -0,0 +1,44 @@
+#!/usr/bin/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/.
+
+
+from optparse import OptionParser
+import sys
+import re
+
+o = OptionParser()
+o.add_option("--buildid", dest="buildid")
+o.add_option("--version", dest="version")
+
+(options, args) = o.parse_args()
+
+if not options.buildid:
+ print >>sys.stderr, "--buildid is required"
+ sys.exit(1)
+
+if not options.version:
+ print >>sys.stderr, "--version is required"
+ sys.exit(1)
+
+# We want to build a version number that matches the format allowed for
+# CFBundleVersion (nnnnn[.nn[.nn]]). We'll incorporate both the version
+# number as well as the date, so that it changes at least daily (for nightly
+# builds), but also so that newly-built older versions (e.g. beta build) aren't
+# considered "newer" than previously-built newer versions (e.g. a trunk nightly)
+
+define, MOZ_BUILDID, buildid = open(options.buildid, 'r').read().split()
+
+# extract only the major version (i.e. "14" from "14.0b1")
+majorVersion = re.match(r'^(\d+)[^\d].*', options.version).group(1)
+# last two digits of the year
+twodigityear = buildid[2:4]
+month = buildid[4:6]
+if month[0] == '0':
+ month = month[1]
+day = buildid[6:8]
+if day[0] == '0':
+ day = day[1]
+
+print '%s.%s.%s' % (majorVersion + twodigityear, month, day)
diff --git a/app/module.ver b/app/module.ver
new file mode 100644
index 0000000..7a00230
--- /dev/null
+++ b/app/module.ver
@@ -0,0 +1,8 @@
+WIN32_MODULE_COMPANYNAME=Moonchild Productions
+WIN32_MODULE_COPYRIGHT=©Pale Moon, Firefox and Mozilla Developers, available under the MPL 2.0.
+WIN32_MODULE_PRODUCTVERSION=@MOZ_APP_WINVERSION@
+WIN32_MODULE_PRODUCTVERSION_STRING=@MOZ_APP_VERSION@
+WIN32_MODULE_TRADEMARKS=The Pale Moon logo and project names are the property of Moonchild Productions.
+WIN32_MODULE_DESCRIPTION=Pale Moon web browser
+WIN32_MODULE_PRODUCTNAME=Pale Moon
+WIN32_MODULE_NAME=Pale Moon
diff --git a/app/moz.build b/app/moz.build
new file mode 100644
index 0000000..8166760
--- /dev/null
+++ b/app/moz.build
@@ -0,0 +1,64 @@
+# -*- 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 += ['profile/extensions']
+
+GeckoProgram(CONFIG['MOZ_APP_NAME'])
+
+JS_PREFERENCE_PP_FILES += [
+ 'profile/palemoon.js',
+]
+
+if CONFIG['LIBXUL_SDK']:
+ PREF_JS_EXPORTS += [
+ 'profile/channel-prefs.js',
+ ]
+
+SOURCES += ['nsBrowserApp.cpp']
+
+FINAL_TARGET_FILES += ['blocklist.xml']
+FINAL_TARGET_FILES.defaults += ['permissions']
+FINAL_TARGET_FILES.defaults.profile += ['profile/prefs.js']
+
+DEFINES['APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
+
+LOCAL_INCLUDES += ['!/build']
+
+LOCAL_INCLUDES += [
+ '/toolkit/xre',
+ '/xpcom/base',
+ '/xpcom/build',
+]
+
+USE_LIBS += ['mozglue']
+
+if CONFIG['_MSC_VER']:
+ # Always enter a Windows program through wmain, whether or not we're
+ # a console application.
+ WIN32_EXE_LDFLAGS += ['-ENTRY:wmainCRTStartup']
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ RCINCLUDE = 'splash.rc'
+ DEFINES['MOZ_PHOENIX'] = True
+
+# Control the default heap size.
+# This is the heap returned by GetProcessHeap().
+# As we use the CRT heap, the default size is too large and wastes VM.
+#
+# The default heap size is 1MB on Win32.
+# The heap will grow if need be.
+#
+# Set it to 256k. See bug 127069.
+if CONFIG['OS_ARCH'] == 'WINNT' and not CONFIG['GNU_CC']:
+ LDFLAGS += ['/HEAP:0x40000']
+
+DISABLE_STL_WRAPPING = True
+
+if CONFIG['MOZ_LINKER']:
+ OS_LIBS += CONFIG['MOZ_ZLIB_LIBS']
+
+if CONFIG['HAVE_CLOCK_MONOTONIC']:
+ OS_LIBS += CONFIG['REALTIME_LIBS']
diff --git a/app/nsBrowserApp.cpp b/app/nsBrowserApp.cpp
new file mode 100644
index 0000000..8b06135
--- /dev/null
+++ b/app/nsBrowserApp.cpp
@@ -0,0 +1,393 @@
+/* -*- 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/. */
+
+#include "nsXULAppAPI.h"
+#include "mozilla/AppData.h"
+#include "application.ini.h"
+#include "nsXPCOMGlue.h"
+#if defined(XP_WIN)
+#include <windows.h>
+#include <stdlib.h>
+#elif defined(XP_UNIX)
+#include <sys/resource.h>
+#include <unistd.h>
+#endif
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <time.h>
+
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsStringGlue.h"
+
+#ifdef XP_WIN
+#define XRE_WANT_ENVIRON
+#define strcasecmp _stricmp
+#endif
+#include "BinaryPath.h"
+
+#include "nsXPCOMPrivate.h" // for MAXPATHLEN and XPCOM_DLL
+
+#include "mozilla/Sprintf.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/WindowsDllBlocklist.h"
+
+#if !defined(MOZ_WIDGET_COCOA) && !defined(MOZ_WIDGET_ANDROID)
+#define MOZ_BROWSER_CAN_BE_CONTENTPROC
+#include "../../ipc/contentproc/plugin-container.cpp"
+#endif
+
+using namespace mozilla;
+
+#ifdef XP_MACOSX
+#define kOSXResourcesFolder "Resources"
+#endif
+#define kDesktopFolder "browser"
+
+static void Output(const char *fmt, ... )
+{
+ va_list ap;
+ va_start(ap, fmt);
+
+#ifndef XP_WIN
+ vfprintf(stderr, fmt, ap);
+#else
+ char msg[2048];
+ vsnprintf_s(msg, _countof(msg), _TRUNCATE, fmt, ap);
+
+ wchar_t wide_msg[2048];
+ MultiByteToWideChar(CP_UTF8,
+ 0,
+ msg,
+ -1,
+ wide_msg,
+ _countof(wide_msg));
+#if MOZ_WINCONSOLE
+ fwprintf_s(stderr, wide_msg);
+#else
+ // Linking user32 at load-time interferes with the DLL blocklist (bug 932100).
+ // This is a rare codepath, so we can load user32 at run-time instead.
+ HMODULE user32 = LoadLibraryW(L"user32.dll");
+ if (user32) {
+ decltype(MessageBoxW)* messageBoxW =
+ (decltype(MessageBoxW)*) GetProcAddress(user32, "MessageBoxW");
+ if (messageBoxW) {
+ messageBoxW(nullptr, wide_msg, L"Pale Moon", MB_OK
+ | MB_ICONERROR
+ | MB_SETFOREGROUND);
+ }
+ FreeLibrary(user32);
+ }
+#endif
+#endif
+
+ va_end(ap);
+}
+
+/**
+ * Return true if |arg| matches the given argument name.
+ */
+static bool IsArg(const char* arg, const char* s)
+{
+ if (*arg == '-')
+ {
+ if (*++arg == '-')
+ ++arg;
+ return !strcasecmp(arg, s);
+ }
+
+#if defined(XP_WIN)
+ if (*arg == '/')
+ return !strcasecmp(++arg, s);
+#endif
+
+ return false;
+}
+
+XRE_GetFileFromPathType XRE_GetFileFromPath;
+XRE_CreateAppDataType XRE_CreateAppData;
+XRE_FreeAppDataType XRE_FreeAppData;
+XRE_TelemetryAccumulateType XRE_TelemetryAccumulate;
+XRE_StartupTimelineRecordType XRE_StartupTimelineRecord;
+XRE_mainType XRE_main;
+XRE_StopLateWriteChecksType XRE_StopLateWriteChecks;
+XRE_XPCShellMainType XRE_XPCShellMain;
+XRE_GetProcessTypeType XRE_GetProcessType;
+XRE_SetProcessTypeType XRE_SetProcessType;
+XRE_InitChildProcessType XRE_InitChildProcess;
+XRE_EnableSameExecutableForContentProcType XRE_EnableSameExecutableForContentProc;
+#ifdef LIBFUZZER
+XRE_LibFuzzerSetMainType XRE_LibFuzzerSetMain;
+XRE_LibFuzzerGetFuncsType XRE_LibFuzzerGetFuncs;
+#endif
+
+static const nsDynamicFunctionLoad kXULFuncs[] = {
+ { "XRE_GetFileFromPath", (NSFuncPtr*) &XRE_GetFileFromPath },
+ { "XRE_CreateAppData", (NSFuncPtr*) &XRE_CreateAppData },
+ { "XRE_FreeAppData", (NSFuncPtr*) &XRE_FreeAppData },
+ { "XRE_TelemetryAccumulate", (NSFuncPtr*) &XRE_TelemetryAccumulate },
+ { "XRE_StartupTimelineRecord", (NSFuncPtr*) &XRE_StartupTimelineRecord },
+ { "XRE_main", (NSFuncPtr*) &XRE_main },
+ { "XRE_StopLateWriteChecks", (NSFuncPtr*) &XRE_StopLateWriteChecks },
+ { "XRE_XPCShellMain", (NSFuncPtr*) &XRE_XPCShellMain },
+ { "XRE_GetProcessType", (NSFuncPtr*) &XRE_GetProcessType },
+ { "XRE_SetProcessType", (NSFuncPtr*) &XRE_SetProcessType },
+ { "XRE_InitChildProcess", (NSFuncPtr*) &XRE_InitChildProcess },
+ { "XRE_EnableSameExecutableForContentProc", (NSFuncPtr*) &XRE_EnableSameExecutableForContentProc },
+#ifdef LIBFUZZER
+ { "XRE_LibFuzzerSetMain", (NSFuncPtr*) &XRE_LibFuzzerSetMain },
+ { "XRE_LibFuzzerGetFuncs", (NSFuncPtr*) &XRE_LibFuzzerGetFuncs },
+#endif
+ { nullptr, nullptr }
+};
+
+#ifdef LIBFUZZER
+int libfuzzer_main(int argc, char **argv);
+
+/* This wrapper is used by the libFuzzer main to call into libxul */
+
+void libFuzzerGetFuncs(const char* moduleName, LibFuzzerInitFunc* initFunc,
+ LibFuzzerTestingFunc* testingFunc) {
+ return XRE_LibFuzzerGetFuncs(moduleName, initFunc, testingFunc);
+}
+#endif
+
+static int do_main(int argc, char* argv[], char* envp[], nsIFile *xreDirectory)
+{
+ nsCOMPtr<nsIFile> appini;
+ nsresult rv;
+ uint32_t mainFlags = 0;
+
+ // Allow palemoon.exe to launch XULRunner apps via -app <application.ini>
+ // Note that -app must be the *first* argument.
+ const char *appDataFile = getenv("XUL_APP_FILE");
+ if (appDataFile && *appDataFile) {
+ rv = XRE_GetFileFromPath(appDataFile, getter_AddRefs(appini));
+ if (NS_FAILED(rv)) {
+ Output("Invalid path found: '%s'", appDataFile);
+ return 255;
+ }
+ }
+ else if (argc > 1 && IsArg(argv[1], "app")) {
+ if (argc == 2) {
+ Output("Incorrect number of arguments passed to -app");
+ return 255;
+ }
+
+ rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(appini));
+ if (NS_FAILED(rv)) {
+ Output("application.ini path not recognized: '%s'", argv[2]);
+ return 255;
+ }
+
+ char appEnv[MAXPATHLEN];
+ SprintfLiteral(appEnv, "XUL_APP_FILE=%s", argv[2]);
+ if (putenv(strdup(appEnv))) {
+ Output("Couldn't set %s.\n", appEnv);
+ return 255;
+ }
+ argv[2] = argv[0];
+ argv += 2;
+ argc -= 2;
+ } else if (argc > 1 && IsArg(argv[1], "xpcshell")) {
+ for (int i = 1; i < argc; i++) {
+ argv[i] = argv[i + 1];
+ }
+
+ return XRE_XPCShellMain(--argc, argv, envp);
+ }
+
+ if (appini) {
+ nsXREAppData *appData;
+ rv = XRE_CreateAppData(appini, &appData);
+ if (NS_FAILED(rv)) {
+ Output("Couldn't read application.ini");
+ return 255;
+ }
+#if defined(HAS_DLL_BLOCKLIST)
+ // The dll blocklist operates in the exe vs. xullib. Pass a flag to
+ // xullib so automated tests can check the result once the browser
+ // is up and running.
+ appData->flags |=
+ DllBlocklist_CheckStatus() ? NS_XRE_DLL_BLOCKLIST_ENABLED : 0;
+#endif
+ // xreDirectory already has a refcount from NS_NewLocalFile
+ appData->xreDirectory = xreDirectory;
+ int result = XRE_main(argc, argv, appData, mainFlags);
+ XRE_FreeAppData(appData);
+ return result;
+ }
+
+ ScopedAppData appData(&sAppData);
+ nsCOMPtr<nsIFile> exeFile;
+ rv = mozilla::BinaryPath::GetFile(argv[0], getter_AddRefs(exeFile));
+ if (NS_FAILED(rv)) {
+ Output("Couldn't find the application directory.\n");
+ return 255;
+ }
+
+ nsCOMPtr<nsIFile> greDir;
+ exeFile->GetParent(getter_AddRefs(greDir));
+#ifdef XP_MACOSX
+ greDir->SetNativeLeafName(NS_LITERAL_CSTRING(kOSXResourcesFolder));
+#endif
+ nsCOMPtr<nsIFile> appSubdir;
+ greDir->Clone(getter_AddRefs(appSubdir));
+ appSubdir->Append(NS_LITERAL_STRING(kDesktopFolder));
+
+ SetStrongPtr(appData.directory, static_cast<nsIFile*>(appSubdir.get()));
+ // xreDirectory already has a refcount from NS_NewLocalFile
+ appData.xreDirectory = xreDirectory;
+
+#if defined(HAS_DLL_BLOCKLIST)
+ appData.flags |=
+ DllBlocklist_CheckStatus() ? NS_XRE_DLL_BLOCKLIST_ENABLED : 0;
+#endif
+
+#ifdef LIBFUZZER
+ if (getenv("LIBFUZZER"))
+ XRE_LibFuzzerSetMain(argc, argv, libfuzzer_main);
+#endif
+
+ return XRE_main(argc, argv, &appData, mainFlags);
+}
+
+static bool
+FileExists(const char *path)
+{
+#ifdef XP_WIN
+ wchar_t wideDir[MAX_PATH];
+ MultiByteToWideChar(CP_UTF8, 0, path, -1, wideDir, MAX_PATH);
+ DWORD fileAttrs = GetFileAttributesW(wideDir);
+ return fileAttrs != INVALID_FILE_ATTRIBUTES;
+#else
+ return access(path, R_OK) == 0;
+#endif
+}
+
+static nsresult
+InitXPCOMGlue(const char *argv0, nsIFile **xreDirectory)
+{
+ char exePath[MAXPATHLEN];
+
+ nsresult rv = mozilla::BinaryPath::Get(argv0, exePath);
+ if (NS_FAILED(rv)) {
+ Output("Couldn't find the application directory.\n");
+ return rv;
+ }
+
+ char *lastSlash = strrchr(exePath, XPCOM_FILE_PATH_SEPARATOR[0]);
+ if (!lastSlash ||
+ (size_t(lastSlash - exePath) > MAXPATHLEN - sizeof(XPCOM_DLL) - 1))
+ return NS_ERROR_FAILURE;
+
+ strcpy(lastSlash + 1, XPCOM_DLL);
+
+ if (!FileExists(exePath)) {
+ Output("Could not find the Mozilla runtime.\n");
+ return NS_ERROR_FAILURE;
+ }
+
+ // We do this because of data in bug 771745
+ XPCOMGlueEnablePreload();
+
+ rv = XPCOMGlueStartup(exePath);
+ if (NS_FAILED(rv)) {
+ Output("Couldn't load XPCOM.\n");
+ return rv;
+ }
+
+ rv = XPCOMGlueLoadXULFunctions(kXULFuncs);
+ if (NS_FAILED(rv)) {
+ Output("Couldn't load XRE functions.\n");
+ return rv;
+ }
+
+ // This will set this thread as the main thread.
+ NS_LogInit();
+
+ if (xreDirectory) {
+ // chop XPCOM_DLL off exePath
+ *lastSlash = '\0';
+#ifdef XP_MACOSX
+ lastSlash = strrchr(exePath, XPCOM_FILE_PATH_SEPARATOR[0]);
+ strcpy(lastSlash + 1, kOSXResourcesFolder);
+#endif
+#ifdef XP_WIN
+ rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(exePath), false,
+ xreDirectory);
+#else
+ rv = NS_NewNativeLocalFile(nsDependentCString(exePath), false,
+ xreDirectory);
+#endif
+ }
+
+ return rv;
+}
+
+int main(int argc, char* argv[], char* envp[])
+{
+ mozilla::TimeStamp start = mozilla::TimeStamp::Now();
+
+#ifdef HAS_DLL_BLOCKLIST
+ DllBlocklist_Initialize();
+
+#ifdef DEBUG
+ // In order to be effective against AppInit DLLs, the blocklist must be
+ // initialized before user32.dll is loaded into the process (bug 932100).
+ if (GetModuleHandleA("user32.dll")) {
+ fprintf(stderr, "DLL blocklist was unable to intercept AppInit DLLs.\n");
+ }
+#endif
+#endif
+
+#ifdef MOZ_BROWSER_CAN_BE_CONTENTPROC
+ // We are launching as a content process, delegate to the appropriate
+ // main
+ if (argc > 1 && IsArg(argv[1], "contentproc")) {
+ nsresult rv = InitXPCOMGlue(argv[0], nullptr);
+ if (NS_FAILED(rv)) {
+ return 255;
+ }
+
+ int result = content_process_main(argc, argv);
+
+ // InitXPCOMGlue calls NS_LogInit, so we need to balance it here.
+ NS_LogTerm();
+
+ return result;
+ }
+#endif
+
+
+ nsIFile *xreDirectory;
+
+ nsresult rv = InitXPCOMGlue(argv[0], &xreDirectory);
+ if (NS_FAILED(rv)) {
+ return 255;
+ }
+
+ XRE_StartupTimelineRecord(mozilla::StartupTimeline::START, start);
+
+#ifdef MOZ_BROWSER_CAN_BE_CONTENTPROC
+ XRE_EnableSameExecutableForContentProc();
+#endif
+
+ int result = do_main(argc, argv, envp, xreDirectory);
+
+ NS_LogTerm();
+
+#ifdef XP_MACOSX
+ // Allow writes again. While we would like to catch writes from static
+ // destructors to allow early exits to use _exit, we know that there is
+ // at least one such write that we don't control (see bug 826029). For
+ // now we enable writes again and early exits will have to use exit instead
+ // of _exit.
+ XRE_StopLateWriteChecks();
+#endif
+
+ return result;
+}
diff --git a/app/palemoon.exe.manifest b/app/palemoon.exe.manifest
new file mode 100644
index 0000000..465effa
--- /dev/null
+++ b/app/palemoon.exe.manifest
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+<assemblyIdentity
+ version="1.0.0.0"
+ processorArchitecture="*"
+ name="Pale Moon"
+ type="win32"
+/>
+<description>Pale Moon</description>
+<dependency>
+ <dependentAssembly>
+ <assemblyIdentity
+ type="win32"
+ name="Microsoft.Windows.Common-Controls"
+ version="6.0.0.0"
+ processorArchitecture="*"
+ publicKeyToken="6595b64144ccf1df"
+ language="*"
+ />
+ </dependentAssembly>
+</dependency>
+<ms_asmv3:trustInfo xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3">
+ <ms_asmv3:security>
+ <ms_asmv3:requestedPrivileges>
+ <ms_asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false" />
+ </ms_asmv3:requestedPrivileges>
+ </ms_asmv3:security>
+</ms_asmv3:trustInfo>
+ <ms_asmv3:application xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3">
+ <ms_asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
+ <dpiAware>true</dpiAware>
+ </ms_asmv3:windowsSettings>
+ </ms_asmv3:application>
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <!-- Windows 10 -->
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+ <!-- Windows 8.1 -->
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+ <!-- Windows 8 -->
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+ <!-- Windows 7 -->
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+ <!-- Windows Vista -->
+ <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
+ </application>
+ </compatibility>
+</assembly>
diff --git a/app/permissions b/app/permissions
new file mode 100644
index 0000000..4d90be8
--- /dev/null
+++ b/app/permissions
@@ -0,0 +1,14 @@
+# This file has default permissions for the permission manager.
+# The file-format is strict:
+# * matchtype \t type \t permission \t host
+# * "origin" should be used for matchtype, "host" is supported for legacy reasons
+# * type is a string that identifies the type of permission (e.g. "cookie")
+# * permission is an integer between 1 and 15
+# See nsPermissionManager.cpp for more...
+
+# XPInstall
+origin install 1 http://www.palemoon.org
+origin install 1 https://www.palemoon.org
+
+origin install 1 http://addons.palemoon.org
+origin install 1 https://addons.palemoon.org
diff --git a/app/profile/channel-prefs.js b/app/profile/channel-prefs.js
new file mode 100644
index 0000000..feb27c1
--- /dev/null
+++ b/app/profile/channel-prefs.js
@@ -0,0 +1,6 @@
+#filter substitution
+/* 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/. */
+
+pref("app.update.channel", "@MOZ_UPDATE_CHANNEL@");
diff --git a/app/profile/extensions/moz.build b/app/profile/extensions/moz.build
new file mode 100644
index 0000000..df43182
--- /dev/null
+++ b/app/profile/extensions/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 += ['{972ce4c6-7e08-4474-a285-3208198ce6fd}']
diff --git a/app/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/Makefile.in b/app/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/Makefile.in
new file mode 100644
index 0000000..ff9319d
--- /dev/null
+++ b/app/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/Makefile.in
@@ -0,0 +1,10 @@
+#
+# 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/.
+
+FILES := \
+ install.rdf.in \
+ $(NULL)
+FILES_PATH = $(FINAL_TARGET)/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}
+PP_TARGETS := FILES
diff --git a/app/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/install.rdf.in b/app/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/install.rdf.in
new file mode 100644
index 0000000..f495013
--- /dev/null
+++ b/app/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/install.rdf.in
@@ -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/. -->
+
+
+#filter substitution
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:em="http://www.mozilla.org/2004/em-rdf#">
+
+ <Description about="urn:mozilla:install-manifest">
+ <em:id>{972ce4c6-7e08-4474-a285-3208198ce6fd}</em:id>
+ <em:version>@MOZ_APP_VERSION@</em:version>
+
+ <!-- Target Application this theme can install into,
+ with minimum and maximum supported versions. -->
+ <em:targetApplication>
+ <Description>
+ <em:id>@MOZ_APP_ID@</em:id>
+ <em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
+ <em:maxVersion>@MOZ_APP_VERSION@</em:maxVersion>
+ </Description>
+ </em:targetApplication>
+
+ <!-- Front End MetaData -->
+ <em:name>Default</em:name>
+ <em:description>The default theme.</em:description>
+
+ <!-- Front End Integration Hooks (used by Theme Manager)-->
+ <em:creator>Moonchild Productions</em:creator>
+ <em:contributor>Mozilla Contributors</em:contributor>
+
+ <!-- Allow lightweight themes to apply to this theme -->
+ <em:skinnable>true</em:skinnable>
+
+ <em:internalName>classic/1.0</em:internalName>
+ </Description>
+
+</RDF>
diff --git a/app/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/moz.build b/app/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/moz.build
new file mode 100644
index 0000000..e14ac8e
--- /dev/null
+++ b/app/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/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/.
+
+DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
+DEFINES['MOZ_APP_ID'] = CONFIG['MOZ_APP_ID'] \ No newline at end of file
diff --git a/app/profile/pagethemes.rdf b/app/profile/pagethemes.rdf
new file mode 100644
index 0000000..3d09b95
--- /dev/null
+++ b/app/profile/pagethemes.rdf
@@ -0,0 +1,7 @@
+<?xml version="1.0"?> <!-- -*- Mode: SGML -*- -->
+<!-- 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/. -->
+
+<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#"/>
+
diff --git a/app/profile/palemoon.js b/app/profile/palemoon.js
new file mode 100644
index 0000000..b7698a8
--- /dev/null
+++ b/app/profile/palemoon.js
@@ -0,0 +1,1205 @@
+# -*- Mode: JavaScript; tab-width: 4; 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/.
+
+// XXX Toolkit-specific preferences should be moved into toolkit.js
+
+#filter substitution
+
+#
+# SYNTAX HINTS:
+#
+# - Dashes are delimiters; use underscores instead.
+# - The first character after a period must be alphabetic.
+# - Computed values (e.g. 50 * 1024) don't work.
+#
+
+#ifdef XP_UNIX
+#ifndef XP_MACOSX
+#define UNIX_BUT_NOT_MAC
+#endif
+#endif
+
+pref("browser.chromeURL","chrome://browser/content/");
+pref("browser.hiddenWindowChromeURL", "chrome://browser/content/hiddenWindow.xul");
+
+// Display the "Get Add-ons" pane in the Add-on Manager
+pref("extensions.getAddons.showPane", true);
+
+// Disables some extra Extension System Logging (can increase performance)
+pref("extensions.logging.enabled", false);
+
+// Disables strict compatibility, making addons compatible-by-default.
+pref("extensions.strictCompatibility", false);
+
+// Specifies a minimum maxVersion an addon needs to say it's compatible with
+// for it to be compatible by default.
+pref("extensions.minCompatibleAppVersion", "1.5");
+
+#define AM_DOMAIN addons.palemoon.org
+#define AM_AUS_ARGS reqVersion=%REQ_VERSION%&id=%ITEM_ID%&version=%ITEM_VERSION%&maxAppVersion=%ITEM_MAXAPPVERSION%&status=%ITEM_STATUS%&appID=%APP_ID%&appVersion=%APP_VERSION%&appOS=%APP_OS%&appABI=%APP_ABI%&locale=%APP_LOCALE%&currentAppVersion=%CURRENT_APP_VERSION%&updateType=%UPDATE_TYPE%&compatMode=%COMPATIBILITY_MODE%
+
+// Preferences for AMO integration
+pref("extensions.getAddons.cache.enabled", false);
+pref("extensions.getAddons.maxResults", 10);
+pref("extensions.getAddons.get.url", "https://@AM_DOMAIN@/?component=integration&type=internal&request=get&addonguid=%IDS%&os=%OS%&version=%VERSION%");
+pref("extensions.getAddons.getWithPerformance.url", "https://@AM_DOMAIN@/?component=integration&type=internal&request=get&addonguid=%IDS%&os=%OS%&version=%VERSION%");
+pref("extensions.getAddons.search.browseURL", "https://@AM_DOMAIN@/search/?terms=%TERMS%");
+pref("extensions.getAddons.search.url", "https://@AM_DOMAIN@/?component=integration&type=internal&request=search&q=%TERMS%&locale=%LOCALE%&os=%OS%&version=%VERSION%");
+pref("extensions.webservice.discoverURL", "http://@AM_DOMAIN@/?component=discover");
+pref("extensions.getAddons.recommended.url", "https://@AM_DOMAIN@/?component=integration&type=internal&request=recommended&locale=%LOCALE%&os=%OS%");
+pref("extensions.getAddons.browseAddons", "http://@AM_DOMAIN@/");
+pref("extensions.getAddons.recommended.browseURL", "https://@AM_DOMAIN@/?component=integration&type=external&request=recommended");
+
+// Blocklist preferences
+pref("extensions.blocklist.enabled", false); // Disable blocklist
+pref("extensions.blocklist.interval", 0); // Never auto-update blocklist
+pref("extensions.blocklist.level.updated", false);
+// Controls what level the blocklist switches from warning about items to forcibly
+// blocking them.
+pref("extensions.blocklist.level", 2);
+pref("extensions.blocklist.url", "https://blocklist.palemoon.org/?version=%VERSION%");
+pref("extensions.blocklist.detailsURL", "https://blocklist.palemoon.org/about.shtml");
+pref("extensions.blocklist.itemURL", "https://blocklist.palemoon.org/info/?id=%blockID%");
+
+pref("extensions.update.autoUpdateDefault", false);
+
+// Disable add-ons that are not installed by the user in all scopes by default.
+// See the SCOPE constants in AddonManager.jsm for values to use here.
+pref("extensions.autoDisableScopes", 15);
+
+// Dictionary download preference
+pref("browser.dictionaries.download.url", "https://@AM_DOMAIN@/dictionaries/");
+
+// Get More Tools link URL
+pref("browser.getdevtools.url","https://@AM_DOMAIN@/?component=integration&type=external&request=devtools");
+
+// Feedback URL
+pref("browser.feedback.url", "https://forum.palemoon.org");
+
+// Help button in slow startup dialog
+pref("browser.slowstartup.help.url", "http://www.palemoon.org/support/slowstartup.shtml");
+
+// Whether to escape to a content-less page if a user presses "Get me out of here"
+// on a network error page (e.g. cert error)
+pref("browser.escape_to_blank", false);
+
+// The minimum delay in seconds for the timer to fire.
+// default=2 minutes
+pref("app.update.timerMinimumDelay", 0);
+
+// App-specific update preferences
+
+// The interval to check for updates (app.update.interval) is defined in
+// palemoon-branding.js
+
+// Alternative windowtype for an application update user interface window. When
+// a window with this windowtype is open the application update service won't
+// open the normal application update user interface window.
+pref("app.update.altwindowtype", "Browser:About");
+
+// Enables some extra Application Update Logging (can reduce performance)
+pref("app.update.log", false);
+
+// The number of general background check failures to allow before notifying the
+// user of the failure. User initiated update checks always notify the user of
+// the failure.
+pref("app.update.backgroundMaxErrors", 10);
+
+// When |app.update.cert.requireBuiltIn| is true or not specified the
+// final certificate and all certificates the connection is redirected to before
+// the final certificate for the url specified in the |app.update.url|
+// preference must be built-in.
+pref("app.update.cert.requireBuiltIn", false);
+
+// When |app.update.cert.checkAttributes| is true or not specified the
+// certificate attributes specified in the |app.update.certs.| preference branch
+// are checked against the certificate for the url specified by the
+// |app.update.url| preference.
+pref("app.update.cert.checkAttributes", true);
+
+// The number of certificate attribute check failures to allow for background
+// update checks before notifying the user of the failure. User initiated update
+// checks always notify the user of the certificate attribute check failure.
+pref("app.update.cert.maxErrors", 5);
+
+// The |app.update.certs.| preference branch contains branches that are
+// sequentially numbered starting at 1 that contain attribute name / value
+// pairs for the certificate used by the server that hosts the update xml file
+// as specified in the |app.update.url| preference. When these preferences are
+// present the following conditions apply for a successful update check:
+// 1. the uri scheme must be https
+// 2. the preference name must exist as an attribute name on the certificate and
+// the value for the name must be the same as the value for the attribute name
+// on the certificate.
+// If these conditions aren't met it will be treated the same as when there is
+// no update available. This validation will not be performed when the
+// |app.update.url.override| user preference has been set for testing updates or
+// when the |app.update.cert.checkAttributes| preference is set to false. Also,
+// the |app.update.url.override| preference should ONLY be used for testing.
+pref("app.update.certs.1.issuerName", "CN=COMODO RSA Domain Validation Secure Server CA,O=COMODO CA Limited,L=Salford,ST=Greater Manchester,C=GB");
+pref("app.update.certs.1.commonName", "*.palemoon.org");
+
+// Whether or not app updates are enabled
+pref("app.update.enabled", false);
+
+// This preference turns on app.update.mode and allows automatic download and
+// install to take place. We use a separate boolean toggle for this to make
+// the UI easier to construct.
+pref("app.update.auto", false);
+
+// See chart in nsUpdateService.js source for more details
+pref("app.update.mode", 1);
+
+// If set to true, the Update Service will present no UI for any event.
+pref("app.update.silent", false);
+
+// If set to true, the Update Service will apply updates in the background
+// when it finishes downloading them.
+pref("app.update.staging.enabled", true);
+
+// app.update.url.manual is in branding section
+// app.update.url.details is in branding section
+
+// User-settable override to app.update.url for testing purposes.
+//pref("app.update.url.override", "");
+
+// app.update.interval is in branding section
+// app.update.promptWaitTime is in branding section
+
+// Show the Update Checking/Ready UI when the user was idle for x seconds
+pref("app.update.idletime", 180);
+
+// Whether or not we show a dialog box informing the user that the update was
+// successfully applied. This is off in Firefox by default since we show a
+// upgrade start page instead! Other apps may wish to show this UI, and supply
+// a whatsNewURL field in their brand.properties that contains a link to a page
+// which tells users what's new in this new update.
+pref("app.update.showInstalledUI", false);
+
+// 0 = suppress prompting for incompatibilities if there are updates available
+// to newer versions of installed addons that resolve them.
+// 1 = suppress prompting for incompatibilities only if there are VersionInfo
+// updates available to installed addons that resolve them, not newer
+// versions.
+pref("app.update.incompatible.mode", 0);
+
+// Symmetric (can be overridden by individual extensions) update preferences.
+// e.g.
+// extensions.{GUID}.update.enabled
+// extensions.{GUID}.update.url
+// .. etc ..
+//
+pref("extensions.update.enabled", false);
+pref("extensions.update.url", "https://@AM_DOMAIN@/?component=aus&@AM_AUS_ARGS@");
+//pref("extensions.update.background.url", "https://@AM_DOMAIN@/?component=aus&@AM_AUS_ARGS@");
+pref("extensions.update.interval", 0); // Do NOT check for updates to Extensions and
+ // Themes
+// Non-symmetric (not shared by extensions) extension-specific [update] preferences
+pref("extensions.dss.enabled", false); // Dynamic Skin Switching
+pref("extensions.dss.switchPending", false); // Non-dynamic switch pending after next
+ // restart.
+
+pref("extensions.{972ce4c6-7e08-4474-a285-3208198ce6fd}.name", "chrome://browser/locale/browser.properties");
+pref("extensions.{972ce4c6-7e08-4474-a285-3208198ce6fd}.description", "chrome://browser/locale/browser.properties");
+
+pref("xpinstall.whitelist.required", false);
+// Allow installing XPI add-ons by direct URL requests (no referrer)
+pref("xpinstall.whitelist.directRequest", true);
+// Allow installing XPI add-ons from file referrers (chrome/file)
+pref("xpinstall.whitelist.fileRequest", true);
+
+pref("extensions.install.requireBuiltInCerts", false);
+// Only allow installation of extensions from https, chrome or file schemes
+pref("extensions.install.requireSecureOrigin", false);
+// Allow installation of distribution/bundles extensions
+pref("extensions.installDistroAddons", true);
+
+pref("lightweightThemes.animation.enabled", false);
+
+pref("keyword.enabled", true);
+
+pref("general.useragent.locale", "@AB_CD@");
+pref("general.skins.selectedSkin", "classic/1.0");
+
+// Native UA mode by default for unbranded
+pref("general.useragent.compatMode", 0);
+pref("general.useragent.compatMode.gecko", false);
+pref("general.useragent.compatMode.firefox", false);
+
+pref("general.smoothScroll", true);
+#ifdef UNIX_BUT_NOT_MAC
+pref("general.autoScroll", false);
+#else
+pref("general.autoScroll", true);
+#endif
+
+pref("general.useragent.complexOverride.moodle", false); // bug 797703
+
+// At startup, check if we're the default browser and prompt user if not.
+pref("browser.shell.checkDefaultBrowser", true);
+pref("browser.shell.shortcutFavicons",true);
+pref("browser.shell.mostRecentDateSetAsDefault", "");
+pref("browser.shell.skipDefaultBrowserCheckOnFirstRun", false);
+pref("browser.shell.skipDefaultBrowserCheck", true);
+pref("browser.shell.defaultBrowserCheckCount", 0);
+pref("browser.defaultbrowser.notificationbar", false);
+
+// 0 = blank, 1 = home (browser.startup.homepage), 2 = last visited page, 3 = resume previous browser session
+// The behavior of option 3 is detailed at: http://wiki.mozilla.org/Session_Restore
+pref("browser.startup.page", 0);
+pref("browser.startup.homepage", "https://wiby.me");
+
+pref("browser.slowStartup.notificationDisabled", false);
+pref("browser.slowStartup.timeThreshold", 60000);
+pref("browser.slowStartup.maxSamples", 5);
+
+pref("browser.enable_automatic_image_resizing", true);
+pref("browser.chrome.site_icons", true);
+pref("browser.chrome.favicons", true);
+// If enabled, will process favicons by drawing them on a canvas,
+// optimizing display size for the UI. This also strips animations.
+pref("browser.chrome.favicons.process", false);
+// browser.warnOnQuit == false will override all other possible prompts when quitting or restarting
+pref("browser.warnOnQuit", true);
+// browser.showQuitWarning specifically controls the quit warning dialog. We
+// might still show the window closing dialog with showQuitWarning == false.
+pref("browser.showQuitWarning", false);
+pref("browser.fullscreen.autohide", true);
+pref("browser.fullscreen.animateUp", 1);
+pref("browser.overlink-delay", 80);
+
+pref("browser.urlbar.clickSelectsAll", true);
+pref("browser.urlbar.doubleClickSelectsAll", false);
+pref("browser.urlbar.autoFill", true);
+pref("browser.urlbar.autoFill.typed", true);
+// 0: Match anywhere (e.g., middle of words)
+// 1: Match on word boundaries and then try matching anywhere
+// 2: Match only on word boundaries (e.g., after / or .)
+// 3: Match at the beginning of the url or title
+pref("browser.urlbar.matchBehavior", 1);
+pref("browser.urlbar.filter.javascript", true);
+
+// the maximum number of results to show in autocomplete when doing richResults
+pref("browser.urlbar.maxRichResults", 12);
+// The amount of time (ms) to wait after the user has stopped typing
+// before starting to perform autocomplete. 50 is the default set in
+// autocomplete.xml.
+pref("browser.urlbar.delay", 50);
+
+// The special characters below can be typed into the urlbar to either restrict
+// the search to visited history, bookmarked, tagged pages; or force a match on
+// just the title text or url.
+pref("browser.urlbar.restrict.history", "^");
+pref("browser.urlbar.restrict.bookmark", "*");
+pref("browser.urlbar.restrict.tag", "+");
+pref("browser.urlbar.restrict.openpage", "%");
+pref("browser.urlbar.restrict.typed", "~");
+pref("browser.urlbar.match.title", "#");
+pref("browser.urlbar.match.url", "@");
+
+// The default behavior for the urlbar can be configured to use any combination
+// of the match filters with each additional filter adding more results (union).
+pref("browser.urlbar.suggest.history", true);
+pref("browser.urlbar.suggest.bookmark", true);
+pref("browser.urlbar.suggest.openpage", true);
+
+// Restrictions to current suggestions can also be applied (intersection).
+// Typed suggestion works only if history is set to true.
+pref("browser.urlbar.suggest.history.onlyTyped", false);
+
+pref("browser.urlbar.formatting.enabled", true);
+pref("browser.urlbar.trimURLs", false);
+
+// Display punycode in identity panel:
+// 0 = Display IDN name
+// 1 = Display punycode name for DV domains
+// 2 = Also display punycode for HTTP sites if IDN name used
+pref("browser.identity.display_punycode", 1);
+
+// Address bar RSS icon control, show by default
+pref("browser.urlbar.rss", true);
+
+pref("browser.altClickSave", true);
+
+// Enable logging downloads operations to the Error Console.
+pref("browser.download.debug", false);
+
+// Number of milliseconds to wait for the http headers (and thus
+// the Content-Disposition filename) before giving up and falling back to
+// picking a filename without that info in hand so that the user sees some
+// feedback from their action.
+pref("browser.download.saveLinkAsFilenameTimeout", 4000);
+
+// Do not use default download location as standard, but ask.
+pref("browser.download.useDownloadDir", false);
+
+pref("browser.download.folderList", 1);
+pref("browser.download.manager.showAlertOnComplete", true);
+pref("browser.download.manager.showAlertInterval", 2000);
+pref("browser.download.manager.retention", 2);
+pref("browser.download.manager.showWhenStarting", true);
+pref("browser.download.manager.closeWhenDone", false);
+pref("browser.download.manager.focusWhenStarting", false);
+pref("browser.download.manager.flashCount", 10);
+pref("browser.download.manager.addToRecentDocs", true);
+pref("browser.download.manager.quitBehavior", 2);
+pref("browser.download.manager.scanWhenDone", true);
+pref("browser.download.manager.resumeOnWakeDelay", 10000);
+
+// This records whether or not the panel has been shown at least once.
+pref("browser.download.panel.shown", false);
+
+// This records whether or not at least one session with the Downloads Panel
+// enabled has been completed already.
+pref("browser.download.panel.firstSessionCompleted", false);
+
+// search engines URL
+pref("browser.search.searchEnginesURL", "https://@AM_DOMAIN@/?component=integration&type=external&request=searchplugins");
+
+// pointer to the default engine name
+pref("browser.search.defaultenginename", "chrome://browser-region/locale/region.properties");
+
+// disable logging for the search service by default
+pref("browser.search.log", false);
+
+// Ordering of Search Engines in the Engine list.
+pref("browser.search.order.1", "chrome://browser-region/locale/region.properties");
+pref("browser.search.order.2", "chrome://browser-region/locale/region.properties");
+pref("browser.search.order.3", "chrome://browser-region/locale/region.properties");
+pref("browser.search.order.4", "chrome://browser-region/locale/region.properties");
+
+// search bar results always open in a new tab
+pref("browser.search.openintab", false);
+
+// do not swap focus to the context search tab.
+pref("browser.search.context.loadInBackground", true);
+
+// if no result, add the search term so that the panel of the new UI is shown anyway
+pref("browser.search.showOneOffButtons", true);
+
+// send ping to the server to update
+pref("browser.search.update", true);
+
+// disable logging for the search service update system by default
+pref("browser.search.update.log", false);
+
+// Check whether we need to perform engine updates every 6 hours
+pref("browser.search.update.interval", 21600);
+
+// enable search suggestions by default
+pref("browser.search.suggest.enabled", true);
+
+#ifdef MOZ_OFFICIAL_BRANDING
+// {moz:official} expands to "official"
+pref("browser.search.official", true);
+#endif
+
+pref("browser.sessionhistory.max_entries", 50);
+
+// handle links targeting new windows
+// 1=current window/tab, 2=new window, 3=new tab in most recent window
+pref("browser.link.open_newwindow", 3);
+
+// handle external links (i.e. links opened from a different application)
+// default: use browser.link.open_newwindow
+// 1-3: see browser.link.open_newwindow for interpretation
+pref("browser.link.open_newwindow.override.external", -1);
+
+// 0: no restrictions - divert everything
+// 1: don't divert window.open at all
+// 2: don't divert window.open with features
+pref("browser.link.open_newwindow.restriction", 2);
+
+// If true, this pref causes windows opened by window.open to be forced into new
+// tabs (rather than potentially opening separate windows, depending on
+// window.open arguments) when the browser is in fullscreen mode.
+// We set this differently on Mac because the fullscreen implementation there is
+// different.
+#ifdef XP_MACOSX
+pref("browser.link.open_newwindow.disabled_in_fullscreen", true);
+#else
+pref("browser.link.open_newwindow.disabled_in_fullscreen", false);
+#endif
+
+// Tabbed browser
+pref("browser.tabs.autoHide", false);
+pref("browser.tabs.closeWindowWithLastTab", true);
+pref("browser.tabs.insertRelatedAfterCurrent", true);
+pref("browser.tabs.warnOnClose", true);
+pref("browser.tabs.warnOnCloseOtherTabs", true);
+pref("browser.tabs.warnOnOpen", true);
+pref("browser.tabs.maxOpenBeforeWarn", 15);
+pref("browser.tabs.loadInBackground", true);
+pref("browser.tabs.opentabfor.middleclick", true);
+pref("browser.tabs.loadDivertedInBackground", false);
+pref("browser.tabs.loadBookmarksInBackground", false);
+pref("browser.tabs.noWindowActivationOnExternal", false);
+pref("browser.tabs.tabClipWidth", 140);
+pref("browser.tabs.animate", true);
+pref("browser.tabs.onTop", false);
+#ifdef XP_WIN
+pref("browser.tabs.drawInTitlebar", true);
+#else
+pref("browser.tabs.drawInTitlebar", false);
+#endif
+pref("browser.tabs.resize_immediately", false);
+
+// Where to show tab close buttons:
+// 0 on active tab only
+// 1 on all tabs until tabClipWidth is reached, then active tab only
+// 2 no close buttons at all
+// 3 at the end of the tabstrip
+pref("browser.tabs.closeButtons", 1);
+
+// When tabs opened by links in other tabs via a combination of
+// browser.link.open_newwindow being set to 3 and target="_blank" etc are
+// closed:
+// true return to the tab that opened this tab (its owner)
+// false return to the adjacent tab (old default)
+pref("browser.tabs.selectOwnerOnClose", true);
+
+pref("browser.tabs.showAudioPlayingIcon", true);
+// This should match Chromium's audio indicator delay.
+pref("browser.tabs.delayHidingAudioPlayingIconMS", 3000);
+
+pref("browser.allTabs.previews", true);
+pref("browser.ctrlTab.previews", true);
+pref("browser.ctrlTab.recentlyUsedLimit", 7);
+
+// By default, do not export HTML at shutdown.
+// If true, at shutdown the bookmarks in your menu and toolbar will
+// be exported as HTML to the bookmarks.html file.
+pref("browser.bookmarks.autoExportHTML", false);
+
+// The maximum number of daily bookmark backups to
+// keep in {PROFILEDIR}/bookmarkbackups. Special values:
+// -1: unlimited
+// 0: no backups created (and deletes all existing backups)
+pref("browser.bookmarks.max_backups", 10);
+
+// Scripts & Windows prefs
+pref("dom.disable_open_during_load", true);
+pref("javascript.options.showInConsole", true);
+#ifdef DEBUG
+pref("general.warnOnAboutConfig", false);
+#endif
+
+// This is the pref to control the location bar, change this to true to
+// force this - this makes the origin of popup windows more obvious to avoid
+// spoofing. We would rather not do it by default because it affects UE for web
+// applications, but without it there isn't a really good way to prevent chrome
+// spoofing, see bug 337344
+pref("dom.disable_window_open_feature.location", true);
+// Allow JS to set status messages
+pref("dom.disable_window_status_change", false);
+// allow JS to move and resize existing windows
+pref("dom.disable_window_move_resize", false);
+// prevent JS from monkeying with window focus, etc
+pref("dom.disable_window_flip", true);
+
+// Disable touch events on Desktop Firefox by default until they are properly
+// supported (bug 736048)
+pref("dom.w3c_touch_events.enabled", 0);
+
+// popups.policy 1=allow,2=reject
+pref("privacy.popups.policy", 1);
+pref("privacy.popups.usecustom", true);
+pref("privacy.popups.showBrowserMessage", true);
+
+pref("privacy.item.cookies", false);
+
+pref("privacy.clearOnShutdown.history", true);
+pref("privacy.clearOnShutdown.formdata", true);
+pref("privacy.clearOnShutdown.passwords", false);
+pref("privacy.clearOnShutdown.downloads", true);
+pref("privacy.clearOnShutdown.cookies", true);
+pref("privacy.clearOnShutdown.cache", true);
+pref("privacy.clearOnShutdown.sessions", true);
+pref("privacy.clearOnShutdown.offlineApps", false);
+pref("privacy.clearOnShutdown.siteSettings", false);
+pref("privacy.clearOnShutdown.connectivityData", false);
+
+pref("privacy.cpd.history", true);
+pref("privacy.cpd.formdata", true);
+pref("privacy.cpd.passwords", false);
+pref("privacy.cpd.downloads", true);
+pref("privacy.cpd.cookies", true);
+pref("privacy.cpd.cache", true);
+pref("privacy.cpd.sessions", true);
+pref("privacy.cpd.offlineApps", false);
+pref("privacy.cpd.siteSettings", false);
+pref("privacy.cpd.connectivityData", false);
+
+// What default should we use for the time span in the sanitizer:
+// 0 - Clear everything
+// 1 - Last Hour
+// 2 - Last 2 Hours
+// 3 - Last 4 Hours
+// 4 - Today
+pref("privacy.sanitize.timeSpan", 1);
+pref("privacy.sanitize.sanitizeOnShutdown", false);
+
+pref("privacy.sanitize.migrateFx3Prefs", false);
+
+pref("network.proxy.share_proxy_settings", false); // use the same proxy settings for all protocols
+
+// Disable speculative half-open connections on Pale Moon
+pref("network.http.speculative-parallel-limit", 0);
+
+// Enable pipelining over SSL
+pref("network.http.pipelining.ssl", true);
+
+// Disable predictor/prefetch of URIs
+pref("network.predictor.enabled", false);
+pref("network.prefetch-next", false);
+
+// Disable DNS prefetching
+pref("network.dns.disablePrefetch", true);
+
+// Tune DNS lookups
+pref("network.dnsCacheEntries", 800);
+pref("network.dnsCacheExpiration", 180); // 3 minutes if no TTL given by DNS resolver
+pref("network.dns.get-ttl", true); // Get and use DNS resolver TTL
+pref("network.dnsCacheExpirationGracePeriod", 60); // 1 minute grace period for stale entry
+
+// simple gestures support
+pref("browser.gesture.swipe.left", "Browser:BackOrBackDuplicate");
+pref("browser.gesture.swipe.right", "Browser:ForwardOrForwardDuplicate");
+pref("browser.gesture.swipe.up", "cmd_scrollTop");
+pref("browser.gesture.swipe.down", "cmd_scrollBottom");
+#ifdef XP_MACOSX
+pref("browser.gesture.pinch.latched", true);
+pref("browser.gesture.pinch.threshold", 150);
+#else
+pref("browser.gesture.pinch.latched", false);
+pref("browser.gesture.pinch.threshold", 25);
+#endif
+#ifdef XP_WIN
+// Enabled for touch input display zoom.
+pref("browser.gesture.pinch.out", "cmd_fullZoomEnlarge");
+pref("browser.gesture.pinch.in", "cmd_fullZoomReduce");
+pref("browser.gesture.pinch.out.shift", "cmd_fullZoomReset");
+pref("browser.gesture.pinch.in.shift", "cmd_fullZoomReset");
+#else
+// Disabled by default due to issues with track pad input.
+pref("browser.gesture.pinch.out", "");
+pref("browser.gesture.pinch.in", "");
+pref("browser.gesture.pinch.out.shift", "");
+pref("browser.gesture.pinch.in.shift", "");
+#endif
+pref("browser.gesture.twist.latched", false);
+pref("browser.gesture.twist.threshold", 0);
+pref("browser.gesture.twist.right", "cmd_gestureRotateRight");
+pref("browser.gesture.twist.left", "cmd_gestureRotateLeft");
+pref("browser.gesture.twist.end", "cmd_gestureRotateEnd");
+pref("browser.gesture.tap", "cmd_fullZoomReset");
+
+pref("browser.snapshots.limit", 0);
+
+// 0: Nothing happens
+// 1: Scroll contents
+// 2: Go back or go forward, in your history
+// 3: Zoom in or out
+// 4: Scroll contents with X and Y swapped
+#ifdef XP_MACOSX
+// On OS X, if the wheel has one axis only, shift+wheel comes through as a
+// horizontal scroll event. Thus, we can't assign anything other than normal
+// scrolling to shift+wheel.
+pref("mousewheel.with_alt.action", 2);
+pref("mousewheel.with_shift.action", 1);
+// On MacOS X, control+wheel is typically handled by system and we don't
+// receive the event. So, command key which is the main modifier key for
+// acceleration is the best modifier for zoom-in/out. However, we should keep
+// the control key setting for backward compatibility.
+pref("mousewheel.with_meta.action", 3); // command key on Mac
+// Disable control-/meta-modified horizontal mousewheel events, since
+// those are used on Mac as part of modified swipe gestures (e.g.
+// Left swipe+Cmd = go back in a new tab).
+pref("mousewheel.with_control.action.override_x", 0);
+pref("mousewheel.with_meta.action.override_x", 0);
+#else
+pref("mousewheel.with_alt.action", 1);
+pref("mousewheel.with_shift.action", 2);
+pref("mousewheel.with_meta.action", 1); // win key on Win, Super/Hyper on Linux
+#endif
+pref("mousewheel.with_control.action",3);
+pref("mousewheel.with_win.action", 1);
+
+pref("browser.xul.error_pages.enabled", true);
+pref("browser.xul.error_pages.expert_bad_cert", false);
+
+// Work Offline is best manually managed by the user.
+pref("network.manage-offline-status", false);
+
+// We want to make sure mail URLs are handled externally...
+pref("network.protocol-handler.external.mailto", true); // for mail
+pref("network.protocol-handler.external.news", true); // for news
+pref("network.protocol-handler.external.snews", true); // for secure news
+pref("network.protocol-handler.external.nntp", true); // also news
+#ifdef XP_WIN
+pref("network.protocol-handler.external.ms-windows-store", true);
+#endif
+
+// ...without warning dialogs
+pref("network.protocol-handler.warn-external.mailto", false);
+pref("network.protocol-handler.warn-external.news", false);
+pref("network.protocol-handler.warn-external.snews", false);
+pref("network.protocol-handler.warn-external.nntp", false);
+#ifdef XP_WIN
+pref("network.protocol-handler.warn-external.ms-windows-store", false);
+#endif
+
+// By default, all protocol handlers are exposed. This means that
+// the browser will respond to openURL commands for all URL types.
+// It will also try to open link clicks inside the browser before
+// failing over to the system handlers.
+pref("network.protocol-handler.expose-all", true);
+pref("network.protocol-handler.expose.mailto", false);
+pref("network.protocol-handler.expose.news", false);
+pref("network.protocol-handler.expose.snews", false);
+pref("network.protocol-handler.expose.nntp", false);
+
+pref("accessibility.typeaheadfind", false);
+pref("accessibility.typeaheadfind.timeout", 5000);
+pref("accessibility.typeaheadfind.linksonly", false);
+pref("accessibility.typeaheadfind.flashBar", 1);
+
+// by default we show an infobar message when pages require plugins that are blocked, or are outdated
+pref("plugins.hide_infobar_for_blocked_plugin", false);
+pref("plugins.hide_infobar_for_outdated_plugin", false);
+
+// Pale Moon:pref to always show the plugin indicator or not (default=false)
+pref("plugins.always_show_indicator", false);
+
+pref("plugins.update.url", "https://aus.palemoon.org/plugincheck/");
+pref("plugins.update.notifyUser", false);
+
+//Enable tri-state option (Always/Never/Ask)
+pref("plugins.click_to_play", true);
+
+#ifdef XP_WIN
+pref("browser.preferences.instantApply", false);
+#else
+pref("browser.preferences.instantApply", true);
+#endif
+#ifdef XP_MACOSX
+pref("browser.preferences.animateFadeIn", true);
+#else
+pref("browser.preferences.animateFadeIn", false);
+#endif
+
+pref("browser.download.show_plugins_in_list", true);
+pref("browser.download.hide_plugins_without_extensions", true);
+
+// Backspace and Shift+Backspace behavior
+// 0 goes Back/Forward
+// 1 act like PgUp/PgDown
+// 2 and other values, nothing
+#ifdef UNIX_BUT_NOT_MAC
+pref("browser.backspace_action", 2);
+#else
+pref("browser.backspace_action", 0);
+#endif
+
+// Pale Moon never eats the space with word selection, regardless of O.S.
+pref("layout.word_select.eat_space_to_next_word", false);
+
+// this will automatically enable inline spellchecking (if it is available) for
+// editable elements in HTML
+// 0 = spellcheck nothing
+// 1 = check multi-line controls [default]
+// 2 = check multi/single line controls
+pref("layout.spellcheckDefault", 1);
+
+pref("browser.send_pings", false);
+
+/* initial web feed readers list */
+pref("browser.contentHandlers.types.0.title", "chrome://browser-region/locale/region.properties");
+pref("browser.contentHandlers.types.0.uri", "chrome://browser-region/locale/region.properties");
+pref("browser.contentHandlers.types.0.type", "application/vnd.mozilla.maybe.feed");
+pref("browser.contentHandlers.types.1.title", "chrome://browser-region/locale/region.properties");
+pref("browser.contentHandlers.types.1.uri", "chrome://browser-region/locale/region.properties");
+pref("browser.contentHandlers.types.1.type", "application/vnd.mozilla.maybe.feed");
+pref("browser.contentHandlers.types.2.title", "chrome://browser-region/locale/region.properties");
+pref("browser.contentHandlers.types.2.uri", "chrome://browser-region/locale/region.properties");
+pref("browser.contentHandlers.types.2.type", "application/vnd.mozilla.maybe.feed");
+pref("browser.contentHandlers.types.3.title", "chrome://browser-region/locale/region.properties");
+pref("browser.contentHandlers.types.3.uri", "chrome://browser-region/locale/region.properties");
+pref("browser.contentHandlers.types.3.type", "application/vnd.mozilla.maybe.feed");
+pref("browser.contentHandlers.types.4.title", "chrome://browser-region/locale/region.properties");
+pref("browser.contentHandlers.types.4.uri", "chrome://browser-region/locale/region.properties");
+pref("browser.contentHandlers.types.4.type", "application/vnd.mozilla.maybe.feed");
+pref("browser.contentHandlers.types.5.title", "chrome://browser-region/locale/region.properties");
+pref("browser.contentHandlers.types.5.uri", "chrome://browser-region/locale/region.properties");
+pref("browser.contentHandlers.types.5.type", "application/vnd.mozilla.maybe.feed");
+
+pref("browser.feeds.handler", "ask");
+pref("browser.videoFeeds.handler", "ask");
+pref("browser.audioFeeds.handler", "ask");
+
+// At startup, if the handler service notices that the version number in the
+// region.properties file is newer than the version number in the handler
+// service datastore, it will add any new handlers it finds in the prefs (as
+// seeded by this file) to its datastore.
+pref("gecko.handlerService.defaultHandlersVersion", "chrome://browser-region/locale/region.properties");
+
+// The default set of web-based protocol handlers shown in the application
+// selection dialog for webcal: ; I've arbitrarily picked 4 default handlers
+// per protocol, but if some locale wants more than that (or defaults for some
+// protocol not currently listed here), we should go ahead and add those.
+
+// webcal
+pref("gecko.handlerService.schemes.webcal.0.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.webcal.0.uriTemplate", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.webcal.1.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.webcal.1.uriTemplate", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.webcal.2.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.webcal.2.uriTemplate", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.webcal.3.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.webcal.3.uriTemplate", "chrome://browser-region/locale/region.properties");
+
+// mailto
+pref("gecko.handlerService.schemes.mailto.0.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.mailto.0.uriTemplate", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.mailto.1.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.mailto.1.uriTemplate", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.mailto.2.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.mailto.2.uriTemplate", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.mailto.3.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.mailto.3.uriTemplate", "chrome://browser-region/locale/region.properties");
+
+// irc
+pref("gecko.handlerService.schemes.irc.0.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.irc.0.uriTemplate", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.irc.1.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.irc.1.uriTemplate", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.irc.2.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.irc.2.uriTemplate", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.irc.3.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.irc.3.uriTemplate", "chrome://browser-region/locale/region.properties");
+
+// ircs
+pref("gecko.handlerService.schemes.ircs.0.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.ircs.0.uriTemplate", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.ircs.1.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.ircs.1.uriTemplate", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.ircs.2.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.ircs.2.uriTemplate", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.ircs.3.name", "chrome://browser-region/locale/region.properties");
+pref("gecko.handlerService.schemes.ircs.3.uriTemplate", "chrome://browser-region/locale/region.properties");
+
+// By default, we don't want protocol/content handlers to be registered from a different host, see bug 402287
+pref("gecko.handlerService.allowRegisterFromDifferentHost", false);
+
+pref("browser.geolocation.warning.infoURL", "http://www.palemoon.org/info-url/geolocation.shtml");
+pref("browser.mixedcontent.warning.infoURL", "http://www.palemoon.org/info-url/mixedcontent.shtml");
+pref("browser.push.warning.infoURL", "https://www.palemoon.org/info-url/push.shtml");
+
+pref("browser.EULA.version", 3);
+pref("browser.rights.version", 3);
+pref("browser.rights.3.shown", false);
+
+#ifdef DEBUG
+// Don't show the about:rights notification in debug builds.
+pref("browser.rights.override", true);
+#endif
+
+pref("browser.sessionstore.resume_from_crash", true);
+pref("browser.sessionstore.resume_session_once", false);
+
+// minimal interval between two save operations in milliseconds
+pref("browser.sessionstore.interval",60000);
+// maximum amount of POSTDATA to be saved in bytes per history entry (-1 = all of it)
+// (NB: POSTDATA will be saved either entirely or not at all)
+pref("browser.sessionstore.postdata", 0);
+// on which sites to save text data, POSTDATA and cookies
+// 0 = everywhere, 1 = unencrypted sites, 2 = nowhere
+pref("browser.sessionstore.privacy_level", 0);
+// the same as browser.sessionstore.privacy_level, but for saving deferred session data
+pref("browser.sessionstore.privacy_level_deferred", 1);
+// how many tabs can be reopened (per window)
+pref("browser.sessionstore.max_tabs_undo", 10);
+// how many windows can be reopened (per session) - on non-OS X platforms this
+// pref may be ignored when dealing with pop-up windows to ensure proper startup
+pref("browser.sessionstore.max_windows_undo", 3);
+// number of crashes that can occur before the about:sessionrestore page is displayed
+// (this pref has no effect if more than 6 hours have passed since the last crash)
+pref("browser.sessionstore.max_resumed_crashes", 1);
+// number of back button session history entries to save (-1 = all of them)
+pref("browser.sessionstore.max_serialize_back", 10);
+// number of forward button session history entries to save (-1 = all of them)
+pref("browser.sessionstore.max_serialize_forward", -1);
+// restore_on_demand overrides browser.sessionstore.max_concurrent_tabs
+// and restore_hidden_tabs. When true, tabs will not be restored until they are
+// focused (also applies to tabs that aren't visible). When false, the values
+// for browser.sessionstore.max_concurrent_tabs and restore_hidden_tabs are
+// respected. Selected tabs are always restored regardless of this pref.
+pref("browser.sessionstore.restore_on_demand", true);
+// The number of tabs that can restore concurrently.
+// Sane values are 1..10, default 3.
+pref("browser.sessionstore.max_concurrent_tabs", 3);
+// Whether to automatically restore hidden tabs (i.e., tabs in other tab groups) or not
+pref("browser.sessionstore.restore_hidden_tabs", false);
+// If restore_on_demand is set, pinned tabs are restored on startup by default.
+// When set to true, this pref overrides that behavior, and pinned tabs will only
+// be restored when they are focused.
+pref("browser.sessionstore.restore_pinned_tabs_on_demand", false);
+// Pale Moon: Allow the user to bypass cached versions of pages when restoring
+// tabs from a previous session
+// 0 = standard behavior: pull fully from cache
+// 1 = perform a soft refresh when restoring a tab (check network)
+// 2 = perform a hard refresh when restoring a tab (bypass cache completely)
+pref("browser.sessionstore.cache_behavior", 0);
+// Pale Moon: Allow exact positioning of windows to previous locations, even
+// if they would be outside of the screen bounds
+pref("browser.sessionstore.exactPos", false);
+
+// allow META refresh by default
+pref("accessibility.blockautorefresh", false);
+
+// Whether history is enabled or not.
+pref("places.history.enabled", true);
+
+// the (maximum) number of the recent visits to sample
+// when calculating frecency
+pref("places.frecency.numVisits", 10);
+
+// buckets (in days) for frecency calculation
+pref("places.frecency.firstBucketCutoff", 4);
+pref("places.frecency.secondBucketCutoff", 14);
+pref("places.frecency.thirdBucketCutoff", 31);
+pref("places.frecency.fourthBucketCutoff", 90);
+
+// weights for buckets for frecency calculations
+pref("places.frecency.firstBucketWeight", 100);
+pref("places.frecency.secondBucketWeight", 70);
+pref("places.frecency.thirdBucketWeight", 50);
+pref("places.frecency.fourthBucketWeight", 30);
+pref("places.frecency.defaultBucketWeight", 10);
+
+// bonus (in percent) for visit transition types for frecency calculations
+pref("places.frecency.embedVisitBonus", 0);
+pref("places.frecency.framedLinkVisitBonus", 0);
+pref("places.frecency.linkVisitBonus", 100);
+pref("places.frecency.typedVisitBonus", 2000);
+pref("places.frecency.bookmarkVisitBonus", 75);
+pref("places.frecency.downloadVisitBonus", 0);
+pref("places.frecency.permRedirectVisitBonus", 0);
+pref("places.frecency.tempRedirectVisitBonus", 0);
+pref("places.frecency.defaultVisitBonus", 0);
+
+// bonus (in percent) for place types for frecency calculations
+pref("places.frecency.unvisitedBookmarkBonus", 140);
+pref("places.frecency.unvisitedTypedBonus", 200);
+
+// Controls behavior of the "Add Exception" dialog launched from SSL error pages
+// 0 - don't pre-populate anything
+// 1 - pre-populate site URL, but don't fetch certificate
+// 2 - pre-populate site URL and pre-fetch certificate
+pref("browser.ssl_override_behavior", 2);
+
+// Controls the behavior of data storage for offline apps
+// 0 - Deny storage of offline app data without prompting (breaks sites!)
+// 1 - Ask the user if a website wants to store offline app data
+// 2 - Allow storage of offline app data without prompting (default)
+pref("offline-apps.permissions", 2);
+// True if storage of offline app data is allowed without prompting.
+pref("offline-apps.allow_by_default", true);
+// True if the user should be prompted when a web application supports
+// offline apps.
+pref("browser.offline-apps.notify", true);
+
+// if true, use full page zoom instead of text zoom
+pref("browser.zoom.full", true);
+
+// Whether or not to save and restore zoom levels on a per-site basis.
+pref("browser.zoom.siteSpecific", true);
+
+// Whether or not to update background tabs to the current zoom level.
+pref("browser.zoom.updateBackgroundTabs", true);
+
+// base URL for web-based support pages
+pref("app.support.baseURL", "http://www.palemoon.org/support/");
+
+// Name of alternate about: page for certificate errors (when undefined, defaults to about:neterror)
+pref("security.alternate_certificate_error_page", "certerror");
+
+// Whether to start the private browsing mode at application startup
+pref("browser.privatebrowsing.autostart", false);
+
+// Don't try to alter this pref, it'll be reset the next time you use the
+// bookmarking dialog
+pref("browser.bookmarks.editDialog.firstEditField", "namePicker");
+
+// Whether to use a panel that looks like an OS X sheet for customization
+#ifdef XP_MACOSX
+pref("toolbar.customization.usesheet", true);
+#else
+pref("toolbar.customization.usesheet", false);
+#endif
+
+#ifdef XP_MACOSX
+// On mac, the default pref is per-architecture
+pref("dom.ipc.plugins.enabled.i386", true);
+pref("dom.ipc.plugins.enabled.x86_64", true);
+#else
+pref("dom.ipc.plugins.enabled", true);
+#endif
+
+pref("browser.tabs.remote", false);
+
+// This pref governs whether we attempt to work around problems caused by
+// plugins using OS calls to manipulate the cursor while running out-of-
+// process. These workarounds all involve intercepting (hooking) certain
+// OS calls in the plugin process, then arranging to make certain OS calls
+// in the browser process. Eventually plugins will be required to use the
+// NPAPI to manipulate the cursor, and these workarounds will be removed.
+// See bug 621117.
+#ifdef XP_MACOSX
+pref("dom.ipc.plugins.nativeCursorSupport", true);
+#endif
+
+#ifdef XP_WIN
+pref("browser.taskbar.previews.enable", false);
+pref("browser.taskbar.previews.max", 20);
+pref("browser.taskbar.previews.cachetime", 5);
+pref("browser.taskbar.lists.enabled", true);
+pref("browser.taskbar.lists.frequent.enabled", true);
+pref("browser.taskbar.lists.recent.enabled", false);
+pref("browser.taskbar.lists.maxListItemCount", 7);
+pref("browser.taskbar.lists.tasks.enabled", true);
+pref("browser.taskbar.lists.refreshInSeconds", 120);
+#endif
+
+#ifdef MOZ_SERVICES_SYNC
+// Info when outdated sync detected
+pref("services.sync.outdated.url", "http://www.palemoon.org/sync/update/");
+// The sync engines to use.
+pref("services.sync.registerEngines", "Bookmarks,Form,History,Password,Prefs,Tab,Addons");
+// Preferences to be synced by default
+pref("services.sync.prefs.sync.accessibility.blockautorefresh", true);
+pref("services.sync.prefs.sync.accessibility.browsewithcaret", true);
+pref("services.sync.prefs.sync.accessibility.typeaheadfind", true);
+pref("services.sync.prefs.sync.accessibility.typeaheadfind.linksonly", true);
+pref("services.sync.prefs.sync.addons.ignoreUserEnabledChanges", true);
+
+// The addons prefs related to repository verification are intentionally
+// not synced for security reasons. If a system is compromised, a user
+// could weaken the pref locally, install an add-on from an untrusted
+// source, and this would propagate automatically to other,
+// uncompromised Sync-connected devices.
+pref("services.sync.prefs.sync.app.update.mode", true);
+pref("services.sync.prefs.sync.browser.download.manager.closeWhenDone", true);
+pref("services.sync.prefs.sync.browser.download.manager.retention", true);
+pref("services.sync.prefs.sync.browser.download.manager.scanWhenDone", true);
+pref("services.sync.prefs.sync.browser.download.manager.showWhenStarting", true);
+pref("services.sync.prefs.sync.browser.formfill.enable", true);
+pref("services.sync.prefs.sync.browser.link.open_newwindow", true);
+pref("services.sync.prefs.sync.browser.offline-apps.notify", true);
+pref("services.sync.prefs.sync.browser.search.selectedEngine", true);
+pref("services.sync.prefs.sync.browser.search.update", true);
+pref("services.sync.prefs.sync.browser.sessionstore.restore_on_demand", true);
+pref("services.sync.prefs.sync.browser.startup.homepage", true);
+pref("services.sync.prefs.sync.browser.startup.page", true);
+pref("services.sync.prefs.sync.browser.tabs.autoHide", true);
+pref("services.sync.prefs.sync.browser.tabs.closeButtons", true);
+pref("services.sync.prefs.sync.browser.tabs.loadInBackground", true);
+pref("services.sync.prefs.sync.browser.tabs.warnOnClose", true);
+pref("services.sync.prefs.sync.browser.tabs.warnOnOpen", true);
+pref("services.sync.prefs.sync.browser.urlbar.autocomplete.enabled", true);
+pref("services.sync.prefs.sync.browser.urlbar.default.behavior", true);
+pref("services.sync.prefs.sync.browser.urlbar.maxRichResults", true);
+pref("services.sync.prefs.sync.dom.disable_open_during_load", true);
+pref("services.sync.prefs.sync.dom.disable_window_flip", true);
+pref("services.sync.prefs.sync.dom.disable_window_move_resize", true);
+pref("services.sync.prefs.sync.dom.event.contextmenu.enabled", true);
+pref("services.sync.prefs.sync.extensions.personas.current", true);
+pref("services.sync.prefs.sync.intl.accept_languages", true);
+pref("services.sync.prefs.sync.javascript.enabled", true);
+pref("services.sync.prefs.sync.layout.spellcheckDefault", true);
+pref("services.sync.prefs.sync.lightweightThemes.isThemeSelected", true);
+pref("services.sync.prefs.sync.lightweightThemes.usedThemes", true);
+pref("services.sync.prefs.sync.network.cookie.cookieBehavior", true);
+pref("services.sync.prefs.sync.network.cookie.lifetimePolicy", true);
+pref("services.sync.prefs.sync.permissions.default.image", true);
+pref("services.sync.prefs.sync.pref.advanced.images.disable_button.view_image", true);
+pref("services.sync.prefs.sync.pref.advanced.javascript.disable_button.advanced", true);
+pref("services.sync.prefs.sync.pref.downloads.disable_button.edit_actions", true);
+pref("services.sync.prefs.sync.pref.privacy.disable_button.cookie_exceptions", true);
+pref("services.sync.prefs.sync.privacy.clearOnShutdown.cache", true);
+pref("services.sync.prefs.sync.privacy.clearOnShutdown.cookies", true);
+pref("services.sync.prefs.sync.privacy.clearOnShutdown.downloads", true);
+pref("services.sync.prefs.sync.privacy.clearOnShutdown.formdata", true);
+pref("services.sync.prefs.sync.privacy.clearOnShutdown.history", true);
+pref("services.sync.prefs.sync.privacy.clearOnShutdown.offlineApps", true);
+pref("services.sync.prefs.sync.privacy.clearOnShutdown.passwords", true);
+pref("services.sync.prefs.sync.privacy.clearOnShutdown.sessions", true);
+pref("services.sync.prefs.sync.privacy.clearOnShutdown.siteSettings", true);
+pref("services.sync.prefs.sync.privacy.clearOnShutdown.connectivityData", true);
+pref("services.sync.prefs.sync.privacy.donottrackheader.enabled", true);
+pref("services.sync.prefs.sync.privacy.sanitize.sanitizeOnShutdown", true);
+pref("services.sync.prefs.sync.security.default_personal_cert", true);
+pref("services.sync.prefs.sync.security.tls.version.min", true);
+pref("services.sync.prefs.sync.security.tls.version.max", true);
+pref("services.sync.prefs.sync.signon.rememberSignons", true);
+pref("services.sync.prefs.sync.spellchecker.dictionary", true);
+pref("services.sync.prefs.sync.xpinstall.whitelist.required", true);
+#endif
+
+// Disable Geolocation services
+pref("geo.enabled", false);
+
+// Enable the error console
+pref("devtools.errorconsole.enabled", true);
+
+// Whether the character encoding menu is under the main Firefox button. This
+// preference is a string so that localizers can alter it.
+pref("browser.menu.showCharacterEncoding", "chrome://browser/locale/browser.properties");
+
+// Allow using tab-modal prompts when possible.
+pref("prompts.tab_modal.enabled", true);
+// Allow tab-modal prompts to switch tab focus
+pref("prompts.tab_modal.focusSwitch", true);
+
+// Defines the url to be used for new tabs.
+pref("browser.newtab.url", "about:logopage");
+pref("browser.newtab.choice", 1);
+
+// Activates preloading of the new tab url.
+pref("browser.newtab.preload", false);
+
+// Toggles the content of 'about:newtab'. Shows the grid when enabled.
+pref("browser.newtabpage.enabled", true);
+
+// Disables capturing of page thumbnails
+pref("browser.pagethumbnails.capturing_disabled", false);
+
+// enables showing basic placeholders for missing thumbnails
+pref("browser.newtabpage.thumbnailPlaceholder", false);
+
+// number of columns of newtab grid
+pref("browser.newtabpage.columns", 4);
+
+// number of rows of newtab grid
+pref("browser.newtabpage.rows", 3);
+
+// Enable the DOM fullscreen API.
+pref("full-screen-api.enabled", true);
+
+// about:permissions
+// Maximum number of sites to return from the places database.
+// 0-100 (currently)
+pref("permissions.places-sites-limit", 50);
+
+// Built-in default permissions.
+pref("permissions.manager.defaultsUrl", "resource://app/defaults/permissions");
+
+// Startup Crash Tracking
+// number of startup crashes that can occur before starting into safe mode automatically
+// (this pref has no effect if more than 6 hours have passed since the last crash)
+pref("toolkit.startup.max_resumed_crashes", 3);
+
+// The maximum amount of decoded image data we'll willingly keep around (we
+// might keep around more than this, but we'll try to get down to this value).
+// (This is intentionally on the high side; see bug 746055.)
+pref("image.mem.max_decoded_image_kb", 256000);
+
+// Turn on the CSP 1.0 parser for Content Security Policy headers
+pref("security.csp.speccompliant", true);
+
+// Block insecure active content on https pages
+pref("security.mixed_content.block_active_content", true);
+
+// Disable Microsoft Family Safety MitM support
+pref("security.family_safety.mode", 0);
+
+// Override the Gecko-default value of false for Pale Moon.
+pref("plain_text.wrap_long_lines", true);
+
+pref("media.webaudio.enabled", true);
+
+// If this turns true, Moz*Gesture events are not called stopPropagation()
+// before content.
+pref("dom.debug.propagate_gesture_events_through_content", false);
+
+// The request URL of the GeoLocation backend.
+pref("geo.wifi.uri", "http://ip-api.com/json/?fields=lat,lon,status,message");
+
+//Pale Moon padlock overlay preferences
+pref("browser.padlock.shown", true);
+/* Where to show the padlock
+ 1 = inside identity button, right side
+ 2 = inside identity button, left side
+ 3 = urlbar, right side (next to bookmark star)
+ 4 = statusbar
+ 5 = tabs bar, right side
+ 6-10 = same locations, classic style padlock */
+pref("browser.padlock.style", 1);
+// address bar border, 0 = no border, 1 = border, 2 = border only on secure sites
+pref("browser.padlock.urlbar_background", 2);
+
+//Pale Moon standalone image background color
+pref("browser.display.standalone_images.background_color", "#2E3B41");
+
+// These are the thumbnail width/height set in about:newtab.
+// If you change this, make sure the size is sufficient for tile sizes
+// in about:newtab. These values are in CSS pixels.
+pref("toolkit.pageThumbs.minWidth", 250);
+pref("toolkit.pageThumbs.minHeight", 180);
+
+// On GTK, we now default to showing the menubar only when alt is pressed:
+#ifdef MOZ_WIDGET_GTK
+pref("ui.key.menuAccessKeyFocuses", true);
+#endif
+
+// When a user cancels this number of authentication dialogs coming from
+// a single web page (eTLD+1) in a row, all following authentication dialogs
+// will be blocked (automatically canceled) for that page.
+// This counter is per-tab and per-domain to minimize false positives.
+// The counter resets when the page is reloaded from the UI
+// (content-reloads do NOT clear this to mitigate reloading tricks).
+pref("prompts.authentication_dialog_abuse_limit", 3);
+
+// ****************** s4e prefs ******************
+pref("status4evar.addonbar.borderStyle", false);
+pref("status4evar.addonbar.closeButton", false);
+pref("status4evar.addonbar.legacyShim", true);
+pref("status4evar.addonbar.windowGripper", true);
+
+pref("status4evar.advanced.status.detectFullScreen", false);
+pref("status4evar.advanced.status.detectVideo", true);
+
+pref("status4evar.download.button.action", 1);
+pref("status4evar.download.button.action.command", "");
+pref("status4evar.download.color.active", "#333399");
+pref("status4evar.download.color.paused", "#808080");
+pref("status4evar.download.force", false);
+pref("status4evar.download.label", 0);
+pref("status4evar.download.label.force", true);
+pref("status4evar.download.notify.animate", true);
+pref("status4evar.download.notify.timeout", 60);
+pref("status4evar.download.progress", 1);
+pref("status4evar.download.tooltip", 2);
+
+pref("status4evar.firstRun", true);
+
+pref("status4evar.progress.toolbar.css", "#333399");
+pref("status4evar.progress.toolbar.force", false);
+pref("status4evar.progress.toolbar.style", false);
+pref("status4evar.progress.toolbar.style.advanced", false);
+
+pref("status4evar.status", 1);
+pref("status4evar.status.default", true);
+pref("status4evar.status.network", true);
+pref("status4evar.status.network.xhr", true);
+pref("status4evar.status.timeout", 30);
+pref("status4evar.status.linkOver", 1);
+pref("status4evar.status.linkOver.delay.show", 0);
+pref("status4evar.status.linkOver.delay.hide", 0);
+
+pref("status4evar.status.toolbar.maxLength", 0);
+
+pref("status4evar.status.popup.invertMirror", false);
+pref("status4evar.status.popup.mouseMirror", true);
diff --git a/app/profile/prefs.js b/app/profile/prefs.js
new file mode 100644
index 0000000..8c6f0d6
--- /dev/null
+++ b/app/profile/prefs.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/. */
+
+# Mozilla User Preferences
+
+/* Do not edit this file.
+ *
+ * If you make changes to this file while the browser is running,
+ * the changes will be overwritten when the browser exits.
+ *
+ * To make a manual change to preferences, you can visit the URL about:config
+ */
diff --git a/app/splash.rc b/app/splash.rc
new file mode 100644
index 0000000..539c342
--- /dev/null
+++ b/app/splash.rc
@@ -0,0 +1,21 @@
+/* -*- 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/. */
+
+#include <windows.h>
+#include "nsNativeAppSupportWin.h"
+
+1 24 "palemoon.exe.manifest"
+
+IDI_APPICON ICON FIREFOX_ICO
+IDI_DOCUMENT ICON DOCUMENT_ICO
+IDI_APPLICATION ICON FIREFOX_ICO
+IDI_NEWWINDOW ICON NEWWINDOW_ICO
+IDI_NEWTAB ICON NEWTAB_ICO
+IDI_PBMODE ICON PBMODE_ICO
+
+STRINGTABLE DISCARDABLE
+BEGIN
+ IDS_STARTMENU_APPNAME, "@MOZ_APP_DISPLAYNAME@"
+END
diff --git a/base/content/aboutDialog.css b/base/content/aboutDialog.css
new file mode 100644
index 0000000..d96eba5
--- /dev/null
+++ b/base/content/aboutDialog.css
@@ -0,0 +1,74 @@
+/* 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/. */
+
+#aboutPMDialogContainer {
+ width: 700px;
+ height: 410px;
+}
+
+#aboutVersionBox {
+ font-family: Arial, helvetica;
+ height: 38 px;
+}
+
+#aboutVersion {
+ text-align: center;
+ font-size: 18px;
+ font-weight: bold;
+ margin: 4px;
+}
+
+#distribution,
+#distributionId {
+ text-align: center;
+ font-size: 14px;
+ font-weight: bold;
+ display: none;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+#aboutTextBox {
+ font-family: Arial, helvetica;
+ font-size: 14px;
+ margin: 5px 20px;
+ padding: 4px 10px 0px;
+ border-radius: 3px;
+ color: black;
+ background-color: rgba(240, 240, 240, .6);
+}
+
+#aboutLinkBox {
+ font-family: Arial, helvetica;
+}
+
+.text-credits {
+ margin: 5px 0px;
+}
+
+.text-center {
+ text-align: center;
+}
+
+.text-link,
+.text-link:focus {
+ margin: 0px;
+ padding: 0px;
+}
+
+.bottom-link,
+.bottom-link:focus {
+ text-align: center;
+ text-decoration: none !important;
+ padding: 4px;
+ border-radius: 3px;
+ color: #244C8A;
+ background-color: rgba(240, 240, 240, .7);
+ margin: 0 40px;
+ transition: background-color 0.5s ease-out;
+}
+
+.bottom-link:hover {
+ background-color: rgba(240, 240, 255, .95);
+}
diff --git a/base/content/aboutDialog.js b/base/content/aboutDialog.js
new file mode 100644
index 0000000..e4e18f2
--- /dev/null
+++ b/base/content/aboutDialog.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/.
+
+// Services = object with smart getters for common XPCOM services
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+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";
+
+ try {
+ // This is in its own try catch due to bug 895473 and bug 900925.
+ var distroAbout = Services.prefs.getComplexValue("distribution.about",
+ Components.interfaces.nsISupportsString);
+ var distroField = document.getElementById("distribution");
+ distroField.value = distroAbout;
+ distroField.style.display = "block";
+ }
+ catch (ex) {
+ // Pref is unset
+ Components.utils.reportError(ex);
+ }
+ }
+ }
+ catch (e) {
+ // Pref is unset
+ }
+
+ // Include the build ID if this is an "a#" or "b#" build
+ let version = Services.appinfo.version;
+ if (/[ab]\d+$/.test(version)) {
+ let buildID = Services.appinfo.appBuildID;
+ let buildDate = buildID.slice(0,4) + "-" + buildID.slice(4,6) + "-" + buildID.slice(6,8);
+ document.getElementById("aboutVersion").textContent += " (" + buildDate + ")";
+ }
+
+#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
+
+// get release notes URL from prefs
+ var formatter = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
+ .getService(Components.interfaces.nsIURLFormatter);
+ var releaseNotesURL = formatter.formatURLPref("app.releaseNotesURL");
+ if (releaseNotesURL != "about:blank") {
+ var relnotes = document.getElementById("releaseNotesURL");
+ relnotes.setAttribute("href", releaseNotesURL);
+ }
+}
diff --git a/base/content/aboutDialog.xul b/base/content/aboutDialog.xul
new file mode 100644
index 0000000..91fa81e
--- /dev/null
+++ b/base/content/aboutDialog.xul
@@ -0,0 +1,86 @@
+<?xml version="1.0"?> <!-- -*- Mode: HTML -*- -->
+
+<?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 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" flex="1">
+ <vbox id="aboutHeaderBox" />
+ <vbox id="aboutVersionBox" flex="3">
+#ifdef HAVE_64BIT_BUILD
+#expand <label id="aboutVersion">Version: __MOZ_APP_VERSION__ (64-bit)</label>
+#else
+#expand <label id="aboutVersion">Version: __MOZ_APP_VERSION__ (32-bit)</label>
+#endif
+ <label id="distribution" class="text-blurb"/>
+ <label id="distributionId" class="text-blurb"/>
+
+ </vbox>
+ <vbox id="aboutTextBox" flex="1">
+ <description class="text-credits text-center">
+#if defined(MOZ_OFFICIAL_BRANDING) || defined(MC_OFFICIAL)
+#ifdef MC_PRIVATE_BUILD
+ This is a private build of Pale Moon. If you did not manually build this copy from source yourself, then please download an official version from the <label class="text-link" href="http://www.palemoon.org/">Pale Moon website</label>.
+#else
+ <label class="text-link" href="http://www.palemoon.org">Pale Moon</label> is released by <label class="text-link" href="http://www.moonchildproductions.info">Moonchild Productions</label>.
+ </description>
+ <description class="text-credits text-center">
+ Special thanks to all our supporters and donors for making this browser possible!
+ </description>
+ <description class="text-credits">
+ If you wish to contribute, please consider helping out by providing support to other users on the <label class="text-link" href="https://forum.palemoon.org/">Pale Moon forum</label>
+ or getting involved in our development by tackling some of the issues found in our GitHub issue tracker.
+#endif
+#else
+ &brandFullName; is released by &vendorShortName;.
+ </description>
+ <description class="text-credits">
+ This is an experimental build of Web Browser.
+#endif
+ </description>
+ </vbox>
+ <vbox id="aboutLinkBox">
+ <hbox pack="center">
+ <label class="text-link bottom-link" href="about:rights">End-user rights</label>
+ <label class="text-link bottom-link" href="about:license">Licensing information</label>
+ <label class="text-link bottom-link" id="releaseNotesURL">Release notes</label>
+ </hbox>
+ <description id="aboutPMtrademark">&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/base/content/autocomplete.css b/base/content/autocomplete.css
new file mode 100644
index 0000000..960bdc4
--- /dev/null
+++ b/base/content/autocomplete.css
@@ -0,0 +1,17 @@
+/* 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");
+
+/* Apply crisp rendering for favicons at exactly 2dppx resolution */
+@media (resolution: 2dppx) {
+ .ac-site-icon {
+ image-rendering: -moz-crisp-edges;
+ }
+}
+
+richlistitem > .ac-title-box > .ac-title > .ac-comment:not([selected]) > html|span.ac-selected-text {
+ display: none;
+}
diff --git a/base/content/autocomplete.xml b/base/content/autocomplete.xml
new file mode 100644
index 0000000..bd09284
--- /dev/null
+++ b/base/content/autocomplete.xml
@@ -0,0 +1,2128 @@
+<?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="privateAutocompleteBindings"
+ 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="private-autocomplete" role="xul:combobox"
+ extends="chrome://global/content/bindings/textbox.xml#textbox">
+ <resources>
+ <stylesheet src="chrome://browser/content/autocomplete.css"/>
+ <stylesheet src="chrome://browser/skin/autocomplete.css"/>
+ </resources>
+
+ <content sizetopopup="pref">
+ <xul:hbox class="private-autocomplete-textbox-container" flex="1" xbl:inherits="focused">
+ <children includes="image|deck|stack|box">
+ <xul:image class="private-autocomplete-icon" allowevents="true"/>
+ </children>
+
+ <xul:hbox anonid="textbox-input-box" class="textbox-input-box" flex="1" xbl:inherits="tooltiptext=inputtooltiptext">
+ <children/>
+ <html:input anonid="input" class="private-autocomplete-textbox textbox-input"
+ allowevents="true"
+ xbl:inherits="tooltiptext=inputtooltiptext,value,type=inputtype,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,mozactionhint"/>
+ </xul:hbox>
+ <children includes="hbox"/>
+ </xul:hbox>
+
+ <xul:dropmarker anonid="historydropmarker" class="private-autocomplete-history-dropmarker"
+ allowevents="true"
+ xbl:inherits="open,enablehistory,parentfocused=focused"/>
+
+ <xul:popupset anonid="popupset" class="private-autocomplete-result-popupset"/>
+
+ <children includes="toolbarbutton"/>
+ </content>
+
+ <implementation implements="nsIAutoCompleteInput, nsIDOMXULMenuListElement">
+ <field name="mController">null</field>
+ <field name="mSearchNames">null</field>
+ <field name="mIgnoreInput">false</field>
+ <field name="mEnterEvent">null</field>
+
+ <field name="_searchBeginHandler">null</field>
+ <field name="_searchCompleteHandler">null</field>
+ <field name="_textEnteredHandler">null</field>
+ <field name="_textRevertedHandler">null</field>
+
+ <constructor><![CDATA[
+ this.mController = Components.classes["@mozilla.org/autocomplete/controller;1"].
+ getService(Components.interfaces.nsIAutoCompleteController);
+
+ this._searchBeginHandler = this.initEventHandler("searchbegin");
+ this._searchCompleteHandler = this.initEventHandler("searchcomplete");
+ this._textEnteredHandler = this.initEventHandler("textentered");
+ this._textRevertedHandler = this.initEventHandler("textreverted");
+
+ // For security reasons delay searches on pasted values.
+ this.inputField.controllers.insertControllerAt(0, this._pasteController);
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ this.inputField.controllers.removeController(this._pasteController);
+ ]]></destructor>
+
+ <!-- =================== nsIAutoCompleteInput =================== -->
+
+ <field name="popup"><![CDATA[
+ // Wrap in a block so that the let statements don't
+ // create properties on 'this' (bug 635252).
+ {
+ let popup = null;
+ let popupId = this.getAttribute("autocompletepopup");
+ if (popupId)
+ popup = document.getElementById(popupId);
+ if (!popup) {
+ popup = document.createElement("panel");
+ popup.setAttribute("type", "autocomplete");
+ popup.setAttribute("noautofocus", "true");
+
+ let popupset = document.getAnonymousElementByAttribute(this, "anonid", "popupset");
+ popupset.appendChild(popup);
+ }
+ popup.mInput = this;
+ popup;
+ }
+ ]]></field>
+
+ <property name="controller" onget="return this.mController;" readonly="true"/>
+
+ <property name="popupOpen"
+ onget="return this.popup.popupOpen;"
+ onset="if (val) this.openPopup(); else this.closePopup();"/>
+
+ <property name="disableAutoComplete"
+ onset="this.setAttribute('disableautocomplete', val); return val;"
+ onget="return this.getAttribute('disableautocomplete') == 'true';"/>
+
+ <property name="completeDefaultIndex"
+ onset="this.setAttribute('completedefaultindex', val); return val;"
+ onget="return this.getAttribute('completedefaultindex') == 'true';"/>
+
+ <property name="completeSelectedIndex"
+ onset="this.setAttribute('completeselectedindex', val); return val;"
+ onget="return this.getAttribute('completeselectedindex') == 'true';"/>
+
+ <property name="forceComplete"
+ onset="this.setAttribute('forcecomplete', val); return val;"
+ onget="return this.getAttribute('forcecomplete') == 'true';"/>
+
+ <property name="minResultsForPopup"
+ onset="this.setAttribute('minresultsforpopup', val); return val;"
+ onget="var m = parseInt(this.getAttribute('minresultsforpopup')); return isNaN(m) ? 1 : m;"/>
+
+ <property name="showCommentColumn"
+ onset="this.setAttribute('showcommentcolumn', val); return val;"
+ onget="return this.getAttribute('showcommentcolumn') == 'true';"/>
+
+ <property name="showImageColumn"
+ onset="this.setAttribute('showimagecolumn', val); return val;"
+ onget="return this.getAttribute('showimagecolumn') == 'true';"/>
+
+ <property name="timeout"
+ onset="this.setAttribute('timeout', val); return val;">
+ <getter><![CDATA[
+ // For security reasons delay searches on pasted values.
+ if (this._valueIsPasted) {
+ let t = parseInt(this.getAttribute('pastetimeout'));
+ return isNaN(t) ? 1000 : t;
+ }
+
+ let t = parseInt(this.getAttribute('timeout'));
+ return isNaN(t) ? 50 : t;
+ ]]></getter>
+ </property>
+
+ <property name="searchParam"
+ onget="return this.getAttribute('autocompletesearchparam') || '';"
+ onset="this.setAttribute('autocompletesearchparam', val); return val;"/>
+
+ <property name="searchCount" readonly="true"
+ onget="this.initSearchNames(); return this.mSearchNames.length;"/>
+
+ <field name="shrinkDelay" readonly="true">
+ parseInt(this.getAttribute("shrinkdelay")) || 0
+ </field>
+
+ <field name="PrivateBrowsingUtils" readonly="true">
+ {
+ let utils = {};
+ Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm", utils);
+ utils.PrivateBrowsingUtils
+ }
+ </field>
+
+ <property name="inPrivateContext" readonly="true"
+ onget="return this.PrivateBrowsingUtils.isWindowPrivate(window);"/>
+
+ <property name="noRollupOnCaretMove" readonly="true"
+ onget="return this.popup.getAttribute('norolluponanchor') == 'true'"/>
+
+ <!-- This is the maximum number of drop-down rows we get when we
+ hit the drop marker beside fields that have it (like the URLbar).-->
+ <field name="maxDropMarkerRows" readonly="true">14</field>
+
+ <method name="getSearchAt">
+ <parameter name="aIndex"/>
+ <body><![CDATA[
+ this.initSearchNames();
+ return this.mSearchNames[aIndex];
+ ]]></body>
+ </method>
+
+ <property name="textValue"
+ onget="return this.value;">
+ <setter><![CDATA[
+ // Completing a result should simulate the user typing the result,
+ // so fire an input event.
+ // 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;
+
+ var 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="selectTextRange">
+ <parameter name="aStartIndex"/>
+ <parameter name="aEndIndex"/>
+ <body><![CDATA[
+ this.inputField.setSelectionRange(aStartIndex, aEndIndex);
+ ]]></body>
+ </method>
+
+ <method name="onSearchBegin">
+ <body><![CDATA[
+ if (this.popup && typeof this.popup.onSearchBegin == "function")
+ this.popup.onSearchBegin();
+ if (this._searchBeginHandler)
+ this._searchBeginHandler();
+ ]]></body>
+ </method>
+
+ <method name="onSearchComplete">
+ <body><![CDATA[
+ if (this.mController.matchCount == 0)
+ this.setAttribute("nomatch", "true");
+ else
+ this.removeAttribute("nomatch");
+
+ if (this.ignoreBlurWhileSearching && !this.focused) {
+ this.handleEnter();
+ this.detachController();
+ }
+
+ if (this._searchCompleteHandler)
+ this._searchCompleteHandler();
+ ]]></body>
+ </method>
+
+ <method name="onTextEntered">
+ <body><![CDATA[
+ let rv = false;
+ if (this._textEnteredHandler)
+ rv = this._textEnteredHandler(this.mEnterEvent);
+ this.mEnterEvent = null;
+ return rv;
+ ]]></body>
+ </method>
+
+ <method name="onTextReverted">
+ <body><![CDATA[
+ if (this._textRevertedHandler)
+ return this._textRevertedHandler();
+ return false;
+ ]]></body>
+ </method>
+
+ <!-- =================== nsIDOMXULMenuListElement =================== -->
+
+ <property name="editable" readonly="true"
+ onget="return true;" />
+
+ <property name="crop"
+ onset="this.setAttribute('crop',val); return val;"
+ onget="return this.getAttribute('crop');"/>
+
+ <property name="open"
+ onget="return this.getAttribute('open') == 'true';">
+ <setter><![CDATA[
+ if (val)
+ this.showHistoryPopup();
+ else
+ this.closePopup();
+ ]]></setter>
+ </property>
+
+ <!-- =================== PUBLIC MEMBERS =================== -->
+
+ <field name="valueIsTyped">false</field>
+ <field name="_disableTrim">false</field>
+ <property name="value">
+ <getter><![CDATA[
+ if (typeof this.onBeforeValueGet == "function") {
+ var result = this.onBeforeValueGet();
+ if (result)
+ return result.value;
+ }
+ return this.inputField.value;
+ ]]></getter>
+ <setter><![CDATA[
+ this.mIgnoreInput = true;
+
+ if (typeof this.onBeforeValueSet == "function")
+ val = this.onBeforeValueSet(val);
+
+ if (typeof this.trimValue == "function" && !this._disableTrim)
+ val = this.trimValue(val);
+
+ this.valueIsTyped = false;
+ this.inputField.value = val;
+
+ if (typeof this.formatValue == "function")
+ this.formatValue();
+
+ this.mIgnoreInput = false;
+ var event = document.createEvent('Events');
+ event.initEvent('ValueChange', true, true);
+ this.inputField.dispatchEvent(event);
+ return val;
+ ]]></setter>
+ </property>
+
+ <property name="focused" readonly="true"
+ onget="return this.getAttribute('focused') == 'true';"/>
+
+ <!-- maximum number of rows to display at a time -->
+ <property name="maxRows"
+ onset="this.setAttribute('maxrows', val); return val;"
+ onget="return parseInt(this.getAttribute('maxrows')) || 0;"/>
+
+ <!-- option to allow scrolling through the list via the tab key, rather than
+ tab moving focus out of the textbox -->
+ <property name="tabScrolling"
+ onset="this.setAttribute('tabscrolling', val); return val;"
+ onget="return this.getAttribute('tabscrolling') == 'true';"/>
+
+ <!-- option to completely ignore any blur events while searches are
+ still going on. -->
+ <property name="ignoreBlurWhileSearching"
+ onset="this.setAttribute('ignoreblurwhilesearching', val); return val;"
+ onget="return this.getAttribute('ignoreblurwhilesearching') == 'true';"/>
+
+ <!-- disable key navigation handling in the popup results -->
+ <property name="disableKeyNavigation"
+ onset="this.setAttribute('disablekeynavigation', val); return val;"
+ onget="return this.getAttribute('disablekeynavigation') == 'true';"/>
+
+ <!-- option to highlight entries that don't have any matches -->
+ <property name="highlightNonMatches"
+ onset="this.setAttribute('highlightnonmatches', val); return val;"
+ onget="return this.getAttribute('highlightnonmatches') == 'true';"/>
+
+ <!-- =================== PRIVATE MEMBERS =================== -->
+
+ <!-- ::::::::::::: autocomplete controller ::::::::::::: -->
+
+ <method name="attachController">
+ <body><![CDATA[
+ this.mController.input = this;
+ ]]></body>
+ </method>
+
+ <method name="detachController">
+ <body><![CDATA[
+ if (this.mController.input == this)
+ this.mController.input = null;
+ ]]></body>
+ </method>
+
+ <!-- ::::::::::::: popup opening ::::::::::::: -->
+
+ <method name="openPopup">
+ <body><![CDATA[
+ if (this.focused)
+ this.popup.openAutocompletePopup(this, this);
+ ]]></body>
+ </method>
+
+ <method name="closePopup">
+ <body><![CDATA[
+ this.popup.closePopup();
+ ]]></body>
+ </method>
+
+ <method name="showHistoryPopup">
+ <body><![CDATA[
+ // history dropmarker pushed state
+ function cleanup(popup) {
+ popup.removeEventListener("popupshowing", onShow, false);
+ }
+ function onShow(event) {
+ var popup = event.target, input = popup.input;
+ cleanup(popup);
+ input.setAttribute("open", "true");
+ function onHide() {
+ input.removeAttribute("open");
+ popup.removeEventListener("popuphiding", onHide, false);
+ }
+ popup.addEventListener("popuphiding", onHide, false);
+ }
+ this.popup.addEventListener("popupshowing", onShow, false);
+ setTimeout(cleanup, 1000, this.popup);
+
+ // Store our "normal" maxRows on the popup, so that it can reset the
+ // value when the popup is hidden.
+ this.popup._normalMaxRows = this.maxRows;
+
+ // Increase our maxRows temporarily, since we want the dropdown to
+ // be bigger in this case. The popup's popupshowing/popuphiding
+ // handlers will take care of resetting this.
+ this.maxRows = this.maxDropMarkerRows;
+
+ // Ensure that we have focus.
+ if (!this.focused)
+ this.focus();
+ this.attachController();
+ this.mController.startSearch("");
+ ]]></body>
+ </method>
+
+ <method name="toggleHistoryPopup">
+ <body><![CDATA[
+ // If this method is called on the same event tick as the popup gets
+ // hidden, do nothing to avoid re-opening the popup when the drop
+ // marker is clicked while the popup is still open.
+ if (!this.popup.isPopupHidingTick && !this.popup.popupOpen)
+ this.showHistoryPopup();
+ else
+ this.closePopup();
+ ]]></body>
+ </method>
+
+ <!-- ::::::::::::: event dispatching ::::::::::::: -->
+
+ <method name="initEventHandler">
+ <parameter name="aEventType"/>
+ <body><![CDATA[
+ let handlerString = this.getAttribute("on" + aEventType);
+ if (handlerString) {
+ return (new Function("eventType", "param", handlerString)).bind(this, aEventType);
+ }
+ return null;
+ ]]></body>
+ </method>
+
+ <!-- ::::::::::::: key handling ::::::::::::: -->
+
+ <field name="_selectionDetails">null</field>
+ <method name="onKeyPress">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ return this.handleKeyPress(aEvent);
+ ]]></body>
+ </method>
+
+ <method name="handleKeyPress">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ if (aEvent.target.localName != "textbox")
+ return true; // Let child buttons of autocomplete take input
+
+ //XXXpch this is so bogus...
+ if (aEvent.defaultPrevented)
+ return false;
+
+ var cancel = false;
+
+ let { AppConstants } =
+ Components.utils.import("resource://gre/modules/AppConstants.jsm", {});
+ // Catch any keys that could potentially move the caret. Ctrl can be
+ // used in combination with these keys on Windows and Linux; and Alt
+ // can be used on OS X, so make sure the unused one isn't used.
+ let metaKey = AppConstants.platform == "macosx" ? aEvent.ctrlKey : aEvent.altKey;
+ if (!this.disableKeyNavigation && !metaKey) {
+ switch (aEvent.keyCode) {
+ case KeyEvent.DOM_VK_LEFT:
+ case KeyEvent.DOM_VK_RIGHT:
+ case KeyEvent.DOM_VK_HOME:
+ cancel = this.mController.handleKeyNavigation(aEvent.keyCode);
+ break;
+ }
+ }
+
+ // Handle keys that are not part of a keyboard shortcut (no Ctrl or Alt)
+ if (!this.disableKeyNavigation && !aEvent.ctrlKey && !aEvent.altKey) {
+ switch (aEvent.keyCode) {
+ case KeyEvent.DOM_VK_TAB:
+ if (this.tabScrolling && this.popup.popupOpen)
+ cancel = this.mController.handleKeyNavigation(aEvent.shiftKey ?
+ KeyEvent.DOM_VK_UP :
+ KeyEvent.DOM_VK_DOWN);
+ else if (this.forceComplete && this.mController.matchCount >= 1)
+ this.mController.handleTab();
+ break;
+ case KeyEvent.DOM_VK_UP:
+ case KeyEvent.DOM_VK_DOWN:
+ case KeyEvent.DOM_VK_PAGE_UP:
+ case KeyEvent.DOM_VK_PAGE_DOWN:
+ cancel = this.mController.handleKeyNavigation(aEvent.keyCode);
+ break;
+ }
+ }
+
+ // Handle keys we know aren't part of a shortcut, even with Alt or
+ // Ctrl.
+ switch (aEvent.keyCode) {
+ case KeyEvent.DOM_VK_ESCAPE:
+ cancel = this.mController.handleEscape();
+ break;
+ case KeyEvent.DOM_VK_RETURN:
+ if (AppConstants.platform == "macosx") {
+ // Prevent the default action, since it will beep on Mac
+ if (aEvent.metaKey)
+ aEvent.preventDefault();
+ }
+ this.mEnterEvent = aEvent;
+ if (this.mController.selection) {
+ this._selectionDetails = {
+ index: this.mController.selection.currentIndex,
+ kind: "key"
+ };
+ }
+ cancel = this.handleEnter();
+ break;
+ case KeyEvent.DOM_VK_DELETE:
+ if (AppConstants.platform == "macosx" && !aEvent.shiftKey) {
+ break;
+ }
+ cancel = this.handleDelete();
+ break;
+ case KeyEvent.DOM_VK_BACK_SPACE:
+ if (AppConstants.platform == "macosx" && aEvent.shiftKey) {
+ cancel = this.handleDelete();
+ }
+ break;
+ case KeyEvent.DOM_VK_DOWN:
+ case KeyEvent.DOM_VK_UP:
+ if (aEvent.altKey)
+ this.toggleHistoryPopup();
+ break;
+ case KeyEvent.DOM_VK_F4:
+ if (AppConstants.platform != "macosx") {
+ this.toggleHistoryPopup();
+ }
+ break;
+ }
+
+ if (cancel) {
+ aEvent.stopPropagation();
+ aEvent.preventDefault();
+ }
+
+ return true;
+ ]]></body>
+ </method>
+
+ <method name="handleEnter">
+ <body><![CDATA[
+ return this.mController.handleEnter(false);
+ ]]></body>
+ </method>
+
+ <method name="handleDelete">
+ <body><![CDATA[
+ return this.mController.handleDelete();
+ ]]></body>
+ </method>
+
+ <!-- ::::::::::::: miscellaneous ::::::::::::: -->
+
+ <method name="initSearchNames">
+ <body><![CDATA[
+ if (!this.mSearchNames) {
+ var names = this.getAttribute("autocompletesearch");
+ if (!names)
+ this.mSearchNames = [];
+ else
+ this.mSearchNames = names.split(" ");
+ }
+ ]]></body>
+ </method>
+
+ <method name="_focus">
+ <!-- doesn't reset this.mController -->
+ <body><![CDATA[
+ this._dontBlur = true;
+ this.focus();
+ this._dontBlur = false;
+ ]]></body>
+ </method>
+
+ <method name="resetActionType">
+ <body><![CDATA[
+ if (this.mIgnoreInput)
+ return;
+ this.removeAttribute("actiontype");
+ ]]></body>
+ </method>
+
+ <field name="_valueIsPasted">false</field>
+ <field name="_pasteController"><![CDATA[
+ ({
+ _autocomplete: this,
+ _kGlobalClipboard: Components.interfaces.nsIClipboard.kGlobalClipboard,
+ supportsCommand: aCommand => aCommand == "cmd_paste",
+ doCommand: function(aCommand) {
+ this._autocomplete._valueIsPasted = true;
+ this._autocomplete.editor.paste(this._kGlobalClipboard);
+ this._autocomplete._valueIsPasted = false;
+ },
+ isCommandEnabled: function(aCommand) {
+ return this._autocomplete.editor.isSelectionEditable &&
+ this._autocomplete.editor.canPaste(this._kGlobalClipboard);
+ },
+ onEvent: function() {}
+ })
+ ]]></field>
+
+ <method name="onInput">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ if (!this.mIgnoreInput && this.mController.input == this) {
+ this.valueIsTyped = true;
+ this.mController.handleText();
+ }
+ this.resetActionType();
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="input"><![CDATA[
+ this.onInput(event);
+ ]]></handler>
+
+ <handler event="keypress" phase="capturing"
+ action="return this.onKeyPress(event);"/>
+
+ <handler event="compositionstart" phase="capturing"
+ action="if (this.mController.input == this) this.mController.handleStartComposition();"/>
+
+ <handler event="compositionend" phase="capturing"
+ action="if (this.mController.input == this) this.mController.handleEndComposition();"/>
+
+ <handler event="focus" phase="capturing"
+ action="this.attachController();"/>
+
+ <handler event="blur" phase="capturing"><![CDATA[
+ if (!this._dontBlur) {
+ if (this.forceComplete && this.mController.matchCount >= 1) {
+ // mousemove sets selected index. Don't blindly use that selected
+ // index in this blur handler since if the popup is open you can
+ // easily "select" another match just by moving the mouse over it.
+ let filledVal = this.value.replace(/.+ >> /, "").toLowerCase();
+ let selectedVal = null;
+ if (this.popup.selectedIndex >= 0) {
+ selectedVal = this.mController.getFinalCompleteValueAt(
+ this.popup.selectedIndex);
+ }
+ if (selectedVal && filledVal != selectedVal.toLowerCase()) {
+ for (let i = 0; i < this.mController.matchCount; i++) {
+ let matchVal = this.mController.getFinalCompleteValueAt(i);
+ if (matchVal.toLowerCase() == filledVal) {
+ this.popup.selectedIndex = i;
+ break;
+ }
+ }
+ }
+ this.mController.handleEnter(false);
+ }
+ if (!this.ignoreBlurWhileSearching)
+ this.detachController();
+ }
+ ]]></handler>
+ </handlers>
+ </binding>
+
+ <binding id="private-autocomplete-result-popup" extends="chrome://browser/content/autocomplete.xml#private-autocomplete-base-popup">
+ <resources>
+ <stylesheet src="chrome://browser/content/autocomplete.css"/>
+ <stylesheet src="chrome://global/skin/tree.css"/>
+ <stylesheet src="chrome://browser/skin/autocomplete.css"/>
+ </resources>
+
+ <content ignorekeys="true" level="top" consumeoutsideclicks="never">
+ <xul:tree anonid="tree" class="private-autocomplete-tree plain" hidecolumnpicker="true" flex="1" seltype="single">
+ <xul:treecols anonid="treecols">
+ <xul:treecol id="treecolAutoCompleteValue" class="private-autocomplete-treecol" flex="1" overflow="true"/>
+ </xul:treecols>
+ <xul:treechildren class="private-autocomplete-treebody"/>
+ </xul:tree>
+ </content>
+
+ <implementation>
+ <field name="mShowCommentColumn">false</field>
+ <field name="mShowImageColumn">false</field>
+
+ <property name="showCommentColumn"
+ onget="return this.mShowCommentColumn;">
+ <setter>
+ <![CDATA[
+ if (!val && this.mShowCommentColumn) {
+ // reset the flex on the value column and remove the comment column
+ document.getElementById("treecolAutoCompleteValue").setAttribute("flex", 1);
+ this.removeColumn("treecolAutoCompleteComment");
+ } else if (val && !this.mShowCommentColumn) {
+ // reset the flex on the value column and add the comment column
+ document.getElementById("treecolAutoCompleteValue").setAttribute("flex", 2);
+ this.addColumn({id: "treecolAutoCompleteComment", flex: 1});
+ }
+ this.mShowCommentColumn = val;
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <property name="showImageColumn"
+ onget="return this.mShowImageColumn;">
+ <setter>
+ <![CDATA[
+ if (!val && this.mShowImageColumn) {
+ // remove the image column
+ this.removeColumn("treecolAutoCompleteImage");
+ } else if (val && !this.mShowImageColumn) {
+ // add the image column
+ this.addColumn({id: "treecolAutoCompleteImage", flex: 1});
+ }
+ this.mShowImageColumn = val;
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+
+ <method name="addColumn">
+ <parameter name="aAttrs"/>
+ <body>
+ <![CDATA[
+ var col = document.createElement("treecol");
+ col.setAttribute("class", "private-autocomplete-treecol");
+ for (var name in aAttrs)
+ col.setAttribute(name, aAttrs[name]);
+ this.treecols.appendChild(col);
+ return col;
+ ]]>
+ </body>
+ </method>
+
+ <method name="removeColumn">
+ <parameter name="aColId"/>
+ <body>
+ <![CDATA[
+ return this.treecols.removeChild(document.getElementById(aColId));
+ ]]>
+ </body>
+ </method>
+
+ <property name="selectedIndex"
+ onget="return this.tree.currentIndex;">
+ <setter>
+ <![CDATA[
+ this.tree.view.selection.select(val);
+ if (this.tree.treeBoxObject.height > 0)
+ this.tree.treeBoxObject.ensureRowIsVisible(val < 0 ? 0 : val);
+ // Fire select event on xul:tree so that accessibility API
+ // support layer can fire appropriate accessibility events.
+ var event = document.createEvent('Events');
+ event.initEvent("select", true, true);
+ this.tree.dispatchEvent(event);
+ return val;
+ ]]></setter>
+ </property>
+
+ <method name="adjustHeight">
+ <body>
+ <![CDATA[
+ // detect the desired height of the tree
+ var bx = this.tree.treeBoxObject;
+ var view = this.tree.view;
+ if (!view)
+ return;
+ var rows = this.maxRows;
+ if (!view.rowCount || (rows && view.rowCount < rows))
+ rows = view.rowCount;
+
+ var height = rows * bx.rowHeight;
+
+ if (height == 0) {
+ this.tree.setAttribute("collapsed", "true");
+ } else {
+ if (this.tree.hasAttribute("collapsed"))
+ this.tree.removeAttribute("collapsed");
+
+ this.tree.setAttribute("height", height);
+ }
+ this.tree.setAttribute("hidescrollbar", view.rowCount <= rows);
+ ]]>
+ </body>
+ </method>
+
+ <method name="openAutocompletePopup">
+ <parameter name="aInput"/>
+ <parameter name="aElement"/>
+ <body><![CDATA[
+ // until we have "baseBinding", (see bug #373652) this allows
+ // us to override openAutocompletePopup(), but still call
+ // the method on the base class
+ this._openAutocompletePopup(aInput, aElement);
+ ]]></body>
+ </method>
+
+ <method name="_openAutocompletePopup">
+ <parameter name="aInput"/>
+ <parameter name="aElement"/>
+ <body><![CDATA[
+ if (!this.mPopupOpen) {
+ this.mInput = aInput;
+ this.view = aInput.controller.QueryInterface(Components.interfaces.nsITreeView);
+ this.invalidate();
+
+ this.showCommentColumn = this.mInput.showCommentColumn;
+ this.showImageColumn = this.mInput.showImageColumn;
+
+ var rect = aElement.getBoundingClientRect();
+ var nav = aElement.ownerDocument.defaultView.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebNavigation);
+ var docShell = nav.QueryInterface(Components.interfaces.nsIDocShell);
+ var docViewer = docShell.contentViewer;
+ var width = (rect.right - rect.left) * docViewer.fullZoom;
+ this.setAttribute("width", width > 100 ? width : 100);
+
+ // Adjust the direction of the autocomplete popup list based on the textbox direction, bug 649840
+ var popupDirection = aElement.ownerDocument.defaultView.getComputedStyle(aElement).direction;
+ this.style.direction = popupDirection;
+
+ this.openPopup(aElement, "after_start", 0, 0, false, false);
+ }
+ ]]></body>
+ </method>
+
+ <method name="invalidate">
+ <body><![CDATA[
+ this.adjustHeight();
+ this.tree.treeBoxObject.invalidate();
+ ]]></body>
+ </method>
+
+ <method name="selectBy">
+ <parameter name="aReverse"/>
+ <parameter name="aPage"/>
+ <body><![CDATA[
+ try {
+ var amount = aPage ? 5 : 1;
+ this.selectedIndex = this.getNextIndex(aReverse, amount, this.selectedIndex, this.tree.view.rowCount-1);
+ if (this.selectedIndex == -1) {
+ this.input._focus();
+ }
+ } catch (ex) {
+ // do nothing - occasionally timer-related js errors happen here
+ // e.g. "this.selectedIndex has no properties", when you type fast and hit a
+ // navigation key before this popup has opened
+ }
+ ]]></body>
+ </method>
+
+ <!-- =================== PUBLIC MEMBERS =================== -->
+
+ <field name="tree">
+ document.getAnonymousElementByAttribute(this, "anonid", "tree");
+ </field>
+
+ <field name="treecols">
+ document.getAnonymousElementByAttribute(this, "anonid", "treecols");
+ </field>
+
+ <property name="view"
+ onget="return this.mView;">
+ <setter><![CDATA[
+ // We must do this by hand because the tree binding may not be ready yet
+ this.mView = val;
+ this.tree.boxObject.view = val;
+ ]]></setter>
+ </property>
+
+ </implementation>
+ </binding>
+
+ <binding id="private-autocomplete-base-popup" role="none"
+extends="chrome://global/content/bindings/popup.xml#popup">
+ <implementation implements="nsIAutoCompletePopup">
+ <field name="mInput">null</field>
+ <field name="mPopupOpen">false</field>
+ <field name="mIsPopupHidingTick">false</field>
+
+ <!-- =================== nsIAutoCompletePopup =================== -->
+
+ <property name="input" readonly="true"
+ onget="return this.mInput"/>
+
+ <property name="overrideValue" readonly="true"
+ onget="return null;"/>
+
+ <property name="popupOpen" readonly="true"
+ onget="return this.mPopupOpen;"/>
+
+ <property name="isPopupHidingTick" readonly="true"
+ onget="return this.mIsPopupHidingTick;"/>
+
+ <method name="closePopup">
+ <body>
+ <![CDATA[
+ if (this.mPopupOpen) {
+ this.hidePopup();
+ this.removeAttribute("width");
+ }
+ ]]>
+ </body>
+ </method>
+
+ <!-- This is the default number of rows that we give the autocomplete
+ popup when the textbox doesn't have a "maxrows" attribute
+ for us to use. -->
+ <field name="defaultMaxRows" readonly="true">6</field>
+
+ <!-- In some cases (e.g. when the input's dropmarker button is clicked),
+ the input wants to display a popup with more rows. In that case, it
+ should increase its maxRows property and store the "normal" maxRows
+ in this field. When the popup is hidden, we restore the input's
+ maxRows to the value stored in this field.
+
+ This field is set to -1 between uses so that we can tell when it's
+ been set by the input and when we need to set it in the popupshowing
+ handler. -->
+ <field name="_normalMaxRows">-1</field>
+
+ <property name="maxRows" readonly="true">
+ <getter>
+ <![CDATA[
+ return (this.mInput && this.mInput.maxRows) || this.defaultMaxRows;
+ ]]>
+ </getter>
+ </property>
+
+ <method name="getNextIndex">
+ <parameter name="aReverse"/>
+ <parameter name="aAmount"/>
+ <parameter name="aIndex"/>
+ <parameter name="aMaxRow"/>
+ <body><![CDATA[
+ if (aMaxRow < 0)
+ return -1;
+
+ var newIdx = aIndex + (aReverse?-1:1)*aAmount;
+ if (aReverse && aIndex == -1 || newIdx > aMaxRow && aIndex != aMaxRow)
+ newIdx = aMaxRow;
+ else if (!aReverse && aIndex == -1 || newIdx < 0 && aIndex != 0)
+ newIdx = 0;
+
+ if (newIdx < 0 && aIndex == 0 || newIdx > aMaxRow && aIndex == aMaxRow)
+ aIndex = -1;
+ else
+ aIndex = newIdx;
+
+ return aIndex;
+ ]]></body>
+ </method>
+
+ <method name="onPopupClick">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ var controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController);
+ controller.handleEnter(true);
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="popupshowing"><![CDATA[
+ // If normalMaxRows wasn't already set by the input, then set it here
+ // so that we restore the correct number when the popup is hidden.
+
+ // Null-check this.mInput; see bug 1017914
+ if (this._normalMaxRows < 0 && this.mInput) {
+ this._normalMaxRows = this.mInput.maxRows;
+ }
+
+ // Set an attribute for styling the popup based on the input.
+ let inputID = "";
+ if (this.mInput && this.mInput.ownerDocument &&
+ this.mInput.ownerDocument.documentURIObject.schemeIs("chrome")) {
+ inputID = this.mInput.id;
+ // Take care of elements with no id that are inside xbl bindings
+ if (!inputID) {
+ let bindingParent = this.mInput.ownerDocument.getBindingParent(this.mInput);
+ if (bindingParent) {
+ inputID = bindingParent.id;
+ }
+ }
+ }
+ this.setAttribute("autocompleteinput", inputID);
+
+ this.mPopupOpen = true;
+ ]]></handler>
+
+ <handler event="popuphiding"><![CDATA[
+ var isListActive = true;
+ if (this.selectedIndex == -1)
+ isListActive = false;
+ var controller = this.view.QueryInterface(Components.interfaces.nsIAutoCompleteController);
+ controller.stopSearch();
+
+ this.removeAttribute("autocompleteinput");
+ this.mPopupOpen = false;
+
+ // Prevent opening popup from historydropmarker mousedown handler
+ // on the same event tick the popup is hidden by the same mousedown
+ // event.
+ this.mIsPopupHidingTick = true;
+ setTimeout(() => {
+ this.mIsPopupHidingTick = false;
+ }, 0);
+
+ // Reset the maxRows property to the cached "normal" value, and reset
+ // _normalMaxRows so that we can detect whether it was set by the input
+ // when the popupshowing handler runs.
+
+ // Null-check this.mInput; see bug 1017914
+ if (this.mInput)
+ this.mInput.maxRows = this._normalMaxRows;
+ this._normalMaxRows = -1;
+ // If the list was being navigated and then closed, make sure
+ // we fire accessible focus event back to textbox
+
+ // Null-check this.mInput; see bug 1017914
+ if (isListActive && this.mInput) {
+ this.mInput.mIgnoreFocus = true;
+ this.mInput._focus();
+ this.mInput.mIgnoreFocus = false;
+ }
+ ]]></handler>
+ </handlers>
+ </binding>
+
+ <binding id="private-autocomplete-rich-result-popup" extends="chrome://browser/content/autocomplete.xml#private-autocomplete-base-popup">
+ <resources>
+ <stylesheet src="chrome://browser/content/autocomplete.css"/>
+ <stylesheet src="chrome://browser/skin/autocomplete.css"/>
+ </resources>
+
+ <content ignorekeys="true" level="top" consumeoutsideclicks="never">
+ <xul:richlistbox anonid="richlistbox" class="private-autocomplete-richlistbox" flex="1"/>
+ <xul:hbox>
+ <children/>
+ </xul:hbox>
+ </content>
+
+ <implementation implements="nsIAutoCompletePopup">
+ <field name="_currentIndex">0</field>
+ <field name="_rowHeight">0</field>
+ <field name="_rlbAnimated">false</field>
+
+ <!-- =================== nsIAutoCompletePopup =================== -->
+
+ <property name="selectedIndex"
+ onget="return this.richlistbox.selectedIndex;">
+ <setter>
+ <![CDATA[
+ this.richlistbox.selectedIndex = val;
+
+ // when clearing the selection (val == -1, so selectedItem will be
+ // null), we want to scroll back to the top. see bug #406194
+ this.richlistbox.ensureElementIsVisible(
+ this.richlistbox.selectedItem || this.richlistbox.firstChild);
+
+ return val;
+ ]]>
+ </setter>
+ </property>
+
+ <method name="onSearchBegin">
+ <body><![CDATA[
+ this.richlistbox.mouseSelectedIndex = -1;
+ ]]></body>
+ </method>
+
+ <method name="openAutocompletePopup">
+ <parameter name="aInput"/>
+ <parameter name="aElement"/>
+ <body>
+ <![CDATA[
+ // until we have "baseBinding", (see bug #373652) this allows
+ // us to override openAutocompletePopup(), but still call
+ // the method on the base class
+ this._openAutocompletePopup(aInput, aElement);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_openAutocompletePopup">
+ <parameter name="aInput"/>
+ <parameter name="aElement"/>
+ <body>
+ <![CDATA[
+ if (!this.mPopupOpen) {
+ this.mInput = aInput;
+ // clear any previous selection, see bugs 400671 and 488357
+ this.selectedIndex = -1;
+
+ var width = aElement.getBoundingClientRect().width;
+ this.setAttribute("width", width > 100 ? width : 100);
+ // invalidate() depends on the width attribute
+ this._invalidate();
+
+ this.openPopup(aElement, "after_start", 0, 0, false, false);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="invalidate">
+ <parameter name="reason"/>
+ <body>
+ <![CDATA[
+ // Don't bother doing work if we're not even showing
+ if (!this.mPopupOpen)
+ return;
+
+ this._invalidate(reason);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_invalidate">
+ <parameter name="reason"/>
+ <body>
+ <![CDATA[
+ // collapsed if no matches
+ this.richlistbox.collapsed = (this._matchCount == 0);
+
+ // Update the richlistbox height.
+ if (this._adjustHeightTimeout) {
+ clearTimeout(this._adjustHeightTimeout);
+ }
+ if (this._shrinkTimeout) {
+ clearTimeout(this._shrinkTimeout);
+ }
+ this._adjustHeightTimeout = setTimeout(() => this.adjustHeight(), 0);
+
+ this._currentIndex = 0;
+ if (this._appendResultTimeout) {
+ clearTimeout(this._appendResultTimeout);
+ }
+ this._appendCurrentResult(reason);
+ ]]>
+ </body>
+ </method>
+
+ <property name="maxResults" readonly="true">
+ <getter>
+ <![CDATA[
+ // this is how many richlistitems will be kept around
+ // (note, this getter may be overridden)
+ return 20;
+ ]]>
+ </getter>
+ </property>
+
+ <property name="_matchCount" readonly="true">
+ <getter>
+ <![CDATA[
+ return Math.min(this.mInput.controller.matchCount, this.maxResults);
+ ]]>
+ </getter>
+ </property>
+
+ <method name="_collapseUnusedItems">
+ <body>
+ <![CDATA[
+ let existingItemsCount = this.richlistbox.childNodes.length;
+ for (let i = this._matchCount; i < existingItemsCount; ++i) {
+ this.richlistbox.childNodes[i].collapsed = true;
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="adjustHeight">
+ <body>
+ <![CDATA[
+ // Figure out how many rows to show
+ let rows = this.richlistbox.childNodes;
+ let numRows = Math.min(this._matchCount, this.maxRows, rows.length);
+
+ this.removeAttribute("height");
+
+ // Default the height to 0 if we have no rows to show
+ let height = 0;
+ if (numRows) {
+ if (!this._rowHeight) {
+ let firstRowRect = rows[0].getBoundingClientRect();
+ this._rowHeight = firstRowRect.height;
+
+ let transition =
+ window.getComputedStyle(this.richlistbox).transitionProperty;
+ this._rlbAnimated = transition && transition != "none";
+
+ // Set a fixed max-height to avoid flicker when growing the panel.
+ this.richlistbox.style.maxHeight = (this._rowHeight * this.maxRows) + "px";
+ }
+
+ // Calculate the height to have the first row to last row shown
+ height = this._rowHeight * numRows;
+ }
+
+ let animate = this._rlbAnimated &&
+ this.getAttribute("dontanimate") != "true";
+ let currentHeight = this.richlistbox.getBoundingClientRect().height;
+ if (height > currentHeight) {
+ // Grow immediately.
+ if (animate) {
+ this.richlistbox.removeAttribute("height");
+ this.richlistbox.style.height = height + "px";
+ } else {
+ this.richlistbox.style.removeProperty("height");
+ this.richlistbox.height = height;
+ }
+ } else {
+ // Delay shrinking to avoid flicker.
+ this._shrinkTimeout = setTimeout(() => {
+ this._collapseUnusedItems();
+ if (animate) {
+ this.richlistbox.removeAttribute("height");
+ this.richlistbox.style.height = height + "px";
+ } else {
+ this.richlistbox.style.removeProperty("height");
+ this.richlistbox.height = height;
+ }
+ }, this.mInput.shrinkDelay);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_appendCurrentResult">
+ <parameter name="invalidateReason"/>
+ <body>
+ <![CDATA[
+ var controller = this.mInput.controller;
+ var matchCount = this._matchCount;
+ var existingItemsCount = this.richlistbox.childNodes.length;
+
+ // Process maxRows per chunk to improve performance and user experience
+ for (let i = 0; i < this.maxRows; i++) {
+ if (this._currentIndex >= matchCount)
+ break;
+
+ var item;
+
+ // trim the leading/trailing whitespace
+ var trimmedSearchString = controller.searchString.replace(/^\s+/, "").replace(/\s+$/, "");
+
+ let url = controller.getValueAt(this._currentIndex);
+
+ if (this._currentIndex < existingItemsCount) {
+ // re-use the existing item
+ item = this.richlistbox.childNodes[this._currentIndex];
+
+ // Completely reuse the existing richlistitem for invalidation
+ // due to new results, but only when: the item is the same, *OR*
+ // we are about to replace the currently mouse-selected item, to
+ // avoid surprising the user.
+ let iface = Components.interfaces.nsIAutoCompletePopup;
+ if (item.getAttribute("text") == trimmedSearchString &&
+ invalidateReason == iface.INVALIDATE_REASON_NEW_RESULT &&
+ (item.getAttribute("url") == url ||
+ this.richlistbox.mouseSelectedIndex === this._currentIndex)) {
+ item.collapsed = false;
+ this._currentIndex++;
+ continue;
+ }
+ }
+ else {
+ // need to create a new item
+ item = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "richlistitem");
+ }
+
+ // set these attributes before we set the class
+ // so that we can use them from the constructor
+ let iconURI = controller.getImageAt(this._currentIndex);
+ item.setAttribute("image", iconURI);
+ item.setAttribute("url", url);
+ item.setAttribute("title", controller.getCommentAt(this._currentIndex));
+ item.setAttribute("type", controller.getStyleAt(this._currentIndex));
+ item.setAttribute("text", trimmedSearchString);
+
+ if (this._currentIndex < existingItemsCount) {
+ // re-use the existing item
+ item._adjustAcItem();
+ item.collapsed = false;
+ }
+ else {
+ // set the class at the end so we can use the attributes
+ // in the xbl constructor
+ item.className = "private-autocomplete-richlistitem";
+ this.richlistbox.appendChild(item);
+ }
+
+ this._currentIndex++;
+ }
+
+ if (typeof this.onResultsAdded == "function")
+ this.onResultsAdded();
+
+ if (this._currentIndex < matchCount) {
+ // yield after each batch of items so that typing the url bar is
+ // responsive
+ this._appendResultTimeout = setTimeout(() => this._appendCurrentResult(), 0);
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="selectBy">
+ <parameter name="aReverse"/>
+ <parameter name="aPage"/>
+ <body>
+ <![CDATA[
+ try {
+ var amount = aPage ? 5 : 1;
+
+ // because we collapsed unused items, we can't use this.richlistbox.getRowCount(), we need to use the matchCount
+ this.selectedIndex = this.getNextIndex(aReverse, amount, this.selectedIndex, this._matchCount - 1);
+ if (this.selectedIndex == -1) {
+ this.input._focus();
+ }
+ } catch (ex) {
+ // do nothing - occasionally timer-related js errors happen here
+ // e.g. "this.selectedIndex has no properties", when you type fast and hit a
+ // navigation key before this popup has opened
+ }
+ ]]>
+ </body>
+ </method>
+
+ <field name="richlistbox">
+ document.getAnonymousElementByAttribute(this, "anonid", "richlistbox");
+ </field>
+
+ <property name="view"
+ onget="return this.mInput.controller;"
+ onset="return val;"/>
+
+ </implementation>
+ </binding>
+
+ <binding id="private-autocomplete-richlistitem" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+ <content>
+ <xul:hbox align="center" class="ac-title-box">
+ <xul:image xbl:inherits="src=image" class="ac-site-icon"/>
+ <xul:hbox anonid="title-box" class="ac-title" flex="1"
+ onunderflow="_doUnderflow('_title');"
+ onoverflow="_doOverflow('_title');">
+ <xul:description anonid="title" class="ac-normal-text ac-comment" xbl:inherits="selected"/>
+ </xul:hbox>
+ <xul:label anonid="title-overflow-ellipsis" xbl:inherits="selected"
+ class="ac-ellipsis-after ac-comment"/>
+ <xul:hbox anonid="extra-box" class="ac-extra" align="center" hidden="true">
+ <xul:image class="ac-result-type-tag"/>
+ <xul:label class="ac-normal-text ac-comment" xbl:inherits="selected" value=":"/>
+ <xul:description anonid="extra" class="ac-normal-text ac-comment" xbl:inherits="selected"/>
+ </xul:hbox>
+ <xul:image anonid="type-image" class="ac-type-icon" xbl:inherits="selected"/>
+ </xul:hbox>
+ <xul:hbox align="center" class="ac-url-box">
+ <xul:spacer class="ac-site-icon"/>
+ <xul:image class="ac-action-icon"/>
+ <xul:hbox anonid="url-box" class="ac-url" flex="1"
+ onunderflow="_doUnderflow('_url');"
+ onoverflow="_doOverflow('_url');">
+ <xul:description anonid="url" class="ac-normal-text ac-url-text"
+ xbl:inherits="selected type"/>
+ <xul:description anonid="action" class="ac-normal-text ac-action-text"
+ xbl:inherits="selected type"/>
+ </xul:hbox>
+ <xul:label anonid="url-overflow-ellipsis" xbl:inherits="selected"
+ class="ac-ellipsis-after ac-url-text"/>
+ <xul:spacer class="ac-type-icon"/>
+ </xul:hbox>
+ </content>
+ <implementation implements="nsIDOMXULSelectControlItemElement">
+ <constructor>
+ <![CDATA[
+ let ellipsis = "\u2026";
+ try {
+ ellipsis = Components.classes["@mozilla.org/preferences-service;1"].
+ getService(Components.interfaces.nsIPrefBranch).
+ getComplexValue("intl.ellipsis",
+ Components.interfaces.nsIPrefLocalizedString).data;
+ } catch (ex) {
+ // Do nothing.. we already have a default
+ }
+
+ this._urlOverflowEllipsis = document.getAnonymousElementByAttribute(this, "anonid", "url-overflow-ellipsis");
+ this._titleOverflowEllipsis = document.getAnonymousElementByAttribute(this, "anonid", "title-overflow-ellipsis");
+
+ this._urlOverflowEllipsis.value = ellipsis;
+ this._titleOverflowEllipsis.value = ellipsis;
+
+ this._typeImage = document.getAnonymousElementByAttribute(this, "anonid", "type-image");
+
+ this._urlBox = document.getAnonymousElementByAttribute(this, "anonid", "url-box");
+ this._url = document.getAnonymousElementByAttribute(this, "anonid", "url");
+ this._action = document.getAnonymousElementByAttribute(this, "anonid", "action");
+
+ this._titleBox = document.getAnonymousElementByAttribute(this, "anonid", "title-box");
+ this._title = document.getAnonymousElementByAttribute(this, "anonid", "title");
+
+ this._extraBox = document.getAnonymousElementByAttribute(this, "anonid", "extra-box");
+ this._extra = document.getAnonymousElementByAttribute(this, "anonid", "extra");
+
+ this._adjustAcItem();
+ ]]>
+ </constructor>
+
+ <property name="label" readonly="true">
+ <getter>
+ <![CDATA[
+ // This property is a string that is read aloud by screen readers,
+ // so it must not contain anything that should not be user-facing.
+
+ let parts = [
+ this.getAttribute("title"),
+ this.getAttribute("displayurl"),
+ ];
+ let label = parts.filter(str => str).join(" ")
+
+ // allow consumers that have extended popups to override
+ // the label values for the richlistitems
+ let panel = this.parentNode.parentNode;
+ if (panel.createResultLabel) {
+ return panel.createResultLabel(this, label);
+ }
+
+ return label;
+ ]]>
+ </getter>
+ </property>
+
+ <property name="_stringBundle">
+ <getter><![CDATA[
+ if (!this.__stringBundle) {
+ this.__stringBundle = Services.strings.createBundle("chrome://global/locale/autocomplete.properties");
+ }
+ return this.__stringBundle;
+ ]]></getter>
+ </property>
+
+ <field name="_boundaryCutoff">null</field>
+
+ <property name="boundaryCutoff" readonly="true">
+ <getter>
+ <![CDATA[
+ if (!this._boundaryCutoff) {
+ this._boundaryCutoff =
+ Components.classes["@mozilla.org/preferences-service;1"].
+ getService(Components.interfaces.nsIPrefBranch).
+ getIntPref("toolkit.autocomplete.richBoundaryCutoff");
+ }
+ return this._boundaryCutoff;
+ ]]>
+ </getter>
+ </property>
+
+ <method name="_getBoundaryIndices">
+ <parameter name="aText"/>
+ <parameter name="aSearchTokens"/>
+ <body>
+ <![CDATA[
+ // Short circuit for empty search ([""] == "")
+ if (aSearchTokens == "")
+ return [0, aText.length];
+
+ // Find which regions of text match the search terms
+ let regions = [];
+ for (let search of Array.prototype.slice.call(aSearchTokens)) {
+ let matchIndex = -1;
+ let searchLen = search.length;
+
+ // Find all matches of the search terms, but stop early for perf
+ let lowerText = aText.substr(0, this.boundaryCutoff).toLowerCase();
+ while ((matchIndex = lowerText.indexOf(search, matchIndex + 1)) >= 0) {
+ regions.push([matchIndex, matchIndex + searchLen]);
+ }
+ }
+
+ // Sort the regions by start position then end position
+ regions = regions.sort((a, b) => {
+ let start = a[0] - b[0];
+ return (start == 0) ? a[1] - b[1] : start;
+ });
+
+ // Generate the boundary indices from each region
+ let start = 0;
+ let end = 0;
+ let boundaries = [];
+ let len = regions.length;
+ for (let i = 0; i < len; i++) {
+ // We have a new boundary if the start of the next is past the end
+ let region = regions[i];
+ if (region[0] > end) {
+ // First index is the beginning of match
+ boundaries.push(start);
+ // Second index is the beginning of non-match
+ boundaries.push(end);
+
+ // Track the new region now that we've stored the previous one
+ start = region[0];
+ }
+
+ // Push back the end index for the current or new region
+ end = Math.max(end, region[1]);
+ }
+
+ // Add the last region
+ boundaries.push(start);
+ boundaries.push(end);
+
+ // Put on the end boundary if necessary
+ if (end < aText.length)
+ boundaries.push(aText.length);
+
+ // Skip the first item because it's always 0
+ return boundaries.slice(1);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_getSearchTokens">
+ <parameter name="aSearch"/>
+ <body>
+ <![CDATA[
+ let search = aSearch.toLowerCase();
+ return search.split(/\s+/);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_setUpDescription">
+ <parameter name="aDescriptionElement"/>
+ <parameter name="aText"/>
+ <parameter name="aNoEmphasis"/>
+ <body>
+ <![CDATA[
+ // Get rid of all previous text
+ while (aDescriptionElement.hasChildNodes())
+ aDescriptionElement.removeChild(aDescriptionElement.firstChild);
+
+ // If aNoEmphasis is specified, don't add any emphasis
+ if (aNoEmphasis) {
+ aDescriptionElement.appendChild(document.createTextNode(aText));
+ return;
+ }
+
+ // Get the indices that separate match and non-match text
+ let search = this.getAttribute("text");
+ let tokens = this._getSearchTokens(search);
+ let indices = this._getBoundaryIndices(aText, tokens);
+
+ let next;
+ let start = 0;
+ let len = indices.length;
+ // Even indexed boundaries are matches, so skip the 0th if it's empty
+ for (let i = indices[0] == 0 ? 1 : 0; i < len; i++) {
+ next = indices[i];
+ let text = aText.substr(start, next - start);
+ start = next;
+
+ if (i % 2 == 0) {
+ // Emphasize the text for even indices
+ let span = aDescriptionElement.appendChild(
+ document.createElementNS("http://www.w3.org/1999/xhtml", "span"));
+ span.className = "ac-emphasize-text";
+ span.textContent = text;
+ } else {
+ // Otherwise, it's plain text
+ aDescriptionElement.appendChild(document.createTextNode(text));
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <!--
+ This will generate an array of emphasis pairs for use with
+ _setUpEmphasisedSections(). Each pair is a tuple (array) that
+ represents a block of text - containing the text of that block, and a
+ boolean for whether that block should have an emphasis styling applied
+ to it.
+
+ These pairs are generated by parsing a localised string (aSourceString)
+ with parameters, in the format that is used by
+ nsIStringBundle.formatStringFromName():
+
+ "textA %1$S textB textC %2$S"
+
+ Or:
+
+ "textA %S"
+
+ Where "%1$S", "%2$S", and "%S" are intended to be replaced by provided
+ replacement strings. These are specified an array of tuples
+ (aReplacements), each containing the replacement text and a boolean for
+ whether that text should have an emphasis styling applied. This is used
+ as a 1-based array - ie, "%1$S" is replaced by the item in the first
+ index of aReplacements, "%2$S" by the second, etc. "%S" will always
+ match the first index.
+ -->
+ <method name="_generateEmphasisPairs">
+ <parameter name="aSourceString"/>
+ <parameter name="aReplacements"/>
+ <body>
+ <![CDATA[
+ let pairs = [];
+
+ // Split on %S, %1$S, %2$S, etc. ie:
+ // "textA %S"
+ // becomes ["textA ", "%S"]
+ // "textA %1$S textB textC %2$S"
+ // becomes ["textA ", "%1$S", " textB textC ", "%2$S"]
+ let parts = aSourceString.split(/(%(?:[0-9]+\$)?S)/);
+
+ for (let part of parts) {
+ // The above regex will actually give us an empty string at the
+ // end - we don't want that, as we don't want to later generate an
+ // empty text node for it.
+ if (part.length === 0)
+ continue;
+
+ // Determine if this token is a replacement token or a normal text
+ // token. If it is a replacement token, we want to extract the
+ // numerical number. However, we still want to match on "$S".
+ let match = part.match(/^%(?:([0-9]+)\$)?S$/);
+
+ if (match) {
+ // "%S" doesn't have a numerical number in it, but will always
+ // be assumed to be 1. Furthermore, the input string specifies
+ // these with a 1-based index, but we want a 0-based index.
+ let index = (match[1] || 1) - 1;
+
+ if (index >= 0 && index < aReplacements.length) {
+ pairs.push([...aReplacements[index]]);
+ }
+ } else {
+ pairs.push([part]);
+ }
+ }
+
+ return pairs;
+ ]]>
+ </body>
+ </method>
+
+ <!--
+ _setUpEmphasisedSections() has the same use as _setUpDescription,
+ except instead of taking a string and highlighting given tokens, it takes
+ an array of pairs generated by _generateEmphasisPairs(). This allows
+ control over emphasising based on specific blocks of text, rather than
+ search for substrings.
+ -->
+ <method name="_setUpEmphasisedSections">
+ <parameter name="aDescriptionElement"/>
+ <parameter name="aTextPairs"/>
+ <body>
+ <![CDATA[
+ // Get rid of all previous text
+ while (aDescriptionElement.hasChildNodes())
+ aDescriptionElement.firstChild.remove();
+
+ for (let [text, emphasise] of aTextPairs) {
+ if (emphasise) {
+ let span = aDescriptionElement.appendChild(
+ document.createElementNS("http://www.w3.org/1999/xhtml", "span"));
+ span.textContent = text;
+ switch(emphasise) {
+ case "match":
+ span.className = "ac-emphasize-text";
+ break;
+ case "selected":
+ span.className = "ac-selected-text";
+ break;
+ }
+ } else {
+ aDescriptionElement.appendChild(document.createTextNode(text));
+ }
+ }
+ ]]>
+ </body>
+ </method>
+
+ <field name="_textToSubURI">null</field>
+ <method name="_unescapeUrl">
+ <parameter name="url"/>
+ <body>
+ <![CDATA[
+ if (!this._textToSubURI) {
+ this._textToSubURI =
+ Components.classes["@mozilla.org/intl/texttosuburi;1"]
+ .getService(Components.interfaces.nsITextToSubURI);
+ }
+ return this._textToSubURI.unEscapeURIForUI("UTF-8", url);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_adjustAcItem">
+ <body>
+ <![CDATA[
+ let originalUrl = this.getAttribute("url");
+ let title = this.getAttribute("title");
+ let type = this.getAttribute("type");
+
+ let displayUrl;
+ let emphasiseTitle = true;
+ let emphasiseUrl = true;
+
+ // Hide the title's extra box by default, until we find out later if
+ // we need extra stuff.
+ this._extraBox.hidden = true;
+ this._titleBox.flex = 1;
+ this._typeImage.hidden = false;
+
+ this.removeAttribute("actiontype");
+ this.classList.remove("overridable-action");
+
+ // The ellipses are hidden via their visibility so that they always
+ // take up space and don't pop in on top of text when shown. For
+ // keyword searches, however, the title ellipsis should not take up
+ // space when hidden. Setting the hidden property accomplishes that.
+ this._titleOverflowEllipsis.hidden = false;
+
+ let types = new Set(type.split(/\s+/));
+
+ // Remove types that should ultimately not be in the `type` string.
+ let initialTypes = new Set(types);
+ types.delete("action");
+ types.delete("autofill");
+ types.delete("heuristic");
+ types.delete("search");
+
+ type = [...types][0] || "";
+
+ // If the type includes an action, set up the item appropriately.
+ if (initialTypes.has("action")) {
+ let action = this._parseActionUrl(originalUrl);
+ this.setAttribute("actiontype", action.type);
+
+ if (action.type == "switchtab") {
+ this.classList.add("overridable-action");
+ displayUrl = this._unescapeUrl(action.params.url);
+ let desc = this._stringBundle.GetStringFromName("switchToTab");
+ this._setUpDescription(this._action, desc, true);
+ } else if (action.type == "remotetab") {
+ displayUrl = this._unescapeUrl(action.params.url);
+ let desc = action.params.deviceName;
+ this._setUpDescription(this._action, desc, true);
+ } else if (action.type == "searchengine") {
+ emphasiseUrl = false;
+
+ // The order here is not localizable, we default to appending
+ // "- Search with Engine" to the search string, to be able to
+ // properly generate emphasis pairs. That said, no localization
+ // changed the order while it was possible, so doesn't look like
+ // there's a strong need for that.
+ let {engineName, searchSuggestion, searchQuery} = action.params;
+ let engineStr = " - " +
+ this._stringBundle.formatStringFromName("searchWithEngine",
+ [engineName], 1);
+
+ // Make the title by generating an array of pairs and its
+ // corresponding interpolation string (e.g., "%1$S") to pass to
+ // _generateEmphasisPairs.
+ let pairs;
+ if (searchSuggestion) {
+ // Check if the search query appears in the suggestion. It may
+ // not. If it does, then emphasize the query in the suggestion
+ // and otherwise just include the suggestion without emphasis.
+ let idx = searchSuggestion.indexOf(searchQuery);
+ if (idx >= 0) {
+ pairs = [
+ [searchSuggestion.substring(0, idx), ""],
+ [searchQuery, "match"],
+ [searchSuggestion.substring(idx + searchQuery.length), ""],
+ ];
+ } else {
+ pairs = [
+ [searchSuggestion, ""],
+ ];
+ }
+ } else {
+ pairs = [
+ [searchQuery, ""],
+ ];
+ }
+ pairs.push([engineStr, "selected"]);
+ let interpStr = pairs.map((pair, i) => `%${i + 1}$S`).join("");
+ title = this._generateEmphasisPairs(interpStr, pairs);
+
+ // If this is a default search match, we remove the image so we
+ // can style it ourselves with a generic search icon.
+ // We don't do this when matching an aliased search engine,
+ // because the icon helps with recognising which engine will be
+ // used (when using the default engine, we don't need that
+ // recognition).
+ if (!action.params.alias) {
+ this.removeAttribute("image");
+ }
+ } else if (action.type == "visiturl") {
+ emphasiseUrl = false;
+ displayUrl = this._unescapeUrl(action.params.url);
+ let sourceStr = this._stringBundle.GetStringFromName("visitURL");
+ title = this._generateEmphasisPairs(sourceStr, [
+ [displayUrl, "match"],
+ ]);
+ }
+ }
+
+ // Check if we have a search engine name
+ if (initialTypes.has("search")) {
+ emphasiseUrl = false;
+
+ const TITLE_SEARCH_ENGINE_SEPARATOR = " \u00B7\u2013\u00B7 ";
+
+ let searchEngine = "";
+ [title, searchEngine] = title.split(TITLE_SEARCH_ENGINE_SEPARATOR);
+ displayUrl = this._stringBundle.formatStringFromName("searchWithEngine", [searchEngine], 1);
+ }
+
+ if (!displayUrl) {
+ let input = this.parentNode.parentNode.input;
+ let url = typeof(input.trimValue) == "function" ?
+ input.trimValue(originalUrl) :
+ originalUrl;
+ displayUrl = this._unescapeUrl(url);
+ }
+ this.setAttribute("displayurl", displayUrl);
+
+ // Check if we have an auto-fill URL
+ if (initialTypes.has("autofill")) {
+ emphasiseUrl = false;
+
+ let sourceStr = this._stringBundle.GetStringFromName("visitURL");
+ title = this._generateEmphasisPairs(sourceStr, [
+ [displayUrl, "match"],
+ ]);
+ }
+
+ // If we have a tag match, show the tags and icon
+ if (type == "tag" || type == "bookmark-tag") {
+ // Configure the extra box for tags display
+ this._extraBox.hidden = false;
+ this._extraBox.childNodes[0].hidden = false;
+ this._extraBox.childNodes[1].hidden = true;
+ this._extraBox.pack = "end";
+ this._titleBox.flex = 1;
+
+ // The title is separated from the tags by an endash
+ let tags;
+ [, title, tags] = title.match(/^(.+) \u2013 (.+)$/);
+
+ // Each tag is split by a comma in an undefined order, so sort it
+ let sortedTags = tags.split(",").sort().join(", ");
+
+ // Emphasize the matching text in the tags
+ this._setUpDescription(this._extra, sortedTags);
+
+ // If we're suggesting bookmarks, then treat tagged matches as
+ // bookmarks for the star.
+ if (type == "bookmark-tag") {
+ type = "bookmark";
+ } else {
+ this._typeImage.hidden = true;
+ }
+ // keyword and favicon type results for search engines
+ // have an extra magnifying glass icon after them
+ } else if (type == "keyword" || (initialTypes.has("search") &&
+ initialTypes.has("favicon"))) {
+ // Configure the extra box for keyword display
+ this._extraBox.hidden = false;
+ this._extraBox.childNodes[0].hidden = true;
+ // The second child node is ":" and it should be hidden for non keyword types
+ this._extraBox.childNodes[1].hidden = type == "keyword" ? false : true;
+ this._extraBox.pack = "start";
+ this._titleBox.flex = 0;
+
+ // Hide the ellipsis so it doesn't take up space.
+ this._titleOverflowEllipsis.hidden = true;
+
+ if (type == "keyword") {
+ // Put the parameters next to the title if we have any
+ let search = this.getAttribute("text");
+ let params = "";
+ let paramsIndex = search.indexOf(" ");
+ if (paramsIndex != -1)
+ params = search.substr(paramsIndex + 1);
+
+ // Emphasize the keyword parameters
+ this._setUpDescription(this._extra, params);
+
+ // Don't emphasize keyword searches in the title or url
+ emphasiseUrl = false;
+ emphasiseTitle = false;
+ } else {
+ // Don't show any description for non keyword types.
+ this._setUpDescription(this._extra, "", true);
+ }
+ // If the result has the type favicon and a known search provider,
+ // customize it the same way as a keyword result.
+ type = "keyword";
+ }
+
+ // Give the image the icon style and a special one for the type
+ this._typeImage.className = "ac-type-icon" +
+ (type ? " ac-result-type-" + type : "");
+
+ // Show the domain as the title if we don't have a title.
+ if (title == "") {
+ title = displayUrl;
+ try {
+ let uri = Services.io.newURI(originalUrl, null, null);
+ // Not all valid URLs have a domain.
+ if (uri.host)
+ title = uri.host;
+ } catch (e) {}
+ }
+
+ // Emphasize the matching search terms for the description
+ if (Array.isArray(title))
+ this._setUpEmphasisedSections(this._title, title);
+ else
+ this._setUpDescription(this._title, title, !emphasiseTitle);
+
+ this._setUpDescription(this._url, displayUrl, !emphasiseUrl);
+
+ // Set up overflow on a timeout because the contents of the box
+ // might not have a width yet even though we just changed them
+ setTimeout(this._setUpOverflow, 0, this._titleBox, this._titleOverflowEllipsis);
+ setTimeout(this._setUpOverflow, 0, this._urlBox, this._urlOverflowEllipsis);
+ ]]>
+ </body>
+ </method>
+
+ <method name="_parseActionUrl">
+ <parameter name="aUrl"/>
+ <body><![CDATA[
+ if (!aUrl.startsWith("moz-action:"))
+ return null;
+
+ // URL is in the format moz-action:ACTION,PARAMS
+ // Where PARAMS is a JSON encoded object.
+ let [, type, params] = aUrl.match(/^moz-action:([^,]+),(.*)$/);
+
+ let action = {
+ type: type,
+ };
+
+ try {
+ action.params = JSON.parse(params);
+ for (let key in action.params) {
+ action.params[key] = decodeURIComponent(action.params[key]);
+ }
+ } catch (e) {
+ // If this failed, we assume that params is not a JSON object, and
+ // is instead just a flat string. This will happen when
+ // UnifiedComplete is disabled - in which case, the param is always
+ // a URL.
+ action.params = {
+ url: params,
+ }
+ }
+
+ return action;
+ ]]></body>
+ </method>
+
+ <method name="_setUpOverflow">
+ <parameter name="aParentBox"/>
+ <parameter name="aEllipsis"/>
+ <body>
+ <![CDATA[
+ // Hide the ellipsis incase there's just enough to not underflow
+ aEllipsis.style.visibility = "hidden";
+
+ // Start with the parent's width and subtract off its children
+ let tooltip = [];
+ let children = aParentBox.childNodes;
+ let widthDiff = aParentBox.boxObject.width;
+
+ for (let i = 0; i < children.length; i++) {
+ // Only consider a child if it actually takes up space
+ let childWidth = children[i].boxObject.width;
+ if (childWidth > 0) {
+ // Subtract a little less to account for subpixel rounding
+ widthDiff -= childWidth - .5;
+
+ // Add to the tooltip if it's not hidden and has text
+ let childText = children[i].textContent;
+ if (childText)
+ tooltip.push(childText);
+ }
+ }
+
+ // If the children take up more space than the parent.. overflow!
+ if (widthDiff < 0) {
+ // Re-show the ellipsis now that we know it's needed
+ aEllipsis.style.visibility = "visible";
+
+ // Separate text components with a ndash --
+ aParentBox.tooltipText = tooltip.join(" \u2013 ");
+ }
+ ]]>
+ </body>
+ </method>
+
+ <method name="_doUnderflow">
+ <parameter name="aName"/>
+ <body>
+ <![CDATA[
+ // Hide the ellipsis right when we know we're underflowing instead of
+ // waiting for the timeout to trigger the _setUpOverflow calculations
+ this[aName + "Box"].tooltipText = "";
+ this[aName + "OverflowEllipsis"].style.visibility = "hidden";
+ ]]>
+ </body>
+ </method>
+
+ <method name="_doOverflow">
+ <parameter name="aName"/>
+ <body>
+ <![CDATA[
+ this._setUpOverflow(this[aName + "Box"],
+ this[aName + "OverflowEllipsis"]);
+ ]]>
+ </body>
+ </method>
+
+ </implementation>
+ </binding>
+
+ <binding id="private-autocomplete-tree" extends="chrome://global/content/bindings/tree.xml#tree">
+ <content>
+ <children includes="treecols"/>
+ <xul:treerows class="private-autocomplete-treerows tree-rows" xbl:inherits="hidescrollbar" flex="1">
+ <children/>
+ </xul:treerows>
+ </content>
+ </binding>
+
+ <binding id="private-autocomplete-richlistbox" extends="chrome://global/content/bindings/richlistbox.xml#richlistbox">
+ <implementation>
+ <field name="mLastMoveTime">Date.now()</field>
+ <field name="mouseSelectedIndex">-1</field>
+ </implementation>
+ <handlers>
+ <handler event="mouseup">
+ <![CDATA[
+ // don't call onPopupClick for the scrollbar buttons, thumb, slider, etc.
+ let item = event.originalTarget;
+ while (item && item.localName != "richlistitem") {
+ item = item.parentNode;
+ }
+
+ if (!item)
+ return;
+
+ this.parentNode.onPopupClick(event);
+ ]]>
+ </handler>
+
+ <handler event="mousemove">
+ <![CDATA[
+ if (Date.now() - this.mLastMoveTime > 30) {
+ let item = event.target;
+ while (item && item.localName != "richlistitem") {
+ item = item.parentNode;
+ }
+
+ if (!item)
+ return;
+
+ let index = this.getIndexOfItem(item);
+ if (index != this.selectedIndex) {
+ this.mouseSelectedIndex = this.selectedIndex = index;
+ }
+
+ this.mLastMoveTime = Date.now();
+ }
+ ]]>
+ </handler>
+ </handlers>
+ </binding>
+
+ <binding id="private-autocomplete-treebody">
+ <implementation>
+ <field name="mLastMoveTime">Date.now()</field>
+ </implementation>
+
+ <handlers>
+ <handler event="mouseup" action="this.parentNode.parentNode.onPopupClick(event);"/>
+
+ <handler event="mousedown"><![CDATA[
+ var rc = this.parentNode.treeBoxObject.getRowAt(event.clientX, event.clientY);
+ if (rc != this.parentNode.currentIndex)
+ this.parentNode.view.selection.select(rc);
+ ]]></handler>
+
+ <handler event="mousemove"><![CDATA[
+ if (Date.now() - this.mLastMoveTime > 30) {
+ var rc = this.parentNode.treeBoxObject.getRowAt(event.clientX, event.clientY);
+ if (rc != this.parentNode.currentIndex)
+ this.parentNode.view.selection.select(rc);
+ this.mLastMoveTime = Date.now();
+ }
+ ]]></handler>
+ </handlers>
+ </binding>
+
+ <binding id="private-autocomplete-treerows">
+ <content>
+ <xul:hbox flex="1" class="tree-bodybox">
+ <children/>
+ </xul:hbox>
+ <xul:scrollbar xbl:inherits="collapsed=hidescrollbar" orient="vertical" class="tree-scrollbar"/>
+ </content>
+ </binding>
+
+ <binding id="private-history-dropmarker" extends="chrome://global/content/bindings/general.xml#dropmarker">
+ <handlers>
+ <handler event="mousedown" button="0"><![CDATA[
+ document.getBindingParent(this).toggleHistoryPopup();
+ ]]></handler>
+ </handlers>
+ </binding>
+
+</bindings>
diff --git a/base/content/autorecovery.js b/base/content/autorecovery.js
new file mode 100644
index 0000000..01a092f
--- /dev/null
+++ b/base/content/autorecovery.js
@@ -0,0 +1,60 @@
+/* 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/. */
+
+/* Auto-recovery module.
+ * This module aims to catch fatal browser initialization errors and either
+ * automatically correct likely causes from them, or automatically restarting
+ * the browser in safe mode. This is hooked into the browser's "onload"
+ * event because it can be assumed that at that point, everything must
+ * have been properly initialized already.
+ */
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+// Services = object with smart getters for common XPCOM services
+Cu.import("resource://gre/modules/Services.jsm");
+
+var browser_autoRecovery =
+{
+ onLoad: function() {
+
+ var nsIAS = Ci.nsIAppStartup; // Application startup interface
+
+ if (typeof gBrowser === "undefined") {
+ // gBrowser should always be defined at this point, but if it is not, then most likely
+ // it is due to an incompatible or outdated language pack being installed and selected.
+ // In this case, we reset "general.useragent.locale" to try to recover browser startup.
+ if (Services.prefs.prefHasUserValue("general.useragent.locale")) {
+ // Restart automatically in en-US.
+ Services.prefs.clearUserPref("general.useragent.locale");
+ Cc["@mozilla.org/toolkit/app-startup;1"].getService(nsIAS).quit(nsIAS.eRestart | nsIAS.eAttemptQuit);
+ } else if (!Services.appinfo.inSafeMode) {
+ // gBrowser isn't defined, and we're not using a custom locale. Most likely
+ // a user-installed add-on causes issues here, so we restart in Safe Mode.
+ let RISM = Services.prompt.confirm(null, "Error",
+ "The Browser didn't start properly!\n"+
+ "This is usually caused by an add-on or misconfiguration.\n\n"+
+ "Restart in Safe Mode?");
+ if (RISM) {
+ Cc["@mozilla.org/toolkit/app-startup;1"].getService(nsIAS).restartInSafeMode(nsIAS.eRestart | nsIAS.eAttemptQuit);
+ } else {
+ // Force quit application
+ Cc["@mozilla.org/toolkit/app-startup;1"].getService(nsIAS).quit(nsIAS.eForceQuit);
+ }
+ }
+ // Something else caused this issue and we're already in Safe Mode, so we return
+ // without doing anything else, and let normal error handling take place.
+ return;
+ } // gBrowser undefined
+
+ // Other checks than gBrowser undefined can go here!
+
+ // Remove our listener, since we don't want this to fire on every load.
+ window.removeEventListener("load", browser_autoRecovery.onLoad, false);
+ }
+};
+
+window.addEventListener("load", browser_autoRecovery.onLoad, false);
diff --git a/base/content/autorecovery.xul b/base/content/autorecovery.xul
new file mode 100644
index 0000000..866bdf2
--- /dev/null
+++ b/base/content/autorecovery.xul
@@ -0,0 +1,12 @@
+<?xml version="1.0"?>
+
+<overlay
+ id="autorecovery"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script type="application/x-javascript" src="chrome://browser/content/autorecovery.js"/>
+
+<!-- This is an empty overlay to allow separation of the script into its
+ own context (needed for locale issues preventing browser start) -->
+
+</overlay>
diff --git a/base/content/baseMenuOverlay.xul b/base/content/baseMenuOverlay.xul
new file mode 100644
index 0000000..bccdec2
--- /dev/null
+++ b/base/content/baseMenuOverlay.xul
@@ -0,0 +1,114 @@
+<?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
+ <menuitem id="troubleShooting"
+ accesskey="&helpTroubleshootingInfo.accesskey;"
+ label="&helpTroubleshootingInfo.label;"
+ oncommand="openTroubleshootingPage()"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="helpSafeMode"
+ accesskey="&helpSafeMode.accesskey;"
+ label="&helpSafeMode.label;"
+ oncommand="restart(true);"/>
+ <menuseparator/>
+ <menuitem id="releaseNotes"
+ accesskey="&helpReleaseNotes.accesskey;"
+ label="&helpReleaseNotes.label;"
+ oncommand="openReleaseNotes();"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="feedbackPage"
+ accesskey="&helpFeedbackPage.accesskey;"
+ label="&helpFeedbackPage.label;"
+ oncommand="openFeedbackPage()"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuseparator id="updatesSeparator"/>
+ <menuitem id="checkForUpdates" class="menuitem-iconic"
+#ifdef MOZ_UPDATER
+ label="&updateCmd.label;"
+ oncommand="checkForUpdates();"/>
+#else
+ hidden="true"/>
+#endif
+ <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/base/content/browser-addons.js b/base/content/browser-addons.js
new file mode 100644
index 0000000..630a0cf
--- /dev/null
+++ b/base/content/browser-addons.js
@@ -0,0 +1,537 @@
+# -*- 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/.
+
+// Removes a doorhanger notification if all of the installs it was notifying
+// about have ended in some way.
+function removeNotificationOnEnd(notification, installs) {
+ let count = installs.length;
+
+ function maybeRemove(install) {
+ install.removeListener(this);
+
+ if (--count == 0) {
+ // Check that the notification is still showing
+ let current = PopupNotifications.getNotification(notification.id, notification.browser);
+ if (current === notification)
+ notification.remove();
+ }
+ }
+
+ for (let install of installs) {
+ install.addListener({
+ onDownloadCancelled: maybeRemove,
+ onDownloadFailed: maybeRemove,
+ onInstallFailed: maybeRemove,
+ onInstallEnded: maybeRemove
+ });
+ }
+}
+
+const gXPInstallObserver = {
+ _findChildShell: function (aDocShell, aSoughtShell)
+ {
+ if (aDocShell == aSoughtShell)
+ return aDocShell;
+
+ var node = aDocShell.QueryInterface(Components.interfaces.nsIDocShellTreeItem);
+ 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 browser = installInfo.browser;
+
+ // Make sure the browser is still alive.
+ if (!browser || gBrowser.browsers.indexOf(browser) == -1)
+ 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-origin-blocked": {
+ messageString = gNavigatorBundle.getFormattedString("xpinstallPromptWarningOrigin",
+ [brandShortName]);
+
+ let popup = PopupNotifications.show(browser, notificationID,
+ messageString, anchorID,
+ null, null, options);
+ removeNotificationOnEnd(popup, installInfo.installs);
+ break; }
+ case "addon-install-blocked":
+ let originatingHost;
+ try {
+ originatingHost = installInfo.originatingURI.host;
+ } catch (ex) {
+ // Need to deal with missing originatingURI and with about:/data: URIs more gracefully,
+ // see bug 1063418 - but for now, bail:
+ return;
+ }
+ messageString = gNavigatorBundle.getFormattedString("xpinstallPromptWarning",
+ [brandShortName, originatingHost]);
+
+ action = {
+ label: gNavigatorBundle.getString("xpinstallPromptAllowButton"),
+ accessKey: gNavigatorBundle.getString("xpinstallPromptAllowButton.accesskey"),
+ callback: function() {
+ installInfo.install();
+ }
+ };
+
+ let popup = PopupNotifications.show(browser, notificationID, messageString,
+ anchorID, action, null, options);
+ removeNotificationOnEnd(popup, installInfo.installs);
+ 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.jetsdk)
+ error += "JetSDK";
+ 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.
+ */
+var 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(),
+};
+
+#ifdef MOZ_PERSONAS
+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);
+ }
+}
+
+/*
+ * Listen for Lightweight Theme styling changes and update the browser's theme accordingly.
+ */
+var LightweightThemeListener = {
+ _modifiedStyles: [],
+
+ init: function () {
+ XPCOMUtils.defineLazyGetter(this, "styleSheet", function() {
+ for (let i = document.styleSheets.length - 1; i >= 0; i--) {
+ let sheet = document.styleSheets[i];
+ if (sheet.href == "chrome://browser/skin/browser-lightweightTheme.css")
+ return sheet;
+ }
+ });
+
+ Services.obs.addObserver(this, "lightweight-theme-styling-update", false);
+ Services.obs.addObserver(this, "lightweight-theme-optimized", false);
+ if (document.documentElement.hasAttribute("lwtheme"))
+ this.updateStyleSheet(document.documentElement.style.backgroundImage);
+ },
+
+ uninit: function () {
+ Services.obs.removeObserver(this, "lightweight-theme-styling-update");
+ Services.obs.removeObserver(this, "lightweight-theme-optimized");
+ },
+
+ /**
+ * Append the headerImage to the background-image property of all rulesets in
+ * browser-lightweightTheme.css.
+ *
+ * @param headerImage - a string containing a CSS image for the lightweight theme header.
+ */
+ updateStyleSheet: function(headerImage) {
+ if (!this.styleSheet)
+ return;
+ this.substituteRules(this.styleSheet.cssRules, headerImage);
+ },
+
+ substituteRules: function(ruleList, headerImage, existingStyleRulesModified = 0) {
+ let styleRulesModified = 0;
+ for (let i = 0; i < ruleList.length; i++) {
+ let rule = ruleList[i];
+ if (rule instanceof Ci.nsIDOMCSSGroupingRule) {
+ // Add the number of modified sub-rules to the modified count
+ styleRulesModified += this.substituteRules(rule.cssRules, headerImage, existingStyleRulesModified + styleRulesModified);
+ } else if (rule instanceof Ci.nsIDOMCSSStyleRule) {
+ if (!rule.style.backgroundImage)
+ continue;
+ let modifiedIndex = existingStyleRulesModified + styleRulesModified;
+ if (!this._modifiedStyles[modifiedIndex])
+ this._modifiedStyles[modifiedIndex] = { backgroundImage: rule.style.backgroundImage };
+
+ rule.style.backgroundImage = this._modifiedStyles[modifiedIndex].backgroundImage + ", " + headerImage;
+ styleRulesModified++;
+ } else {
+ Cu.reportError("Unsupported rule encountered");
+ }
+ }
+ return styleRulesModified;
+ },
+
+ // nsIObserver
+ observe: function (aSubject, aTopic, aData) {
+ if ((aTopic != "lightweight-theme-styling-update" && aTopic != "lightweight-theme-optimized") ||
+ !this.styleSheet)
+ return;
+
+ if (aTopic == "lightweight-theme-optimized" && aSubject != window)
+ return;
+
+ let themeData = JSON.parse(aData);
+ if (!themeData)
+ return;
+ this.updateStyleSheet("url(" + themeData.headerURL + ")");
+ },
+};
+#endif
diff --git a/base/content/browser-appmenu.inc b/base/content/browser-appmenu.inc
new file mode 100644
index 0000000..9d202c9
--- /dev/null
+++ b/base/content/browser-appmenu.inc
@@ -0,0 +1,381 @@
+# -*- 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">
+ <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"/>
+ <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_openFile"
+ label="&openFileCmd.label;"
+ command="Browser:OpenFile"
+ key="openFileKb"/>
+ <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"
+ label="&appMenuWebDeveloper.label;">
+ <menupopup id="appmenu_webDeveloper_popup">
+#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();"/>
+ <menuseparator/>
+ <menuitem id="appmenu_pageSource"
+ observes="devtoolsMenuBroadcaster_PageSource"/>
+ <menuitem id="appmenu_javascriptConsole"
+ observes="devtoolsMenuBroadcaster_ErrorConsole"/>
+ </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"/>
+ <menuitem id="appmenu_restart"
+ class="menuitem-iconic"
+ label="&appMenuRestart.label;"
+ command="cmd_restartApplication"/>
+ <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"
+ class="menuitem-iconic"
+ label="&organizeBookmarks.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"
+ class="menuitem-iconic"
+ label="&appMenuUnsorted.label;"
+ oncommand="PlacesCommandHook.showPlacesOrganizer('UnfiledBookmarks');"/>
+ </menupopup>
+ </splitmenu>
+ <splitmenu id="appmenu_history"
+ iconic="true"
+ label="&historyMenu.label;"
+ command="Browser:ShowAllHistory">
+ <menupopup id="appmenu_historyMenupopup"
+ placespopup="true"
+ context="placesContext"
+ 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"
+ class="menuitem-iconic"
+ label="&showAllHistoryCmd2.label;"
+ command="Browser:ShowAllHistory"
+ key="showAllHistoryKb"/>
+ <menuseparator/>
+ <menuitem id="appmenu_sanitizeHistory"
+ class="menuitem-iconic"
+ 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"/>
+ <menuitem id="appmenu_permissions"
+ class="menuitem-iconic"
+ label="&permissions.label;"
+ command="Tools:Permissions"
+ key="key_openPermissions"/>
+#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
+ <splitmenu id="appmenu_customize"
+ label="&preferencesCmd2.label;"
+ oncommand="openPreferences();">
+ <menupopup id="appmenu_customizeMenu"
+ onpopupshowing="onViewToolbarsPopupShowing(event, document.getElementById('appmenu_toggleToolbarsSeparator'));">
+ <menuitem id="appmenu_preferences"
+ label="&preferencesCmd2.label;"
+ 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" onpopupshowing="buildHelpMenu();">
+ <menuitem id="appmenu_openHelp"
+ label="&helpMenu.label;"
+ oncommand="openHelpLink('firefox-help')"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="appmenu_troubleshootingInfo"
+ label="&helpTroubleshootingInfo.label;"
+ oncommand="openTroubleshootingPage()"
+ onclick="checkForMiddleClick(this,event);"/>
+ <menuitem id="appmenu_safeMode"
+ label="&appMenuSafeMode.label;"
+ oncommand="restart(true);"/>
+ <menuseparator/>
+ <menuitem id="appmenu_releaseNotes"
+ accesskey="&helpReleaseNotes.accesskey;"
+ label="&helpReleaseNotes.label;"
+ oncommand="openReleaseNotes();"
+ onclick="checkForMiddleClick(this, event);"/>
+ <menuitem id="appmenu_feedbackPage"
+ label="&helpFeedbackPage.label;"
+ oncommand="openFeedbackPage()"
+ onclick="checkForMiddleClick(this, event);"/>
+#ifdef MOZ_UPDATER
+ <menuseparator/>
+ <menuitem id="appmenu_checkForUpdates"
+ class="menuitem-iconic"
+ label="&updateCmd.label;"
+ oncommand="checkForUpdates();"/>
+#endif
+ <menuseparator/>
+ <menuitem id="appmenu_about"
+ label="&aboutProduct.label;"
+ oncommand="openAboutDialog();"/>
+ </menupopup>
+ </splitmenu>
+ </vbox>
+ </hbox>
+</menupopup>
diff --git a/base/content/browser-charsetmenu.inc b/base/content/browser-charsetmenu.inc
new file mode 100644
index 0000000..628de13
--- /dev/null
+++ b/base/content/browser-charsetmenu.inc
@@ -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/.
+
+#filter substitution
+
+#expand <menu id="__ID_PREFIX__charsetMenu"
+ label="&charsetMenu.label;"
+#ifndef OMIT_ACCESSKEYS
+ accesskey="&charsetMenu.accesskey;"
+#endif
+ oncommand="MultiplexHandler(event)"
+#ifdef OMIT_ACCESSKEYS
+#expand onpopupshowing="CharsetMenu.build(event, '__ID_PREFIX__');"
+#else
+#expand onpopupshowing="CharsetMenu.build(event, '__ID_PREFIX__', true);"
+#endif
+ onpopupshown="UpdateMenus(event);">
+ <menupopup>
+ <menu label="&charsetMenuAutodet.label;"
+#ifndef OMIT_ACCESSKEYS
+ accesskey="&charsetMenuAutodet.accesskey;"
+#endif
+ >
+ <menupopup>
+ <menuitem type="radio"
+ name="detectorGroup"
+#expand id="__ID_PREFIX__chardet.off"
+ label="&charsetMenuAutodet.off.label;"
+#ifndef OMIT_ACCESSKEYS
+ accesskey="&charsetMenuAutodet.off.accesskey;"
+#endif
+ />
+ <menuitem type="radio"
+ name="detectorGroup"
+#expand id="__ID_PREFIX__chardet.ja_parallel_state_machine"
+ label="&charsetMenuAutodet.ja.label;"
+#ifndef OMIT_ACCESSKEYS
+ accesskey="&charsetMenuAutodet.ja.accesskey;"
+#endif
+ />
+ <menuitem type="radio"
+ name="detectorGroup"
+#expand id="__ID_PREFIX__chardet.ruprob"
+ label="&charsetMenuAutodet.ru.label;"
+#ifndef OMIT_ACCESSKEYS
+ accesskey="&charsetMenuAutodet.ru.accesskey;"
+#endif
+ />
+ <menuitem type="radio"
+ name="detectorGroup"
+#expand id="__ID_PREFIX__chardet.ukprob"
+ label="&charsetMenuAutodet.uk.label;"
+#ifndef OMIT_ACCESSKEYS
+ accesskey="&charsetMenuAutodet.uk.accesskey;"
+#endif
+ />
+ </menupopup>
+ </menu>
+ <menuseparator/>
+ </menupopup>
+</menu>
diff --git a/base/content/browser-context.inc b/base/content/browser-context.inc
new file mode 100644
index 0000000..38c4725
--- /dev/null
+++ b/base/content/browser-context.inc
@@ -0,0 +1,384 @@
+# -*- 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-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();"/>
+ <menuitem id="context-openlinkincurrent"
+ label="&openLinkCmdInCurrent.label;"
+ accesskey="&openLinkCmdInCurrent.accesskey;"
+ oncommand="gContextMenu.openLinkInCurrent();"/>
+ <menuseparator id="context-sep-open"/>
+ <menuitem id="context-bookmarklink"
+ label="&bookmarkThisLinkCmd.label;"
+ accesskey="&bookmarkThisLinkCmd.accesskey;"
+ oncommand="gContextMenu.bookmarkLink();"/>
+ <menuitem id="context-savelink"
+ label="&saveLinkCmd.label;"
+ accesskey="&saveLinkCmd.accesskey;"
+ oncommand="gContextMenu.saveLink();"/>
+ <menuitem id="context-sendlink"
+ label="&sendLinkCmd.label;"
+ accesskey="&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="&mediaPlaybackRate2.label;" accesskey="&mediaPlaybackRate2.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-loop"
+ label="&mediaLoop.label;"
+ accesskey="&mediaLoop.accesskey;"
+ type="checkbox"
+ oncommand="gContextMenu.mediaCommand('loop');"/>
+ <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-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-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-savepage"
+ label="&savePageCmd.label;"
+ accesskey="&savePageCmd.accesskey2;"
+ oncommand="gContextMenu.savePageAs();"/>
+ <menuitem id="context-sendpage"
+ label="&sendPageCmd.label;"
+ accesskey="&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(this.searchTerms);"/>
+ <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 id="open-frame-sep"/>
+ <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();"/>
+#ifdef MOZ_DEVTOOLS
+ <menuseparator id="inspect-separator" hidden="true"/>
+ <menuitem id="context-inspect"
+ hidden="true"
+ label="&inspectContextMenu.label;"
+ accesskey="&inspectContextMenu.accesskey;"
+ oncommand="gContextMenu.inspectNode();"/>
+#endif
diff --git a/base/content/browser-devtools-theme.js b/base/content/browser-devtools-theme.js
new file mode 100644
index 0000000..7b21dde
--- /dev/null
+++ b/base/content/browser-devtools-theme.js
@@ -0,0 +1,91 @@
+/* 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/. */
+
+/**
+ * Listeners for the DevTools theme.
+ */
+var DevToolsTheme = {
+ _devtoolsThemePrefName: "devtools.theme",
+ styleSheet: null,
+ initialized: false,
+
+ get isStyleSheetEnabled() {
+ return this.styleSheet && !this.styleSheet.sheet.disabled;
+ },
+
+ init: function () {
+ this.initialized = true;
+ Services.prefs.addObserver(this._devtoolsThemePrefName, this, false);
+ Services.obs.addObserver(this, "lightweight-theme-styling-update", false);
+ Services.obs.addObserver(this, "lightweight-theme-window-updated", false);
+ this._updateDevtoolsThemeAttribute();
+ },
+
+ observe: function (subject, topic, data) {
+ if (topic == "lightweight-theme-styling-update") {
+ let newTheme = JSON.parse(data);
+ this._toggleStyleSheet();
+ }
+
+ if (topic == "nsPref:changed" && data == this._devtoolsThemePrefName) {
+ this._updateDevtoolsThemeAttribute();
+ }
+ },
+
+ _inferBrightness: function() {
+ ToolbarIconColor.inferFromText();
+ // Get an inverted full screen button if the dark theme is applied.
+ if (this.isStyleSheetEnabled &&
+ document.documentElement.getAttribute("devtoolstheme") == "dark") {
+ document.documentElement.setAttribute("brighttitlebarforeground", "true");
+ } else {
+ document.documentElement.removeAttribute("brighttitlebarforeground");
+ }
+ },
+
+ _updateDevtoolsThemeAttribute: function() {
+ // Set an attribute on root element to make it possible
+ // to change colors based on the selected devtools theme.
+ let devtoolsTheme = Services.prefs.getCharPref(this._devtoolsThemePrefName);
+ if (devtoolsTheme != "dark") {
+ devtoolsTheme = "light";
+ }
+ document.documentElement.setAttribute("devtoolstheme", devtoolsTheme);
+ this._inferBrightness();
+ },
+
+ handleEvent: function(e) {
+ if (e.type === "load") {
+ this.styleSheet.removeEventListener("load", this);
+ this.refreshBrowserDisplay();
+ }
+ },
+
+ refreshBrowserDisplay: function() {
+ // Don't touch things on the browser if gBrowserInit.onLoad hasn't
+ // yet fired.
+ if (this.initialized) {
+ gBrowser.tabContainer._positionPinnedTabs();
+ this._inferBrightness();
+ }
+ },
+
+ _toggleStyleSheet: function() {
+ let wasEnabled = this.isStyleSheetEnabled;
+ if (wasEnabled) {
+ this.styleSheet.sheet.disabled = true;
+ this.refreshBrowserDisplay();
+ }
+ },
+
+ uninit: function () {
+ Services.prefs.removeObserver(this._devtoolsThemePrefName, this);
+ Services.obs.removeObserver(this, "lightweight-theme-styling-update", false);
+ Services.obs.removeObserver(this, "lightweight-theme-window-updated", false);
+ if (this.styleSheet) {
+ this.styleSheet.removeEventListener("load", this);
+ }
+ this.styleSheet = null;
+ }
+};
diff --git a/base/content/browser-doctype.inc b/base/content/browser-doctype.inc
new file mode 100644
index 0000000..6ee6384
--- /dev/null
+++ b/base/content/browser-doctype.inc
@@ -0,0 +1,19 @@
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd" >
+%browserDTD;
+<!ENTITY % baseMenuDTD SYSTEM "chrome://browser/locale/baseMenuOverlay.dtd" >
+%baseMenuDTD;
+<!ENTITY % charsetDTD SYSTEM "chrome://browser/locale/charsetMenu.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;
+<!ENTITY % aboutHomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd">
+%aboutHomeDTD;
+]>
+
diff --git a/base/content/browser-feeds.js b/base/content/browser-feeds.js
new file mode 100644
index 0000000..c678772
--- /dev/null
+++ b/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/base/content/browser-fullScreen.js b/base/content/browser-fullScreen.js
new file mode 100644
index 0000000..e816ce5
--- /dev/null
+++ b/base/content/browser-fullScreen.js
@@ -0,0 +1,462 @@
+# -*- 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",
+
+ toggle: function () {
+ var enterFS = window.fullScreen;
+
+ // 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
+
+ if (!this._fullScrToggler) {
+ this._fullScrToggler = document.getElementById("fullscr-toggler");
+ this._fullScrToggler.addEventListener("mouseover", this._expandCallback, false);
+ this._fullScrToggler.addEventListener("dragenter", this._expandCallback, false);
+ }
+
+ // 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) {
+ document.addEventListener("keypress", this._keyToggleCallback, false);
+ document.addEventListener("popupshown", this._setPopupOpen, false);
+ document.addEventListener("popuphidden", this._setPopupOpen, false);
+ this._shouldAnimate = true;
+ if (gPrefService.getBoolPref("browser.fullscreen.autohide")) {
+ gBrowser.mPanelContainer.addEventListener("mousemove",
+ this._collapseCallback, 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.hideNavToolbox(document.mozFullScreen);
+ }
+ else {
+ this.showNavToolbox(false);
+ // This is needed if they use the context menu to quit fullscreen
+ this._isPopupOpen = false;
+
+ document.documentElement.removeAttribute("inDOMFullscreen");
+
+ 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 "MozDOMFullscreen:NewOrigin" 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;
+ }
+
+ document.documentElement.setAttribute("inDOMFullscreen", true);
+
+ 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.hideNavToolbox(true);
+ },
+
+ 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);
+
+ 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;
+ }
+ },
+
+ // Event callbacks
+ _expandCallback: function()
+ {
+ FullScreen.showNavToolbox();
+ },
+ _collapseCallback: function()
+ {
+ FullScreen.hideNavToolbox();
+ },
+ _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.hideNavToolbox(true);
+ }
+ // F6 is another shortcut to the address bar, but its not covered in OpenLocation()
+ else if (aEvent.keyCode == aEvent.DOM_VK_F6)
+ FullScreen.showNavToolbox();
+ },
+
+ // 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,
+
+ 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 = null;
+ },
+
+ warningBox: null,
+ warningFadeOutTimeout: null,
+ fullscreenDoc: null,
+
+ // Shows a warning that the site has entered fullscreen for a short duration.
+ showWarning: function(targetDoc) {
+ let timeout = gPrefService.getIntPref("full-screen-api.warning.timeout");
+ if (!document.mozFullScreen || timeout <= 0)
+ return;
+
+ // Set the strings on the fullscreen warning 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");
+ if (host) {
+ // Document's principal's URI has a host. Display a warning including the hostname.
+ 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");
+ } else {
+ hostLabel.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");
+ }
+
+ // Set a timeout to fade the warning out after a few moments.
+ this.warningFadeOutTimeout = setTimeout(() => {
+ if (this.warningBox) {
+ this.warningBox.setAttribute("fade-warning-out", "true");
+ }
+ }, timeout);
+ },
+
+ showNavToolbox: function(trackMouse = true) {
+ this._fullScrToggler.hidden = true;
+ gNavToolbox.removeAttribute("fullscreenShouldAnimate");
+ gNavToolbox.style.marginTop = "";
+
+ if (!this._isChromeCollapsed) {
+ return;
+ }
+
+ // Track whether mouse is near the toolbox
+ this._isChromeCollapsed = false;
+ if (trackMouse) {
+ gBrowser.mPanelContainer.addEventListener("mousemove",
+ this._collapseCallback, false);
+ }
+ },
+
+ hideNavToolbox: function(forceHide = false) {
+ this._fullScrToggler.hidden = document.mozFullScreen;
+ if (this._isChromeCollapsed) {
+ if (forceHide) {
+ gNavToolbox.removeAttribute("fullscreenShouldAnimate");
+ }
+ return;
+ }
+ if (!this._safeToCollapse(forceHide)) {
+ this._fullScrToggler.hidden = true;
+ 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
+ let animateUp = gPrefService.getIntPref("browser.fullscreen.animateUp");
+ if (animateUp == 0) {
+ this._shouldAnimate = false;
+ } else if (animateUp == 2) {
+ this._shouldAnimate = true;
+ }
+ if (this._shouldAnimate && !forceHide) {
+ gNavToolbox.setAttribute("fullscreenShouldAnimate", true);
+ this._shouldAnimate = false;
+ // Hide the fullscreen toggler until the transition ends.
+ let listener = () => {
+ gNavToolbox.removeEventListener("transitionend", listener, true);
+ if (this._isChromeCollapsed)
+ this._fullScrToggler.hidden = false;
+ };
+ gNavToolbox.addEventListener("transitionend", listener, true);
+ this._fullScrToggler.hidden = true;
+ }
+
+ gNavToolbox.style.marginTop =
+ -gNavToolbox.getBoundingClientRect().height + "px";
+ this._isChromeCollapsed = true;
+ gBrowser.mPanelContainer.removeEventListener("mousemove",
+ this._collapseCallback, false);
+ },
+
+ 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;
+
+ ToolbarIconColor.inferFromText();
+ }
+};
+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/base/content/browser-fullZoom.js b/base/content/browser-fullZoom.js
new file mode 100644
index 0000000..890cd84
--- /dev/null
+++ b/base/content/browser-fullZoom.js
@@ -0,0 +1,526 @@
+/* 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/. */
+
+/**
+ * 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 the browser to monotonically increasing integer
+ // tokens. _browserTokenMap[browser] is increased each time the zoom is
+ // changed in the browser. See _getBrowserToken and _ignorePendingZoomAccesses.
+ _browserTokenMap: new WeakMap(),
+
+ // Stores initial locations if we receive onLocationChange
+ // events before we're initialized.
+ _initialLocations: new WeakMap(),
+
+ 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() {
+ gBrowser.addEventListener("ZoomChangeUsingMouseWheel", this);
+
+ // 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);
+
+ // If we received onLocationChange events for any of the current browsers
+ // before we were initialized we want to replay those upon initialization.
+ for (let browser of gBrowser.browsers) {
+ if (this._initialLocations.has(browser)) {
+ this.onLocationChange(...this._initialLocations.get(browser), browser);
+ }
+ }
+
+ // This should be nulled after initialization.
+ this._initialLocations = null;
+ },
+
+ destroy: function FullZoom_destroy() {
+ gPrefService.removeObserver("browser.zoom.", this);
+ this._cps2.removeObserverForName(this.name, this);
+ gBrowser.removeEventListener("ZoomChangeUsingMouseWheel", this);
+ },
+
+
+ // Event Handlers
+
+ // nsIDOMEventListener
+
+ handleEvent: function FullZoom_handleEvent(event) {
+ switch (event.type) {
+ case "ZoomChangeUsingMouseWheel":
+ let browser = this._getTargetedBrowser(event);
+ this._ignorePendingZoomAccesses(browser);
+ this._applyZoomToPref(browser);
+ break;
+ }
+ },
+
+ // 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;
+ }
+ },
+
+ // nsIContentPrefObserver
+
+ onContentPrefSet: function FullZoom_onContentPrefSet(aGroup, aName, aValue, aIsPrivate) {
+ this._onContentPrefChanged(aGroup, aValue, aIsPrivate);
+ },
+
+ onContentPrefRemoved: function FullZoom_onContentPrefRemoved(aGroup, aName, aIsPrivate) {
+ this._onContentPrefChanged(aGroup, undefined, aIsPrivate);
+ },
+
+ /**
+ * 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, aIsPrivate) {
+ 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 ctxt = this._loadContextFromBrowser(browser);
+ let domain = this._cps2.extractDomain(browser.currentURI.spec);
+ if (aGroup) {
+ if (aGroup == domain && ctxt.usePrivateBrowsing == aIsPrivate)
+ 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 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) {
+ let browser = aBrowser || gBrowser.selectedBrowser;
+
+ // If we haven't been initialized yet but receive an onLocationChange
+ // notification then let's store and replay it upon initialization.
+ if (this._initialLocations) {
+ this._initialLocations.set(browser, [aURI, aIsTabSwitch]);
+ return;
+ }
+
+ // 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.
+ this._ignorePendingZoomAccesses(browser);
+
+ if (!aURI || (aIsTabSwitch && !this.siteSpecific)) {
+ this._notifyOnLocationChange(browser);
+ return;
+ }
+
+ // Avoid the cps roundtrip and apply the default/global pref.
+ if (aURI.spec == "about:blank") {
+ this._applyPrefToZoom(undefined, browser,
+ this._notifyOnLocationChange.bind(this, browser));
+ return;
+ }
+
+ // Media documents should always start at 1, and are not affected by prefs.
+ if (!aIsTabSwitch && browser.isSyntheticDocument) {
+ ZoomManager.setZoomForBrowser(browser, 1);
+ // _ignorePendingZoomAccesses already called above, so no need here.
+ this._notifyOnLocationChange(browser);
+ return;
+ }
+
+ // See if the zoom pref is cached.
+ let ctxt = this._loadContextFromBrowser(browser);
+ let pref = this._cps2.getCachedByDomainAndName(aURI.spec, this.name, ctxt);
+ if (pref) {
+ this._applyPrefToZoom(pref.value, browser,
+ this._notifyOnLocationChange.bind(this, browser));
+ 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(browser);
+ return;
+ }
+ this._applyPrefToZoom(value, browser,
+ this._notifyOnLocationChange.bind(this, browser));
+ }.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 for the given browser to the given floating
+ * point value, where 1 is the default zoom level.
+ */
+ setZoom: function (value, browser = gBrowser.selectedBrowser) {
+ ZoomManager.setZoomForBrowser(browser, value);
+ this._ignorePendingZoomAccesses(browser);
+ this._applyZoomToPref(browser);
+ },
+
+ /**
+ * Sets the zoom level of the page in the given browser to the global zoom
+ * level.
+ *
+ * @return A promise which resolves when the zoom reset has been applied.
+ */
+ reset: function FullZoom_reset(browser = gBrowser.selectedBrowser) {
+ let token = this._getBrowserToken(browser);
+ let result = this._getGlobalValue(browser).then(value => {
+ if (token.isCurrent) {
+ ZoomManager.setZoomForBrowser(browser, value === undefined ? 1 : value);
+ this._ignorePendingZoomAccesses(browser);
+ Services.obs.notifyObservers(browser, "browser-fullZoom:zoomReset", "");
+ }
+ });
+ this._removePref(browser);
+ return result;
+ },
+
+ /**
+ * 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;
+ }
+
+ // The browser is sometimes half-destroyed 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.parentNode || aBrowser.isSyntheticDocument) {
+ 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).then(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) {
+ Services.obs.notifyObservers(browser, "browser-fullZoom:zoomChange", "");
+ if (!this.siteSpecific ||
+ gInPrintPreviewMode ||
+ browser.isSyntheticDocument)
+ return;
+
+ this._cps2.set(browser.currentURI.spec, this.name,
+ ZoomManager.getZoomForBrowser(browser),
+ this._loadContextFromBrowser(browser), {
+ 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) {
+ Services.obs.notifyObservers(browser, "browser-fullZoom:zoomReset", "");
+ if (browser.isSyntheticDocument)
+ return;
+ let ctxt = this._loadContextFromBrowser(browser);
+ 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 map = this._browserTokenMap;
+ if (!map.has(browser))
+ map.set(browser, 0);
+ return {
+ token: map.get(browser),
+ 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(browser) === this.token && browser.parentNode;
+ },
+ };
+ },
+
+ /**
+ * Returns the browser that the supplied zoom event is associated with.
+ * @param event The ZoomChangeUsingMouseWheel event.
+ * @return The associated browser element, if one exists, otherwise null.
+ */
+ _getTargetedBrowser: function FullZoom__getTargetedBrowser(event) {
+ let target = event.originalTarget;
+
+ // With remote content browsers, the event's target is the browser
+ // we're looking for.
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ if (target instanceof window.XULElement &&
+ target.localName == "browser" &&
+ target.namespaceURI == XUL_NS)
+ return target;
+
+ // With in-process content browsers, the event's target is the content
+ // document.
+ if (target.nodeType == Node.DOCUMENT_NODE)
+ return gBrowser.getBrowserForDocument(target);
+
+ throw new Error("Unexpected ZoomChangeUsingMouseWheel event source");
+ },
+
+ /**
+ * 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 map = this._browserTokenMap;
+ map.set(browser, (map.get(browser) || 0) + 1);
+ },
+
+ _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.
+ *
+ * @param browser The browser pertaining to the zoom.
+ * @returns Promise<prefValue>
+ * Resolves to the preference value when done.
+ */
+ _getGlobalValue: function FullZoom__getGlobalValue(browser) {
+ // * !("_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.
+ return new Promise(resolve => {
+ if ("_globalValue" in this) {
+ resolve(this._globalValue);
+ return;
+ }
+ let value = undefined;
+ this._cps2.getGlobal(this.name, this._loadContextFromBrowser(browser), {
+ handleResult: function (pref) { value = pref.value; },
+ handleCompletion: (reason) => {
+ this._globalValue = this._ensureValid(value);
+ resolve(this._globalValue);
+ }
+ });
+ });
+ },
+
+ /**
+ * Gets the load context from the given Browser.
+ *
+ * @param Browser The Browser whose load context will be returned.
+ * @return The nsILoadContext of the given Browser.
+ */
+ _loadContextFromBrowser: function FullZoom__loadContextFromBrowser(browser) {
+ return browser.loadContext;
+ },
+
+ /**
+ * Asynchronously broadcasts "browser-fullZoom:location-change" so that
+ * listeners can 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(browser) {
+ this._executeSoon(function () {
+ Services.obs.notifyObservers(browser, "browser-fullZoom:location-change", "");
+ });
+ },
+
+ _executeSoon: function FullZoom__executeSoon(callback) {
+ if (!callback)
+ return;
+ Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
+ },
+};
diff --git a/base/content/browser-gestureSupport.js b/base/content/browser-gestureSupport.js
new file mode 100644
index 0000000..13eb71b
--- /dev/null
+++ b/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.
+
+var 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)
+var 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/base/content/browser-menubar.inc b/base/content/browser-menubar.inc
new file mode 100644
index 0000000..fc6bc76
--- /dev/null
+++ b/base/content/browser-menubar.inc
@@ -0,0 +1,564 @@
+# -*- 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_restart"
+ label="&restartCmd.label;"
+ accesskey="&restartCmd.accesskey;"
+ command="cmd_restartApplication"/>
+ <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"/>
+ </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"
+ label="&bookmarksButton.label;"/>
+ <menuitem id="menu_historySidebar"
+ key="key_gotoHistory"
+ observes="viewHistorySidebar"
+ label="&historyButton.label;"/>
+ </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
+ context="placesContext"
+ 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
+ class="menuitem-iconic"
+ key="showAllHistoryKb"
+#endif
+ command="Browser:ShowAllHistory"/>
+ <menuitem id="sanitizeItem"
+#ifndef XP_MACOSX
+ class="menuitem-iconic"
+#endif
+ 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"
+#ifndef XP_MACOSX
+ class="menuitem-iconic"
+#endif
+ label="&organizeBookmarks.label;"
+ command="Browser:ShowAllBookmarks"
+ key="manBookmarkKb"/>
+ <menuseparator id="organizeBookmarksSeparator"/>
+ <menuitem id="menu_bookmarkThisPage"
+#ifndef XP_MACOSX
+ class="menuitem-iconic"
+#endif
+ 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"
+#ifndef XP_MACOSX
+ class="menuitem-iconic"
+#endif
+ 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"/>
+ <menuitem id="menu_openPermissions"
+ label="&permissions.label;"
+ command="Tools:Permissions"
+ accesskey="&permissions.accesskey;"/>
+#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_pageSource"
+ observes="devtoolsMenuBroadcaster_PageSource"
+ accesskey="&pageSourceCmd.accesskey;"/>
+ <menuitem id="javascriptConsole"
+ observes="devtoolsMenuBroadcaster_ErrorConsole"
+ accesskey="&errorConsoleCmd.accesskey;"/>
+ </menupopup>
+ </menu>
+ <menuitem id="menu_pageInfo"
+ accesskey="&pageInfoCmd.accesskey;"
+ label="&pageInfoCmd.label;"
+#ifndef XP_WIN
+ key="key_viewInfo"
+#endif
+ command="View:PageInfo"/>
+ <menuseparator id="prefSep"/>
+ <menuitem id="menu_preferences"
+ label="&preferencesCmd2.label;"
+ accesskey="&preferencesCmd2.accesskey;"
+ oncommand="openPreferences();"/>
+ </menupopup>
+ </menu>
+
+#ifdef XP_MACOSX
+ <menu id="windowMenu" />
+#endif
+ <menu id="helpMenu" />
+ </menubar>
diff --git a/base/content/browser-menudragging.js b/base/content/browser-menudragging.js
new file mode 100644
index 0000000..f3f00d7
--- /dev/null
+++ b/base/content/browser-menudragging.js
@@ -0,0 +1,340 @@
+// 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/.
+//
+// Based on original code by alice0775 https://github.com/alice0775
+
+
+"use strict";
+var browserMenuDragging = {
+ //-- config --
+ STAY_OPEN_ONDRAGEXIT: false,
+ DEBUG: false,
+ //-- config --
+
+ menupopup: ['bookmarksMenuPopup',
+ 'PlacesToolbar',
+ 'BMB_bookmarksPopup',
+ 'appmenu_bookmarksPopup',
+ 'BookmarksMenuToolButtonPopup',
+ 'UnsortedBookmarksFolderToolButtonPopup',
+ 'bookmarksMenuPopup-context'],
+ timer:[],
+ count:[],
+
+
+ init: function(){
+ window.removeEventListener('load', this, false);
+ window.addEventListener('unload', this, false);
+ this.addPrefListener(this.PrefListener);
+
+ window.addEventListener('aftercustomization', this, false);
+
+ this.initPref();
+ this.delayedStartup();
+ },
+
+ uninit: function(){
+ window.removeEventListener('unload', this, false);
+ this.removePrefListener(this.PrefListener);
+
+ window.removeEventListener('aftercustomization', this, false);
+
+ for (var i = 0; i < this.menupopup.length; i++){
+ var menupopup = document.getElementById(this.menupopup[i]);
+ if (menupopup){
+ menupopup.removeEventListener('popupshowing', this, false);
+ menupopup.removeEventListener('popuphiding', this, false);
+ }
+ }
+
+ },
+
+ initPref: function(){
+ this.STAY_OPEN_ONDRAGEXIT =
+ Services.prefs.getBoolPref('browser.menu.dragging.stayOpen', false);
+ this.DEBUG =
+ Services.prefs.getBoolPref('browser.menu.dragging.debug', false);
+ },
+
+ //delayed startup
+ delayedStartup: function(){
+ //wait until construction of bookmarksBarContent is completed.
+ for (var i = 0; i < this.menupopup.length; i++){
+ this.count[i] = 0;
+ this.timer[i] = setInterval(function(self, i){
+ if(++self.count[i] > 50 || document.getElementById(self.menupopup[i])){
+ clearInterval(self.timer[i]);
+ var menupopup = document.getElementById(self.menupopup[i]);
+ if (menupopup) {
+ menupopup.addEventListener('popupshowing', self, false);
+ menupopup.addEventListener('popuphiding', self, false);
+ }
+ }
+ }, 250, this, i);
+ }
+ },
+
+ handleEvent: function(event){
+ switch (event.type) {
+ case 'popupshowing':
+ this.popupshowing(event);
+ break;
+ case 'popuphiding':
+ this.popuphiding(event);
+ break;
+ case 'aftercustomization':
+ setTimeout(function(self){self.delayedStartup(self);}, 0, this);
+ break;
+ case 'load':
+ this.init();
+ break;
+ case 'unload':
+ this.uninit();
+ break;
+ }
+ },
+
+ popuphiding: function(event) {
+ var menupopup = event.originalTarget;
+ menupopup.parentNode.parentNode.openNode = null;
+
+ if (menupopup.parentNode.localName == 'toolbarbutton') {
+ // Fix for Bug 225434 - dragging bookmark from personal toolbar and releasing
+ // (on same bookmark or elsewhere) or clicking on bookmark menu then cancelling
+ // leaves button depressed/sunken when hovered
+ menupopup.parentNode.parentNode._openedMenuButton = null;
+
+ if (!PlacesControllerDragHelper.getSession())
+ // Clear the dragover attribute if present, if we are dragging into a
+ // folder in the hierachy of current opened popup we don't clear
+ // this attribute on clearOverFolder. See Notify for closeTimer.
+ if (menupopup.parentNode.hasAttribute('dragover'))
+ menupopup.parentNode.removeAttribute('dragover');
+ }
+ },
+
+ popupshowing: function(event) {
+ var menupopup = event.originalTarget;
+ browserMenuDragging.debug("popupshowing ===============\n" + menupopup.parentNode.getAttribute('label'));
+
+ var parentPopup = menupopup.parentNode.parentNode;
+
+ if (!!parentPopup.openNode){
+ try {
+ parentPopup.openNode.hidePopup();
+ } catch(e){}
+ }
+ parentPopup.openNode = menupopup;
+
+ menupopup.onDragStart = function (event) {
+ // Bug 555474 - While bookmark is dragged, the tooltip should not appear
+ browserMenuDragging.hideTooltip();
+ }
+
+ menupopup.onDragOver = function (event) {
+ // Bug 555474 - While bookmark is dragged, the tooltip should not appear
+ browserMenuDragging.hideTooltip();
+
+ var target = event.originalTarget;
+ while (target) {
+ if (/menupopup/.test(target.localName))
+ break;
+ target = target.parentNode;
+ }
+ if (this != target)
+ return;
+ event.stopPropagation();
+ browserMenuDragging.debug("onDragOver " + "\n" + this.parentNode.getAttribute('label'));
+
+ PlacesControllerDragHelper.currentDropTarget = event.target;
+ let dt = event.dataTransfer;
+
+ let dropPoint = this._getDropPoint(event);
+
+ if (!dropPoint || !dropPoint.ip ||
+ !PlacesControllerDragHelper.canDrop(dropPoint.ip, dt)) {
+ this._indicatorBar.hidden = true;
+ event.stopPropagation();
+ return;
+ }
+
+ // Mark this popup as being dragged over.
+ this.setAttribute('dragover', 'true');
+
+ if (dropPoint.folderElt) {
+ // We are dragging over a folder.
+ // _overFolder should take the care of opening it on a timer.
+ if (this._overFolder.elt &&
+ this._overFolder.elt != dropPoint.folderElt) {
+ }
+ if (!this._overFolder.elt) {
+ this._overFolder.elt = dropPoint.folderElt;
+ // Create the timer to open this folder.
+ this._overFolder.openTimer = this._overFolder
+ .setTimer(this._overFolder.hoverTime);
+ }
+ }
+ else {
+ // We are not dragging over a folder.
+ }
+
+ // Autoscroll the popup strip if we drag over the scroll buttons.
+ let anonid = event.originalTarget.getAttribute('anonid');
+ let scrollDir = anonid == 'scrollbutton-up' ? -1 :
+ anonid == 'scrollbutton-down' ? 1 : 0;
+ if (scrollDir != 0) {
+ this._scrollBox.scrollByIndex(scrollDir, false);
+ }
+
+ // Check if we should hide the drop indicator for this target.
+ if (dropPoint.folderElt || this._hideDropIndicator(event)) {
+ this._indicatorBar.hidden = true;
+ event.preventDefault();
+ event.stopPropagation();
+ return;
+ }
+
+ // We should display the drop indicator relative to the arrowscrollbox.
+ let sbo = this._scrollBox.scrollBoxObject;
+ let newMarginTop = 0;
+ if (scrollDir == 0) {
+ let elt = this.firstChild;
+ while (elt && event.screenY > elt.boxObject.screenY +
+ elt.boxObject.height / 2)
+ elt = elt.nextSibling;
+ newMarginTop = elt ? elt.boxObject.screenY - sbo.screenY :
+ sbo.height;
+ }
+ else if (scrollDir == 1)
+ newMarginTop = sbo.height;
+
+ // Set the new marginTop based on arrowscrollbox.
+ newMarginTop += sbo.y - this._scrollBox.boxObject.y;
+ this._indicatorBar.firstChild.style.marginTop = newMarginTop + 'px';
+ this._indicatorBar.hidden = false;
+
+ event.preventDefault();
+ event.stopPropagation();
+ }
+
+ menupopup.onDragExit = function (event) {
+ var target = event.originalTarget;
+ while (target) {
+ if (/menupopup/.test(target.localName))
+ break;
+ target = target.parentNode;
+ }
+ if (this != target)
+ return;
+ event.stopPropagation();
+ browserMenuDragging.debug("onDragExit " + browserMenuDragging.STAY_OPEN_ONDRAGEXIT);
+
+ PlacesControllerDragHelper.currentDropTarget = null;
+ this.removeAttribute('dragover');
+
+ // If we have not moved to a valid new target clear the drop indicator
+ // this happens when moving out of the popup.
+ target = event.relatedTarget;
+ if (!target)
+ this._indicatorBar.hidden = true;
+
+ // Close any folder being hovered over
+ if (this._overFolder.elt) {
+ this._overFolder.closeTimer = this._overFolder
+ .setTimer(this._overFolder.hoverTime);
+ }
+
+ // The auto-opened attribute is set when this folder was automatically
+ // opened after the user dragged over it. If this attribute is set,
+ // auto-close the folder on drag exit.
+ // We should also try to close this popup if the drag has started
+ // from here, the timer will check if we are dragging over a child.
+ if (this.hasAttribute('autoopened') ||
+ !browserMenuDragging.STAY_OPEN_ONDRAGEXIT &&
+ this.hasAttribute('dragstart')) {
+ this._overFolder.closeMenuTimer = this._overFolder
+ .setTimer(this._overFolder.hoverTime);
+ }
+
+ event.stopPropagation();
+ }
+
+ menupopup.addEventListener('dragstart', menupopup.onDragStart, true);
+ menupopup.addEventListener('dragover', menupopup.onDragOver, true);
+ menupopup.addEventListener('dragleave', menupopup.onDragExit, true);
+ },
+
+ hideTooltip: function() {
+ ['bhTooltip', 'btTooltip2'].forEach(function(id) {
+ var tooltip = document.getElementById(id);
+ if (tooltip)
+ tooltip.hidePopup();
+ });
+ },
+
+ get getVer(){
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+ var info = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo);
+ var ver = parseInt(info.version.substr(0,3) * 10,10) / 10;
+ return ver;
+ },
+
+ debug: function(aMsg){
+ if (!browserMenuDragging.DEBUG)
+ return;
+ Components.classes["@mozilla.org/consoleservice;1"]
+ .getService(Components.interfaces.nsIConsoleService)
+ .logStringMessage(aMsg);
+ },
+
+ setPref: function(aPrefString, aPrefType, aValue){
+ var xpPref = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefService);
+ try{
+ switch (aPrefType){
+ case 'complex':
+ return xpPref.setComplexValue(aPrefString, Components.interfaces.nsILocalFile, aValue); break;
+ case 'str':
+ return xpPref.setCharPref(aPrefString, aValue); break;
+ case 'int':
+ aValue = parseInt(aValue);
+ return xpPref.setIntPref(aPrefString, aValue); break;
+ case 'bool':
+ default:
+ return xpPref.setBoolPref(aPrefString, aValue); break;
+ }
+ }catch(e){
+ }
+ return null;
+ },
+
+ addPrefListener: function(aObserver) {
+ try {
+ var pbi = Components.classes["@mozilla.org/preferences;1"].
+ getService(Components.interfaces.nsIPrefBranch2);
+ pbi.addObserver(aObserver.domain, aObserver, false);
+ } catch(e) {}
+ },
+
+ removePrefListener: function(aObserver) {
+ try {
+ var pbi = Components.classes["@mozilla.org/preferences;1"].
+ getService(Components.interfaces.nsIPrefBranch2);
+ pbi.removeObserver(aObserver.domain, aObserver);
+ } catch(e) {}
+ },
+
+ PrefListener:{
+ domain : 'browser.menu.dragging.stayOpen',
+
+ observe : function(aSubject, aTopic, aPrefstring) {
+ if (aTopic == 'nsPref:changed') {
+ browserMenuDragging.initPref();
+ }
+ }
+ }
+}
+
+window.addEventListener('load', browserMenuDragging, false); \ No newline at end of file
diff --git a/base/content/browser-menudragging.xul b/base/content/browser-menudragging.xul
new file mode 100644
index 0000000..f5cabe5
--- /dev/null
+++ b/base/content/browser-menudragging.xul
@@ -0,0 +1,13 @@
+<?xml version="1.0"?>
+
+
+<overlay id="menuDraggingOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<window id="main-window">
+
+ <script type="application/x-javascript" src="chrome://browser/content/browser-menudragging.js" />
+
+</window>
+
+</overlay>
diff --git a/base/content/browser-places.js b/base/content/browser-places.js
new file mode 100644
index 0000000..590fe7e
--- /dev/null
+++ b/base/content/browser-places.js
@@ -0,0 +1,1316 @@
+# 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();
+
+ let onPanelReady = fn => {
+ let target = this.panel;
+ if (target.parentNode) {
+ // By targeting the panel's parent and using a capturing listener, we
+ // can have our listener called before others waiting for the panel to
+ // be shown (which probably expect the panel to be fully initialized)
+ target = target.parentNode;
+ }
+ target.addEventListener("popupshown", function(event) {
+ fn();
+ }, {"capture": true, "once": true});
+ };
+ gEditItemOverlay.initPanel(this._itemId,
+ { onPanelReady,
+ hiddenRows: ["description", "location",
+ "loadInSidebar", "keyword"] });
+
+ this.panel.openPopup(aAnchorElement, aPosition);
+ },
+
+ 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) {
+ let toolbarIP = new InsertionPoint(PlacesUtils.toolbarFolderId, -1);
+
+ let feedURI = makeURI(url);
+ let title = feedTitle || gBrowser.contentTitle;
+ let description = feedSubtitle;
+ if (!description) {
+ description = PlacesUIUtils.getDescriptionFromDocument(gBrowser.contentDocument);
+ }
+
+ 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");
+ // Due to bug 528706, getMostRecentWindow can return closed windows.
+ if (!organizer || organizer.closed) {
+ // 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 = {
+ toggleRestoreLastSession: function HM_toggleRestoreLastSession() {
+ let restoreItem = this._rootElt.ownerDocument.getElementById("Browser:RestoreLastSession");
+
+ if (this._ss.canRestoreLastSession &&
+ !PrivateBrowsingUtils.isWindowPrivate(window))
+ restoreItem.removeAttribute("disabled");
+ else
+ restoreItem.setAttribute("disabled", true);
+ },
+
+ 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.toggleRestoreLastSession();
+ 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 tbo = tree.treeBoxObject;
+ var cell = tbo.getCellAt(aEvent.clientX, aEvent.clientY);
+ if (cell.row == -1)
+ return false;
+ node = tree.view.nodeForTreeIndex(cell.row);
+ cropped = tbo.isCellCropped(cell.row, cell.col);
+ }
+ 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.
+ */
+var 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.
+ */
+
+var 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();
+ }
+ // Also uninit the main menubar placesView, since it would have the same
+ // issues.
+ let menubar = document.getElementById("bookmarksMenu");
+ if (menubar && menubar._placesView) {
+ menubar._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;
+ }
+ },
+
+ onLocationChange: function BUI_onLocationChange() {
+ if (this._uri && gBrowser.currentURI.equals(this._uri)) {
+ return;
+ }
+ this.updateStarState();
+ },
+
+ updateStarState: function BUI_updateStarState() {
+ // 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, (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/base/content/browser-plugins.js b/base/content/browser-plugins.js
new file mode 100644
index 0000000..8382682
--- /dev/null
+++ b/base/content/browser-plugins.js
@@ -0,0 +1,781 @@
+# -*- 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 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, anonid) {
+ return plugin.ownerDocument.
+ getAnonymousElementByAttribute(plugin, "anonid", anonid);
+ },
+
+ _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.
+ // Clamp width/height to properly show CTP overlay on different
+ // zoom levels when embedded in iframes (rounding bug). (Bug 972237)
+ let overflows = (overlay.scrollWidth > Math.ceil(pluginRect.width)) ||
+ (overlay.scrollHeight - 5 > Math.ceil(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;
+ }
+ },
+
+ 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 = this.getPluginUI(plugin, "main");
+ 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);
+ if (!browser)
+ return;
+
+ switch (eventType) {
+ case "PluginCrashed":
+ this.pluginInstanceCrashed(plugin, event);
+ break;
+
+ case "PluginNotFound":
+ /* No action (plugin finder obsolete) */
+ break;
+
+ case "PluginBlocklisted":
+ case "PluginOutdated":
+ shouldShowNotification = true;
+ break;
+
+ case "PluginVulnerableUpdatable":
+ let updateLink = this.getPluginUI(plugin, "checkForUpdatesLink");
+ this.addLinkClickCallback(updateLink, "openPluginUpdatePage");
+ /* FALLTHRU */
+
+ case "PluginVulnerableNoUpdate":
+ case "PluginClickToPlay":
+ this._handleClickToPlayEvent(plugin);
+ let overlay = this.getPluginUI(plugin, "main");
+ let pluginName = this._getPluginInfo(plugin).pluginName;
+ let messageString = gNavigatorBundle.getFormattedString("PluginClickToActivate", [pluginName]);
+ let overlayText = this.getPluginUI(plugin, "clickToPlay");
+ overlayText.textContent = messageString;
+ if (eventType == "PluginVulnerableUpdatable" ||
+ eventType == "PluginVulnerableNoUpdate") {
+ let vulnerabilityString = gNavigatorBundle.getString(eventType);
+ let vulnerabilityText = this.getPluginUI(plugin, "vulnerabilityStatus");
+ vulnerabilityText.textContent = vulnerabilityString;
+ }
+ shouldShowNotification = true;
+ break;
+
+ case "PluginPlayPreview":
+ this._handlePlayPreviewEvent(plugin);
+ break;
+
+ case "PluginDisabled":
+ let manageLink = this.getPluginUI(plugin, "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;
+ }
+
+ // Show the in-content UI if it's not too big. The crashed plugin handler already did this.
+ if (eventType != "PluginCrashed" && eventType != "PluginRemoved") {
+ let overlay = this.getPluginUI(plugin, "main");
+ if (overlay != null) {
+ if (!this.isTooSmall(plugin, overlay)) {
+ overlay.style.visibility = "visible";
+ }
+ plugin.addEventListener("overflow", function(event) {
+ overlay.style.visibility = "hidden";
+ });
+ plugin.addEventListener("underflow", function(event) {
+ // this is triggered if only one dimension underflows,
+ // the other dimension might still overflow
+ if (!gPluginHandler.isTooSmall(plugin, overlay)) {
+ overlay.style.visibility = "visible";
+ }
+ });
+ }
+ }
+
+ // 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 = this.getPluginUI(aPlugin, "main");
+ 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();
+ },
+
+ // 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"));
+ },
+
+ // 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);
+ },
+
+ // 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 = this.getPluginUI(aPlugin, "main");
+
+ if (pluginPermission == Ci.nsIPermissionManager.DENY_ACTION) {
+ if (overlay)
+ overlay.style.visibility = "hidden";
+ return;
+ }
+
+ if (overlay) {
+ overlay.addEventListener("click", gPluginHandler._overlayClickListener, true);
+ let closeIcon = gPluginHandler.getPluginUI(aPlugin, "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 = this.getPluginUI(aPlugin, "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, "anonid", "main");
+ 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;
+ }
+ },
+
+ _makeCenterActions: function PH_makeCenterActions(notification) {
+ let contentWindow = notification.browser.contentWindow;
+ let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+
+ let principal = contentWindow.document.nodePrincipal;
+
+ 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.pluginPermissionPrePath = permissionObj.principal.originNoSuffix;
+ pluginInfo.pluginPermissionType = permissionObj.expireType;
+ }
+ else {
+ pluginInfo.pluginPermissionPrePath = principal.originNoSuffix;
+ 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, "anonid", "main");
+ 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;
+ },
+
+ // 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");
+
+ 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(); },
+ }];
+
+ 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/base/content/browser-sets.inc b/base/content/browser-sets.inc
new file mode 100644
index 0000000..78fce26
--- /dev/null
+++ b/base/content/browser-sets.inc
@@ -0,0 +1,357 @@
+# -*- 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_toggleMute" oncommand="gBrowser.selectedTab.toggleMuteAudio()"/>
+ <command id="cmd_ToggleTabsOnTop" oncommand="TabsOnTop.toggle()"/>
+ <command id="cmd_CustomizeToolbars" oncommand="BrowserCustomizeToolbar()"/>
+ <command id="cmd_restartApplication" oncommand="restart(false);"/>
+ <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="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:Addons" oncommand="BrowserOpenAddonsMgr();"/>
+ <command id="Tools:Permissions" oncommand="BrowserOpenPermissionsMgr();"/>
+ <command id="Tools:ErrorConsole" oncommand="toJavaScriptConsole()" 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();"/>
+ </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" sidebartitle="&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="devtoolsMenuBroadcaster_PageSource"
+ label="&pageSourceCmd.label;"
+ key="key_viewSource"
+ command="View:PageSource"/>
+ <broadcaster id="devtoolsMenuBroadcaster_ErrorConsole"
+ label="&errorConsoleCmd.label;"
+ command="Tools:ErrorConsole"/>
+ </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="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_toggleMute" key="&toggleMuteCmd.key;" command="cmd_toggleMute" modifiers="control"/>
+ <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 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
+
+# Navigation cancel keys: Esc performs a cancel on loading (i.e.: stop button equivalent)
+# Shift-Esc (and similar Shift-modified stop on Mac) performs a "superstop": this halts all
+# networking requests, XHR, animated gifs, etc.
+ <key id="key_stop" keycode="VK_ESCAPE" command="Browser:Stop"/>
+ <key id="key_stop_all" keycode="VK_ESCAPE" modifiers="shift" oncommand="BrowserStop();"/>
+#ifdef XP_MACOSX
+ <key id="key_stop_mac" modifiers="accel" key="&stopCmd.macCommandKey;" command="Browser:Stop"/>
+ <key id="key_stop_all_mac" modifiers="accel,shift" key="&stopCmd.macCommandKey;" oncommand="BrowserStop();"/>
+#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/base/content/browser-syncui.js b/base/content/browser-syncui.js
new file mode 100644
index 0000000..86f6f48
--- /dev/null
+++ b/base/content/browser-syncui.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/.
+
+// gSyncUI handles updating the tools menu
+var 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 = Services.prefs.getCharPref("services.sync.firstSync", "");
+ 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");
+ button.setAttribute("label", this._stringBundle.GetStringFromName("setupsync.label"));
+ } else {
+ button.setAttribute("label", this._stringBundle.GetStringFromName("syncnow.label"));
+ }
+ },
+
+
+ // Functions called by observers
+ onActivityStart: function SUI_onActivityStart() {
+ if (!gBrowser)
+ return;
+
+ let button = document.getElementById("sync-button");
+ if (!button)
+ return;
+
+ button.setAttribute("status", "active");
+ button.setAttribute("label", this._stringBundle.GetStringFromName("syncing2.label"));
+ },
+
+ 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 = Services.prefs.getCharPref("services.sync.lastSync", "");
+ 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(Services.prefs.getCharPref("services.sync.outdated.url"), "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/base/content/browser-tabPreviews.js b/base/content/browser-tabPreviews.js
new file mode 100644
index 0000000..e0755b8
--- /dev/null
+++ b/base/content/browser-tabPreviews.js
@@ -0,0 +1,1058 @@
+/*
+#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.includes(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(PopupBoxObject.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");
+
+ aPreview.removeAttribute("soundplaying");
+ aPreview.removeAttribute("muted");
+ if (aPreview._tab.hasAttribute("muted"))
+ aPreview.setAttribute("muted", "true");
+ else if (aPreview._tab.hasAttribute("soundplaying"))
+ aPreview.setAttribute("soundplaying", "true");
+
+ 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/base/content/browser-tabPreviews.xml b/base/content/browser-tabPreviews.xml
new file mode 100644
index 0000000..4f54321
--- /dev/null
+++ b/base/content/browser-tabPreviews.xml
@@ -0,0 +1,78 @@
+<?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:hbox align="center">
+ <xul:image xbl:inherits="soundplaying,muted" class="allTabs-endimage"/>
+ <xul:label flex="1" xbl:inherits="value=label,crop" class="allTabs-preview-label plain"/>
+ </xul:hbox>
+ </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/base/content/browser-thumbnails.js b/base/content/browser-thumbnails.js
new file mode 100644
index 0000000..079b0ac
--- /dev/null
+++ b/base/content/browser-thumbnails.js
@@ -0,0 +1,203 @@
+#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.
+ */
+var 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) {
+ // Tycho: aCallback([browser.currentURI.spec for (browser of gBrowser.browsers)]);
+ let result = [];
+ for (let browser of gBrowser.browsers) {
+ result.push(browser.currentURI.spec);
+ }
+ aCallback(result);
+ },
+
+ /**
+ * 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 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/base/content/browser-title.css b/base/content/browser-title.css
new file mode 100644
index 0000000..5f7e775
--- /dev/null
+++ b/base/content/browser-title.css
@@ -0,0 +1,204 @@
+/* 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;
+}
+
+/* Win10 doesn't respond to inactive caption, so dim it instead */
+@media (-moz-os-version: windows-win10) {
+ #main-window:-moz-window-inactive::after {
+ opacity: 0.5;
+ }
+}
+
+/* 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;
+ }
+
+}
+
+
+/* Generic other themes */
+
+@media 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/10 */
+
+@media all and (-moz-windows-compositor) {
+ @media not all and (-moz-windows-glass) {
+
+ #main-window::after {
+ background-position: 4px 17px;
+ top: -13px;
+ }
+
+ @media (-moz-os-version: windows-win8) {
+ #main-window::after {
+ font-size: 15px;
+ text-align: center;
+ }
+
+ #main-window[darkwindowframe="true"]:not(:-moz-window-inactive):not(:-moz-lwtheme)::after {
+ /* Dark window frame/accent color on Win 8 */
+ color: white;
+ }
+ }
+
+ @media (-moz-os-version: windows-win10) {
+ #main-window::after {
+ text-align: left;
+ }
+
+ @media (-moz-windows-accent-color-applies: 0) {
+ #main-window:not(:-moz-window-inactive):not(:-moz-lwtheme)::after {
+ /* Default Windows 10 styling is white - apply black text styling */
+ color: black;
+ }
+ }
+
+ @media (-moz-windows-accent-color-applies) {
+ #main-window:not(:-moz-window-inactive):not(:-moz-lwtheme)::after {
+ /* Accent color is applied - use the associated text styling */
+ color: -moz-win-accentcolortext;
+ }
+ }
+ }
+
+ #main-window[sizemode="maximized"]::after {
+ top: -5px;
+ }
+ }
+
+}
+
+/* Lightweight Themes */
+
+#main-window:-moz-lwtheme::after {
+ color: inherit;
+ text-shadow: inherit;
+}
+
+
+/* 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/base/content/browser-uacompat.js b/base/content/browser-uacompat.js
new file mode 100644
index 0000000..933aa55
--- /dev/null
+++ b/base/content/browser-uacompat.js
@@ -0,0 +1,45 @@
+/* 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 UserAgentCompatibility = {
+ PREF_UA_COMPAT: "general.useragent.compatMode",
+ PREF_UA_COMPAT_GECKO: "general.useragent.compatMode.gecko",
+ PREF_UA_COMPAT_FIREFOX: "general.useragent.compatMode.firefox",
+
+ init: function() {
+ this.checkPreferences();
+ Services.prefs.addObserver(this.PREF_UA_COMPAT, this, false);
+ Services.prefs.addObserver(this.PREF_UA_COMPAT_GECKO, this, false);
+ Services.prefs.addObserver(this.PREF_UA_COMPAT_FIREFOX, this, false);
+ },
+
+ uninit: function() {
+ Services.prefs.removeObserver(this.PREF_UA_COMPAT, this, false);
+ Services.prefs.removeObserver(this.PREF_UA_COMPAT_GECKO, this, false);
+ Services.prefs.removeObserver(this.PREF_UA_COMPAT_FIREFOX, this, false);
+ },
+
+ observe: function() {
+ this.checkPreferences();
+ },
+
+ checkPreferences: function() {
+ var compatMode = Services.prefs.getIntPref(this.PREF_UA_COMPAT);
+
+ switch(compatMode) {
+ case 0:
+ Services.prefs.setBoolPref(this.PREF_UA_COMPAT_GECKO, false);
+ Services.prefs.setBoolPref(this.PREF_UA_COMPAT_FIREFOX, false);
+ break;
+ case 1:
+ Services.prefs.setBoolPref(this.PREF_UA_COMPAT_GECKO, true);
+ Services.prefs.setBoolPref(this.PREF_UA_COMPAT_FIREFOX, false);
+ break;
+ case 2:
+ Services.prefs.setBoolPref(this.PREF_UA_COMPAT_GECKO, true);
+ Services.prefs.setBoolPref(this.PREF_UA_COMPAT_FIREFOX, true);
+ break;
+ }
+ }
+};
diff --git a/base/content/browser-webrtcUI.js b/base/content/browser-webrtcUI.js
new file mode 100644
index 0000000..d59134c
--- /dev/null
+++ b/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/.
+
+var 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/base/content/browser.css b/base/content/browser.css
new file mode 100644
index 0000000..a2970ae
--- /dev/null
+++ b/base/content/browser.css
@@ -0,0 +1,759 @@
+/* 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");
+ -moz-window-dragging: drag;
+}
+
+%ifdef XP_WIN
+#main-window[tabsontop="true"] #TabsToolbar,
+#main-window[tabsontop="true"] #toolbar-menubar,
+#main-window[tabsontop="true"] #navigator-toolbox > toolbar:-moz-lwtheme {
+ -moz-window-dragging: drag;
+}
+%endif
+
+#titlebar-spacer {
+ pointer-events: none;
+}
+
+#main-window[tabsintitlebar] #appmenu-button-container,
+#main-window[tabsintitlebar] #titlebar-buttonbox {
+ position: relative;
+}
+%endif
+
+#main-window[inDOMFullscreen] #sidebar-box,
+#main-window[inDOMFullscreen] #sidebar-splitter {
+ visibility: collapse;
+}
+
+.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");
+}
+
+#DateTimePickerPanel[active="true"] {
+ -moz-binding: url("chrome://global/content/bindings/datetimepopup.xml#datetime-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;
+}
+
+/* Private Autocomplete */
+textbox[type="private-autocomplete"] {
+ -moz-binding: url("chrome://browser/content/autocomplete.xml#autocomplete");
+}
+
+panel[type="private-autocomplete"] {
+ -moz-binding: url("chrome://browser/content/autocomplete.xml#autocomplete-result-popup");
+}
+
+panel[type="private-autocomplete-richlistbox"] {
+ -moz-binding: url("chrome://browser/content/autocomplete.xml#private-autocomplete-rich-result-popup");
+}
+
+.private-autocomplete-tree {
+ -moz-binding: url("chrome://browser/content/autocomplete.xml#private-autocomplete-tree");
+ -moz-user-focus: ignore;
+}
+
+.private-autocomplete-treebody {
+ -moz-binding: url("chrome://browser/content/autocomplete.xml#private-autocomplete-treebody");
+}
+
+.private-autocomplete-richlistbox {
+ -moz-binding: url("chrome://browser/content/autocomplete.xml#private-autocomplete-richlistbox");
+ -moz-user-focus: ignore;
+}
+
+.private-autocomplete-richlistbox > scrollbox {
+ overflow-x: hidden !important;
+}
+
+.private-autocomplete-richlistitem {
+ -moz-binding: url("chrome://browser/content/autocomplete.xml#private-autocomplete-richlistitem");
+ -moz-box-orient: vertical;
+ overflow: -moz-hidden-unscrollable;
+}
+
+.private-autocomplete-treerows {
+ -moz-binding: url("chrome://browser/content/autocomplete.xml#private-autocomplete-treerows");
+}
+
+.private-autocomplete-history-dropmarker {
+ display: none;
+}
+
+.private-autocomplete-history-dropmarker[enablehistory="true"] {
+ display: -moz-box;
+ -moz-binding: url("chrome://browser/content/autocomplete.xml#private-history-dropmarker");
+}
+
+.ac-ellipsis-after {
+ visibility: hidden;
+}
+
+.ac-url-text[type~="action"],
+.ac-action-text:not([type~="action"]) {
+ visibility: collapse;
+}
+
+
+/* ::::: 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,
+#toolbar-menubar:not([autohide="true"]) ~ toolbar > #history-menu-button,
+#toolbar-menubar:not([autohide="true"]) > #history-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(#nav-bar):not(#TabsToolbar):not(#print-preview-toolbar):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;
+ pointer-events: none;
+}
+
+#full-screen-warning-container[fade-warning-out] {
+ transition-property: opacity !important;
+ transition-duration: 500ms !important;
+ opacity: 0.0;
+}
+
+#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 {
+ 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;
+}
+
+/* This was added with the identity toolkit, does it have any other purpose? */
+#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;
+}
+
+.popup-anchor {
+ /* should occupy space but not be visible */
+ opacity: 0;
+ pointer-events: none;
+ -moz-stack-sizing: ignore;
+}
+
+#addon-progress-notification {
+ -moz-binding: url("chrome://browser/content/urlbarBindings.xml#addon-progress-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;
+}
+
+/* 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");
+}
+
+/* Strict icon size for PMkit 'ui/button' */
+toolbarbutton[pmkit-button="true"] > .toolbarbutton-badge-stack > .toolbarbutton-icon {
+ width: 16px;
+ height: 16px;
+}
+
+/* Remove white bar at the bottom of the screen when watching HTML5 video in fullscreen */
+#main-window[inFullscreen] #global-notificationbox,
+#main-window[inFullscreen] #high-priority-global-notificationbox {
+ visibility: collapse;
+}
+
+#navigator-toolbox[fullscreenShouldAnimate] {
+ transition: 0.7s margin-top ease-out;
+ transition-delay: 0.8s;
+}
diff --git a/base/content/browser.js b/base/content/browser.js
new file mode 100644
index 0000000..7615bc9
--- /dev/null
+++ b/base/content/browser.js
@@ -0,0 +1,7440 @@
+# -*- 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 Ci = Components.interfaces;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource:///modules/RecentWindow.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu",
+ "resource:///modules/CharsetMenu.jsm");
+
+const nsIWebNavigation = Ci.nsIWebNavigation;
+const gToolbarInfoSeparators = ["|", "-"];
+
+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.mCurrentBrowser;
+ window.gFindBarInitialized = true;
+ return findbar;
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
+ "resource://gre/modules/BrowserUtils.jsm");
+
+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;
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+ "resource://gre/modules/PluralForm.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:///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.defineLazyModuleGetter(this, "PageThumbs",
+ "resource://gre/modules/PageThumbs.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "gBrowserNewTabPreloader",
+ "resource:///modules/BrowserNewTabPreloader.jsm", "BrowserNewTabPreloader");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FormValidationHandler",
+ "resource:///modules/FormValidationHandler.jsm");
+
+var gInitialPages = [
+ "about:blank",
+ "about:newtab",
+ "about:home",
+ "about:privatebrowsing",
+ "about:sessionrestore",
+ "about:logopage"
+];
+
+#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-tabPreviews.js
+#include browser-thumbnails.js
+#include browser-uacompat.js
+
+#ifdef MOZ_WEBRTC
+#include browser-webrtcUI.js
+#endif
+
+#include browser-gestureSupport.js
+
+#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;
+});
+
+XPCOMUtils.defineLazyGetter(this, "PageMenu", function() {
+ let tmp = {};
+ Cu.import("resource:///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()
+ }
+ }
+};
+
+var gFindBarSettings = {
+ messageName: "Findbar:Keypress",
+ prefName: "accessibility.typeaheadfind",
+ findAsYouType: null,
+
+ init: function() {
+ window.messageManager.addMessageListener(this.messageName, this);
+
+ gPrefService.addObserver(this.prefName, this, false);
+ this.writeFindAsYouType();
+ },
+
+ uninit: function() {
+ window.messageManager.removeMessageListener(this.messageName, this);
+
+ try {
+ gPrefService.removeObserver(this.prefName, this);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ },
+
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic != "nsPref:changed") {
+ return;
+ }
+
+ this.writeFindAsYouType();
+ },
+
+ writeFindAsYouType: function() {
+ this.findAsYouType = gPrefService.getBoolPref(this.prefName);
+ },
+
+ receiveMessage: function(aMessage) {
+ switch (aMessage.name) {
+ case this.messageName:
+ // If the find bar for chrome window's context is not yet alive,
+ // only initialize it if there's a possibility FindAsYouType
+ // will be used.
+ // There's no point in doing it for most random keypresses.
+ if (!gFindBarInitialized && aMessage.data.shouldFastFind) {
+ let shouldFastFind = this.findAsYouType;
+ if (!shouldFastFind) {
+ // Please keep in sync with toolkit/content/widgets/findbar.xml
+ const FAYT_LINKS_KEY = "'";
+ const FAYT_TEXT_KEY = "/";
+ let charCode = aMessage.data.fakeEvent.charCode;
+ let key = charCode ? String.fromCharCode(charCode) : null;
+ shouldFastFind = key == FAYT_LINKS_KEY || key == FAYT_TEXT_KEY;
+ }
+ if (shouldFastFind) {
+ // Make sure we return the result.
+ return gFindBar.receiveMessage(aMessage);
+ }
+ }
+ break;
+ }
+ }
+};
+
+var gURLBarSettings = {
+ prefSuggest: "browser.urlbar.suggest.",
+ /*
+ For searching in the source code:
+ browser.urlbar.suggest.bookmark
+ browser.urlbar.suggest.history
+ browser.urlbar.suggest.openpage
+ */
+ prefSuggests: [
+ "bookmark",
+ "history",
+ "openpage"
+ ],
+ prefKeyword: "keyword.enabled",
+
+ observe: function(aSubject, aTopic, aData) {
+ if (aTopic != "nsPref:changed")
+ return;
+
+ this.writePlaceholder();
+ },
+
+ writePlaceholder: function() {
+ if (!gURLBar) {
+ return;
+ }
+
+ let attribute = "placeholder";
+ let prefs = this.prefSuggests.map(pref => {
+ return this.prefSuggest + pref;
+ });
+ prefs.push(this.prefKeyword);
+ let placeholderDefault = prefs.some(pref => {
+ return gPrefService.getBoolPref(pref);
+ });
+
+ if (placeholderDefault) {
+ gURLBar.setAttribute(
+ attribute, gNavigatorBundle.getString("urlbar.placeholder"));
+ } else {
+ gURLBar.setAttribute(
+ attribute, gNavigatorBundle.getString("urlbar.placeholderURLOnly"));
+ }
+ }
+};
+
+/**
+ * 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.nsIDocShellTreeItem);
+ 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.selectedBrowser.blockedPopups ||
+ !gBrowser.selectedBrowser.blockedPopups.length) {
+ // 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.selectedBrowser.blockedPopups.reported) {
+ if (gPrefService.getBoolPref("privacy.popups.showBrowserMessage")) {
+ var brandBundle = document.getElementById("bundle_brand");
+ var brandShortName = brandBundle.getString("brandShortName");
+ var popupCount = gBrowser.selectedBrowser.blockedPopups.length;
+ var popupButtonText = gNavigatorBundle.getString("popupWarningButton");
+ var popupButtonAccesskey = gNavigatorBundle.getString("popupWarningButton.accesskey");
+ 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.selectedBrowser.blockedPopups.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 blockedPopups 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.
+ let browser = gBrowser.selectedBrowser;
+ var uri = browser.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");
+
+ let blockedPopupDontShowMessage = document.getElementById("blockedPopupDontShowMessage");
+ let 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"));
+ }
+
+ let blockedPopupsSeparator =
+ document.getElementById("blockedPopupsSeparator");
+ blockedPopupsSeparator.setAttribute("hidden", true);
+
+ gBrowser.selectedBrowser.retrieveListOfBlockedPopups().then(blockedPopups => {
+ let foundUsablePopupURI = false;
+ if (blockedPopups) {
+ for (let i = 0; i < blockedPopups.length; i++) {
+ let blockedPopup = blockedPopups[i];
+
+ // 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 (!blockedPopup.popupWindowURIspec)
+ continue;
+
+ var popupURIspec = blockedPopup.popupWindowURIspec;
+
+ // Sometimes the popup URI that we get back from the blockedPopup
+ // 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 == "<self>" ||
+ 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("oncommand", "gPopupBlockerObserver.showBlockedPopup(event);");
+ menuitem.setAttribute("popupReportIndex", i);
+ menuitem.popupReportBrowser = browser;
+ aEvent.target.appendChild(menuitem);
+ }
+ }
+
+ // Show the separator if we added any
+ // showable popup addresses to the menu.
+ if (foundUsablePopupURI)
+ blockedPopupsSeparator.removeAttribute("hidden");
+ }, null);
+ },
+
+ 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 popupReportIndex = target.getAttribute("popupReportIndex");
+ let browser = target.popupReportBrowser;
+ browser.unblockPopup(popupReportIndex);
+ },
+
+ 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 gXSSObserver = {
+
+ observe: function (aSubject, aTopic, aData)
+ {
+
+ // Don't do anything if the notification is disabled.
+ if (!gPrefService.getBoolPref("security.xssfilter.displayWarning"))
+ return;
+
+ // Parse incoming XSS array
+ aSubject.QueryInterface(Ci.nsIArray);
+ var policy = aSubject.queryElementAt(0, Ci.nsISupportsString).data;
+ var content = aSubject.queryElementAt(1, Ci.nsISupportsString).data;
+ var domain = aSubject.queryElementAt(2, Ci.nsISupportsString).data;
+ var url = aSubject.queryElementAt(3, Ci.nsISupportsCString).data;
+ var blockMode = aSubject.queryElementAt(4, Ci.nsISupportsPRBool).data;
+
+ // If it is a block mode event, do not display the infobar
+ if (blockMode)
+ return;
+
+ var nb = gBrowser.getNotificationBox();
+ const priority = nb.PRIORITY_WARNING_MEDIUM;
+
+ var buttons = [{
+ label: 'View Unsafe Content',
+ accessKey: 'V',
+ popup: null,
+ callback: function () {
+ alert(content);
+ }
+ }];
+
+ if (domain !== "")
+ buttons.push({
+ label: 'Add Domain Exception',
+ accessKey: 'A',
+ popup: null,
+ callback: function () {
+ let whitelist = gPrefService.getCharPref("security.xssfilter.whitelist");
+ if (whitelist != "") {
+ whitelist = whitelist + "," + domain;
+ } else {
+ whitelist = domain;
+ }
+ // Write the updated whitelist. Since this is observed by the XSS filter,
+ // it will automatically sync to the back-end and update immediately.
+ gPrefService.setCharPref("security.xssfilter.whitelist", whitelist);
+ // After setting this, we automatically reload the page.
+ BrowserReloadSkipCache();
+ }
+ });
+
+ nb.appendNotification("The XSS Filter has detected a potential XSS attack. Type: " +
+ policy, 'popup-blocked', 'chrome://browser/skin/Info.png',
+ priority, buttons);
+ }
+};
+
+var gBrowserInit = {
+ delayedStartupFinished: false,
+
+ 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);
+
+ Services.obs.addObserver(gPluginHandler.pluginCrashed, "plugin-crashed", false);
+
+ window.addEventListener("AppCommand", HandleAppCommandEvent, true);
+
+ // These routines add message listeners. They must run before
+ // loading the frame script to ensure that we don't miss any
+ // message sent between when the frame script is loaded and when
+ // the listener is registered.
+#ifdef MOZ_DEVTOOLS
+ DevToolsTheme.init();
+#endif
+ gFindBarSettings.init();
+
+ 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
+ //TFE FIXME: this is now a wrappednative and can't be set this way.
+ //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);
+
+ // 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();
+ AudioIndicator.init();
+ gPrivateBrowsingUI.init();
+ TabsInTitlebar.init();
+ retrieveToolbarIconsizesFromTheme();
+ ToolbarIconColor.init();
+ UserAgentCompatibility.init();
+
+#ifdef XP_WIN
+ if (window.matchMedia("(-moz-os-version: windows-win8)").matches &&
+ window.matchMedia("(-moz-windows-default-theme)").matches) {
+ let windows8WindowFrameColor = Cu.import("resource:///modules/Windows8WindowFrameColor.jsm", {}).Windows8WindowFrameColor;
+
+ var windowFrameColor;
+ windowFrameColor = windows8WindowFrameColor.get_win8();
+
+ // Formula from Microsoft's UWP guideline.
+ let backgroundLuminance = (windowFrameColor[0] * 2 +
+ windowFrameColor[1] * 5 +
+ windowFrameColor[2]) / 8;
+ if (backgroundLuminance <= 128) {
+ document.documentElement.setAttribute("darkwindowframe", "true");
+ }
+ }
+#endif
+
+ // 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 = {};
+
+ 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 | string)
+ // [3]: postData (nsIInputStream)
+ // [4]: allowThirdPartyFixup (bool)
+ // [5]: referrerPolicy (int)
+ // [6]: originPrincipal (nsIPrincipal)
+ // [7]: triggeringPrincipal (nsIPrincipal)
+ else if (window.arguments.length >= 3) {
+ let referrerURI = window.arguments[2];
+ if (typeof(referrerURI) == "string") {
+ try {
+ referrerURI = makeURI(referrerURI);
+ } catch (e) {
+ referrerURI = null;
+ }
+ }
+ let referrerPolicy = (window.arguments[5] != undefined ?
+ window.arguments[5] : Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT);
+ loadURI(uriToLoad, referrerURI, window.arguments[3] || null,
+ window.arguments[4] || false, referrerPolicy,
+ // pass the origin principal (if any) and force its use to create
+ // an initial about:blank viewer if present:
+ window.arguments[6], !!window.arguments[6], window.arguments[7]);
+ 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);
+ }
+
+ 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-origin-blocked", false);
+ Services.obs.addObserver(gXPInstallObserver, "addon-install-failed", false);
+ Services.obs.addObserver(gXPInstallObserver, "addon-install-complete", false);
+ Services.obs.addObserver(gXSSObserver, "xss-on-violate-policy", false);
+
+ gPrefService.addObserver(gURLBarSettings.prefSuggest, gURLBarSettings, false);
+ gPrefService.addObserver(gURLBarSettings.prefKeyword, gURLBarSettings, false);
+
+ gURLBarSettings.writePlaceholder();
+
+ BrowserOffline.init();
+ OfflineApps.init();
+ IndexedDBPromptHelper.init();
+ AddonManager.addAddonListener(AddonsMgrListener);
+#ifdef MOZ_WEBRTC
+ WebrtcIndicator.init();
+#endif
+
+ // 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 (!Services.prefs.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);
+ let ssPromise = ss.init(window);
+
+ 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() {
+ try {
+ Cu.import("resource:///modules/DownloadsCommon.jsm", {})
+ .DownloadsCommon.initializeAllDataLinks();
+ Cu.import("resource:///modules/DownloadsTaskbar.jsm", {})
+ .DownloadsTaskbar.registerIndicator(window);
+ } catch(ex) {
+ Cu.reportError(ex);
+ }
+ }, 10000);
+
+ // Load the Login Manager data from disk off the main thread, some time
+ // after startup. If the data is required before the timeout, for example
+ // because a restored page contains a password field, it will be loaded on
+ // the main thread, and this initialization request will be ignored.
+ setTimeout(function() {
+ try {
+ Services.logins;
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }, 3000);
+
+ // 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
+
+#ifdef MOZ_PERSONAS
+ gBrowser.mPanelContainer.addEventListener("InstallBrowserTheme", LightWeightThemeWebInstaller, false, true);
+ gBrowser.mPanelContainer.addEventListener("PreviewBrowserTheme", LightWeightThemeWebInstaller, false, true);
+ gBrowser.mPanelContainer.addEventListener("ResetBrowserThemePreview", LightWeightThemeWebInstaller, false, true);
+#endif
+
+ // 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("MozDOMFullscreen:NewOrigin", onMozEnteredDomFullscreen, true);
+
+ if (window.fullScreen)
+ onFullScreen();
+ if (document.mozFullScreen)
+ onMozEnteredDomFullscreen();
+
+#ifdef MOZ_SERVICES_SYNC
+ // initialize the sync UI
+ gSyncUI.init();
+#endif
+
+ gBrowserThumbnails.init();
+
+ setUrlAndSearchBarWidthForConditionalForwardButton();
+ window.addEventListener("resize", function resizeHandler(event) {
+ if (event.target == window)
+ setUrlAndSearchBarWidthForConditionalForwardButton();
+ });
+
+ // Enable Error Console?
+ let consoleEnabled = gPrefService.getBoolPref("devtools.errorconsole.enabled");
+ if (consoleEnabled) {
+ let cmd = document.getElementById("Tools:ErrorConsole");
+ 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
+
+ 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;
+ }, 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);
+ }
+
+ ssPromise.then(() =>{
+ // Bail out if the window has been closed in the meantime.
+ if (window.closed) {
+ return;
+ }
+ if ("TabView" in window) {
+ TabView.init();
+ }
+ // XXX: do we still need this?...
+ setTimeout(function () { BrowserChromeTest.markAsReady(); }, 0);
+ });
+
+ this.delayedStartupFinished = true;
+
+ Services.obs.notifyObservers(window, "browser-delayed-startup-finished", "");
+ },
+
+ // 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;
+
+ // 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();
+
+ AudioIndicator.uninit();
+
+ TabsInTitlebar.uninit();
+
+ ToolbarIconColor.uninit();
+
+#ifdef MOZ_DEVTOOLS
+ DevToolsTheme.uninit();
+#endif
+ gFindBarSettings.uninit();
+
+ UserAgentCompatibility.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();
+ if ("TabView" in window) {
+ TabView.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-origin-blocked");
+ Services.obs.removeObserver(gXPInstallObserver, "addon-install-failed");
+ Services.obs.removeObserver(gXPInstallObserver, "addon-install-complete");
+ Services.obs.removeObserver(gXSSObserver, "xss-on-violate-policy");
+
+ try {
+ gPrefService.removeObserver(gURLBarSettings.prefSuggest, gURLBarSettings);
+ gPrefService.removeObserver(gURLBarSettings.prefKeyword, gURLBarSettings);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+
+ try {
+ gPrefService.removeObserver(gHomeButton.prefDomain, gHomeButton);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+
+ BrowserOffline.uninit();
+ OfflineApps.uninit();
+ IndexedDBPromptHelper.uninit();
+ AddonManager.removeAddonListener(AddonsMgrListener);
+ }
+
+ // 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) {
+ }
+ }
+ }
+
+ 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 = Services.prefs.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.showNavToolbox();
+
+ 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, referrerPolicy,
+ originPrincipal, forceAboutBlankViewerInCurrent,
+ triggeringPrincipal) {
+ if (postData === undefined)
+ postData = null;
+
+ var flags = nsIWebNavigation.LOAD_FLAGS_NONE;
+ if (allowThirdPartyFixup) {
+ flags |= nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ flags |= nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
+ }
+
+ try {
+ gBrowser.loadURIWithFlags(uri, {
+ flags: flags,
+ referrerURI: referrer,
+ referrerPolicy: referrerPolicy,
+ postData: postData,
+ originPrincipal: originPrincipal,
+ triggeringPrincipal: triggeringPrincipal,
+ forceAboutBlankViewerInCurrent: forceAboutBlankViewerInCurrent,
+ });
+ } catch (e) {}
+}
+
+/**
+ * Given a urlbar value, discerns between URIs, keywords and aliases.
+ *
+ * @param url
+ * The urlbar value.
+ * @param callback (optional, deprecated)
+ * The callback function invoked when done. This parameter is
+ * deprecated, please use the Promise that is returned.
+ *
+ * @return Promise<{ postData, url, mayInheritPrincipal }>
+ */
+function getShortcutOrURIAndPostData(url, callback = null) {
+ if (callback) {
+ Deprecated.warning("Please use the Promise returned by " +
+ "getShortcutOrURIAndPostData() instead of passing a " +
+ "callback",
+ "https://bugzilla.mozilla.org/show_bug.cgi?id=1100294");
+ }
+
+ return Task.spawn(function* () {
+ let mayInheritPrincipal = false;
+ let postData = null;
+ let shortcutURL = null;
+ let keyword = url;
+ let param = "";
+
+ let offset = url.indexOf(" ");
+ if (offset > 0) {
+ keyword = url.substr(0, offset);
+ param = url.substr(offset + 1);
+ }
+
+ let engine = Services.search.getEngineByAlias(keyword);
+ if (engine) {
+ let submission = engine.getSubmission(param, null, "keyword");
+ postData = submission.postData;
+ return { postData: submission.postData, url: submission.uri.spec,
+ mayInheritPrincipal };
+ }
+
+ let entry = yield PlacesUtils.keywords.fetch(keyword);
+ if (entry) {
+ shortcutURL = entry.url.href;
+ postData = entry.postData;
+ }
+
+ if (!shortcutURL) {
+ return { postData, url, mayInheritPrincipal };
+ }
+
+ let escapedPostData = "";
+ if (postData)
+ escapedPostData = unescape(postData);
+
+ if (/%s/i.test(shortcutURL) || /%s/i.test(escapedPostData)) {
+ let charset = "";
+ const re = /^(.*)\&mozcharset=([a-zA-Z][_\-a-zA-Z0-9]+)\s*$/;
+ let matches = shortcutURL.match(re);
+
+ if (matches) {
+ [, shortcutURL, charset] = matches;
+ } else {
+ let uri;
+ try {
+ // makeURI() throws if URI is invalid.
+ uri = makeURI(shortcutURL);
+ } catch (ex) {}
+
+ if (uri) {
+ // Try to get the saved character-set.
+ // Will return an empty string if character-set is not found.
+ charset = yield PlacesUtils.getCharsetForURI(uri);
+ }
+ }
+
+ // 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
+ let 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(escapedPostData)) // POST keyword
+ postData = getPostDataStream(escapedPostData, param, encodedParam,
+ "application/x-www-form-urlencoded");
+
+ // This URL came from a bookmark, so it's safe to let it inherit the current
+ // document's principal.
+ mayInheritPrincipal = true;
+
+ return { postData, url: shortcutURL, mayInheritPrincipal };
+ }
+
+ if (param) {
+ // This keyword doesn't take a parameter, but one was provided. Just return
+ // the original URL.
+ postData = null;
+
+ return { postData, url, mayInheritPrincipal };
+ }
+
+ // This URL came from a bookmark, so it's safe to let it inherit the current
+ // document's principal.
+ mayInheritPrincipal = true;
+
+ return { postData, url: shortcutURL, mayInheritPrincipal };
+ }).then(data => {
+ if (callback) {
+ callback(data);
+ }
+
+ return data;
+ });
+}
+
+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(aArgsOrDocument)
+{
+ let args;
+
+ if (aArgsOrDocument instanceof Document) {
+ let doc = aArgsOrDocument;
+
+ let requestor = doc.defaultView
+ .QueryInterface(Ci.nsIInterfaceRequestor);
+ let browser = requestor.getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+ let outerWindowID = requestor.getInterface(Ci.nsIDOMWindowUtils)
+ .outerWindowID;
+ let URL = browser.currentURI.spec;
+ args = { browser, outerWindowID, URL };
+ } else {
+ args = aArgsOrDocument;
+ }
+
+ let viewInternal = () => {
+ top.gViewSourceUtils.viewSource(args);
+ }
+
+ // Check if external view source is enabled. If so, try it. If it fails,
+ // fallback to internal view source.
+ if (Services.prefs.getBoolPref("view_source.editor.external")) {
+ top.gViewSourceUtils
+ .openInExternalEditor(args, null, null, null, result => {
+ if (!result) {
+ viewInternal();
+ }
+ });
+ } else {
+ // Display using internal view source
+ viewInternal();
+ }
+}
+
+// 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);
+ }
+
+ let isDifferentValidValue = valid && value != gURLBar.value;
+ gURLBar.value = value;
+ gURLBar.valueIsTyped = !valid;
+ if (isDifferentValidValue) {
+ gURLBar.selectionStart = gURLBar.selectionEnd = 0;
+ }
+
+ SetPageProxyState(valid ? "valid" : "invalid");
+}
+
+function losslessDecodeURI(aURI) {
+ let scheme = aURI.scheme;
+ let decodeASCIIOnly = !(/(https|http|file|ftp)/i.test(scheme));
+
+ 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)) {
+ if (decodeASCIIOnly) {
+ // This only decodes ASCII characters (hex) 20-7e, except 25 (%).
+ // This avoids both cases stipulated below (%-related issues, and \r, \n
+ // and \t, which would be %0d, %0a and %09, respectively) as well as any
+ // non-US-ascii characters.
+ value = value.replace(/%(2[0-4]|2[6-9a-f]|[3-6][0-9a-f]|7[0-9a-e])/g, decodeURI);
+ } else {
+ 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 (C0/C1 control characters, U+007F [DEL],
+ // U+00A0 [no-break space], line and paragraph separator,
+ // object replacement character) (bug 452979, bug 909264)
+ value = value.replace(/[\u0000-\u001f\u007f-\u00a0\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/skin/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) {
+
+ /* === about:home === */
+
+ if (doc.documentURI.toLowerCase() == "about:home") {
+ if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
+ let wrapper = {};
+ Cu.import("resource:///modules/sessionstore/SessionStore.jsm", wrapper);
+ let ss = wrapper.SessionStore;
+ ss.promiseInitialized.then(function() {
+ if (ss.canRestoreLastSession) {
+ doc.getElementById("launcher").setAttribute("session", "true");
+ }
+ }).then(null, function onError(x) {
+ Cu.reportError("Error in SessionStore init while processing 'about:home': " + x);
+ });
+ }
+
+ // Inject search engine and snippets URL.
+ let docElt = doc.documentElement;
+ 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);
+ }
+
+ function updateSearchEngine() {
+ let engine = AboutHomeUtils.defaultSearchEngine;
+ docElt.setAttribute("searchEngineName", engine.name);
+ docElt.setAttribute("searchEnginePostData", engine.postDataString || "");
+ docElt.setAttribute("searchEngineURL", engine.searchURL);
+ }
+ Services.search.init(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);
+ }
+
+ /* === about:newtab === */
+
+ if (doc.documentURI.toLowerCase() == "about:newtab") {
+
+ let docElt = doc.documentElement;
+
+ function updateSearchEngine() {
+ let engine = AboutHomeUtils.defaultSearchEngine;
+ docElt.setAttribute("searchEngineName", engine.name);
+ docElt.setAttribute("searchEnginePostData", engine.postDataString || "");
+ docElt.setAttribute("searchEngineURL", engine.searchURL);
+ }
+ Services.search.init(updateSearchEngine);
+
+ // Listen for the event that's triggered when the user changes search engine.
+ // At this point we simply reload about:newtab 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);
+ }
+
+}
+
+/**
+ * Handle command events bubbling up from error page content
+ */
+var 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: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 isTopFrame = (aOwnerDoc.defaultView.parent === aOwnerDoc.defaultView);
+
+ switch (elmId) {
+ case "exceptionDialogButton":
+ 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":
+ getMeOutOfHere();
+ break;
+
+ case "technicalContent":
+ break;
+
+ case "expertContent":
+ break;
+
+ }
+ },
+
+ 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 "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() {
+ try {
+ let toBlank = Services.prefs.getBoolPref("browser.escape_to_blank");
+ if (toBlank) {
+ content.location = "about:logopage";
+ return;
+ }
+ } catch(e) {
+ Components.utils.reportError("Couldn't get escape pref: " + e);
+ }
+ // 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.includes("|"))
+ 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() {
+ FullScreen.toggle();
+}
+
+function onMozEnteredDomFullscreen(event) {
+ FullScreen.enterDomFullscreen(event);
+}
+
+function getWebNavigation()
+{
+ return gBrowser.webNavigation;
+}
+
+function BrowserReloadWithFlags(reloadFlags) {
+
+ // Reset DOS mitigation for auth prompts when user initiates a reload.
+ let browser = gBrowser.selectedBrowser;
+ delete browser.authPromptCounter;
+
+ /* 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 () {
+ // We might have accidentally switched tabs since the user invoked print
+ // preview
+ if (gBrowser.selectedTab != this._printPreviewTab) {
+ gBrowser.selectedTab = this._printPreviewTab;
+ }
+ 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();
+ }
+ },
+
+ dropLinks: function (aEvent, aDisallowInherit) {
+ return Services.droppedLinkHandler.dropLinks(aEvent, aDisallowInherit);
+ }
+};
+
+var homeButtonObserver = {
+ onDrop: function (aEvent)
+ {
+ // disallow setting home pages that inherit the principal
+ let links = browserDragAndDrop.dropLinks(aEvent, true);
+ if (links.length) {
+ setTimeout(openHomeDialog, 0, links.map(link => link.url).join("|"));
+ }
+ },
+
+ onDragOver: function (aEvent)
+ {
+ browserDragAndDrop.dragOver(aEvent);
+ aEvent.dropEffect = "link";
+ },
+ onDragExit: function (aEvent)
+ {
+ }
+}
+
+function openHomeDialog(aURL)
+{
+ var promptTitle = gNavigatorBundle.getString("droponhometitle");
+ var promptMsg;
+ if (aURL.includes("|")) {
+ promptMsg = gNavigatorBundle.getString("droponhomemsgMultiple");
+ } else {
+ 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 homepageStr = Components.classes["@mozilla.org/supports-string;1"]
+ .createInstance(Components.interfaces.nsISupportsString);
+ homepageStr.data = aURL;
+ gPrefService.setComplexValue("browser.startup.homepage",
+ Components.interfaces.nsISupportsString, homepageStr);
+ } 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 links = browserDragAndDrop.dropLinks(aEvent);
+ Task.spawn(function*() {
+ for (let link of links) {
+ let data = yield getShortcutOrURIAndPostData(link.url);
+ if (data.url) {
+ // allow third-party services to fixup this URL
+ openNewTabWith(data.url, null, data.postData, aEvent, true);
+ }
+ }
+ });
+ }
+}
+
+var newWindowButtonObserver = {
+ onDragOver: function (aEvent)
+ {
+ browserDragAndDrop.dragOver(aEvent);
+ },
+ onDragExit: function (aEvent)
+ {
+ },
+ onDrop: function (aEvent)
+ {
+ let links = browserDragAndDrop.dropLinks(aEvent);
+ Task.spawn(function*() {
+ for (let link of links) {
+ let data = yield getShortcutOrURIAndPostData(link.url);
+ if (data.url) {
+ // allow third-party services to fixup this URL
+ openNewWindowWith(data.url, null, data.postData, 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, "feed" in rels)) {
+ 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, link.ownerDocument.nodePrincipal);
+ 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 };
+ Services.search.init(function () {
+ 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.showNavToolbox();
+ 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");
+ },
+
+ /**
+ * 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);
+ },
+};
+
+XPCOMUtils.defineConstant(this, "BrowserSearch", BrowserSearch);
+
+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.includes(" ") &&
+ !/[\x00-\x1F]/.test(aUrlToAdd))
+ PlacesUIUtils.markPageAsTyped(aUrlToAdd);
+}
+
+function toJavaScriptConsole()
+{
+ toOpenWindowByType("global:console", "chrome://global/content/console.xul");
+}
+
+function BrowserDownloadsUI()
+{
+ if (PrivateBrowsingUtils.isWindowPrivate(window)) {
+ openUILinkIn("about:downloads", "tab");
+ } else {
+ PlacesCommandHook.showPlacesOrganizer("Downloads");
+ }
+}
+
+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)
+{
+ function newDocumentShown(doc, topic, data) {
+ if (topic == "document-shown" &&
+ doc != document &&
+ doc.defaultView == win) {
+ Services.obs.removeObserver(newDocumentShown, "document-shown");
+ }
+ };
+ 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 = Services.prefs.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) {
+ gURLBarSettings.writePlaceholder();
+ URLBarSetURI();
+ XULBrowserWindow.asyncUpdateUI();
+ BookmarkingUI.updateStarState();
+ }
+
+ 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 (!Services.prefs.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");
+ }
+ }
+}
+
+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"],*/
+ 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);
+ 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 && BrowserUtils.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;
+
+ // If displayed, hide the form validation popup.
+ FormValidationHandler.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 && BrowserUtils.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();
+ }
+
+ // 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();
+ }
+
+ // XXX
+ // See: https://github.com/MoonchildProductions/Pale-Moon/issues/364
+ // An actual preference: findbar.highlightAll
+ /*
+ if (!(gPrefService.getBoolPref("accessibility.typeaheadfind.highlightallremember") ||
+ gPrefService.getBoolPref("accessibility.typeaheadfind.highlightallbydefault"))) {
+ // 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) {
+
+ // 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 (aOpener && isExternal) {
+ Cu.reportError("nsBrowserAccess.openURI did not expect an opener to be " +
+ "passed if the context is OPEN_EXTERNAL.");
+ throw Cr.NS_ERROR_FAILURE;
+ }
+
+ 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");
+ }
+
+ let referrer = aOpener ? makeURI(aOpener.location.href) : null;
+ let triggeringPrincipal = null;
+ let referrerPolicy = Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT;
+ if (aOpener && aOpener.document) {
+ referrerPolicy = aOpener.document.referrerPolicy;
+ triggeringPrincipal = aOpener.document.nodePrincipal;
+ }
+
+ 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 openerWindow = (aContext & Ci.nsIBrowserDOMWindow.OPEN_NO_OPENER) ? null : aOpener;
+
+ let tab = win.gBrowser.loadOneTab(aURI ? aURI.spec : "about:blank", {
+ triggeringPrincipal: triggeringPrincipal,
+ referrerURI: referrer,
+ referrerPolicy: referrerPolicy,
+ fromExternal: isExternal,
+ inBackground: loadInBackground,
+ opener: openerWindow });
+ let browser = win.gBrowser.getBrowserForTab(tab);
+
+ if (gPrefService.getBoolPref("browser.tabs.noWindowActivationOnExternal")) {
+ isExternal = false; // this is a hack, but it works
+ }
+
+ newWindow = browser.contentWindow;
+ if (needToFocusWin || (!loadInBackground && isExternal))
+ newWindow.focus();
+ break;
+ default : // OPEN_CURRENTWINDOW or an illegal value
+ newWindow = content;
+ if (aURI) {
+ let loadflags = isExternal ?
+ Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL :
+ Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
+ gBrowser.loadURIWithFlags(aURI.spec, {
+ flags: loadflags,
+ triggeringPrincipal: triggeringPrincipal,
+ referrerURI: referrer,
+ referrerPolicy: referrerPolicy,
+ });
+ }
+ 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);
+
+ // Customizable toolbars - persist the hiding attribute.
+ if (toolbar.hasAttribute("customindex")) {
+ var toolbox = toolbar.parentNode;
+ var name = toolbar.getAttribute("toolbarname");
+ if (toolbox.toolbarset) {
+ try {
+ // Checking all attributes starting with "toolbar".
+ Array.prototype.slice.call(toolbox.toolbarset.attributes, 0)
+ .find(x => {
+ if (x.name.startsWith("toolbar")) {
+ var toolbarInfo = x.value;
+ var infoSplit = toolbarInfo.split(gToolbarInfoSeparators[0]);
+ if (infoSplit[0] == name) {
+ infoSplit[1] = [
+ infoSplit[1].split(gToolbarInfoSeparators[1], 1), !isVisible
+ ].join(gToolbarInfoSeparators[1]);
+ toolbox.toolbarset.setAttribute(
+ x.name, infoSplit.join(gToolbarInfoSeparators[0]));
+ document.persist(toolbox.toolbarset.id, x.name);
+ }
+ }
+ });
+ } catch (e) {
+ Components.utils.reportError(
+ "Customizable toolbars - persist the hiding attribute: " + e);
+ }
+ }
+ }
+
+ PlacesToolbarHelper.init();
+ BookmarkingUI.onToolbarVisibilityChange();
+ gBrowser.updateWindowResizers();
+
+#ifdef MENUBAR_CAN_AUTOHIDE
+ updateAppButtonDisplay();
+#endif
+
+ if (isVisible)
+ ToolbarIconColor.inferFromText();
+}
+
+var AudioIndicator = {
+ init: function () {
+ Services.prefs.addObserver(this._prefName, this, false);
+ this.syncUI();
+ },
+
+ uninit: function () {
+ Services.prefs.removeObserver(this._prefName, this);
+ },
+
+ toggle: function () {
+ this.enabled = !Services.prefs.getBoolPref(this._prefName);
+ },
+
+ syncUI: function () {
+ document.getElementById("context_toggleMuteTab").setAttribute("hidden", this.enabled);
+ document.getElementById("key_toggleMute").setAttribute("disabled", this.enabled);
+ },
+
+ get enabled () {
+ return !Services.prefs.getBoolPref(this._prefName);
+ },
+
+ 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.showAudioPlayingIcon"
+}
+
+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 = "";
+ }
+
+ ToolbarIconColor.inferFromText();
+ },
+
+ _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);
+}
+
+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 &&
+ (node.localName == "a" ||
+ node.namespaceURI == "http://www.w3.org/1998/Math/MathML")) {
+ href = node.getAttributeNS("http://www.w3.org/1999/xlink", "href");
+ if (href) {
+ baseURI = node.baseURI;
+ break;
+ }
+ }
+ 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,
+ referrerPolicy: doc.referrerPolicy,
+ originPrincipal: doc.nodePrincipal,
+ triggeringPrincipal: doc.nodePrincipal });
+ 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, "");
+
+ // if it's not the current tab, we don't need to do anything because the
+ // browser doesn't exist.
+ let where = whereToOpenLink(event, true, false);
+ let lastLocationChange;
+ if (where == "current") {
+ lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
+ }
+
+ getShortcutOrURIAndPostData(clipboard).then(data => {
+ try {
+ makeURI(data.url);
+ } catch (ex) {
+ // Not a valid URI.
+ return;
+ }
+
+ try {
+ addToUrlbarHistory(data.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);
+ }
+
+ if (where != "current" ||
+ lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) {
+ openUILink(data.url, event,
+ { ignoreButton: true,
+ disallowInheritPrincipal: !data.mayInheritPrincipal,
+ initiatingDoc: event ? event.target.ownerDocument : null });
+ }
+ });
+
+ event.stopPropagation();
+}
+
+// handleDroppedLink has the following 2 overloads:
+// handleDroppedLink(event, url, name)
+// handleDroppedLink(event, links)
+function handleDroppedLink(event, urlOrLinks, name)
+{
+ let links;
+ if (Array.isArray(urlOrLinks)) {
+ links = urlOrLinks;
+ } else {
+ links = [{ url: urlOrLinks, name, type: "" }];
+ }
+
+ let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
+
+ let inBackground = Services.prefs.getBoolPref("browser.tabs.loadInBackground");
+ if (event.shiftKey)
+ inBackground = !inBackground;
+
+ Task.spawn(function*() {
+ let urls = [];
+ let postDatas = [];
+ for (let link of links) {
+ let data = yield getShortcutOrURIAndPostData(link.url);
+ urls.push(data.url);
+ postDatas.push(data.postData);
+ }
+ if (lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) {
+ gBrowser.loadTabs(urls, {
+ inBackground,
+ replace: true,
+ allowThirdPartyFixup: false,
+ postDatas,
+ });
+ }
+ });
+
+ // Keep the event from being handled by the dragDrop listeners
+ // built-in to goanna 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.indexOf('charset.') + '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(uri.indexOf('chardet.') + 'chardet.'.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.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 charsetLoadListener() {
+ var charset = window.content.document.characterSet;
+
+ if (charset.length > 0 && (charset != gLastBrowserCharset)) {
+ 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 BrowserOpenPermissionsMgr() {
+ switchToTabHavingURI("about:permissions", true);
+}
+
+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 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,
+ learnMoreURL: Services.urlFormatter.formatURLPref("browser.mixedcontent.warning.infoURL"),
+ };
+ 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() {
+ 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";
+
+ if (!this._IDNService)
+ this._IDNService = Cc["@mozilla.org/network/idn-service;1"]
+ .getService(Ci.nsIIDNService);
+ let punyID = gPrefService.getIntPref("browser.identity.display_punycode", 1);
+
+ switch (newMode) {
+ case this.IDENTITY_MODE_DOMAIN_VERIFIED: {
+ let iData = this.getIdentityData();
+
+ let label_display = "";
+
+ //Pale Moon: honor browser.identity.ssl_domain_display!
+ switch (gPrefService.getIntPref("browser.identity.ssl_domain_display")) {
+ case 2 : // Show full domain
+ label_display = this._lastLocation.hostname;
+ break;
+ case 1 : // Show eTLD.
+ label_display = this.getEffectiveHost();
+ }
+
+ if (punyID >= 1) {
+ // Display punycode version in identity panel
+ icon_label = this._IDNService.convertUTF8toACE(label_display);
+ } else {
+ icon_label = label_display;
+ }
+
+ // 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");
+ if (punyID == 2) {
+ // Check for IDN and display if so...
+ let rawHost = this._IDNService.convertUTF8toACE(this._lastLocation.hostname);
+ if (this._IDNService.isACE(rawHost)) {
+ icon_label = rawHost;
+ }
+ }
+ }
+
+ // 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) {
+ event.stopPropagation();
+
+ if ((event.type == "click" && event.button != 0) ||
+ (event.type == "keypress" && event.charCode != KeyEvent.DOM_VK_SPACE &&
+ event.keyCode != KeyEvent.DOM_VK_RETURN)) {
+ 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") {
+ 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) {
+ 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;
+
+var 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,
+ _updateToggleMuteMenuItem(aTab, aConditionFn) {
+ ["muted", "soundplaying"].forEach(attr => {
+ if (!aConditionFn || aConditionFn(attr)) {
+ if (aTab.hasAttribute(attr)) {
+ aTab.toggleMuteMenuItem.setAttribute(attr, "true");
+ } else {
+ aTab.toggleMuteMenuItem.removeAttribute(attr);
+ }
+ }
+ });
+ },
+ 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();
+
+ // Adjust the state of the toggle mute menu item.
+ let toggleMute = document.getElementById("context_toggleMuteTab");
+ if (this.contextTab.hasAttribute("muted")) {
+ toggleMute.label = gNavigatorBundle.getString("unmuteTab.label");
+ toggleMute.accessKey = gNavigatorBundle.getString("unmuteTab.accesskey");
+ } else {
+ toggleMute.label = gNavigatorBundle.getString("muteTab.label");
+ toggleMute.accessKey = gNavigatorBundle.getString("muteTab.accesskey");
+ }
+
+ this.contextTab.toggleMuteMenuItem = toggleMute;
+ this._updateToggleMuteMenuItem(this.contextTab);
+
+ this.contextTab.addEventListener("TabAttrModified", this, false);
+ aPopupMenu.addEventListener("popuphiding", this, false);
+ },
+ handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "popuphiding":
+ gBrowser.removeEventListener("TabAttrModified", this);
+ aEvent.target.removeEventListener("popuphiding", this);
+ break;
+ case "TabAttrModified":
+ let tab = aEvent.target;
+ this._updateToggleMuteMenuItem(tab,
+ attr => aEvent.detail.changed.indexOf(attr) >= 0);
+ break;
+ }
+ }
+};
+
+#ifdef MOZ_DEVTOOLS
+// Note: Do not delete! It is used for: base/content/nsContextMenu.js
+XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
+ "resource://devtools/client/framework/gDevTools.jsm");
+#endif
+
+// Prompt user to restart the browser in safe mode or normally
+function restart(safeMode)
+{
+ let promptTitleString = null;
+ let promptMessageString = null;
+ let restartTextString = null;
+ if (safeMode) {
+ promptTitleString = "safeModeRestartPromptTitle";
+ promptMessageString = "safeModeRestartPromptMessage";
+ restartTextString = "safeModeRestartButton";
+ } else {
+ promptTitleString = "restartPromptTitle";
+ promptMessageString = "restartPromptMessage";
+ restartTextString = "restartButton";
+ }
+
+ let flags = Ci.nsIAppStartup.eAttemptQuit;
+
+ // Prompt the user to confirm
+ let promptTitle = gNavigatorBundle.getString(promptTitleString);
+ let brandBundle = document.getElementById("bundle_brand");
+ let brandShortName = brandBundle.getString("brandShortName");
+ let promptMessage =
+ gNavigatorBundle.getFormattedString(promptMessageString, [brandShortName]);
+ let restartText = gNavigatorBundle.getString(restartTextString);
+ 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) {
+ // Notify all windows that an application quit has been requested.
+ let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"]
+ .createInstance(Ci.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
+
+ // Something aborted the quit process.
+ if (cancelQuit.data) {
+ return;
+ }
+
+ if (safeMode) {
+ Services.startup.restartInSafeMode(flags);
+ } else {
+ Services.startup.quit(flags | Ci.nsIAppStartup.eRestart);
+ }
+ }
+}
+
+/* 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);
+}
+
+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();
+ }
+ }
+};
+
+var 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;
+ }
+};
+
+var ToolbarIconColor = {
+ init: function () {
+ this._initialized = true;
+
+ window.addEventListener("activate", this);
+ window.addEventListener("deactivate", this);
+ Services.obs.addObserver(this, "lightweight-theme-styling-update", false);
+ gPrefService.addObserver("ui.colorChanged", this, false);
+
+ // If the window isn't active now, we assume that it has never been active
+ // before and will soon become active such that inferFromText will be
+ // called from the initial activate event.
+ if (Services.focus.activeWindow == window)
+ this.inferFromText();
+ },
+
+ uninit: function () {
+ this._initialized = false;
+
+ window.removeEventListener("activate", this);
+ window.removeEventListener("deactivate", this);
+ Services.obs.removeObserver(this, "lightweight-theme-styling-update");
+ gPrefService.removeObserver("ui.colorChanged", this);
+ },
+
+ handleEvent: function (event) {
+ switch (event.type) {
+ case "activate":
+ case "deactivate":
+ this.inferFromText();
+ break;
+ }
+ },
+
+ observe: function (aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "lightweight-theme-styling-update":
+ // inferFromText needs to run after LightweightThemeConsumer.jsm's
+ // lightweight-theme-styling-update observer.
+ setTimeout(() => { this.inferFromText(); }, 0);
+ break;
+ case "nsPref:changed":
+ // system color change
+ var colorChangedPref = false;
+ try {
+ colorChangedPref = gPrefService.getBoolPref("ui.colorChanged");
+ } catch(e) { }
+ // if pref indicates change, call inferFromText() on a small delay
+ if (colorChangedPref == true)
+ setTimeout(() => { this.inferFromText(); }, 300);
+ break;
+ default:
+ console.error("ToolbarIconColor: Uncaught topic " + aTopic);
+ }
+ },
+
+ inferFromText: function () {
+ if (!this._initialized)
+ return;
+
+ function parseRGB(aColorString) {
+ let rgb = aColorString.match(/^rgba?\((\d+), (\d+), (\d+)/);
+ rgb.shift();
+ return rgb.map(x => parseInt(x));
+ }
+
+ let toolbarSelector = "toolbar:not([collapsed=true])";
+#ifdef XP_MACOSX
+ toolbarSelector += ":not([type=menubar])";
+#endif
+
+ // The getComputedStyle calls and setting the brighttext are separated in
+ // two loops to avoid flushing layout and making it dirty repeatedly.
+
+ let luminances = new Map;
+ for (let toolbar of document.querySelectorAll(toolbarSelector)) {
+ let [r, g, b] = parseRGB(getComputedStyle(toolbar).color);
+ let luminance = (2 * r + 5 * g + b) / 8;
+ luminances.set(toolbar, luminance);
+ }
+
+ for (let [toolbar, luminance] of luminances) {
+ if (luminance <= 128)
+ toolbar.removeAttribute("brighttext");
+ else
+ toolbar.setAttribute("brighttext", "true");
+ }
+
+ // Clear pref if set, since we're done applying the color changes.
+ gPrefService.clearUserPref("ui.colorChanged");
+ }
+}
diff --git a/base/content/browser.xul b/base/content/browser.xul
new file mode 100644
index 0000000..0a249af
--- /dev/null
+++ b/base/content/browser.xul
@@ -0,0 +1,1046 @@
+#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"?>
+
+# 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"?>
+#ifdef MOZ_DEVTOOLS
+<?xml-stylesheet href="chrome://devtools/skin/devtools-browser.css" type="text/css"?>
+#endif
+<?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"?>
+
+# Padlock feature
+<?xul-overlay href="chrome://browser/content/padlock.xul"?>
+# Improve bookmark menu dragging
+<?xul-overlay href="chrome://browser/content/browser-menudragging.xul"?>
+# Automatic browser recovery
+<?xul-overlay href="chrome://browser/content/autorecovery.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;"
+ title_normal="&mainWindow.title;"
+#ifdef XP_MACOSX
+ title_privatebrowsing="&mainWindow.title;&mainWindow.titlemodifiermenuseparator;&mainWindow.titlePrivateBrowsingSuffix;"
+ titledefault="&mainWindow.title;"
+ titlemodifier=""
+ titlemodifier_normal=""
+ titlemodifier_privatebrowsing="&mainWindow.titlePrivateBrowsingSuffix;"
+#else
+ title_privatebrowsing="&mainWindow.titlemodifier; &mainWindow.titlePrivateBrowsingSuffix;"
+ titlemodifier="&mainWindow.titlemodifier;"
+ titlemodifier_normal="&mainWindow.titlemodifier;"
+ titlemodifier_privatebrowsing="&mainWindow.titlemodifier; &mainWindow.titlePrivateBrowsingSuffix;"
+#endif
+ titlemenuseparator="&mainWindow.titlemodifiermenuseparator;"
+#ifdef MOZ_PERSONAS
+ lightweightthemes="true"
+ lightweightthemesfooter="browser-bottombox"
+#endif
+ windowtype="navigator:browser"
+ macanimationtype="document"
+ screenX="4" screenY="4"
+ fullscreenbutton="true"
+ retargetdocumentfocus="urlbar"
+ 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
+#ifdef MOZ_DEVTOOLS
+#include global-devtools-theme-scripts.inc
+#endif
+<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);"/>
+ <menuitem id="context_toggleMuteTab" oncommand="TabContextMenu.contextTab.toggleMuteAudio();"/>
+ <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"/>
+ <panel type="private-autocomplete" id="PopupAutoComplete" noautofocus="true" hidden="true"/>
+
+ <!-- for url bar autocomplete -->
+ <panel type="autocomplete-richlistbox" id="PopupAutoCompleteRichResult" noautofocus="true" hidden="true"/>
+ <panel type="private-autocomplete-richlistbox" id="PopupAutoCompleteRichResult" noautofocus="true" hidden="true"/>
+
+ <!-- for date/time picker. consumeoutsideclicks is set to never, so that
+ clicks on the anchored input box are never consumed. -->
+ <panel id="DateTimePickerPanel"
+ type="arrow"
+ hidden="true"
+ orient="vertical"
+ noautofocus="true"
+ norolluponanchor="true"
+ consumeoutsideclicks="never"
+ level="parent"
+ tabspecific="true">
+ <iframe id="dateTimePopupFrame"/>
+ </panel>
+
+ <!-- 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"
+ 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>
+
+ <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 close-icon"
+ 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;"
+ tooltiptext="&appMenuButton.tooltip;"
+ style="-moz-user-focus: ignore;">
+#include browser-appmenu.inc
+ </button>
+ </hbox>
+#endif
+#ifndef XP_MACOSX
+ <spacer id="titlebar-spacer" flex="1"/>
+#endif
+ <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,history-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=""
+ type="private-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="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="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="mixed-content-blocked-notification-icon" class="notification-anchor-icon" role="button"/>
+#ifdef MOZ_WEBRTC
+ <image id="webRTC-shareDevices-notification-icon" class="notification-anchor-icon" role="button"/>
+ <image id="webRTC-sharingDevices-notification-icon" class="notification-anchor-icon" role="button"/>
+#endif
+ <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>
+#ifdef MOZ_WEBRTC
+ <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>
+#endif
+ <toolbarbutton id="bookmarks-menu-button"
+ class="toolbarbutton-1 chromeclass-toolbar-additional"
+ persist="class"
+ removable="true"
+ type="menu"
+ label="&bookmarksMenuButton.label;"
+ tooltiptext="&bookmarksMenuButton.tooltip;"
+ onclick="if (event.button == 1)
+ toggleSidebar('viewBookmarksSidebar');"
+ 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="event.stopPropagation();
+ 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"
+#ifndef XP_MACOSX
+ class="menuitem-iconic"
+#endif
+ label="&organizeBookmarks.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"
+ class="menuitem-iconic"
+ label="&bookmarksMenuButton.unsorted.label;"
+ oncommand="PlacesCommandHook.showPlacesOrganizer('UnfiledBookmarks');"/>
+ </menupopup>
+ </toolbarbutton>
+
+ <toolbarbutton id="history-menu-button"
+ class="toolbarbutton-1 chromeclass-toolbar-additional"
+ persist="class"
+ removable="true"
+ type="menu"
+ label="&historyButton.label;"
+ tooltiptext="&historyButton.tooltip;"
+ onclick="if (event.button == 1)
+ toggleSidebar('viewHistorySidebar');">
+ <menupopup id="HMB_historyPopup"
+ placespopup="true"
+ context="placesContext"
+ oncommand="this.parentNode._placesView._onCommand(event);"
+ onclick="event.stopPropagation();
+ checkForMiddleClick(this, event);"
+ onpopupshowing="if (!this.parentNode._placesView)
+ new HistoryMenu(event);"
+ tooltip="bhTooltip"
+ popupsinherittooltip="true">
+ <menuitem id="HMB_showAllHistory"
+ label="&showAllHistoryCmd2.label;"
+#ifndef XP_MACOSX
+ class="menuitem-iconic"
+ key="showAllHistoryKb"
+#endif
+ command="Browser:ShowAllHistory"/>
+ <menuitem id="HMB_sanitizeItem"
+#ifndef XP_MACOSX
+ class="menuitem-iconic"
+#endif
+ label="&clearRecentHistory.label;"
+ key="key_sanitize"
+ command="Tools:Sanitize"/>
+ <menuseparator id="HMB_sanitizeSeparator"/>
+#ifdef MOZ_SERVICES_SYNC
+ <menuitem id="HMB_sync-tabs-menuitem"
+ class="syncTabsMenuItem"
+ label="&syncTabsMenu2.label;"
+ oncommand="BrowserOpenSyncTabs();"
+ disabled="true"/>
+#endif
+ <menuitem id="HMB_historyRestoreLastSession"
+ label="&historyRestoreLastSession.label;"
+ command="Browser:RestoreLastSession"/>
+ <menu id="HMB_historyUndoMenu"
+ class="recentlyClosedTabsMenu"
+ label="&historyUndoMenu.label;"
+ disabled="true">
+ <menupopup id="HMB_historyUndoPopup"
+ placespopup="true"
+ onpopupshowing="document.getElementById('history-menu-button')._placesView.populateUndoSubmenu();"/>
+ </menu>
+ <menu id="HMB_historyUndoWindowMenu"
+ class="recentlyClosedWindowsMenu"
+ label="&historyUndoWindowMenu.label;"
+ disabled="true">
+ <menupopup id="HMB_historyUndoWindowPopup"
+ placespopup="true"
+ onpopupshowing="document.getElementById('history-menu-button')._placesView.populateUndoWindowSubmenu();"/>
+ </menu>
+ <menuseparator id="HMB_startHistorySeparator"
+ class="hide-if-empty-places-result"/>
+ <!-- History menu items -->
+ </menupopup>
+ </toolbarbutton>
+
+ <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">
+ <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 close-icon"
+ 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" label="&bookmarksButton.label;"
+ 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" hidden="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 close-icon" 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"
+ datetimepicker="DateTimePickerPanel"
+ authdosprotected="true"/>
+ <chatbar id="pinnedchats" layer="true" mousethrough="always" hidden="true"/>
+ <statuspanel id="statusbar-display" inactive="true"/>
+ </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>
+ </hbox>
+ </hbox>
+
+ <vbox id="browser-bottombox" layer="true">
+ <notificationbox id="global-notificationbox"/>
+ <toolbar id="addon-bar"
+ toolbarname="&statusBar.label;" accesskey="&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"
+ class="close-icon"
+ 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/base/content/browserMountPoints.inc b/base/content/browserMountPoints.inc
new file mode 100644
index 0000000..e4315b0
--- /dev/null
+++ b/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/base/content/content.js b/base/content/content.js
new file mode 100644
index 0000000..211a24a
--- /dev/null
+++ b/base/content/content.js
@@ -0,0 +1,177 @@
+/* -*- 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 Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
+ "resource://gre/modules/BrowserUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent",
+ "resource://gre/modules/LoginManagerContent.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "LoginFormFactory",
+ "resource://gre/modules/LoginManagerContent.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
+ "resource://gre/modules/InsecurePasswordUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FormSubmitObserver",
+ "resource:///modules/FormSubmitObserver.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);
+});
+
+// TabChildGlobal
+var global = this;
+
+// Load the form validation popup handler
+var formSubmitObserver = new FormSubmitObserver(content, this);
+
+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("DOMFormHasPassword", function(event) {
+ LoginManagerContent.onDOMFormHasPassword(event, content);
+ let formLike = LoginFormFactory.createFromForm(event.target);
+ InsecurePasswordUtils.reportInsecurePasswords(formLike);
+});
+addEventListener("DOMAutoComplete", function(event) {
+ LoginManagerContent.onUsernameInput(event);
+});
+addEventListener("blur", function(event) {
+ LoginManagerContent.onUsernameInput(event);
+});
+
+// Provide gContextMenuContentData for 'sdk/context-menu'
+var handleContentContextMenu = function (event) {
+ let defaultPrevented = event.defaultPrevented;
+ if (!Services.prefs.getBoolPref("dom.event.contextmenu.enabled")) {
+ let plugin = null;
+ try {
+ plugin = event.target.QueryInterface(Ci.nsIObjectLoadingContent);
+ } catch (e) {}
+ if (plugin && plugin.displayedType == Ci.nsIObjectLoadingContent.TYPE_PLUGIN) {
+ // Don't open a context menu for plugins.
+ return;
+ }
+
+ defaultPrevented = false;
+ }
+
+ if (defaultPrevented)
+ return;
+
+ let addonInfo = {};
+ let subject = {
+ event: event,
+ addonInfo: addonInfo,
+ };
+ subject.wrappedJSObject = subject;
+ Services.obs.notifyObservers(subject, "content-contextmenu", null);
+
+ let doc = event.target.ownerDocument;
+ let docLocation = doc.mozDocumentURIIfNotForErrorPages;
+ docLocation = docLocation && docLocation.spec;
+ let charSet = doc.characterSet;
+ let baseURI = doc.baseURI;
+ let referrer = doc.referrer;
+ let referrerPolicy = doc.referrerPolicy;
+ let frameOuterWindowID = doc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .outerWindowID;
+ let loginFillInfo = LoginManagerContent.getFieldContext(event.target);
+
+ // The same-origin check will be done in nsContextMenu.openLinkInTab.
+ let parentAllowsMixedContent = !!docShell.mixedContentChannel;
+
+ // get referrer attribute from clicked link and parse it
+ // if per element referrer is enabled, the element referrer overrules
+ // the document wide referrer
+ if (Services.prefs.getBoolPref("network.http.enablePerElementReferrer")) {
+ let referrerAttrValue = Services.netUtils.parseAttributePolicyString(event.target.
+ getAttribute("referrerpolicy"));
+ if (referrerAttrValue !== Ci.nsIHttpChannel.REFERRER_POLICY_UNSET) {
+ referrerPolicy = referrerAttrValue;
+ }
+ }
+
+ // Media related cache info parent needs for saving
+ let contentType = null;
+ let contentDisposition = null;
+ if (event.target.nodeType == Ci.nsIDOMNode.ELEMENT_NODE &&
+ event.target instanceof Ci.nsIImageLoadingContent &&
+ event.target.currentURI) {
+
+ try {
+ let imageCache =
+ Cc["@mozilla.org/image/tools;1"].getService(Ci.imgITools)
+ .getImgCacheForDocument(doc);
+ let props =
+ imageCache.findEntryProperties(event.target.currentURI, doc);
+ try {
+ contentType = props.get("type", Ci.nsISupportsCString).data;
+ } catch (e) {}
+ try {
+ contentDisposition =
+ props.get("content-disposition", Ci.nsISupportsCString).data;
+ } catch (e) {}
+ } catch (e) {}
+ }
+
+ let selectionInfo = BrowserUtils.getSelectionDetails(content);
+
+ let loadContext = docShell.QueryInterface(Ci.nsILoadContext);
+
+ let browser = docShell.chromeEventHandler;
+ let mainWin = browser.ownerGlobal;
+
+ mainWin.gContextMenuContentData = {
+ isRemote: false,
+ event: event,
+ popupNode: event.target,
+ browser: browser,
+ addonInfo: addonInfo,
+ documentURIObject: doc.documentURIObject,
+ docLocation: docLocation,
+ charSet: charSet,
+ referrer: referrer,
+ referrerPolicy: referrerPolicy,
+ contentType: contentType,
+ contentDisposition: contentDisposition,
+ selectionInfo: selectionInfo,
+ loginFillInfo,
+ parentAllowsMixedContent,
+ };
+}
+
+Cc["@mozilla.org/eventlistenerservice;1"]
+ .getService(Ci.nsIEventListenerService)
+ .addSystemEventListener(global, "contextmenu", handleContentContextMenu, false);
+
+// Lazily load the finder code
+addMessageListener("Finder:Initialize", function () {
+ let {RemoteFinderListener} = Cu.import("resource://gre/modules/RemoteFinder.jsm", {});
+ new RemoteFinderListener(global);
+});
+
+addEventListener("DOMWebNotificationClicked", function(event) {
+ sendAsyncMessage("DOMWebNotificationClicked", {});
+}, false);
diff --git a/base/content/downloadManagerOverlay.xul b/base/content/downloadManagerOverlay.xul
new file mode 100644
index 0000000..9987820
--- /dev/null
+++ b/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/base/content/global-devtools-theme-scripts.inc b/base/content/global-devtools-theme-scripts.inc
new file mode 100644
index 0000000..408728e
--- /dev/null
+++ b/base/content/global-devtools-theme-scripts.inc
@@ -0,0 +1,6 @@
+# -*- 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://browser/content/browser-devtools-theme.js"/>
diff --git a/base/content/global-scripts.inc b/base/content/global-scripts.inc
new file mode 100644
index 0000000..b4de574
--- /dev/null
+++ b/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/base/content/hiddenWindow.xul b/base/content/hiddenWindow.xul
new file mode 100644
index 0000000..bf201fd
--- /dev/null
+++ b/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/base/content/highlighter.css b/base/content/highlighter.css
new file mode 100644
index 0000000..8fb9d80
--- /dev/null
+++ b/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/base/content/jsConsoleOverlay.xul b/base/content/jsConsoleOverlay.xul
new file mode 100644
index 0000000..1bc518d
--- /dev/null
+++ b/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/base/content/macBrowserOverlay.xul b/base/content/macBrowserOverlay.xul
new file mode 100644
index 0000000..b1ae838
--- /dev/null
+++ b/base/content/macBrowserOverlay.xul
@@ -0,0 +1,67 @@
+<?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
+#ifdef MOZ_DEVTOOLS
+#include global-devtools-theme-scripts.inc
+#endif
+
+<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/base/content/nsContextMenu.js b/base/content/nsContextMenu.js
new file mode 100644
index 0000000..916dd26
--- /dev/null
+++ b/base/content/nsContextMenu.js
@@ -0,0 +1,1603 @@
+# 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");
+
+var gContextMenuContentData = null;
+
+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.isContentSelected = this.isContentSelection();
+ this.onPlainTextLink = false;
+
+ // Initialize (disable/remove) menu items.
+ this.initItems();
+ },
+
+ hiding: function CM_hiding() {
+ gContextMenuContentData = null;
+ 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\d]{2,}[-_=~:#%&\?\w\/\.]*$/i.test(linkText)) {
+ 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", shouldShow);
+ 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.showItem("context-back", shouldShow);
+ this.showItem("context-forward", shouldShow);
+
+ let stopped = XULBrowserWindow.stopCommand.getAttribute("disabled") == "true";
+
+ let stopReloadItem = "";
+ if (shouldShow) {
+ stopReloadItem = stopped ? "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);
+ this.showItem("context-viewsource", shouldShow);
+ this.showItem("context-viewinfo", shouldShow);
+#ifdef MOZ_DEVTOOLS
+ var showInspect = gPrefService.getBoolPref("devtools.inspector.enabled");
+ this.showItem("inspect-separator", showInspect);
+ this.showItem("context-inspect", showInspect);
+#endif
+
+ 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() {
+ // 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.showItem("context-bookmarklink", (this.onLink && !this.onMailtoLink) ||
+ this.onPlainTextLink);
+ this.showItem("context-keywordfield",
+ this.onTextInput && this.onKeywordField);
+ this.showItem("frame", this.inFrame);
+
+ let showSearchSelect = (this.isTextSelected || this.onLink) && !this.onImage;
+ this.showItem("context-searchselect", showSearchSelect);
+ if (showSearchSelect) {
+ this.formatSearchContextItem();
+ }
+
+ // srcdoc cannot be opened separately due to concerns about web
+ // content with about:srcdoc in location bar masquerading as trusted
+ // chrome/addon content.
+ // No need to also test for this.inFrame as this is checked in the parent
+ // submenu.
+ this.showItem("context-showonlythisframe", !this.inSrcdocFrame);
+ this.showItem("context-openframeintab", !this.inSrcdocFrame);
+ this.showItem("context-openframe", !this.inSrcdocFrame);
+ this.showItem("context-bookmarkframe", !this.inSrcdocFrame);
+ this.showItem("open-frame-sep", !this.inSrcdocFrame);
+
+ this.showItem("frame-sep", this.inFrame && this.isTextSelected);
+
+ // Hide menu entries for images, show otherwise
+ if (this.inFrame) {
+ if (BrowserUtils.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);
+ },
+
+ 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-loop", 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);
+ this.setItemAttr("context-media-loop", "checked", this.target.loop);
+ 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://devtools/shared/Loader.jsm", {});
+ let gBrowser = this.browser.ownerDocument.defaultView.gBrowser;
+ let target = devtools.TargetFactory.forTab(gBrowser.selectedTab);
+
+ return gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
+ let inspector = toolbox.getCurrentPanel();
+
+ this.browser.messageManager.sendAsyncMessage("debug:inspect", {}, {node: this.target});
+ inspector.walker.findInspectingNode().then(nodeFront => {
+ inspector.selection.setNodeFront(nodeFront, "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.linkDownload = "";
+ this.onMathML = false;
+ this.inFrame = false;
+ this.inSrcdocFrame = false;
+ this.inSyntheticDoc = false;
+ this.hasBGImage = false;
+ this.bgImageURL = "";
+ this.onEditableArea = false;
+ this.isDesignMode = false;
+ this.onCTPPlugin = false;
+ this.canSpellCheck = false;
+ this.textSelected = getBrowserSelection();
+ this.isTextSelected = this.textSelected.length != 0;
+
+ // 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;
+
+ // 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.displayedType == HTMLObjectElement.TYPE_NULL &&
+ this.target.pluginFallbackType == HTMLObjectElement.PLUGIN_CLICK_TO_PLAY) {
+ 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 );
+ try {
+ if (elem.download) {
+ // Ignore download attribute on cross-origin links?
+ // This shoudn't be an issue because the download link presents
+ // the originating URL domain and protocol to help user understand
+ // from where file is downloaded and make right decision.
+ // If we decide we want this restriction:
+ // this.principal.checkMayLoad(this.linkURI, false, true);
+ this.linkDownload = elem.download;
+ }
+ }
+ catch (ex) {}
+ }
+
+ // 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 (this.target.ownerDocument.isSrcdocDocument) {
+ this.inSrcdocFrame = 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.inSrcdocFrame = 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,
+ referrerPolicy: doc.referrerPolicy,
+ originPrincipal: doc.nodePrincipal,
+ triggeringPrincipal: doc.nodePrincipal });
+ },
+
+ // 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,
+ referrerPolicy: doc.referrerPolicy,
+ originPrincipal: doc.nodePrincipal,
+ triggeringPrincipal: doc.nodePrincipal,
+ 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,
+ referrerPolicy: doc.referrerPolicy,
+ originPrincipal: doc.nodePrincipal,
+ triggeringPrincipal: doc.nodePrincipal });
+ },
+
+ // 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,
+ originPrincipal: doc.nodePrincipal,
+ triggeringPrincipal: doc.nodePrincipal });
+ },
+
+ // 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) {
+ BrowserReloadOrDuplicate(event);
+ },
+
+ // View Partial Source
+ viewPartialSource: function(aContext) {
+ let target = aContext == "mathml" ? this.target : null;
+ top.gViewSourceUtils.viewPartialSourceInBrowser(gBrowser.selectedBrowser, target);
+ },
+
+ // 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;
+ var doc = this.target.ownerDocument;
+ if (this.onCanvas) {
+ var target = this.target;
+ var win = doc.defaultView;
+ if (!win) {
+ Components.utils.reportError(
+ "View Image (on the <canvas> element):\n" +
+ "This feature cannot be used, because it hasn't found " +
+ "an appropriate window.");
+ } else {
+ new Promise.resolve({then: function (resolve) {
+ target.toBlob((blob) => {
+ resolve(win.URL.createObjectURL(blob));
+ })
+ }}).then(function (blobURL) {
+ openUILink(blobURL, e, { disallowInheritPrincipal: true,
+ referrerURI: doc.documentURIObject });
+ }, Components.utils.reportError);
+ }
+ } else {
+ viewURL = this.mediaURL;
+ urlSecurityCheck(viewURL,
+ this.browser.contentPrincipal,
+ Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
+ let doc = this.target.ownerDocument;
+ openUILink(viewURL, e, { disallowInheritPrincipal: true,
+ referrerURI: doc.documentURIObject,
+ forceAllowDataURI: true });
+ }
+ },
+
+ saveVideoFrameAsImage: function () {
+ let referrerURI = document.documentURIObject;
+ let isPrivate = PrivateBrowsingUtils.isBrowserPrivate(this.browser);
+
+ 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, referrerURI, null, null, null,
+ isPrivate);
+ },
+
+ 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,
+ linkDownload) {
+ // 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;
+ }
+ }
+
+ // setting up a new channel for 'right click - save link as ...'
+ var channel = NetUtil.newChannel({
+ uri: makeURI(linkURL),
+ loadingPrincipal: this.target.ownerDocument.nodePrincipal,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_SAVEAS_DOWNLOAD,
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS,
+ });
+
+ if (linkDownload)
+ channel.contentDispositionFilename = linkDownload;
+ 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.asyncOpen2(new saveAsListener());
+ },
+
+ // 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,
+ this.linkDownload);
+ },
+
+ 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;
+ let referrerURI = doc.documentURIObject;
+ let isPrivate = PrivateBrowsingUtils.isBrowserPrivate(this.browser);
+ if (this.onCanvas) {
+ // Bypass cache, since it's a blob: URL.
+ var target = this.target;
+ var win = doc.defaultView;
+ if (!win) {
+ Components.utils.reportError(
+ "Save Image As (on the <canvas> element):\n" +
+ "This feature cannot be used, because it hasn't found " +
+ "an appropriate window.");
+ } else {
+ new Promise.resolve({then: function (resolve) {
+ target.toBlob((blob) => {
+ resolve(win.URL.createObjectURL(blob));
+ })
+ }}).then(function (blobURL) {
+ saveImageURL(blobURL, "canvas.png", "SaveImageTitle",
+ true, false, referrerURI, null, null, null,
+ isPrivate);
+ }, Components.utils.reportError);
+ }
+ } else if (this.onImage) {
+ urlSecurityCheck(this.mediaURL, doc.nodePrincipal);
+ saveImageURL(this.mediaURL, null, "SaveImageTitle",
+ false, false, referrerURI, doc, null, null,
+ isPrivate);
+ } 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;
+ },
+
+ // 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);
+ }
+ },
+
+ 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 "loop":
+ media.loop = !media.loop;
+ 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 "";
+ },
+
+ // Formats the 'Search <engine> for "<selection or link text>"' context menu.
+ formatSearchContextItem: function() {
+ var menuItem = document.getElementById("context-searchselect");
+ var selectedText = this.isTextSelected ? this.textSelected : this.linkText();
+
+ // Store searchTerms in context menu item so we know what to search onclick
+ menuItem.searchTerms = selectedText;
+
+ 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]);
+ menuItem.label = menuLabel;
+ menuItem.accessKey = gNavigatorBundle.getString("contextMenuSearch.accesskey");
+ }
+};
diff --git a/base/content/openLocation.js b/base/content/openLocation.js
new file mode 100644
index 0000000..7ad2f7c
--- /dev/null
+++ b/base/content/openLocation.js
@@ -0,0 +1,150 @@
+/* -*- 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;
+var 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);
+var 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 openData = {
+ "url": null,
+ "postData": null,
+ "mayInheritPrincipal": false
+ };
+ if (browser) {
+ browser.getShortcutOrURIAndPostData(dialog.input.value).then(data => {
+ openData.url = data.url;
+ openData.postData = data.postData;
+ openData.mayInheritPrincipal = data.mayInheritPrincipal;
+
+ openLocation(openData);
+ });
+ } else {
+ openData.url = dialog.input.value;
+
+ openLocation(openData);
+ }
+
+ return false;
+}
+
+function openLocation(openData)
+{
+ try {
+ // Whichever target we use for the load, we allow third-party services to
+ // fix up the URI
+ switch (dialog.openWhereList.value) {
+ case "0":
+ var webNav = Components.interfaces.nsIWebNavigation;
+ var flags = webNav.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP |
+ webNav.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
+ if (!openData.mayInheritPrincipal)
+ flags |= webNav.LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL;
+ browser.gBrowser.loadURIWithFlags(
+ openData.url, flags, null, null, openData.postData);
+ break;
+ case "1":
+ window.opener.delayedOpenWindow(getBrowserURL(), "all,dialog=no",
+ openData.url, openData.postData,
+ null, null, true);
+ break;
+ case "3":
+ browser.delayedOpenTab(
+ openData.url, null, null, openData.postData, true);
+ break;
+ }
+ } catch (ex) {}
+
+ if (pref) {
+ gOpenLocationLastURL.value = dialog.input.value;
+ pref.setIntPref(
+ "general.open_location.last_window_choice", dialog.openWhereList.value);
+ }
+
+ window.close();
+}
+
+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/base/content/openLocation.xul b/base/content/openLocation.xul
new file mode 100644
index 0000000..7bafed0
--- /dev/null
+++ b/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/base/content/overrides/app-license.html b/base/content/overrides/app-license.html
new file mode 100644
index 0000000..2d2e3d5
--- /dev/null
+++ b/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.palemoon.org/">Pale Moon Project</a> under the Pale Moon
+ Binary <a href="http://www.palemoon.org/redist.shtml">redistribution license</a>.</p> \ No newline at end of file
diff --git a/base/content/padlock.css b/base/content/padlock.css
new file mode 100644
index 0000000..649cb27
--- /dev/null
+++ b/base/content/padlock.css
@@ -0,0 +1,203 @@
+#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"],
+#padlock-ib[padshow="ib-trans-bg"][level="mixed"] {
+ 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"],
+#padlock-ib-left[padshow="ib-left"][level="mixed"] {
+ 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"],
+#padlock-ub-right[padshow="ub-right"][level="mixed"] {
+ 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"],
+#padlock-sb[padshow="statbar"][level="mixed"] {
+ 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"],
+#padlock-tab[padshow="tabs-bar"][level="mixed"] {
+ 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"],
+#padlock-ib[padshow="ib-trans-bg"][padstyle="classic"][level="mixed"],
+#padlock-ib-left[padshow="ib-left"][padstyle="classic"][level="mixed"],
+#padlock-ub-right[padshow="ub-right"][padstyle="classic"][level="mixed"],
+#padlock-sb[padshow="statbar"][padstyle="classic"][level="mixed"],
+#padlock-tab[padshow="tabs-bar"][padstyle="classic"][level="mixed"] {
+ list-style-image: url("chrome://browser/content/padlock_classic_low.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/base/content/padlock.js b/base/content/padlock.js
new file mode 100644
index 0000000..9c29524
--- /dev/null
+++ b/base/content/padlock.js
@@ -0,0 +1,234 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var 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 | wpl.STATE_SECURE_LOW:
+ level = "mixed";
+ is_insecure = "insecure";
+ highlight_urlbar = true;
+ 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 (ub) { // Only call if URL bar is present.
+ if (highlight_urlbar) {
+ ub.setAttribute("security_level", level);
+ } else {
+ ub.removeAttribute("security_level");
+ }
+ }
+
+ try { // URL bar may be hidden
+ padlock_PadLock.setPadlockLevel("padlock-ib", level);
+ padlock_PadLock.setPadlockLevel("padlock-ib-left", level);
+ padlock_PadLock.setPadlockLevel("padlock-ub-right", level);
+ } catch(e) {}
+ padlock_PadLock.setPadlockLevel("padlock-sb", level);
+ padlock_PadLock.setPadlockLevel("padlock-tab", level);
+ },
+ setPadlockLevel: function(item, level) {
+ let secbut = document.getElementById(item);
+ var sectooltip = "";
+
+ if (level) {
+ secbut.setAttribute("level", level);
+ secbut.hidden = false;
+ } else {
+ secbut.hidden = true;
+ secbut.removeAttribute("level");
+ }
+
+ switch (level) {
+ case "ev":
+ sectooltip = "Extended Validated";
+ break;
+ case "high":
+ sectooltip = "Secure";
+ break;
+ case "low":
+ sectooltip = "Weak security";
+ break;
+ case "mixed":
+ sectooltip = "Mixed mode (partially encrypted)";
+ break;
+ case "broken":
+ sectooltip = "Not secure";
+ break;
+ default:
+ sectooltip = "";
+ }
+ secbut.setAttribute("tooltiptext", sectooltip);
+ },
+ 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("", padlock_PadLock);
+ },
+ 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");
+ switch (colprefval) {
+ case 3:
+ colshow = "all";
+ break;
+ case 2:
+ colshow = "secure-mixed";
+ break;
+ case 1:
+ colshow = "secure-only";
+ break;
+ default:
+ colshow = ""; // 0 or anything else: no shading
+ }
+ try { // URL bar may be hidden
+ document.getElementById("urlbar").setAttribute("https_color", colshow);
+ } catch(e) {}
+
+ var lockenabled = padlock_PadLock.prefbranch.getBoolPref("shown");
+ var padshow = "";
+ if (lockenabled) {
+ padshow = position;
+ }
+
+ try { // URL bar may be hidden
+ document.getElementById("padlock-ib").setAttribute("padshow", padshow);
+ document.getElementById("padlock-ib-left").setAttribute("padshow", padshow);
+ document.getElementById("padlock-ub-right").setAttribute("padshow", padshow);
+ } catch(e) {}
+ document.getElementById("padlock-sb").setAttribute("padshow", padshow);
+ document.getElementById("padlock-tab").setAttribute("padshow", padshow);
+
+ try { // URL bar may be hidden
+ document.getElementById("padlock-ib").setAttribute("padstyle", padstyle);
+ document.getElementById("padlock-ib-left").setAttribute("padstyle", padstyle);
+ document.getElementById("padlock-ub-right").setAttribute("padstyle", padstyle);
+ } catch(e) {}
+ document.getElementById("padlock-sb").setAttribute("padstyle", padstyle);
+ document.getElementById("padlock-tab").setAttribute("padstyle", padstyle);
+
+ }
+};
+
+window.addEventListener("load", padlock_PadLock.onLoad, false );
+window.addEventListener("unload", padlock_PadLock.onUnLoad, false );
diff --git a/base/content/padlock.xul b/base/content/padlock.xul
new file mode 100644
index 0000000..e820c19
--- /dev/null
+++ b/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/base/content/padlock_classic_broken.png b/base/content/padlock_classic_broken.png
new file mode 100644
index 0000000..437036f
--- /dev/null
+++ b/base/content/padlock_classic_broken.png
Binary files differ
diff --git a/base/content/padlock_classic_ev.png b/base/content/padlock_classic_ev.png
new file mode 100644
index 0000000..b3f80c0
--- /dev/null
+++ b/base/content/padlock_classic_ev.png
Binary files differ
diff --git a/base/content/padlock_classic_https.png b/base/content/padlock_classic_https.png
new file mode 100644
index 0000000..86026c0
--- /dev/null
+++ b/base/content/padlock_classic_https.png
Binary files differ
diff --git a/base/content/padlock_classic_low.png b/base/content/padlock_classic_low.png
new file mode 100644
index 0000000..652ad09
--- /dev/null
+++ b/base/content/padlock_classic_low.png
Binary files differ
diff --git a/base/content/padlock_mod_broken.png b/base/content/padlock_mod_broken.png
new file mode 100644
index 0000000..33a6c06
--- /dev/null
+++ b/base/content/padlock_mod_broken.png
Binary files differ
diff --git a/base/content/padlock_mod_ev.png b/base/content/padlock_mod_ev.png
new file mode 100644
index 0000000..3dfdcbd
--- /dev/null
+++ b/base/content/padlock_mod_ev.png
Binary files differ
diff --git a/base/content/padlock_mod_https.png b/base/content/padlock_mod_https.png
new file mode 100644
index 0000000..d494b42
--- /dev/null
+++ b/base/content/padlock_mod_https.png
Binary files differ
diff --git a/base/content/padlock_mod_low.png b/base/content/padlock_mod_low.png
new file mode 100644
index 0000000..29179ef
--- /dev/null
+++ b/base/content/padlock_mod_low.png
Binary files differ
diff --git a/base/content/palemoon.xhtml b/base/content/palemoon.xhtml
new file mode 100644
index 0000000..f145550
--- /dev/null
+++ b/base/content/palemoon.xhtml
@@ -0,0 +1,66 @@
+<!DOCTYPE html
+[
+ <!ENTITY % mozillaDTD SYSTEM "chrome://browser/locale/palemoon.dtd" >
+ %mozillaDTD;
+ <!ENTITY % directionDTD SYSTEM "chrome://global/locale/global.dtd" >
+ %directionDTD;
+]>
+
+<!-- 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">
+ <head>
+ <meta charset='utf-8' />
+ <title>&chronicles.title.66.1;</title>
+
+<style type="text/css">
+html {
+ background: #333399 radial-gradient( circle at 75% 25%, #6666b0 0%, #333399 40%, #111177 80%) center center / cover no-repeat;
+ color: white;
+ font-style: italic;
+ text-rendering: optimizeLegibility;
+ min-height: 100%;
+}
+
+#moztext {
+ margin-top: 15%;
+ font-size: 1.1em;
+ font-family: serif;
+ text-align: center;
+ line-height: 1.5;
+}
+
+#from {
+ font-size: 1.95em;
+ font-family: serif;
+ text-align: right;
+}
+
+em {
+ font-size: 1.3em;
+ line-height: 0;
+}
+
+a {
+ text-decoration: none;
+ color: white;
+}
+</style>
+</head>
+
+<body dir="&locale.dir;">
+
+<section>
+ <p id="moztext">
+ &chronicles.quote.66.1;
+ </p>
+
+ <p id="from">
+ &chronicles.from.66.1;
+ </p>
+</section>
+
+</body>
+</html>
diff --git a/base/content/popup-notifications.inc b/base/content/popup-notifications.inc
new file mode 100644
index 0000000..31a72b4
--- /dev/null
+++ b/base/content/popup-notifications.inc
@@ -0,0 +1,104 @@
+# to be included inside a popupset element
+
+ <panel id="notification-popup"
+ type="arrow"
+ 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>
+
+#ifdef MOZ_WEBRTC
+ <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>
+#endif
+ <popupnotification id="servicesInstall-notification" hidden="true">
+ <popupnotificationcontent orient="vertical" align="start">
+ <!-- XXX bug 974146, tests are looking for this, can't remove yet. -->
+ </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="password-notification" hidden="true">
+ <popupnotificationcontent orient="vertical">
+ <textbox id="password-notification-username"/>
+ <textbox id="password-notification-password" type="password" show-content=""/>
+ <checkbox id="password-notification-visibilityToggle" hidden="true"/>
+ </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>
+ </popupnotificationcontent>
+ </popupnotification>
diff --git a/base/content/safeMode.css b/base/content/safeMode.css
new file mode 100644
index 0000000..4f093a4
--- /dev/null
+++ b/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/base/content/safeMode.js b/base/content/safeMode.js
new file mode 100644
index 0000000..e1e5c72
--- /dev/null
+++ b/base/content/safeMode.js
@@ -0,0 +1,128 @@
+/* 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,
+ Ci = Components.interfaces,
+ Cu = Components.utils;
+
+Cu.import("resource://gre/modules/AddonManager.jsm");
+
+function restartApp() {
+ let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
+ .getService(Ci.nsIAppStartup);
+ appStartup.quit(Ci.nsIAppStartup.eForceQuit | Ci.nsIAppStartup.eRestart);
+}
+
+function clearAllPrefs() {
+ var prefService = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefService);
+ prefService.resetUserPrefs();
+
+ // Remove the pref-overrides dir, if it exists
+ try {
+ var fileLocator = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties);
+ const NS_APP_PREFS_OVERRIDE_DIR = "PrefDOverride";
+ var prefOverridesDir = fileLocator.get(NS_APP_PREFS_OVERRIDE_DIR,
+ Ci.nsIFile);
+ prefOverridesDir.remove(true);
+ } catch (ex) {
+ Components.utils.reportError(ex);
+ }
+}
+
+function restoreDefaultBookmarks() {
+ var prefBranch = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ prefBranch.setBoolPref("browser.bookmarks.restore_default_bookmarks", true);
+}
+
+function deleteLocalstore() {
+ const nsIDirectoryServiceContractID = "@mozilla.org/file/directory_service;1";
+ const nsIProperties = Ci.nsIProperties;
+ var directoryService = Cc[nsIDirectoryServiceContractID]
+ .getService(nsIProperties);
+ // Local store file
+ var localstoreFile = directoryService.get("LStoreS", Components.interfaces.nsIFile);
+ // XUL store file
+ var xulstoreFile = directoryService.get("ProfD", Components.interfaces.nsIFile);
+ xulstoreFile.append("xulstore.json");
+ try {
+ xulstoreFile.remove(false);
+ if (localstoreFile.exists()) {
+ 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 = Cc["@mozilla.org/browser/search-service;1"]
+ .getService(Ci.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() {
+ let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
+ .getService(Ci.nsIAppStartup);
+ appStartup.quit(Ci.nsIAppStartup.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/base/content/safeMode.xul b/base/content/safeMode.xul
new file mode 100644
index 0000000..656df6e
--- /dev/null
+++ b/base/content/safeMode.xul
@@ -0,0 +1,55 @@
+<?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 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/base/content/sanitize.js b/base/content/sanitize.js
new file mode 100644
index 0000000..b4d13d8
--- /dev/null
+++ b/base/content/sanitize.js
@@ -0,0 +1,534 @@
+# -*- 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, "Downloads",
+ "resource://gre/modules/Downloads.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "console",
+ "resource://gre/modules/Console.jsm");
+
+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: "",
+ isShutDown: false,
+
+ 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;
+ item.isShutDown = this.isShutDown;
+ 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;
+ console.error("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 cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"].
+ getService(Ci.nsICacheStorageService);
+ try {
+ // Cache doesn't consult timespan, nor does it have the
+ // facility for timespan-based eviction. Wipe it.
+ cache.clear();
+ } 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, cookie.originAttributes);
+ }
+ }
+ 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();
+ if (!this.range || this.isShutDown) {
+ Components.utils.import("resource:///modules/QuotaManager.jsm");
+ QuotaManagerHelper.clear(this.isShutDown);
+ }
+ },
+
+ get canClear()
+ {
+ return true;
+ }
+ },
+
+ history: {
+ clear: function ()
+ {
+ if (this.range) {
+ PlacesUtils.history.removeVisitsByFilter({
+ beginDate: new Date(this.range[0] / 1000),
+ endDate: new Date(this.range[1] / 1000)
+ }).catch(Components.utils.reportError);;
+ } else {
+ // Remove everything.
+ PlacesUtils.history.clear()
+ .catch(Components.utils.reportError);
+ }
+
+ 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: Task.async(function* (range) {
+ let refObj = {};
+ try {
+ let filterByTime = null;
+ if (range) {
+ // Convert microseconds back to milliseconds for date comparisons.
+ let rangeBeginMs = range[0] / 1000;
+ let rangeEndMs = range[1] / 1000;
+ filterByTime = download => download.startTime >= rangeBeginMs &&
+ download.startTime <= rangeEndMs;
+ }
+
+ // Clear all completed/cancelled downloads
+ let list = yield Downloads.getList(Downloads.ALL);
+ list.removeFinished(filterByTime);
+ } finally {}
+ }),
+
+ get canClear()
+ {
+ //Clearing is always possible with JSTransfers
+ return true;
+ }
+ },
+
+ 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;
+ }
+ },
+
+ connectivityData: {
+ clear: function ()
+ {
+ // Clear site security settings
+ var sss = Components.classes["@mozilla.org/ssservice;1"]
+ .getService(Components.interfaces.nsISiteSecurityService);
+ sss.clearAll();
+ },
+
+ 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;
+
+Sanitizer.IS_SHUTDOWN = true;
+
+// 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(Sanitizer.IS_SHUTDOWN);
+};
+
+// this is called on startup and shutdown, to perform pending sanitizations
+Sanitizer._checkAndSanitize = function(isShutDown)
+{
+ 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.isShutDown = isShutDown;
+ s.sanitize().then(function() {
+ prefs.setBoolPref(Sanitizer.prefDidShutdown, true);
+ });
+ }
+};
diff --git a/base/content/sanitize.xul b/base/content/sanitize.xul
new file mode 100644
index 0000000..691be92
--- /dev/null
+++ b/base/content/sanitize.xul
@@ -0,0 +1,190 @@
+<?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"/>
+ <preference id="privacy.cpd.connectivityData" name="privacy.cpd.connectivityData" 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="8" 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();"/>
+ <listitem label="&itemConnectivityData.label;"
+ type="checkbox"
+ accesskey="&itemConnectivityData.accesskey;"
+ preference="privacy.cpd.connectivityData"
+ noduration="true"
+ onsyncfrompreference="return gSanitizePromptDialog.onReadGeneric();"/>
+ </listbox>
+
+ </prefpane>
+</prefwindow>
diff --git a/base/content/sanitizeDialog.css b/base/content/sanitizeDialog.css
new file mode 100644
index 0000000..27c3c08
--- /dev/null
+++ b/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/base/content/sanitizeDialog.js b/base/content/sanitizeDialog.js
new file mode 100644
index 0000000..7861132
--- /dev/null
+++ b/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/. */
+
+var Cc = Components.classes;
+var 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/base/content/softwareUpdateOverlay.xul b/base/content/softwareUpdateOverlay.xul
new file mode 100644
index 0000000..01170e4
--- /dev/null
+++ b/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/base/content/tabbrowser.css b/base/content/tabbrowser.css
new file mode 100644
index 0000000..43536b2
--- /dev/null
+++ b/base/content/tabbrowser.css
@@ -0,0 +1,77 @@
+/* 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");
+ /* Make the content area follow the system colors before load */
+ background: Menu;
+ color: MenuText;
+}
+
+.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,
+.tab-icon-sound:not([soundplaying]):not([muted]):not([blocked]),
+.tab-icon-sound[pinned],
+.tab-icon-overlay {
+ display: none;
+}
+
+.tab-icon-overlay[soundplaying][pinned],
+.tab-icon-overlay[muted][pinned],
+.tab-icon-overlay[blocked][pinned] {
+ display: -moz-box;
+}
+
+.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/base/content/tabbrowser.xml b/base/content/tabbrowser.xml
new file mode 100644
index 0000000..b5edd54
--- /dev/null
+++ b/base/content/tabbrowser.xml
@@ -0,0 +1,5403 @@
+<?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,datetimepicker,authdosprotected"/>
+ </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="_outerWindowIDBrowserMap">
+ new Map();
+ </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>
+
+ <property name="popupAnchor" readonly="true">
+ <getter><![CDATA[
+ if (this.mCurrentTab._popupAnchor) {
+ return this.mCurrentTab._popupAnchor;
+ }
+ let stack = this.mCurrentBrowser.parentNode;
+ // Create an anchor for the popup
+ const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ let popupAnchor = document.createElementNS(NS_XUL, "hbox");
+ popupAnchor.className = "popup-anchor";
+ popupAnchor.hidden = true;
+ stack.appendChild(popupAnchor);
+ return this.mCurrentTab._popupAnchor = popupAnchor;
+ ]]></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="getBrowserForContentWindow">
+ <parameter name="aWindow"/>
+ <body>
+ <![CDATA[
+ var tab = this._getTabForContentWindow(aWindow);
+ return tab ? tab.linkedBrowser : null;
+ ]]>
+ </body>
+ </method>
+
+ <method name="getBrowserForOuterWindowID">
+ <parameter name="aID"/>
+ <body>
+ <![CDATA[
+ return this._outerWindowIDBrowserMap.get(aID);
+ ]]>
+ </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>
+
+ <!-- Binding from browser to tab -->
+ <field name="_tabForBrowser" readonly="true">
+ <![CDATA[
+ new WeakMap();
+ ]]>
+ </field>
+
+ <method name="_getTabForBrowser">
+ <parameter name="aBrowser" />
+ <body>
+ <![CDATA[
+ let Deprecated = Components.utils.import("resource://gre/modules/Deprecated.jsm", {}).Deprecated;
+ let text = "_getTabForBrowser` is now deprecated, please use `getTabForBrowser";
+ let url = "https://developer.mozilla.org/docs/Mozilla/Tech/XUL/Method/getTabForBrowser";
+ Deprecated.warning(text, url);
+ return this.getTabForBrowser(aBrowser);
+ ]]>
+ </body>
+ </method>
+
+ <method name="getTabForBrowser">
+ <parameter name="aBrowser"/>
+ <body>
+ <![CDATA[
+ return this._tabForBrowser.get(aBrowser);
+ ]]>
+ </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);
+ stack.insertBefore(newPrompt, browser.nextSibling);
+ browser.setAttribute("tabmodalPromptShowing", true);
+
+ newPrompt.clientTop; // style flush to assure binding is attached
+
+ let prompts = this.listPrompts();
+ if (prompts.length > 1) {
+ // Let's hide ourself behind the current prompt.
+ newPrompt.hidden = true;
+ }
+
+ 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.hidden = false;
+ 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="getTabFromAudioEvent">
+ <parameter name="aEvent"/>
+ <body>
+ <![CDATA[
+ if (!Services.prefs.getBoolPref("browser.tabs.showAudioPlayingIcon") ||
+ !aEvent.isTrusted) {
+ return null;
+ }
+
+ var browser = aEvent.originalTarget;
+ var tab = this.getTabForBrowser(browser);
+ return tab;
+ ]]>
+ </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;
+ let location, originalLocation;
+ try {
+ aRequest.QueryInterface(nsIChannel)
+ location = aRequest.URI;
+ originalLocation = aRequest.originalURI;
+ } catch (ex) {}
+
+ 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) {
+ if (aWebProgress.isTopLevel)
+ this.mBrowser.urlbarChangeTracker.startedLoad();
+
+ if (this._shouldShowProgress(aRequest)) {
+ if (!(aStateFlags & nsIWebProgressListener.STATE_RESTORING)) {
+ this.mTab.setAttribute("busy", "true");
+ if (!gMultiProcessBrowser) {
+ if (aWebProgress.isTopLevel &&
+ !(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, ["busy"]);
+ if (!this.mTab.selected)
+ this.mTab.setAttribute("unread", "true");
+ }
+ this.mTab.removeAttribute("progress");
+
+ if (aWebProgress.isTopLevel) {
+ let isSuccessful = Components.isSuccessCode(aStatus);
+ if (!isSuccessful && !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 if (isSuccessful) {
+ this.mBrowser.urlbarChangeTracker.finishedLoad();
+ }
+
+ if (!this.mBrowser.mIconURL)
+ this.mTabBrowser.useDefaultIcon(this.mTab);
+ }
+
+ if (this.mBlank)
+ this.mBlank = false;
+
+ // 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) {
+ let isSameDocument =
+ !!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT);
+ // We need to clear the typed value
+ // if the document failed to load, to make sure the urlbar reflects the
+ // failed URI (particularly for SSL errors). However, don't clear the value
+ // if the error page's URI is about:blank, because that causes complete
+ // loss of urlbar contents for invalid URI errors (see bug 867957).
+ if (this.mBrowser.didStartLoadSinceLastUserTyping() ||
+ ((aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) &&
+ aLocation.spec != "about:blank"))
+ this.mBrowser.userTypedValue = null;
+
+ // If the browser was playing audio, we should remove the playing state.
+ if (this.mTab.hasAttribute("soundplaying") && !isSameDocument) {
+ clearTimeout(this.mTab._soundPlayingAttrRemovalTimer);
+ this.mTab._soundPlayingAttrRemovalTimer = 0;
+ this.mTab.removeAttribute("soundplaying");
+ this.mTabBrowser._tabAttrModified(this.mTab, ["soundplaying"]);
+ }
+
+ // If the browser was previously muted, we should restore the muted state.
+ if (this.mTab.hasAttribute("muted")) {
+ this.mTab.linkedBrowser.mute();
+ }
+
+ // 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;
+ this.mBrowser.lastLocationChange = Date.now();
+ }
+ },
+
+ 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"/>
+ <parameter name="aLoadingPrincipal"/>
+ <body>
+ <![CDATA[
+ let 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);
+ }
+ // We do not serialize the principal from within SessionStore.jsm,
+ // hence if aLoadingPrincipal is null we default to the
+ // systemPrincipal which will allow the favicon to load.
+ let loadingPrincipal = aLoadingPrincipal
+ ? aLoadingPrincipal
+ : Services.scriptSecurityManager.getSystemPrincipal();
+ let loadType = PrivateBrowsingUtils.isWindowPrivate(window)
+ ? this.mFaviconService.FAVICON_LOAD_PRIVATE
+ : this.mFaviconService.FAVICON_LOAD_NON_PRIVATE;
+
+ this.mFaviconService.setAndFetchFaviconForPage(
+ browser.currentURI, aURI, false, loadType, null, loadingPrincipal);
+ }
+
+ let sizedIconUrl = browser.mIconURL || "";
+ if (sizedIconUrl != aTab.getAttribute("image")) {
+ if (sizedIconUrl)
+ aTab.setAttribute("image", sizedIconUrl);
+ else
+ aTab.removeAttribute("image");
+ this._tabAttrModified(aTab, ["image"]);
+ }
+
+ if (Services.prefs.getBoolPref("browser.chrome.favicons.process")) {
+ let favImage = new Image;
+ favImage.src = browser.mIconURL;
+ var tabBrowser = this;
+ favImage.onload = function () {
+ try {
+ // Draw the icon 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(favImage, 0, 0, w, h);
+ icon = canvas.toDataURL();
+ browser.mIconURL = icon;
+ aTab.setAttribute("image", icon);
+ }
+ catch (e) {
+ console.warn("Processing of favicon failed.");
+ // Canvas failed: icon remains as it was
+ }
+ tabBrowser._callProgressListeners(browser, "onLinkIconAvailable", [browser.mIconURL]);
+ }
+ }
+
+ 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, browser.contentPrincipal);
+ ]]>
+ </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").includes("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) {
+ 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;
+ this.finder.mListeners.forEach(l => oldBrowser.finder.removeResultListener(l));
+ }
+
+ var updateBlockedPopups = false;
+ if (!oldBrowser ||
+ (oldBrowser.blockedPopups && !newBrowser.blockedPopups) ||
+ (!oldBrowser.blockedPopups && newBrowser.blockedPopups))
+ updateBlockedPopups = true;
+
+ newBrowser.setAttribute("type", "content-primary");
+ newBrowser.docShellIsActive =
+ (window.windowState != window.STATE_MINIMIZED);
+ this.mCurrentBrowser = newBrowser;
+ this.mCurrentTab = this.tabContainer.selectedItem;
+ this.finder.mListeners.forEach(l => this.mCurrentBrowser.finder.addResultListener(l));
+ 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 (updateBlockedPopups)
+ this.mCurrentBrowser.updateBlockedPopups();
+
+ // 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;
+
+ // Update global findbar with new content browser
+ if (gFindBarInitialized) {
+ gFindBar.browser = newBrowser;
+ }
+
+ 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 = new CustomEvent("TabSelect", {
+ bubbles: true,
+ cancelable: false,
+ detail: {
+ previousTab: oldTab
+ }
+ });
+ this.mCurrentTab.dispatchEvent(event);
+
+ this._tabAttrModified(oldTab, ["selected"]);
+ this._tabAttrModified(this.mCurrentTab, ["selected"]);
+
+ // 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();
+ ]]>
+ </body>
+ </method>
+
+ <method name="_tabAttrModified">
+ <parameter name="aTab"/>
+ <parameter name="aChanged"/>
+ <body><![CDATA[
+ if (aTab.closing)
+ return;
+
+ let event = new CustomEvent("TabAttrModified", {
+ bubbles: true,
+ cancelable: false,
+ detail: {
+ changed: aChanged,
+ }
+ });
+ 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, ["label", "crop"]);
+ ]]>
+ </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, ["label", "crop"]);
+
+ 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 aTriggeringPrincipal;
+ var aReferrerPolicy;
+ var aFromExternal;
+ var aRelatedToCurrent;
+ var aOriginPrincipal;
+ var aOpener;
+ if (arguments.length == 2 &&
+ typeof arguments[1] == "object" &&
+ !(arguments[1] instanceof Ci.nsIURI)) {
+ let params = arguments[1];
+ aTriggeringPrincipal = params.triggeringPrincipal;
+ aReferrerURI = params.referrerURI;
+ aReferrerPolicy = params.referrerPolicy;
+ aCharset = params.charset;
+ aPostData = params.postData;
+ aLoadInBackground = params.inBackground;
+ aAllowThirdPartyFixup = params.allowThirdPartyFixup;
+ aFromExternal = params.fromExternal;
+ aRelatedToCurrent = params.relatedToCurrent;
+ aOriginPrincipal = params.originPrincipal;
+ aOpener = params.opener;
+ }
+
+ var bgLoad = (aLoadInBackground != null) ? aLoadInBackground :
+ Services.prefs.getBoolPref("browser.tabs.loadInBackground");
+ var owner = bgLoad ? null : this.selectedTab;
+ var tab = this.addTab(aURI, {
+ triggeringPrincipal: aTriggeringPrincipal,
+ referrerURI: aReferrerURI,
+ referrerPolicy: aReferrerPolicy,
+ charset: aCharset,
+ postData: aPostData,
+ ownerTab: owner,
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ fromExternal: aFromExternal,
+ originPrincipal: aOriginPrincipal,
+ relatedToCurrent: aRelatedToCurrent,
+ opener: aOpener });
+ if (!bgLoad)
+ this.selectedTab = tab;
+
+ return tab;
+ ]]>
+ </body>
+ </method>
+
+ <method name="loadTabs">
+ <parameter name="aURIs"/>
+ <parameter name="aLoadInBackground"/>
+ <parameter name="aReplace"/>
+ <body><![CDATA[
+ let aAllowThirdPartyFixup;
+ let aTargetTab;
+ let aNewIndex = -1;
+ let aPostDatas = [];
+ if (arguments.length == 2 &&
+ typeof arguments[1] == "object") {
+ let params = arguments[1];
+ aLoadInBackground = params.inBackground;
+ aReplace = params.replace;
+ aAllowThirdPartyFixup = params.allowThirdPartyFixup;
+ aTargetTab = params.targetTab;
+ aNewIndex = typeof params.newIndex === "number" ?
+ params.newIndex : aNewIndex;
+ aPostDatas = params.postDatas || aPostDatas;
+ }
+
+ 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;
+ var targetTabIndex = -1;
+
+ if (aReplace) {
+ let browser;
+ if (aTargetTab) {
+ browser = this.getBrowserForTab(aTargetTab);
+ targetTabIndex = aTargetTab._tPos;
+ } else {
+ browser = this.mCurrentBrowser;
+ targetTabIndex = this.tabContainer.selectedIndex;
+ }
+ let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
+ if (aAllowThirdPartyFixup) {
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP |
+ Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
+ }
+ try {
+ browser.loadURIWithFlags(aURIs[0], {
+ flags, postData: aPostDatas[0]
+ });
+ } 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,
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ postData: aPostDatas[0]
+ });
+ if (aNewIndex !== -1) {
+ this.moveTabTo(firstTabAdded, aNewIndex);
+ targetTabIndex = firstTabAdded._tPos;
+ }
+ }
+
+ let tabNum = targetTabIndex;
+ for (let i = 1; i < aURIs.length; ++i) {
+ let tab = this.addTab(aURIs[i], {
+ skipAnimation: true,
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ postData: aPostDatas[i]
+ });
+ if (targetTabIndex !== -1)
+ 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 aTriggeringPrincipal;
+ var aReferrerPolicy;
+ var aFromExternal;
+ var aRelatedToCurrent;
+ var aSkipAnimation;
+ var aOriginPrincipal;
+ var aSkipBackgroundNotify;
+ var aOpener;
+ if (arguments.length == 2 &&
+ typeof arguments[1] == "object" &&
+ !(arguments[1] instanceof Ci.nsIURI)) {
+ let params = arguments[1];
+ aTriggeringPrincipal = params.triggeringPrincipal;
+ aReferrerURI = params.referrerURI;
+ aReferrerPolicy = params.referrerPolicy;
+ aCharset = params.charset;
+ aPostData = params.postData;
+ aOwner = params.ownerTab;
+ aAllowThirdPartyFixup = params.allowThirdPartyFixup;
+ aFromExternal = params.fromExternal;
+ aRelatedToCurrent = params.relatedToCurrent;
+ aSkipAnimation = params.skipAnimation;
+ aOriginPrincipal = params.originPrincipal;
+ aOpener = params.opener;
+ aSkipBackgroundNotify = params.skipBackgroundNotify;
+ }
+
+ // 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");
+
+ let aURIObject = null;
+ try {
+ aURIObject = Services.io.newURI(aURI || "about:blank");
+ } catch (ex) { /* we'll try to fix up this URL later */ }
+
+ 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');");
+
+ if (aSkipBackgroundNotify) {
+ t.setAttribute("skipbackgroundnotify", true);
+ }
+
+ 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 (aOpener) {
+ b.QueryInterface(Ci.nsIFrameLoaderOwner).presetOpenerWindow(aOpener);
+ }
+
+ if (this.hasAttribute("autocompletepopup"))
+ b.setAttribute("autocompletepopup", this.getAttribute("autocompletepopup"));
+ b.setAttribute("autoscrollpopup", this._autoScrollPopup.id);
+
+ if (this.hasAttribute("datetimepicker")) {
+ b.setAttribute("datetimepicker", this.getAttribute("datetimepicker"));
+ }
+
+ if (this.hasAttribute("authdosprotected")) {
+ b.setAttribute("authdosprotected", this.getAttribute("authdosprotected"));
+ }
+
+ // 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;
+ this._tabForBrowser.set(b, t);
+ 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 (aOriginPrincipal && aURI) {
+ let {URI_INHERITS_SECURITY_CONTEXT} = Ci.nsIProtocolHandler;
+ // Unless we know for sure we're not inheriting principals,
+ // force the about:blank viewer to have the right principal:
+ if (!aURIObject ||
+ (Services.io.getProtocolFlags(aURIObject.scheme) & URI_INHERITS_SECURITY_CONTEXT)) {
+ b.createAboutBlankContentViewer(aOriginPrincipal);
+ }
+ }
+
+ // 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;
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
+ }
+ if (aFromExternal)
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FROM_EXTERNAL;
+ try {
+ b.loadURIWithFlags(aURI, {
+ flags: flags,
+ triggeringPrincipal: aTriggeringPrincipal,
+ referrerURI: aReferrerURI,
+ referrerPolicy: aReferrerPolicy,
+ charset: aCharset,
+ postData: aPostData,
+ });
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+
+ // We start our browsers out as inactive, and then maintain
+ // activeness in the tab switcher.
+ b.docShellIsActive = false;
+
+ // When addTab() is called with an URL that is not "about:blank" we
+ // set the "nodefaultsrc" attribute that prevents a frameLoader
+ // from being created as soon as the linked <browser> is inserted
+ // into the DOM. We thus have to register the new outerWindowID
+ // for non-remote browsers after we have called browser.loadURI().
+ //
+ // Note: Only do this of we still have a docShell. The TabOpen
+ // event was dispatched above and a gBrowser.removeTab() call from
+ // one of its listeners could cause us to fail here.
+ if (Services.prefs.getPrefType("browser.tabs.remote") == Services.prefs.PREF_BOOL &&
+ !Services.prefs.getBoolPref("browser.tabs.remote")
+ && b.docShell) {
+ this._outerWindowIDBrowserMap.set(b.outerWindowID, b);
+ }
+
+ // 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) {
+ requestAnimationFrame(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:
+ // If there are multiple windows, pinned tabs will be closed, so
+ // we warn about them, too; if there is just one window, pinned
+ // tabs should come back on restart, so exclude them from warning.
+ var numberOfWindows = 0;
+ var browserEnum = Services.wm.getEnumerator("navigator:browser");
+ while (browserEnum.hasMoreElements() && numberOfWindows < 2) {
+ numberOfWindows++;
+ browserEnum.getNext();
+ }
+ if (numberOfWindows > 1) {
+ tabsToClose = this.tabs.length - this._removingTabs.length
+ }
+ else {
+ 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.disableDialogs();
+ }
+
+ // 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);
+ this._outerWindowIDBrowserMap.delete(browser.outerWindowID);
+
+ // 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.
+ this._tabForBrowser.delete(aTab.linkedBrowser);
+ 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");
+
+ // Expedite the removal of the icon if it was already scheduled.
+ if (aOtherTab._soundPlayingAttrRemovalTimer) {
+ clearTimeout(aOtherTab._soundPlayingAttrRemovalTimer);
+ aOtherTab._soundPlayingAttrRemovalTimer = 0;
+ aOtherTab.removeAttribute("soundplaying");
+ remoteBrowser._tabAttrModified(aOtherTab, ["soundplaying"]);
+ }
+
+ // 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;
+
+ let modifiedAttrs = [];
+ if (aOtherTab.hasAttribute("muted")) {
+ aOurTab.setAttribute("muted", "true");
+ aOurTab.muteReason = aOtherTab.muteReason;
+ ourBrowser.mute();
+ modifiedAttrs.push("muted");
+ }
+ if (aOtherTab.hasAttribute("soundplaying")) {
+ aOurTab.setAttribute("soundplaying", "true");
+ modifiedAttrs.push("soundplaying");
+ }
+
+ // 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, otherBrowser.contentPrincipal);
+ var isBusy = aOtherTab.hasAttribute("busy");
+ if (isBusy) {
+ aOurTab.setAttribute("busy", "true");
+ modifiedAttrs.push("busy");
+ 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);
+
+ if (modifiedAttrs.length) {
+ this._tabAttrModified(aOurTab, modifiedAttrs);
+ }
+ ]]>
+ </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);
+
+ // Unmap old outerWindowIDs.
+ this._outerWindowIDBrowserMap.delete(ourBrowser.outerWindowID);
+ let remoteBrowser = aOtherBrowser.ownerDocument.defaultView.gBrowser;
+ if (remoteBrowser) {
+ remoteBrowser._outerWindowIDBrowserMap.delete(aOtherBrowser.outerWindowID);
+ }
+ // Swap the docshells
+ ourBrowser.swapDocShells(aOtherBrowser);
+
+ // Register new outerWindowIDs.
+ this._outerWindowIDBrowserMap.set(ourBrowser.outerWindowID, ourBrowser);
+ if (remoteBrowser) {
+ remoteBrowser._outerWindowIDBrowserMap.set(aOtherBrowser.outerWindowID, 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[
+ let browser = this.getBrowserForTab(aTab);
+ // Reset DOS mitigation for basic auth prompt
+ delete browser.authPromptCounter;
+ browser.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[
+ if (gNavToolbox.collapsed) {
+ return this.mTabBox.selectedTab;
+ }
+ // 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[
+ // Note - the callee understands both:
+ // (a) loadURIWithFlags(aURI, aFlags, ...)
+ // (b) loadURIWithFlags(aURI, { flags: aFlags, ... })
+ // Forwarding it as (a) here actually supports both (a) and (b),
+ // so you can call us either way too.
+ 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="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>
+
+ <field name="_lastSearchString">null</field>
+ <field name="_lastSearchHighlight">false</field>
+
+ <field name="finder"><![CDATA[
+ ({
+ mTabBrowser: this,
+ mListeners: new Set(),
+ get finder() {
+ return this.mTabBrowser.mCurrentBrowser.finder;
+ },
+ destroy: function() {
+ this.finder.destroy();
+ },
+ addResultListener: function(aListener) {
+ this.mListeners.add(aListener);
+ this.finder.addResultListener(aListener);
+ },
+ removeResultListener: function(aListener) {
+ this.mListeners.delete(aListener);
+ this.finder.removeResultListener(aListener);
+ },
+ get searchString() {
+ return this.finder.searchString;
+ },
+ get clipboardSearchString() {
+ return this.finder.clipboardSearchString;
+ },
+ set clipboardSearchString(val) {
+ return this.finder.clipboardSearchString = val;
+ },
+ set caseSensitive(val) {
+ return this.finder.caseSensitive = val;
+ },
+ set entireWord(val) {
+ return this.finder.entireWord = val;
+ },
+ get highlighter() {
+ return this.finder.highlighter;
+ },
+ get matchesCountLimit() {
+ return this.finder.matchesCountLimit;
+ },
+ fastFind: function(...args) {
+ this.finder.fastFind(...args);
+ },
+ findAgain: function(...args) {
+ this.finder.findAgain(...args);
+ },
+ setSearchStringToSelection: function() {
+ return this.finder.setSearchStringToSelection();
+ },
+ highlight: function(...args) {
+ this.finder.highlight(...args);
+ },
+ getInitialSelection: function() {
+ this.finder.getInitialSelection();
+ },
+ getActiveSelectionText: function() {
+ return this.finder.getActiveSelectionText();
+ },
+ enableSelection: function() {
+ this.finder.enableSelection();
+ },
+ removeSelection: function() {
+ this.finder.removeSelection();
+ },
+ focusContent: function() {
+ this.finder.focusContent();
+ },
+ onFindbarClose: function() {
+ this.finder.onFindbarClose();
+ },
+ onFindbarOpen: function() {
+ this.finder.onFindbarOpen();
+ },
+ onModalHighlightChange: function(...args) {
+ return this.finder.onModalHighlightChange(...args);
+ },
+ onHighlightAllChange: function(...args) {
+ return this.finder.onHighlightAllChange(...args);
+ },
+ keyPress: function(aEvent) {
+ this.finder.keyPress(aEvent);
+ },
+ requestMatchesCount: function(...args) {
+ this.finder.requestMatchesCount(...args);
+ },
+ onIteratorRangeFound: function(...args) {
+ this.finder.onIteratorRangeFound(...args);
+ },
+ onIteratorReset: function() {
+ this.finder.onIteratorReset();
+ },
+ onIteratorRestart: function(...args) {
+ this.finder.onIteratorRestart(...args);
+ },
+ onIteratorStart: function(...args) {
+ this.finder.onIteratorStart(...args);
+ },
+ onLocationChange: function(...args) {
+ this.finder.onLocationChange(...args);
+ }
+ })
+ ]]></field>
+
+ <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="contentWindowAsCPOW"
+ 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="contentDocumentAsCPOW"
+ 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"/>
+
+ <property name="fullZoom"
+ onget="return this.mCurrentBrowser.fullZoom;"
+ onset="this.mCurrentBrowser.fullZoom = val;"/>
+
+ <property name="textZoom"
+ onget="return this.mCurrentBrowser.textZoom;"
+ onset="this.mCurrentBrowser.textZoom = val;"/>
+
+ <property name="isSyntheticDocument"
+ onget="return this.mCurrentBrowser.isSyntheticDocument;"
+ 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;
+ }
+ }
+
+#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="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;
+ }
+
+ var stringID, label;
+ if (tab.mOverCloseButton) {
+ stringID = "tabs.closeTab";
+ } else if (tab._overPlayingIcon) {
+ if (tab.linkedBrowser.audioBlocked) {
+ stringID = "tabs.unblockAudio.tooltip";
+ } else {
+ stringID = tab.linkedBrowser.audioMuted ?
+ "tabs.unmuteAudio.tooltip" :
+ "tabs.muteAudio.tooltip";
+ }
+ } else {
+ label = tab.getAttribute("label");
+ }
+ if (stringID && !label) {
+ label = this.mStringBundle.getString(stringID);
+ }
+ event.target.setAttribute("label", 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");
+ break;
+ }
+ case "DOMWebNotificationClicked": {
+ let tab = this.getTabForBrowser(browser);
+ if (!tab)
+ return;
+ this.selectedTab = tab;
+ window.focus();
+ break;
+ }
+ }
+ ]]></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;
+ this._tabForBrowser.set(this.mCurrentBrowser, this.mCurrentTab);
+
+ // 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);
+ } else {
+ this._outerWindowIDBrowserMap.set(this.mCurrentBrowser.outerWindowID,
+ this.mCurrentBrowser);
+ }
+ messageManager.addMessageListener("DOMWebNotificationClicked", 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>
+ <field name="_soundPlayingAttrRemovalTimer">0</field>
+ </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);
+ if (!tab) {
+ return;
+ }
+
+ var titleChanged = this.setTabTitle(tab);
+ if (titleChanged && !tab.selected && !tab.hasAttribute("busy"))
+ tab.setAttribute("titlechanged", "true");
+ ]]>
+ </handler>
+ <handler event="DOMAudioPlaybackStarted">
+ <![CDATA[
+ var tab = getTabFromAudioEvent(event)
+ if (!tab) {
+ return;
+ }
+
+ clearTimeout(tab._soundPlayingAttrRemovalTimer);
+ tab._soundPlayingAttrRemovalTimer = 0;
+
+ let modifiedAttrs = [];
+ if (tab.hasAttribute("soundplaying-scheduledremoval")) {
+ tab.removeAttribute("soundplaying-scheduledremoval");
+ modifiedAttrs.push("soundplaying-scheduledremoval");
+ }
+
+ if (!tab.hasAttribute("soundplaying")) {
+ tab.setAttribute("soundplaying", true);
+ modifiedAttrs.push("soundplaying");
+ }
+
+ this._tabAttrModified(tab, modifiedAttrs);
+ ]]>
+ </handler>
+ <handler event="DOMAudioPlaybackStopped">
+ <![CDATA[
+ var tab = getTabFromAudioEvent(event)
+ if (!tab) {
+ return;
+ }
+
+ if (tab.hasAttribute("soundplaying")) {
+ let removalDelay = Services.prefs.getIntPref("browser.tabs.delayHidingAudioPlayingIconMS");
+
+ tab.style.setProperty("--soundplaying-removal-delay", `${removalDelay - 300}ms`);
+ tab.setAttribute("soundplaying-scheduledremoval", "true");
+ this._tabAttrModified(tab, ["soundplaying-scheduledremoval"]);
+
+ tab._soundPlayingAttrRemovalTimer = setTimeout(() => {
+ tab.removeAttribute("soundplaying-scheduledremoval");
+ tab.removeAttribute("soundplaying");
+ this._tabAttrModified(tab, ["soundplaying", "soundplaying-scheduledremoval"]);
+ }, removalDelay);
+ }
+ ]]>
+ </handler>
+ <handler event="DOMAudioPlaybackBlockStarted">
+ <![CDATA[
+ var tab = getTabFromAudioEvent(event)
+ if (!tab) {
+ return;
+ }
+
+ if (!tab.hasAttribute("blocked")) {
+ tab.setAttribute("blocked", true);
+ this._tabAttrModified(tab, ["blocked"]);
+ }
+ ]]>
+ </handler>
+ <handler event="DOMAudioPlaybackBlockStopped">
+ <![CDATA[
+ var tab = getTabFromAudioEvent(event)
+ if (!tab) {
+ return;
+ }
+
+ if (tab.hasAttribute("blocked")) {
+ tab.removeAttribute("blocked");
+ this._tabAttrModified(tab, ["blocked"]);
+ }
+ ]]>
+ </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);
+
+ this._tabAnimationLoggingEnabled = Services.prefs.getBoolPref("browser.tabs.animationLogging.enabled", 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, if available space allows
+ // 2 - no close buttons at all
+ // 3 - close button at the end of the tabstrip
+ switch (this.mCloseButtons) {
+ case 0:
+ // If we decide we want to hide the close tab button on the last tab
+ // when closing the window with the last tab, then we should check
+ // if (numTabs == 1 && this._closeWindowWithLastTab) here and set
+ // this.setAttribute("closebuttons", "hidden") appropriately
+ this.setAttribute("closebuttons", "activetab");
+ break;
+ case 1:
+ if (numTabs == 1) {
+ // See remark about potentially hiding the close tab button, above.
+ 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") {
+#ifdef XP_WIN
+ // Don't need to do anything if we're in overflow mode and we're closing
+ // the last tab.
+ if (isEndTab)
+#else
+ // 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)
+#endif
+ 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;
+
+ // Resize immediately if preffed
+ // XXX: we may want to make this a three-state pref to disable this early
+ // exit if people prefer a mix of behavior (don't resize in overflow,
+ // but resize if not overflowing)
+ 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;
+
+ // Resize immediately if preffed
+ // XXX: we may want to make this a three-state pref to disable this early
+ // exit if people prefer a mix of behavior (don't resize in overflow,
+ // but resize if not overflowing)
+ 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 {
+ if (tab.hasAttribute("skipbackgroundnotify")) {
+ tab.removeAttribute("skipbackgroundnotify");
+ } 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 (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;
+ }
+
+ 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);
+
+ if (aTab._recordingTabOpenPlain) {
+ delete aTab._recordingTabOpenPlain;
+ }
+ }
+ ]]>
+ </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 comments in the "mousedown" and "click" event handlers of the
+ // tabbrowser-tabs binding.
+ if (!this._blockDblClick)
+ BrowserOpenTab();
+
+ event.preventDefault();
+ ]]></handler>
+
+ <!-- Consider that the in-tab close button is only shown on the active
+ tab. When clicking on an inactive tab at the position where the
+ close button will appear during the click, no "click" event will be
+ dispatched, because the mousedown and mouseup events don't have the
+ same event target. For that reason use "mousedown" instead of "click"
+ to implement in-tab close button behavior. (Pale Moon UXP issue #775)
+ -->
+ <handler event="mousedown" button="0" phase="capturing"><![CDATA[
+ /* 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.
+ *
+ * Also prevent errant doubleclick on the close button from opening
+ * a new tab (bug 343628):
+ * Since we're removing the event target, if the user double-clicks
+ * the 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.
+ */
+
+ // Reset flags at the beginning of a series of clicks:
+ if (event.detail == 1) {
+ this.flagClickOnCloseButton = false;
+ this.flagActivateTabOrClickOnTabbar = false;
+ }
+
+ this.blockCloseButtonOnDblclick = this.flagActivateTabOrClickOnTabbar;
+ this._blockDblClick = this.flagClickOnCloseButton;
+
+ // Set flags:
+ let eventTargetIsCloseButton =
+ event.originalTarget.classList.contains("tab-close-button");
+ this.flagClickOnCloseButton = eventTargetIsCloseButton;
+ this.flagActivateTabOrClickOnTabbar =
+ ((!eventTargetIsCloseButton && event.detail == 1) ||
+ event.originalTarget.localName == "box");
+ ]]></handler>
+
+ <handler event="click" button="0"><![CDATA[
+ // See comment in the "mousedown" event handler of the
+ // tabbrowser-tabs binding.
+ if (event.originalTarget.classList.contains("tab-close-button") &&
+ !this.blockCloseButtonOnDblclick) {
+ gBrowser.removeTab(document.getBindingParent(event.originalTarget),
+ {animate: true, byMouse: true,});
+ this._blockDblClick = true;
+ }
+ ]]></handler>
+
+ <handler event="click" button="1"><![CDATA[
+ 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, 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 links;
+ try {
+ links = browserDragAndDrop.dropLinks(event, true);
+ } catch (ex) {}
+
+// // valid urls don't contain spaces ' '; if we have a space it isn't a valid url.
+// if (!url || url.includes(" ")) //PMed
+ if (!links || links.length === 0) //FF
+ return;
+
+ let inBackground = Services.prefs.getBoolPref("browser.tabs.loadInBackground");
+
+ if (event.shiftKey)
+ inBackground = !inBackground;
+
+ let targetTab = this._getDragTargetTab(event);
+ let replace = !(!targetTab || dropEffect == "copy");
+ let newIndex = this._getDropIndex(event);
+ let urls = links.map(link => link.url);
+ this.tabbrowser.loadTabs(urls, {
+ inBackground,
+ replace,
+ allowThirdPartyFixup: true,
+ targetTab,
+ newIndex,
+ });
+ }
+
+ 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="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:image xbl:inherits="busy,soundplaying,soundplaying-scheduledremoval,pinned,muted,blocked,selected"
+ anonid="overlay-icon"
+ class="tab-icon-overlay"
+ role="presentation"/>
+ <xul:label flex="1"
+ xbl:inherits="value=label,crop,accesskey,fadein,pinned,selected"
+ class="tab-text tab-label"
+ role="presentation"/>
+ <xul:image xbl:inherits="soundplaying,soundplaying-scheduledremoval,pinned,muted,blocked,selected"
+ anonid="soundplaying-icon"
+ class="tab-icon-sound"
+ role="presentation"/>
+ <xul:toolbarbutton anonid="close-button"
+ xbl:inherits="fadein,pinned,selected"
+ class="tab-close-button close-icon"/>
+ </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>
+ <property name="_overPlayingIcon" readonly="true">
+ <getter><![CDATA[
+ let iconVisible = this.hasAttribute("soundplaying") ||
+ this.hasAttribute("muted") ||
+ this.hasAttribute("blocked");
+ let soundPlayingIcon =
+ document.getAnonymousElementByAttribute(this, "anonid", "soundplaying-icon");
+ let overlayIcon =
+ document.getAnonymousElementByAttribute(this, "anonid", "overlay-icon");
+
+ return soundPlayingIcon && soundPlayingIcon.matches(":hover") ||
+ (overlayIcon && overlayIcon.matches(":hover") && iconVisible);
+ ]]></getter>
+ </property>
+ <field name="mCorrespondingMenuitem">null</field>
+ <field name="closing">false</field>
+ <field name="lastAccessed">0</field>
+
+ <method name="toggleMuteAudio">
+ <parameter name="aMuteReason"/>
+ <body>
+ <![CDATA[
+ let tabContainer = this.parentNode;
+ let browser = this.linkedBrowser;
+ let modifiedAttrs = [];
+ if (browser.audioBlocked) {
+ this.removeAttribute("blocked");
+ modifiedAttrs.push("blocked");
+
+ // We don't want sound icon flickering between "blocked", "none" and
+ // "sound-playing", here adding the "soundplaying" is to keep the
+ // transition smoothly.
+ if (!this.hasAttribute("soundplaying")) {
+ this.setAttribute("soundplaying", true);
+ modifiedAttrs.push("soundplaying");
+ }
+
+ browser.resumeMedia();
+ } else {
+ if (browser.audioMuted) {
+ browser.unmute();
+ this.removeAttribute("muted");
+ } else {
+ browser.mute();
+ this.setAttribute("muted", "true");
+ }
+ this.muteReason = aMuteReason || null;
+ modifiedAttrs.push("muted");
+ }
+ tabContainer.tabbrowser._tabAttrModified(this, modifiedAttrs);
+ ]]>
+ </body>
+ </method>
+ </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 ||
+ this._overPlayingIcon) {
+ // Prevent tabbox.xml from selecting the tab.
+ event.stopPropagation();
+ }
+ ]]>
+ </handler>
+ <handler event="mouseup">
+ this.style.MozUserFocus = '';
+ </handler>
+ <handler event="click">
+ <![CDATA[
+ if (event.button != 0) {
+ return;
+ }
+
+ if (this._overPlayingIcon) {
+ this.toggleMuteAudio();
+ }
+ ]]>
+ </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");
+
+ function addEndImage() {
+ let endImage = document.createElement("image");
+ endImage.setAttribute("class", "allTabs-endimage");
+ let endImageContainer = document.createElement("hbox");
+ endImageContainer.setAttribute("align", "center");
+ endImageContainer.setAttribute("pack", "center");
+ endImageContainer.appendChild(endImage);
+ aMenuitem.appendChild(endImageContainer);
+ return endImage;
+ }
+
+ if (aMenuitem.firstChild)
+ aMenuitem.firstChild.remove();
+ if (aTab.hasAttribute("muted"))
+ addEndImage().setAttribute("muted", "true");
+ else if (aTab.hasAttribute("soundplaying"))
+ addEndImage().setAttribute("soundplaying", "true");
+ ]]></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/base/content/test/general/audio.ogg b/base/content/test/general/audio.ogg
new file mode 100644
index 0000000..7e6ef77
--- /dev/null
+++ b/base/content/test/general/audio.ogg
Binary files differ
diff --git a/base/content/urlbarBindings.xml b/base/content/urlbarBindings.xml
new file mode 100644
index 0000000..d2d9cc7
--- /dev/null
+++ b/base/content/urlbarBindings.xml
@@ -0,0 +1,1800 @@
+<?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://browser/content/autocomplete.xml#private-autocomplete">
+
+ <content sizetopopup="pref">
+ <xul:hbox anonid="textbox-container"
+ class="private-autocomplete-textbox-container urlbar-textbox-container"
+ flex="1" xbl:inherits="focused">
+ <children includes="image|deck|stack|box">
+ <xul:image class="private-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="private-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="private-autocomplete-history-dropmarker urlbar-history-dropmarker"
+ allowevents="true"
+ xbl:inherits="open,enablehistory,parentfocused=focused"/>
+ <xul:popupset anonid="popupset"
+ class="private-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;
+ }
+
+ // Set the actiontype only if the user is not overriding actions.
+ if (action && this._noActionsKeys.size == 0) {
+ 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);
+ let lastLocationChange = gBrowser.selectedBrowser.lastLocationChange;
+
+ let matchLastLocationChange = true;
+ 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;
+ }
+ continueOperation.call(this);
+ }
+ else {
+ this._canonizeURL(aTriggeringEvent, response => {
+ [url, postData, mayInheritPrincipal] = response;
+ if (url) {
+ matchLastLocationChange = (lastLocationChange ==
+ gBrowser.selectedBrowser.lastLocationChange);
+ continueOperation.call(this);
+ }
+ });
+ }
+
+ function continueOperation()
+ {
+ 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);
+ }
+
+ // Reset DOS mitigations for the basic auth prompt.
+ let browser = gBrowser.selectedBrowser;
+ delete browser.authPromptCounter;
+
+ function loadCurrent() {
+ let flags = Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ // Pass LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL 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_PRINCIPAL;
+ // 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;
+
+ // If the current tab is empty, ignore Alt+Enter (just reuse this tab)
+ let altEnter = !isMouseEvent && aTriggeringEvent &&
+ aTriggeringEvent.altKey && !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") {
+ if (matchLastLocationChange) {
+ loadCurrent();
+ }
+ } else {
+ this.handleRevert();
+ let params = { allowThirdPartyFixup: true,
+ postData: postData,
+ initiatingDoc: document };
+ if (!this.valueIsTyped)
+ params.isUTF8 = true;
+ openUILinkIn(url, where, params);
+ }
+ } else {
+ if (matchLastLocationChange) {
+ loadCurrent();
+ }
+ }
+ }
+ ]]></body>
+ </method>
+
+ <method name="_canonizeURL">
+ <parameter name="aTriggeringEvent"/>
+ <parameter name="aCallback"/>
+ <body><![CDATA[
+ var url = this.value;
+ if (!url) {
+ aCallback(["", null, false]);
+ return;
+ }
+
+ // 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;
+ }
+ }
+
+ getShortcutOrURIAndPostData(url).then(data => {
+ aCallback([data.url, data.postData, data.mayInheritPrincipal]);
+ });
+ ]]></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 links = browserDragAndDrop.dropLinks(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 (links.length > 0 && links[0].url) {
+ let url = links[0].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();
+ // Force not showing the dropped URI immediately.
+ gBrowser.userTypedValue = null;
+ URLBarSetURI();
+ }
+ ]]></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.includes("/")) {
+ 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_NONE);
+ } 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="_noActionsKeys"><![CDATA[
+ new Set();
+ ]]></field>
+
+ <method name="_clearNoActions">
+ <parameter name="aURL"/>
+ <body><![CDATA[
+ this._noActionsKeys.clear();
+ 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._noActionsKeys.has(event.keyCode)) {
+ if (this._noActionsKeys.size == 0) {
+ this.popup.setAttribute("noactions", "true");
+ this.removeAttribute("actiontype");
+ }
+ this._noActionsKeys.add(event.keyCode);
+ }
+ ]]></handler>
+
+ <handler event="keyup"><![CDATA[
+ if ((event.keyCode === KeyEvent.DOM_VK_ALT ||
+ event.keyCode === KeyEvent.DOM_VK_SHIFT) &&
+ this._noActionsKeys.has(event.keyCode)) {
+ this._noActionsKeys.delete(event.keyCode);
+ if (this._noActionsKeys.size == 0)
+ this._clearNoActions();
+ }
+ ]]></handler>
+
+ <handler event="blur"><![CDATA[
+ if (event.originalTarget != this.inputField)
+ return;
+ 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[
+ if (event.originalTarget != this.inputField)
+ return;
+ 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;
+
+ // Check if this selection was actually user-generated, and exit if not
+ // to prevent copying the selection (e.g autofill) to clipboard/primary
+ if (!window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .isHandlingUserInput)
+ 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://browser/content/autocomplete.xml#private-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 close-icon"
+ 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 close-icon 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="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 close-icon 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 prePath = this.notification.browser.contentWindow.document.nodePrincipal.URI.prePath;
+ this._setupDescription("pluginActivateMultiple.message", null, prePath);
+
+ 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 prePath = action.pluginPermissionPrePath;
+
+ 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, prePath);
+ 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="prePath" />
+ <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 = ["__prepath__", this._brandShortName];
+ if (pluginName) {
+ args.unshift(pluginName);
+ }
+ var bases = gNavigatorBundle.getFormattedString(baseString, args).
+ split("__prepath__", 2);
+
+ span.appendChild(document.createTextNode(bases[0]));
+ var prePathSpan = document.createElementNS("http://www.w3.org/1999/xhtml", "em");
+ prePathSpan.appendChild(document.createTextNode(prePath));
+ span.appendChild(prePathSpan);
+ 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="toolbarbutton-badged" display="xul:button"
+ extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
+ <content>
+ <children includes="observes|template|menupopup|panel|tooltip"/>
+ <xul:stack class="toolbarbutton-badge-stack">
+ <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label"/>
+ <xul:label class="toolbarbutton-badge" xbl:inherits="value=badge" top="0" end="0"/>
+ </xul:stack>
+ <xul:label class="toolbarbutton-text" crop="right" flex="1"
+ xbl:inherits="value=label,accesskey,crop"/>
+ </content>
+ </binding>
+
+</bindings>
diff --git a/base/content/utilityOverlay.js b/base/content/utilityOverlay.js
new file mode 100644
index 0000000..1d284ba
--- /dev/null
+++ b/base/content/utilityOverlay.js
@@ -0,0 +1,901 @@
+# -*- 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.defineLazyModuleGetter(this, "ShellService",
+ "resource:///modules/ShellService.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
+ "resource://gre/modules/Deprecated.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", the logopage, 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" || aURL == "about:logopage";
+}
+
+function getBrowserURL()
+{
+ return "chrome://browser/content/browser.xul";
+}
+
+function getBoolPref(pref, defaultValue) {
+ Deprecated.warning("getBoolPref is deprecated and will be removed in a future release. " +
+ "You should use Services.pref.getBoolPref (Services.jsm).",
+ "https://github.com/MoonchildProductions/UXP/issues/989");
+ return Services.prefs.getBoolPref(pref, defaultValue);
+}
+
+
+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");
+}
+
+/* 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,
+ referrerPolicy: Components.interfaces.nsIHttpChannel.REFERRER_POLICY_DEFAULT,
+ 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 = Services.prefs.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 && Services.prefs.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,
+ referrerPolicy: Components.interfaces.nsIHttpChannel.REFERRER_POLICY_DEFAULT,
+ };
+ }
+
+ params.fromChrome = true;
+
+ openLinkIn(url, where, params);
+}
+
+/* eslint-disable complexity */
+function openLinkIn(url, where, params) {
+ if (!where || !url)
+ return;
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+
+ var aFromChrome = params.fromChrome;
+ var aAllowThirdPartyFixup = params.allowThirdPartyFixup;
+ var aPostData = params.postData;
+ var aCharset = params.charset;
+ var aReferrerURI = params.referrerURI;
+ var aReferrerPolicy = ('referrerPolicy' in params ?
+ params.referrerPolicy : Ci.nsIHttpChannel.REFERRER_POLICY_DEFAULT);
+ var aRelatedToCurrent = params.relatedToCurrent;
+ var aForceAllowDataURI = params.forceAllowDataURI;
+ var aInBackground = params.inBackground;
+ var aDisallowInheritPrincipal = params.disallowInheritPrincipal;
+ var aInitiatingDoc = params.initiatingDoc;
+ var aIsPrivate = params.private;
+ var aPrincipal = params.originPrincipal;
+ var aTriggeringPrincipal = params.triggeringPrincipal;
+ var aForceAboutBlankViewerInCurrent =
+ params.forceAboutBlankViewerInCurrent;
+ var sendReferrerURI = true;
+
+ if (where == "save") {
+ if (!aInitiatingDoc) {
+ Components.utils.reportError("openUILink/openLinkIn was called with " +
+ "where == 'save' but without initiatingDoc. See bug 814264.");
+ return;
+ }
+ // TODO(1073187): propagate referrerPolicy.
+ saveURL(url, null, null, true, null, aReferrerURI, aInitiatingDoc);
+ return;
+ }
+
+ var w = getTopWin();
+ if ((where == "tab" || where == "tabshifted") &&
+ w && !w.toolbar.visible) {
+ w = getTopWin(true);
+ aRelatedToCurrent = false;
+ }
+
+ // We can only do this after we're sure of what |w| will be the rest of this function.
+ // Note that if |w| is null we might have no current browser (we'll open a new window).
+ var aCurrentBrowser = params.currentBrowser || (w && w.gBrowser.selectedBrowser);
+
+ // Teach the principal about the right OA to use, e.g. in case when
+ // opening a link in a new private window.
+ // Please note we do not have to do that for SystemPrincipals and we
+ // can not do it for NullPrincipals since NullPrincipals are only
+ // identical if they actually are the same object (See Bug: 1346759)
+ function useOAForPrincipal(principal) {
+ if (principal && principal.isCodebasePrincipal) {
+ let attrs = {
+ privateBrowsingId: aIsPrivate || (w && PrivateBrowsingUtils.isWindowPrivate(w)),
+ };
+ return Services.scriptSecurityManager.createCodebasePrincipal(principal.URI, attrs);
+ }
+ return principal;
+ }
+ aPrincipal = useOAForPrincipal(aPrincipal);
+ aTriggeringPrincipal = useOAForPrincipal(aTriggeringPrincipal);
+
+ if (!w || where == "window") {
+ // This propagates to window.arguments.
+ // Strip referrer data when opening a new private window, to prevent
+ // regular browsing data from leaking into it.
+ if (aIsPrivate) {
+ sendReferrerURI = false;
+ }
+
+ 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;
+
+ var referrerURISupports = null;
+ if (aReferrerURI && sendReferrerURI) {
+ referrerURISupports = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ referrerURISupports.data = aReferrerURI.spec;
+ }
+
+ var referrerPolicySupports = Cc["@mozilla.org/supports-PRUint32;1"].
+ createInstance(Ci.nsISupportsPRUint32);
+ referrerPolicySupports.data = aReferrerPolicy;
+
+ sa.AppendElement(wuri);
+ sa.AppendElement(charset);
+ sa.AppendElement(referrerURISupports);
+ sa.AppendElement(aPostData);
+ sa.AppendElement(allowThirdPartyFixupSupports);
+ sa.AppendElement(referrerPolicySupports);
+ sa.AppendElement(aPrincipal);
+ sa.AppendElement(aTriggeringPrincipal);
+
+ 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 :
+ Services.prefs.getBoolPref("browser.tabs.loadInBackground");
+ }
+
+ let uriObj;
+ if (where == "current") {
+ try {
+ uriObj = Services.io.newURI(url, null, null);
+ } catch (e) {}
+ }
+
+ if (where == "current" && w.gBrowser.selectedTab.pinned) {
+ try {
+ // nsIURI.host can throw for non-nsStandardURL nsIURIs.
+ if (!uriObj || !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();
+
+ let browserUsedForLoad = null;
+ switch (where) {
+ case "current":
+ let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
+ if (aAllowThirdPartyFixup) {
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP;
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FIXUP_SCHEME_TYPOS;
+ }
+ if (aDisallowInheritPrincipal)
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL;
+ if (aForceAllowDataURI) {
+ flags |= Ci.nsIWebNavigation.LOAD_FLAGS_FORCE_ALLOW_DATA_URI;
+ }
+ let {URI_INHERITS_SECURITY_CONTEXT} = Ci.nsIProtocolHandler;
+ if (aForceAboutBlankViewerInCurrent &&
+ (!uriObj ||
+ (Services.io.getProtocolFlags(uriObj.scheme) & URI_INHERITS_SECURITY_CONTEXT))) {
+ // Unless we know for sure we're not inheriting principals,
+ // force the about:blank viewer to have the right principal:
+ w.gBrowser.selectedBrowser.createAboutBlankContentViewer(aPrincipal);
+ }
+
+ w.gBrowser.loadURIWithFlags(url, {
+ flags: flags,
+ triggeringPrincipal: aTriggeringPrincipal,
+ referrerURI: aReferrerURI,
+ referrerPolicy: aReferrerPolicy,
+ postData: aPostData,
+ originPrincipal: aPrincipal,
+ });
+ browserUsedForLoad = aCurrentBrowser;
+ break;
+ case "tabshifted":
+ loadInBackground = !loadInBackground;
+ // fall through
+ case "tab":
+ let browser = w.gBrowser;
+ let tabUsedForLoad = browser.loadOneTab(url, {
+ referrerURI: aReferrerURI,
+ referrerPolicy: aReferrerPolicy,
+ charset: aCharset,
+ postData: aPostData,
+ inBackground: loadInBackground,
+ allowThirdPartyFixup: aAllowThirdPartyFixup,
+ relatedToCurrent: aRelatedToCurrent,
+ originPrincipal: aPrincipal,
+ triggeringPrincipal: aTriggeringPrincipal });
+ browserUsedForLoad = tabUsedForLoad.linkedBrowser;
+ break;
+ }
+
+ // Focus the content, but only if the browser used for the load is selected.
+ if (browserUsedForLoad &&
+ browserUsedForLoad == browserUsedForLoad.getTabBrowser().selectedBrowser) {
+ browserUsedForLoad.focus();
+ }
+
+ if (!loadInBackground && w.isBlankPageURL(url))
+ if (!w.focusAndSelectUrlBar()) {
+ console.error("Unable to focus and select address bar.")
+ }
+}
+
+// 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;
+}
+
+// This function exists for legacy reasons.
+function getShellService()
+{
+ return ShellService;
+}
+
+function isBidiEnabled() {
+ // first check the pref.
+ if (Services.prefs.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;
+}
+
+#ifdef MOZ_UPDATER
+/**
+ * Opens the update manager and checks for updates to the application.
+ */
+function checkForUpdates()
+{
+ var um =
+ Components.classes["@mozilla.org/updates/update-manager;1"].
+ getService(Components.interfaces.nsIUpdateManager);
+ var prompter =
+ Components.classes["@mozilla.org/updates/update-prompt;1"].
+ createInstance(Components.interfaces.nsIUpdatePrompt);
+
+ // If there's an update ready to be applied, show the "Update Downloaded"
+ // UI instead and let the user know they have to restart the application for
+ // the changes to be applied.
+ if (um.activeUpdate && ["pending", "pending-elevate", "applied"].includes(um.activeUpdate.state))
+ prompter.showUpdateDownloaded(um.activeUpdate);
+ else
+ prompter.checkForUpdates();
+}
+#endif
+
+/**
+ * Set up the help menu software update items to show proper status,
+ * also disabling the items if update is disabled.
+ */
+function buildHelpMenu()
+{
+#ifdef MOZ_UPDATER
+ var updates =
+ Components.classes["@mozilla.org/updates/update-service;1"].
+ getService(Components.interfaces.nsIApplicationUpdateService);
+ var um =
+ Components.classes["@mozilla.org/updates/update-manager;1"].
+ getService(Components.interfaces.nsIUpdateManager);
+
+ // Disable the UI if the update enabled pref has been locked by the
+ // administrator or if we cannot update for some other reason.
+ var checkForUpdates = document.getElementById("checkForUpdates");
+ var appMenuCheckForUpdates = document.getElementById("appmenu_checkForUpdates");
+ var canCheckForUpdates = updates.canCheckForUpdates;
+
+ checkForUpdates.setAttribute("disabled", !canCheckForUpdates);
+
+ if (appMenuCheckForUpdates) {
+ appMenuCheckForUpdates.setAttribute("disabled", !canCheckForUpdates);
+ }
+
+ if (!canCheckForUpdates) {
+ return;
+ }
+
+ var strings = document.getElementById("bundle_browser");
+ var activeUpdate = um.activeUpdate;
+
+ // If there's an active update, substitute its name into the label
+ // we show for this item, otherwise display a generic label.
+ function getStringWithUpdateName(key) {
+ if (activeUpdate && activeUpdate.name)
+ return strings.getFormattedString(key, [activeUpdate.name]);
+ return strings.getString(key + "Fallback");
+ }
+
+ // By default, show "Check for Updates..." from updatesItem_default or
+ // updatesItem_defaultFallback
+ var key = "default";
+ if (activeUpdate) {
+ switch (activeUpdate.state) {
+ case "downloading":
+ // If we're downloading an update at present, show the text:
+ // "Downloading Thunderbird x.x..." from updatesItem_downloading or
+ // updatesItem_downloadingFallback, otherwise we're paused, and show
+ // "Resume Downloading Thunderbird x.x..." from updatesItem_resume or
+ // updatesItem_resumeFallback
+ key = updates.isDownloading ? "downloading" : "resume";
+ break;
+ case "pending":
+ // If we're waiting for the user to restart, show: "Apply Downloaded
+ // Updates Now..." from updatesItem_pending or
+ // updatesItem_pendingFallback
+ key = "pending";
+ break;
+ }
+ }
+
+ checkForUpdates.label = getStringWithUpdateName("updatesItem_" + key);
+
+ if (appMenuCheckForUpdates) {
+ appMenuCheckForUpdates.label = getStringWithUpdateName("updatesItem_" + key);
+ }
+
+ // updatesItem_default.accesskey, updatesItem_downloading.accesskey,
+ // updatesItem_resume.accesskey or updatesItem_pending.accesskey
+ checkForUpdates.accessKey = strings.getString("updatesItem_" + key +
+ ".accesskey");
+
+ if (appMenuCheckForUpdates) {
+ appMenuCheckForUpdates.accessKey = strings.getString("updatesItem_" + key +
+ ".accesskey");
+ }
+
+ if (um.activeUpdate && updates.isDownloading) {
+ checkForUpdates.setAttribute("loading", "true");
+ if (appMenuCheckForUpdates) {
+ appMenuCheckForUpdates.setAttribute("loading", "true");
+ }
+ } else {
+ checkForUpdates.removeAttribute("loading");
+ if (appMenuCheckForUpdates) {
+ appMenuCheckForUpdates.removeAttribute("loading");
+ }
+ }
+#else
+#ifndef XP_MACOSX
+ // Some extensions may rely on these being present so only hide the about
+ // separator when there are no elements besides the check for updates menuitem
+ // in between the about separator and the updates separator.
+ var updatesSeparator = document.getElementById("updatesSeparator");
+ var aboutSeparator = document.getElementById("aboutSeparator");
+ var checkForUpdates = document.getElementById("checkForUpdates");
+ if (updatesSeparator.nextSibling === checkForUpdates &&
+ checkForUpdates.nextSibling === aboutSeparator)
+ updatesSeparator.hidden = true;
+#endif
+#endif
+}
+
+
+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)
+{
+ var instantApply = Services.prefs.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 win;
+ }
+
+ return openDialog("chrome://browser/content/preferences/preferences.xul",
+ "Preferences", features, paneID, extraArgs);
+}
+
+function openAdvancedPreferences(tabID)
+{
+ openPreferences("paneAdvanced", { "advancedTab" : tabID });
+}
+
+/**
+ * Opens the release notes page for this version of the application.
+ */
+function openReleaseNotes()
+{
+ var relnotesURL = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
+ .getService(Components.interfaces.nsIURLFormatter)
+ .formatURLPref("app.releaseNotesURL");
+
+ openUILinkIn(relnotesURL, "tab");
+}
+
+/**
+ * Opens the troubleshooting information (about:support) page for this version
+ * of the application.
+ */
+function openTroubleshootingPage()
+{
+ openUILinkIn("about:support", "tab");
+}
+
+/**
+ * Opens the feedback page for this version of the application.
+ */
+function openFeedbackPage()
+{
+ openUILinkIn(Services.prefs.getCharPref("browser.feedback.url"), "tab");
+}
+
+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.
+ * @param [optional] aReferrerPolicy
+ * Referrer policy - Ci.nsIHttpChannel.REFERRER_POLICY_*.
+ */
+function openNewTabWith(aURL, aDocument, aPostData, aEvent,
+ aAllowThirdPartyFixup, aReferrer, aReferrerPolicy) {
+ 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,
+ referrerPolicy: aReferrerPolicy,
+ });
+}
+
+function openNewWindowWith(aURL, aDocument, aPostData, aAllowThirdPartyFixup,
+ aReferrer, aReferrerPolicy) {
+ 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,
+ referrerPolicy: aReferrerPolicy,
+ });
+}
+
+/**
+ * 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 = Services.prefs.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/base/content/viewSourceOverlay.xul b/base/content/viewSourceOverlay.xul
new file mode 100644
index 0000000..8b40ddf
--- /dev/null
+++ b/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/base/content/web-panels.js b/base/content/web-panels.js
new file mode 100644
index 0000000..6e2bf5b
--- /dev/null
+++ b/base/content/web-panels.js
@@ -0,0 +1,102 @@
+/* -*- 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/base/content/web-panels.xul b/base/content/web-panels.xul
new file mode 100644
index 0000000..ea1e2eb
--- /dev/null
+++ b/base/content/web-panels.xul
@@ -0,0 +1,84 @@
+<?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 % 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();"/>
+ <command id="Browser:BackOrBackDuplicate"
+ oncommand="getPanelBrowser().webNavigation.goBack(event);"
+ disabled="true">
+ <observes element="Browser:Back" attribute="disabled"/>
+ </command>
+ <command id="Browser:ForwardOrForwardDuplicate"
+ oncommand="getPanelBrowser().webNavigation.goForward(event);"
+ disabled="true">
+ <observes element="Browser:Forward" attribute="disabled"/>
+ </command>
+ <command id="Browser:ReloadOrDuplicate"
+ oncommand="PanelBrowserReload(event)"
+ disabled="true">
+ <observes element="Browser:Reload" attribute="disabled"/>
+ </command>
+ </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/base/content/win6BrowserOverlay.xul b/base/content/win6BrowserOverlay.xul
new file mode 100644
index 0000000..a69e3f6
--- /dev/null
+++ b/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/base/jar.mn b/base/jar.mn
new file mode 100644
index 0000000..735b6d0
--- /dev/null
+++ b/base/jar.mn
@@ -0,0 +1,79 @@
+# 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/autorecovery.js (content/autorecovery.js)
+ content/browser/autorecovery.xul (content/autorecovery.xul)
+* content/browser/browser.css (content/browser.css)
+ content/browser/browser-menudragging.xul (content/browser-menudragging.xul)
+ content/browser/browser-menudragging.js (content/browser-menudragging.js)
+ content/browser/browser-title.css (content/browser-title.css)
+* content/browser/browser.js (content/browser.js)
+* content/browser/browser.xul (content/browser.xul)
+#ifdef MOZ_DEVTOOLS
+ content/browser/browser-devtools-theme.js (content/browser-devtools-theme.js)
+#endif
+* content/browser/browser-tabPreviews.xml (content/browser-tabPreviews.xml)
+ content/browser/content.js (content/content.js)
+ 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_low.png (content/padlock_classic_low.png)
+ content/browser/padlock_classic_broken.png (content/padlock_classic_broken.png)
+ content/browser/palemoon.xhtml (content/palemoon.xhtml)
+ 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/autocomplete.css (content/autocomplete.css)
+* content/browser/autocomplete.xml (content/autocomplete.xml)
+ 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
+# 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
diff --git a/base/moz.build b/base/moz.build
new file mode 100644
index 0000000..e81e45c
--- /dev/null
+++ b/base/moz.build
@@ -0,0 +1,20 @@
+# -*- 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/.
+
+DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
+DEFINES['APP_LICENSE_BLOCK'] = '%s/content/overrides/app-license.html' % SRCDIR
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3', 'cocoa'):
+ DEFINES['HAVE_SHELL_SERVICE'] = 1
+ DEFINES['CONTEXT_COPY_IMAGE_CONTENTS'] = 1
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'cocoa'):
+ DEFINES['CAN_DRAW_IN_TITLEBAR'] = 1
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3'):
+ DEFINES['MENUBAR_CAN_AUTOHIDE'] = 1
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/branding/official/LICENSE b/branding/official/LICENSE
new file mode 100644
index 0000000..97c9de4
--- /dev/null
+++ b/branding/official/LICENSE
@@ -0,0 +1,4 @@
+These files are released under the Mozilla Public License, however please note that you
+are not granted any trademark rights or licenses to the intellectual property of
+Moonchild Productions or any other party's property, including without limitation the
+Pale Moon name or logo. \ No newline at end of file
diff --git a/branding/official/VisualElements_150.png b/branding/official/VisualElements_150.png
new file mode 100644
index 0000000..0100b7d
--- /dev/null
+++ b/branding/official/VisualElements_150.png
Binary files differ
diff --git a/branding/official/VisualElements_70.png b/branding/official/VisualElements_70.png
new file mode 100644
index 0000000..147a6f8
--- /dev/null
+++ b/branding/official/VisualElements_70.png
Binary files differ
diff --git a/branding/official/appname.bmp b/branding/official/appname.bmp
new file mode 100644
index 0000000..e029b1f
--- /dev/null
+++ b/branding/official/appname.bmp
Binary files differ
diff --git a/branding/official/branding.nsi b/branding/official/branding.nsi
new file mode 100644
index 0000000..3bceda9
--- /dev/null
+++ b/branding/official/branding.nsi
@@ -0,0 +1,16 @@
+# 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/.
+
+# NSIS branding defines for official release builds.
+# The nightly build branding.nsi is located in browser/installer/windows/nsis/
+# The unofficial build branding.nsi is located in browser/branding/unofficial/
+
+# BrandFullNameInternal is used for some registry and file system values
+# instead of BrandFullName and typically should not be modified.
+!define BrandFullNameInternal "Pale Moon"
+!define CompanyName "Moonchild Productions"
+!define URLInfoAbout "http://www.palemoon.org/"
+!define URLUpdateInfo "http://www.palemoon.org/releasenotes.shtml"
+!define HelpLink "http://www.palemoon.org/troubleshooting.shtml"
+!define URLSystemRequirements "http://www.palemoon.org/download.shtml"
diff --git a/branding/official/configure.sh b/branding/official/configure.sh
new file mode 100644
index 0000000..8943f58
--- /dev/null
+++ b/branding/official/configure.sh
@@ -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/.
+
+MOZ_APP_DISPLAYNAME="Pale Moon"
+# MOZ_UA_BUILDID=20100101
diff --git a/branding/official/content/about-background.jpg b/branding/official/content/about-background.jpg
new file mode 100644
index 0000000..21e9798
--- /dev/null
+++ b/branding/official/content/about-background.jpg
Binary files differ
diff --git a/branding/official/content/about-logo.png b/branding/official/content/about-logo.png
new file mode 100644
index 0000000..93536f0
--- /dev/null
+++ b/branding/official/content/about-logo.png
Binary files differ
diff --git a/branding/official/content/about-logo@2x.png b/branding/official/content/about-logo@2x.png
new file mode 100644
index 0000000..d87a623
--- /dev/null
+++ b/branding/official/content/about-logo@2x.png
Binary files differ
diff --git a/branding/official/content/about-wordmark.png b/branding/official/content/about-wordmark.png
new file mode 100644
index 0000000..1b849be
--- /dev/null
+++ b/branding/official/content/about-wordmark.png
Binary files differ
diff --git a/branding/official/content/about-wordmark.svg b/branding/official/content/about-wordmark.svg
new file mode 100644
index 0000000..708adaa
--- /dev/null
+++ b/branding/official/content/about-wordmark.svg
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.1"
+ id="svg2"
+ height="44.113129"
+ width="314.56464">
+ <defs
+ id="defs4">
+ <filter
+ id="filter4610"
+ style="color-interpolation-filters:sRGB">
+ <feFlood
+ id="feFlood4600"
+ result="flood"
+ flood-color="rgb(252,252,247)"
+ flood-opacity="1" />
+ <feComposite
+ id="feComposite4602"
+ result="composite1"
+ operator="in"
+ in2="SourceGraphic"
+ in="flood" />
+ <feGaussianBlur
+ id="feGaussianBlur4604"
+ result="blur"
+ stdDeviation="1"
+ in="composite1" />
+ <feOffset
+ id="feOffset4606"
+ result="offset"
+ dy="0"
+ dx="2.77556e-017" />
+ <feComposite
+ id="feComposite4608"
+ result="composite2"
+ operator="over"
+ in2="offset"
+ in="SourceGraphic" />
+ </filter>
+ </defs>
+ <metadata
+ id="metadata7">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(-29.067649,-53.687969)"
+ id="layer1">
+ <text
+ id="text2985"
+ y="32.093662"
+ x="34.479874"
+ style="font-style:normal;font-weight:normal;font-size:12.80000019px;line-height:0%;font-family:'Museo 500';-inkscape-font-specification:'Museo 500';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"
+ xml:space="preserve"><tspan
+ style="font-size:51.20000076px;line-height:1.25;stroke-width:1.06666672"
+ y="32.093662"
+ x="34.479874"
+ id="tspan2987"> </tspan></text>
+ <flowRoot
+ style="font-style:normal;font-weight:normal;font-size:12.80000019px;line-height:0.01%;font-family:'Museo 500';-inkscape-font-specification:'Museo 500';letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.06666672"
+ id="flowRoot2989"
+ xml:space="preserve"><flowRegion
+ style="stroke-width:1.06666672"
+ id="flowRegion2991"><rect
+ style="stroke-width:1.13777781"
+ y="111.82838"
+ x="47.409828"
+ height="396.51855"
+ width="698.21747"
+ id="rect2993" /></flowRegion><flowPara
+ style="font-size:51.20000076px;line-height:1.25;stroke-width:1.06666672"
+ id="flowPara2995"> </flowPara></flowRoot> <g
+ id="text2997"
+ style="font-style:normal;font-weight:normal;font-size:48px;line-height:125%;font-family:'Museo 500';-inkscape-font-specification:'Museo 500';letter-spacing:0px;word-spacing:0px;fill:#0b3575;fill-opacity:1;stroke:none;filter:url(#filter4610)">
+ <path
+ id="path3779"
+ d="M 58.609493,93.510938 V 61.767187 h -3.328124 v -4.403124 h 15.923436 c 3.34477,3.5e-5 6.066904,1.032587 8.166407,3.097655 2.09945,2.065136 3.149189,4.804196 3.149219,8.217189 -3e-5,3.413042 -1.058101,6.177882 -3.174219,8.294531 -2.11617,2.11668 -4.82997,3.175013 -8.141407,3.174999 h -7.474999 v 13.362501 z m 5.120313,-17.765626 h 6.656249 c 2.115607,1.8e-5 3.788,-0.631491 5.017188,-1.894531 1.229143,-1.263 1.843727,-2.986696 1.84375,-5.171094 -2.3e-5,-2.151014 -0.606013,-3.840856 -1.817969,-5.069531 -1.212,-1.228615 -2.875801,-1.842937 -4.991406,-1.842969 h -6.707812 z"
+ style="fill:#0b3575;fill-opacity:1;stroke-width:1.06666672" />
+ <path
+ id="path3781"
+ d="m 85.529806,86.240625 c -2e-6,-1.331242 0.273174,-2.517438 0.819531,-3.558594 0.54635,-1.041134 1.305986,-1.860403 2.278906,-2.457812 0.972911,-0.597382 1.97994,-1.100767 3.021094,-1.510156 1.041137,-0.40936 2.21874,-0.708058 3.532812,-0.896094 1.314049,-0.188005 2.397643,-0.307536 3.250781,-0.358594 0.853109,-0.05103 1.7578,-0.07654 2.71406,-0.07656 h 1.12657 v -0.256251 c -2e-5,-2.252064 -0.44377,-3.847635 -1.33125,-4.786718 -0.88752,-0.939041 -2.355227,-1.408572 -4.403129,-1.408594 -0.956263,2.2e-5 -1.903658,0.145074 -2.842188,0.435156 -0.93855,0.290126 -1.407821,0.810699 -1.407812,1.561719 v 1.485938 h -4.659375 v -2.560938 c -4e-6,-0.98956 0.332807,-1.834352 0.998437,-2.534375 0.665619,-0.699976 1.527337,-1.20336 2.585156,-1.510156 1.057804,-0.306746 2.013532,-0.520027 2.867188,-0.639844 0.853634,-0.119766 1.689831,-0.179661 2.508594,-0.179688 3.926029,2.7e-5 6.682529,0.89612 8.269529,2.688282 1.58696,1.792209 2.38045,4.27528 2.38047,7.449218 v 11.007813 c -2e-5,0.751046 0.3755,1.126567 1.12656,1.126562 h 2.09844 v 4.25 h -4.65781 c -2.15106,0 -3.22658,-0.990103 -3.22656,-2.970312 l 0.10312,-1.535937 h -0.10312 c -0.0677,0.136462 -0.15289,0.324223 -0.25547,0.563281 -0.10263,0.239065 -0.3844,0.648701 -0.84532,1.228905 -0.46095,0.580212 -0.98152,1.09219 -1.561714,1.535939 -0.580224,0.44375 -1.365119,0.853385 -2.354688,1.228905 -0.989596,0.375521 -2.064596,0.563281 -3.225,0.563281 -2.389591,0 -4.454955,-0.699738 -6.196094,-2.099218 -1.741149,-1.399476 -2.61172,-3.32786 -2.611718,-5.785156 z m 6.042187,-3.020312 c -0.682299,0.716676 -1.023445,1.638029 -1.023437,2.764062 -8e-6,1.126048 0.426554,2.098703 1.279687,2.917968 0.853116,0.819275 2.047906,1.22891 3.584375,1.228907 1.979153,3e-6 3.625766,-0.861714 4.939842,-2.585156 1.31405,-1.723431 1.97108,-3.609105 1.9711,-5.657032 V 81.01875 h -1.27969 c -1.262519,1.3e-5 -2.363298,0.04246 -3.302346,0.127344 -0.939075,0.08491 -2.031522,0.272668 -3.277344,0.563281 -1.245843,0.290637 -2.209904,0.794282 -2.892187,1.510938 z"
+ style="fill:#0b3575;fill-opacity:1;stroke-width:1.06666672" />
+ <path
+ id="path3783"
+ d="M 115.78606,90.234375 V 62.739062 c -1e-5,-0.749968 -0.37553,-1.124968 -1.12657,-1.124999 h -2.1 v -4.25 h 4.91562 c 1.16042,3.5e-5 1.99661,0.256024 2.5086,0.767968 0.51197,0.512014 0.76796,1.348211 0.76797,2.508594 v 27.49375 c -10e-6,0.751046 0.37551,1.126567 1.12656,1.126563 h 2.1 v 4.25 h -4.91563 c -1.16041,0 -1.99662,-0.25599 -2.50858,-0.767969 -0.51199,-0.511978 -0.76798,-1.348175 -0.76797,-2.508594 z"
+ style="fill:#0b3575;fill-opacity:1;stroke-width:1.06666672" />
+ <path
+ id="path3785"
+ d="m 127.07981,80.557812 c 0,-4.062483 1.22864,-7.339562 3.68593,-9.83125 2.45729,-2.49164 5.56353,-3.737473 9.31875,-3.7375 3.51561,2.7e-5 6.25467,1.143515 8.2172,3.43047 1.96247,2.286999 2.94372,5.188298 2.94374,8.703906 l -0.15312,2.048437 h -18.89375 c 0.13749,2.66251 1.05936,4.77032 2.76562,6.323437 1.70624,1.55313 3.75416,2.329692 6.14375,2.329687 1.33124,5e-6 2.62837,-0.255985 3.89141,-0.767968 1.263,-0.511974 2.18461,-1.007026 2.76484,-1.485156 l 0.92188,-0.76875 2.14999,3.532812 c -0.27294,0.273962 -0.68257,0.624222 -1.2289,1.050782 -0.54638,0.426564 -1.68987,0.989584 -3.43047,1.689062 -1.74065,0.699479 -3.51565,1.049218 -5.325,1.049218 -4.06147,0 -7.37214,-1.28854 -9.93203,-3.865624 -2.5599,-2.577077 -3.83984,-5.810928 -3.83984,-9.701563 z m 5.325,-3.021875 h 13.72031 c -0.0677,-2.081232 -0.68179,-3.702323 -1.84219,-4.863281 -1.16043,-1.160915 -2.5771,-1.741383 -4.25,-1.741406 -1.94585,2.3e-5 -3.60991,0.571897 -4.99218,1.715625 -1.38231,1.143769 -2.26095,2.773456 -2.63594,4.889062 z"
+ style="fill:#0b3575;fill-opacity:1;stroke-width:1.06666672" />
+ <path
+ id="path3787"
+ d="m 168.35949,93.510938 v -4.403126 h 1.74063 c 0.68333,5e-6 1.05885,-0.375515 1.12656,-1.126562 l 2.45781,-30.617187 h 5.27344 l 8.5,19.148437 1.79219,4.55625 h 0.10155 c 0.61456,-1.740611 1.21196,-3.259359 1.79219,-4.55625 l 8.5,-19.148437 h 5.27343 l 2.45781,30.617187 c 0.0676,0.751047 0.44267,1.126567 1.12501,1.126562 h 1.69062 v 4.403126 h -4.55781 c -1.12608,0 -1.93649,-0.25599 -2.43125,-0.767969 -0.49482,-0.511978 -0.77659,-1.348175 -0.84531,-2.508594 L 200.87198,71.135937 200.77043,65.8125 h -0.10314 c -0.68233,2.252109 -1.31358,4.026586 -1.89375,5.323437 l -7.32188,15.871875 h -4.30156 l -7.32188,-15.871875 c -0.27292,-0.54581 -0.57137,-1.27107 -0.89531,-2.175781 -0.32397,-0.904662 -0.57136,-1.663775 -0.74219,-2.277343 l -0.30781,-0.921875 h -0.10154 c 0.0333,2.150025 -0.001,3.94169 -0.10315,5.374999 l -1.43281,19.098438 c -0.0688,1.160419 -0.35053,1.996616 -0.84531,2.508594 -0.4948,0.511979 -1.3224,0.767969 -2.48282,0.767969 z"
+ style="fill:#0b3575;fill-opacity:1;stroke-width:1.06666672" />
+ <path
+ id="path3789"
+ d="m 217.12355,90.234375 c -2.69583,-2.594786 -4.04374,-5.837751 -4.04374,-9.728906 0,-3.891129 1.34817,-7.116647 4.04453,-9.676562 2.69634,-2.559871 5.99035,-3.839818 9.88203,-3.839845 3.92498,2.7e-5 7.23566,1.279974 9.93203,3.839845 2.69632,2.559915 4.0445,5.785433 4.04453,9.676562 -3e-5,3.891155 -1.3568,7.13386 -4.07031,9.728125 -2.71357,2.594272 -6.01591,3.891405 -9.90703,3.891405 -3.89116,0 -7.18516,-1.296873 -9.88204,-3.890624 z m 3.60938,-16.332812 c -1.72292,1.740643 -2.58439,3.942202 -2.58438,6.604687 -1e-5,2.662511 0.86171,4.881258 2.58516,6.65625 1.72343,1.775005 3.81431,2.662504 6.27266,2.662499 2.49165,5e-6 4.5992,-0.878901 6.32265,-2.636717 1.72342,-1.757805 2.58513,-3.985147 2.58516,-6.682032 -3e-5,-2.662485 -0.86174,-4.864044 -2.58516,-6.604687 -1.72345,-1.740604 -3.831,-2.610916 -6.32265,-2.610938 -2.45835,2.2e-5 -4.5495,0.870334 -6.27344,2.610938 z"
+ style="fill:#0b3575;fill-opacity:1;stroke-width:1.06666672" />
+ <path
+ id="path3791"
+ d="m 248.72355,90.234375 c -2.69583,-2.594786 -4.04374,-5.837751 -4.04374,-9.728906 0,-3.891129 1.34817,-7.116647 4.04453,-9.676562 2.69634,-2.559871 5.99035,-3.839818 9.88203,-3.839845 3.92498,2.7e-5 7.23566,1.279974 9.93203,3.839845 2.69632,2.559915 4.0445,5.785433 4.04453,9.676562 -3e-5,3.891155 -1.3568,7.13386 -4.07031,9.728125 -2.71357,2.594272 -6.01591,3.891405 -9.90703,3.891405 -3.89116,0 -7.18516,-1.296873 -9.88204,-3.890624 z m 3.60938,-16.332812 c -1.72292,1.740643 -2.58439,3.942202 -2.58438,6.604687 -1e-5,2.662511 0.86171,4.881258 2.58516,6.65625 1.72343,1.775005 3.81431,2.662504 6.27266,2.662499 2.49165,5e-6 4.5992,-0.878901 6.32265,-2.636717 1.72342,-1.757805 2.58513,-3.985147 2.58516,-6.682032 -3e-5,-2.662485 -0.86174,-4.864044 -2.58516,-6.604687 -1.72345,-1.740604 -3.831,-2.610916 -6.32265,-2.610938 -2.45835,2.2e-5 -4.5495,0.870334 -6.27344,2.610938 z"
+ style="fill:#0b3575;fill-opacity:1;stroke-width:1.06666672" />
+ <path
+ id="path3793"
+ d="M 278.78762,93.510938 V 72.979687 c 0,-0.75102 -0.37552,-1.12654 -1.12656,-1.126562 h -2.09845 v -4.25 h 4.76094 c 2.18438,2.6e-5 3.27656,0.955754 3.27657,2.867188 v 0.973437 l -0.10155,1.484375 h 0.10155 c 0.68333,-1.467687 1.84425,-2.824456 3.48281,-4.070313 1.63853,-1.245807 3.72029,-1.868723 6.24532,-1.86875 3.03851,2.7e-5 5.28305,0.819297 6.73358,2.457813 1.4505,1.638564 2.17576,4.130228 2.17579,7.475 v 11.2125 c -3e-5,0.751046 0.37549,1.126567 1.12656,1.126563 h 2.09843 v 4.25 h -4.91406 c -1.16043,0 -1.99664,-0.25599 -2.50859,-0.767969 -0.512,-0.511978 -0.76799,-1.348175 -0.76797,-2.508594 V 78.048437 c -2e-5,-2.082274 -0.33283,-3.686439 -0.99843,-4.8125 -0.66566,-1.12602 -1.95419,-1.68904 -3.86563,-1.689062 -1.98023,2.2e-5 -3.71277,0.588823 -5.19766,1.766406 -1.4849,1.177624 -2.48308,2.704965 -2.99453,4.582031 -0.3073,0.887516 -0.46095,2.047931 -0.46093,3.48125 v 12.134376 z"
+ style="fill:#0b3575;fill-opacity:1;stroke-width:1.06666672" />
+ </g>
+ <g
+ id="text3769"
+ style="font-style:normal;font-weight:normal;font-size:10px;line-height:125%;font-family:'Museo 500';-inkscape-font-specification:'Museo 500';letter-spacing:0px;word-spacing:0px;fill:#0b3575;fill-opacity:1;stroke:none;filter:url(#filter4610)">
+ <path
+ id="path3774"
+ d="m 304.55762,64.957287 v -6.613281 h -1.51465 c -0.15647,7e-6 -0.2347,0.07824 -0.2347,0.234701 v 0.479818 h -0.94922 v -0.991863 c 0,-0.241746 0.0462,-0.408847 0.13851,-0.501302 0.0924,-0.09244 0.2595,-0.138663 0.50146,-0.138671 h 5.18392 c 0.24175,8e-6 0.40885,0.04623 0.50131,0.138671 0.0925,0.09246 0.13866,0.259556 0.13866,0.501302 v 0.991863 h -0.94921 v -0.479818 c 0,-0.156461 -0.0783,-0.234694 -0.2347,-0.234701 h -1.51465 v 6.613281 z"
+ style="fill:#0b3575;fill-opacity:1;stroke-width:1.06666672" />
+ <path
+ id="path3776"
+ d="M 308.70378,64.957287 V 64.03997 h 0.36263 c 0.14236,10e-7 0.22059,-0.07823 0.2347,-0.234701 l 0.51204,-6.37858 h 1.09864 l 1.77082,3.989258 0.37338,0.949218 h 0.0211 c 0.12803,-0.362627 0.25249,-0.679033 0.37336,-0.949218 l 1.77084,-3.989258 h 1.09864 l 0.51204,6.37858 c 0.0141,0.156469 0.0923,0.234702 0.23438,0.234701 h 0.35221 v 0.917317 h -0.94955 c -0.2346,0 -0.40343,-0.05333 -0.50651,-0.159993 -0.10304,-0.106656 -0.16178,-0.280869 -0.17611,-0.522623 l -0.30924,-3.978842 -0.0211,-1.109049 h -0.0214 c -0.14215,0.469189 -0.27366,0.838872 -0.39452,1.109049 l -1.5254,3.306641 h -0.89616 l -1.52539,-3.306641 c -0.0568,-0.113711 -0.11903,-0.264806 -0.18653,-0.453287 -0.0675,-0.188472 -0.11903,-0.346621 -0.15461,-0.474447 l -0.0641,-0.192058 h -0.0211 c 0.007,0.447922 -2.2e-4,0.821186 -0.0214,1.119792 l -0.2985,3.978842 c -0.0143,0.241754 -0.0731,0.415962 -0.17611,0.522623 -0.10304,0.106667 -0.2755,0.159993 -0.51725,0.159993 z"
+ style="fill:#0b3575;fill-opacity:1;stroke-width:1.06666672" />
+ </g>
+ </g>
+</svg>
diff --git a/branding/official/content/about.png b/branding/official/content/about.png
new file mode 100644
index 0000000..0a4fbb3
--- /dev/null
+++ b/branding/official/content/about.png
Binary files differ
diff --git a/branding/official/content/aboutDialog.css b/branding/official/content/aboutDialog.css
new file mode 100644
index 0000000..5bcc229
--- /dev/null
+++ b/branding/official/content/aboutDialog.css
@@ -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/. */
+
+#aboutPMDialogContainer {
+ background-image: url("chrome://branding/content/about-background.jpg");
+ background-repeat: no-repeat;
+ background-color: #9ABCD5;
+ color: #101020;
+}
+
+#aboutHeaderBox {
+ background-image: url("chrome://branding/content/about-wordmark.png");
+ background-repeat: no-repeat;
+ background-position: center center;
+ height: 44px;
+}
+
+#aboutVersionBox {
+ text-shadow: 1px 1px 0px #9ABCD5;
+}
+
+#aboutTextBox {
+ animation: 3s fadeIn;
+ animation-fill-mode: forwards;
+ text-shadow: 1px 1px 0px #9ABCD5;
+ color: #101020;
+}
+
+@keyframes fadeIn {
+ 0% {
+ opacity: 0;
+ }
+ 50% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
+
+#aboutLinkBox {
+ padding: 15px 10px 0;
+}
+
+#aboutPMtrademark {
+ font-size: 10px;
+ text-align: center;
+ color: #C0C0C0;
+ text-shadow: 1px 1px 0px #000000;
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
diff --git a/branding/official/content/icon48.png b/branding/official/content/icon48.png
new file mode 100644
index 0000000..c3a98f1
--- /dev/null
+++ b/branding/official/content/icon48.png
Binary files differ
diff --git a/branding/official/content/icon64.png b/branding/official/content/icon64.png
new file mode 100644
index 0000000..93a7ce2
--- /dev/null
+++ b/branding/official/content/icon64.png
Binary files differ
diff --git a/branding/official/content/jar.mn b/branding/official/content/jar.mn
new file mode 100644
index 0000000..83ef1ed
--- /dev/null
+++ b/branding/official/content/jar.mn
@@ -0,0 +1,16 @@
+# 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 branding %content/branding/ contentaccessible=yes
+ content/branding/about.png (about.png)
+ content/branding/about-background.jpg (about-background.jpg)
+ content/branding/about-logo.png (about-logo.png)
+ content/branding/about-logo@2x.png (about-logo@2x.png)
+ content/branding/about-wordmark.png (about-wordmark.png)
+ content/branding/icon48.png (icon48.png)
+ content/branding/icon64.png (icon64.png)
+ content/branding/icon16.png (../default16.png)
+ content/branding/icon32.png (../default32.png)
+ content/branding/aboutDialog.css (aboutDialog.css)
diff --git a/branding/official/content/moz.build b/branding/official/content/moz.build
new file mode 100644
index 0000000..c97072b
--- /dev/null
+++ b/branding/official/content/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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/branding/official/default16.png b/branding/official/default16.png
new file mode 100644
index 0000000..b840a3a
--- /dev/null
+++ b/branding/official/default16.png
Binary files differ
diff --git a/branding/official/default22.png b/branding/official/default22.png
new file mode 100644
index 0000000..4e290f8
--- /dev/null
+++ b/branding/official/default22.png
Binary files differ
diff --git a/branding/official/default24.png b/branding/official/default24.png
new file mode 100644
index 0000000..f47d3ef
--- /dev/null
+++ b/branding/official/default24.png
Binary files differ
diff --git a/branding/official/default256.png b/branding/official/default256.png
new file mode 100644
index 0000000..53adcfa
--- /dev/null
+++ b/branding/official/default256.png
Binary files differ
diff --git a/branding/official/default32.png b/branding/official/default32.png
new file mode 100644
index 0000000..fde9707
--- /dev/null
+++ b/branding/official/default32.png
Binary files differ
diff --git a/branding/official/default48.png b/branding/official/default48.png
new file mode 100644
index 0000000..c3a98f1
--- /dev/null
+++ b/branding/official/default48.png
Binary files differ
diff --git a/branding/official/disk.icns b/branding/official/disk.icns
new file mode 100644
index 0000000..f3a5495
--- /dev/null
+++ b/branding/official/disk.icns
Binary files differ
diff --git a/branding/official/document.icns b/branding/official/document.icns
new file mode 100644
index 0000000..c9f33db
--- /dev/null
+++ b/branding/official/document.icns
Binary files differ
diff --git a/branding/official/document.ico b/branding/official/document.ico
new file mode 100644
index 0000000..2f843db
--- /dev/null
+++ b/branding/official/document.ico
Binary files differ
diff --git a/branding/official/dsstore b/branding/official/dsstore
new file mode 100644
index 0000000..8ea7036
--- /dev/null
+++ b/branding/official/dsstore
Binary files differ
diff --git a/branding/official/firefox.icns b/branding/official/firefox.icns
new file mode 100644
index 0000000..87f9ac7
--- /dev/null
+++ b/branding/official/firefox.icns
Binary files differ
diff --git a/branding/official/firefox.ico b/branding/official/firefox.ico
new file mode 100644
index 0000000..ea25fd8
--- /dev/null
+++ b/branding/official/firefox.ico
Binary files differ
diff --git a/branding/official/locales/en-US/brand.dtd b/branding/official/locales/en-US/brand.dtd
new file mode 100644
index 0000000..9a26025
--- /dev/null
+++ b/branding/official/locales/en-US/brand.dtd
@@ -0,0 +1,4 @@
+<!ENTITY brandShortName "Pale Moon">
+<!ENTITY brandFullName "Pale Moon">
+<!ENTITY vendorShortName "Moonchild">
+<!ENTITY trademarkInfo.part1 "The Pale Moon logo and project names are trademarks of Moonchild Productions (M.C. Straver BASc). All rights reserved.">
diff --git a/branding/official/locales/en-US/brand.properties b/branding/official/locales/en-US/brand.properties
new file mode 100644
index 0000000..7d4b469
--- /dev/null
+++ b/branding/official/locales/en-US/brand.properties
@@ -0,0 +1,5 @@
+brandShortName=Pale Moon
+brandFullName=Pale Moon
+vendorShortName=Moonchild
+
+syncBrandShortName=Sync
diff --git a/branding/official/locales/jar.mn b/branding/official/locales/jar.mn
new file mode 100644
index 0000000..f02dd95
--- /dev/null
+++ b/branding/official/locales/jar.mn
@@ -0,0 +1,11 @@
+#filter substitution
+# 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/.
+
+
+@AB_CD@.jar:
+% locale branding @AB_CD@ %locale/branding/
+ locale/branding/brand.dtd (%brand.dtd)
+ locale/branding/brand.properties (%brand.properties)
+ locale/branding/browserconfig.properties (../../shared/locales/browserconfig.properties)
diff --git a/branding/official/locales/moz.build b/branding/official/locales/moz.build
new file mode 100644
index 0000000..c97072b
--- /dev/null
+++ b/branding/official/locales/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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/branding/official/moz.build b/branding/official/moz.build
new file mode 100644
index 0000000..8cb9013
--- /dev/null
+++ b/branding/official/moz.build
@@ -0,0 +1,13 @@
+# -*- 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 += ['content', 'locales']
+
+DIST_SUBDIR = 'browser'
+export('DIST_SUBDIR')
+
+include('../shared/branding.mozbuild')
+ApplicationBranding()
diff --git a/branding/official/mozicon128.png b/branding/official/mozicon128.png
new file mode 100644
index 0000000..35eafcb
--- /dev/null
+++ b/branding/official/mozicon128.png
Binary files differ
diff --git a/branding/official/palemoon.VisualElementsManifest.xml b/branding/official/palemoon.VisualElementsManifest.xml
new file mode 100644
index 0000000..e9e1f13
--- /dev/null
+++ b/branding/official/palemoon.VisualElementsManifest.xml
@@ -0,0 +1,8 @@
+<Application xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
+ <VisualElements
+ ShowNameOnSquare150x150Logo='on'
+ Square150x150Logo='browser\VisualElements\VisualElements_150.png'
+ Square70x70Logo='browser\VisualElements\VisualElements_70.png'
+ ForegroundText='light'
+ BackgroundColor='#244C8A'/>
+</Application>
diff --git a/branding/official/palemoon.desktop b/branding/official/palemoon.desktop
new file mode 100644
index 0000000..440092b
--- /dev/null
+++ b/branding/official/palemoon.desktop
@@ -0,0 +1,353 @@
+[Desktop Entry]
+Name=Pale Moon
+GenericName=Web Browser
+GenericName[ar]=متصÙØ­ ويب
+GenericName[ast]=Restolador Web
+GenericName[bn]=ওয়েব বà§à¦°à¦¾à¦‰à¦œà¦¾à¦°
+GenericName[ca]=Navegador web
+GenericName[cs]=Webový prohlížeÄ
+GenericName[da]=Webbrowser
+GenericName[el]=ΠεÏιηγητής διαδικτÏου
+GenericName[es]=Navegador web
+GenericName[et]=Veebibrauser
+GenericName[fa]=مرورگر اینترنتی
+GenericName[fi]=WWW-selain
+GenericName[fr]=Navigateur Web
+GenericName[gl]=Navegador Web
+GenericName[he]=דפדפן ×ינטרנט
+GenericName[hr]=Web preglednik
+GenericName[hu]=Webböngésző
+GenericName[it]=Browser web
+GenericName[ja]=ウェブ・ブラウザ
+GenericName[ko]=웹 브ë¼ìš°ì €
+GenericName[ku]=Geroka torê
+GenericName[lt]=Interneto naršyklė
+GenericName[nb]=Nettleser
+GenericName[nl]=Webbrowser
+GenericName[nn]=Nettlesar
+GenericName[no]=Nettleser
+GenericName[pl]=PrzeglÄ…darka WWW
+GenericName[pt]=Navegador Web
+GenericName[pt_BR]=Navegador Web
+GenericName[ro]=Navigator Internet
+GenericName[ru]=Веб-браузер
+GenericName[sk]=Internetový prehliadaÄ
+GenericName[sl]=Spletni brskalnik
+GenericName[sv]=Webbläsare
+GenericName[tr]=Web Tarayıcı
+GenericName[ug]=توركۆرگۈ
+GenericName[uk]=Веб-браузер
+GenericName[vi]=Trình duyệt Web
+GenericName[zh_CN]=网络æµè§ˆå™¨
+GenericName[zh_TW]=網路ç€è¦½å™¨
+Comment=Browse the World Wide Web
+Comment[ar]=تصÙØ­ الشبكة العنكبوتية العالمية
+Comment[ast]=Restola pela Rede
+Comment[bn]=ইনà§à¦Ÿà¦¾à¦°à¦¨à§‡à¦Ÿ বà§à¦°à¦¾à¦‰à¦œ করà§à¦¨
+Comment[ca]=Navegueu per la web
+Comment[cs]=Prohlížení stránek World Wide Webu
+Comment[da]=Surf på internettet
+Comment[de]=Im Internet surfen
+Comment[el]=ΜποÏείτε να πεÏιηγηθείτε στο διαδίκτυο (Web)
+Comment[es]=Navegue por la web
+Comment[et]=Lehitse veebi
+Comment[fa]=صÙحات شبکه جهانی اینترنت را مرور نمایید
+Comment[fi]=Selaa Internetin WWW-sivuja
+Comment[fr]=Naviguer sur le Web
+Comment[gl]=Navegar pola rede
+Comment[he]=גלישה ברחבי ×”×ינטרנט
+Comment[hr]=Pretražite web
+Comment[hu]=A világháló böngészése
+Comment[it]=Esplora il web
+Comment[ja]=ウェブを閲覧ã—ã¾ã™
+Comment[ko]=ì›¹ì„ ëŒì•„ 다닙니다
+Comment[ku]=Li torê bigere
+Comment[lt]=Naršykite internete
+Comment[nb]=Surf på nettet
+Comment[nl]=Surf op het internet
+Comment[nn]=Surf på nettet
+Comment[no]=Surf på nettet
+Comment[pl]=PrzeglÄ…danie stron WWW
+Comment[pt]=Navegue na Internet
+Comment[pt_BR]=Navegue na Internet
+Comment[ro]=Navigați pe Internet
+Comment[ru]=ДоÑтуп в Интернет
+Comment[sk]=Prehliadanie internetu
+Comment[sl]=Brskajte po spletu
+Comment[sv]=Surfa på webben
+Comment[tr]=Ä°nternet'te Gezinin
+Comment[ug]=دۇنيادىكى توربەتلەرنى كۆرگىلى بولىدۇ
+Comment[uk]=ПереглÑд Ñторінок Інтернету
+Comment[vi]=Äể duyệt các trang web
+Comment[zh_CN]=æµè§ˆäº’è”网
+Comment[zh_TW]=ç€è¦½ç¶²éš›ç¶²è·¯
+Exec=palemoon %u
+Terminal=false
+Type=Application
+Icon=palemoon
+Categories=Network;WebBrowser;
+MimeType=text/html;text/xml;application/xhtml+xml;application/vnd.mozilla.xul+xml;text/mml;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/ftp;
+StartupNotify=false
+Actions=NewTab;NewWindow;NewPrivateWindow;
+StartupWMClass="pale moon"
+
+[Desktop Action NewTab]
+Name=Open new tab
+Name[ach]=Yab dirica matidi manyen
+Name[af]=Open nuwe oortjie
+Name[an]=Ubrir una pestanya nueva
+Name[ar]=اÙتح لسانًا جديدًا
+Name[as]=নতà§à¦¨ টেব খোলক
+Name[ast]=Abrir llingüeta nueva
+Name[az]=Yeni vərəq aç
+Name[be]=Ðдкрыць новую ÑžÑтаўку
+Name[bg]=ОтварÑне на нов подпрозорец
+Name[bn_BD]=নতà§à¦¨ টà§à¦¯à¦¾à¦¬ খà§à¦²à§à¦¨
+Name[bn_IN]=নতà§à¦¨ টà§à¦¯à¦¾à¦¬ খà§à¦²à§à¦¨
+Name[br]=Digeriñ un ivinell nevez
+Name[bs]=Otvori novi tab
+Name[ca]=Obre una pestanya nova
+Name[cs]=Otevřít nový panel
+Name[cy]=Agor tab newydd
+Name[da]=Ã…bn nyt faneblad
+Name[de]=Neuen Tab öffnen
+Name[dsb]=Nowy rejtark wócyniś
+Name[el]=Άνοιγμα νέας καÏτέλας
+Name[eo]=Malfermi novan langeton
+Name[es_AR]=Abrir nueva pestaña
+Name[es_CL]=Abrir nueva pestaña
+Name[es_ES]=Abrir pestaña nueva
+Name[es_MX]=Abrir una pestaña nueva
+Name[et]=Ava uus kaart
+Name[eu]=Ireki fitxa berria
+Name[ff]=Uddit tabbere hesere
+Name[fi]=Avaa uusi välilehti
+Name[fr]=Ouvrir un nouvel onglet
+Name[fy_NL]=Iepenje nij ljepblêd
+Name[ga_IE]=Oscail i gcluaisín nua
+Name[gd]=Fosgail taba ùr
+Name[gl]=Abrir unha nova lapela
+Name[gu_IN]=નવી ટૅબને ખોલો
+Name[he]=פתיחת לשונית חדשה
+Name[hi_IN]=नया टैब खोलें
+Name[hr]=Otvori novu karticu
+Name[hsb]=Nowy rajtark woÄinić
+Name[hu]=Új lap megnyitása
+Name[hy_AM]=Ô²Õ¡ÖÕ¥Õ¬ Õ¶Õ¸Ö€ Õ¶Õ¥Ö€Õ¤Õ«Ö€
+Name[id]=Buka tab baru
+Name[is]=Opna nýjan flipa
+Name[it]=Apri nuova scheda
+Name[ja]=æ–°ã—ã„タブ
+Name[kk]=Жаңа бетті ашу
+Name[kn]=ಹೊಸ ಹಾಳೆಯನà³à²¨à³ ತೆರೆ
+Name[ko]=새 탭 열기
+Name[lij]=Àrvi nêuvo féuggio
+Name[lt]=Atverti naujÄ… kortelÄ™
+Name[mai]=नव टैब खोलू
+Name[mk]=Отвори ново јазиче
+Name[ml]=à´ªàµà´¤à´¿à´¯ à´±àµà´±à´¾à´¬àµ à´¤àµà´±à´•àµà´•àµà´•
+Name[mr]=नवीन टॅब उघडा
+Name[ms]=Buka tab baru
+Name[nb_NO]=Ã…pne ny fane
+Name[nl]=Nieuw tabblad openen
+Name[nn_NO]=Opna ny fane
+Name[or]=ନୂତନ ଟà­à­Ÿà¬¾à¬¬ ଖୋଲନà­à¬¤à­
+Name[pa_IN]=ਨਵੀਂ ਟੈਬ ਖੋਲà©à¨¹à©‹
+Name[pl]=Otwórz nową kartę
+Name[pt_BR]=Nova aba
+Name[pt_PT]=Abrir novo separador
+Name[rm]=Avrir in nov tab
+Name[ro]=Deschide o filă nouă
+Name[ru]=Открыть новую вкладку
+Name[si]=නව ටà·à¶¶à¶º විවෘත කරන්න
+Name[sk]=Otvoriť novú kartu
+Name[sl]=Odpri nov zavihek
+Name[son]=Nor loku taaga feeri
+Name[sq]=Hap skedë të re
+Name[sr]=Отвори нови језичак
+Name[sv_SE]=Öppna ny flik
+Name[ta]=பà¯à®¤à®¿à®¯ கீறà¯à®±à¯ˆà®¤à¯ திற
+Name[te]=కొతà±à°¤ టాబౠతెరà±à°µà±à°®à±
+Name[th]=เปิดà¹à¸—็บใหม่
+Name[tr]=Yeni sekme aç
+Name[uk]=Відкрити нову вкладку
+Name[uz]=Yangi ichki oyna ochish
+Name[vi]=Mở thẻ mới
+Name[xh]=Vula ithebhu entsha
+Name[zh_CN]=打开新标签页
+Name[zh_TW]=開啟新分é 
+Exec=palemoon -new-tab https://start.palemoon.org
+
+[Desktop Action NewWindow]
+Name=Open new window
+Name[ach]=Yab dirica manyen
+Name[af]=Open nuwe venster
+Name[an]=Ubrir una nueva finestra
+Name[ar]=اÙتح ناÙذة جديدة
+Name[as]=নতà§à¦¨ উইনà§à¦¡à§‹ খোলক
+Name[ast]=Abrir ventana nueva
+Name[az]=Yeni pəncərə aç
+Name[be]=Ðдкрыць новае акно
+Name[bg]=ОтварÑне на нов прозорец
+Name[bn_BD]=নতà§à¦¨ উইনà§à¦¡à§‹ খà§à¦²à§à¦¨
+Name[bn_IN]=নতà§à¦¨ উইনà§à¦¡à§‹ খà§à¦²à§à¦¨
+Name[br]=Digeriñ ur prenestr nevez
+Name[bs]=Otvori novi prozor
+Name[ca]=Obre una finestra nova
+Name[cs]=Otevřít nové okno
+Name[cy]=Agor ffenestr newydd
+Name[da]=Ã…bn nyt vindue
+Name[de]=Neues Fenster öffnen
+Name[dsb]=Nowe wokno wócyniś
+Name[el]=Άνοιγμα νέου παÏαθÏÏου
+Name[eo]=Malfermi novan fenestron
+Name[es_AR]=Abrir nueva ventana
+Name[es_CL]=Abrir nueva ventana
+Name[es_ES]=Abrir nueva ventana
+Name[es_MX]=Abrir nueva ventana
+Name[et]=Ava uus aken
+Name[eu]=Ireki leiho berria
+Name[ff]=Uddit henorde hesere
+Name[fi]=Avaa uusi ikkuna
+Name[fr]=Ouvrir une nouvelle fenêtre
+Name[fy_NL]=Iepenje nij finster
+Name[ga_IE]=Oscail fuinneog nua
+Name[gd]=Fosgail uinneag ùr
+Name[gl]=Abrir unha nova xanela
+Name[gu_IN]=નવી વિનà«àª¡à«‹àª¨à«‡ ખોલો
+Name[he]=פתח חלון חדש
+Name[hi_IN]=नई विंडो खोलें
+Name[hr]=Otvori novi prozor
+Name[hsb]=Nowe wokno woÄinić
+Name[hu]=Új ablak megnyitása
+Name[hy_AM]=Ô²Õ¡ÖÕ¥Õ¬ Õ¶Õ¸Ö€ ÕºÕ¡Õ¿Õ¸Ö‚Õ°Õ¡Õ¶
+Name[id]=Buka jendela baru
+Name[is]=Opna nýjan glugga
+Name[it]=Apri nuova finestra
+Name[ja]=æ–°ã—ã„ウィンドウ
+Name[kk]=Жаңа терезені ашу
+Name[kn]=ಹೊಸ ವಿಂಡೊವನà³à²¨à³ ತೆರೆ
+Name[ko]=새 창 열기
+Name[lij]=Àrvi nêuvo barcón
+Name[lt]=Atverti naujÄ… langÄ…
+Name[mai]=नई विंडो खोलू
+Name[mk]=Отвори нов прозорец
+Name[ml]=à´ªàµà´¤à´¿à´¯ ജാലകം à´¤àµà´±à´•àµà´•àµà´•
+Name[mr]=नवीन पटल उघडा
+Name[ms]=Buka tetingkap baru
+Name[nb_NO]=Ã…pne nytt vindu
+Name[nl]=Een nieuw venster openen
+Name[nn_NO]=Opna nytt vindauge
+Name[or]=ନୂତନ ୱିଣà­à¬¡à­‹ ଖୋଲନà­à¬¤à­
+Name[pa_IN]=ਨਵੀਂ ਵਿੰਡੋ ਖੋਲà©à¨¹à©‹
+Name[pl]=Otwórz nowe okno
+Name[pt_BR]=Nova janela
+Name[pt_PT]=Abrir nova janela
+Name[rm]=Avrir ina nova fanestra
+Name[ro]=Deschide o nouă fereastră
+Name[ru]=Открыть новое окно
+Name[si]=නව කවුළුවක් විවෘත කරන්න
+Name[sk]=Otvoriť nové okno
+Name[sl]=Odpri novo okno
+Name[son]=Zanfun taaga feeri
+Name[sq]=Hap dritare të re
+Name[sr]=Отвори нови прозор
+Name[sv_SE]=Öppna nytt fönster
+Name[ta]=பà¯à®¤à®¿à®¯ சாளரதà¯à®¤à¯ˆ திற
+Name[te]=కొతà±à°¤ విండో తెరà±à°µà±à°®à±
+Name[th]=เปิดหน้าต่างใหม่
+Name[tr]=Yeni pencere aç
+Name[uk]=Відкрити нове вікно
+Name[uz]=Yangi oyna ochish
+Name[vi]=Mở cửa sổ mới
+Name[xh]=Vula iwindow entsha
+Name[zh_CN]=打开新窗å£
+Name[zh_TW]=開啟新視窗
+Exec=palemoon -new-window
+
+[Desktop Action NewPrivateWindow]
+Name=New private window
+Name[ach]=Dirica manyen me mung
+Name[af]=Nuwe privaatvenster
+Name[an]=Nueva finestra de navegación privada
+Name[ar]=ناÙذة خاصة جديدة
+Name[as]=নতà§à¦¨ বà§à¦¯à¦•à§à¦¤à¦¿à¦—ত উইনà§à¦¡à§‹
+Name[ast]=Ventana privada nueva
+Name[az]=Yeni məxfi pəncərə
+Name[be]=Ðовае акно адаÑабленнÑ
+Name[bg]=Ðов прозорец за поверително Ñърфиране
+Name[bn_BD]=নতà§à¦¨ বà§à¦¯à¦•à§à¦¤à¦¿à¦—ত উইনà§à¦¡à§‹
+Name[bn_IN]=নতà§à¦¨ বà§à¦¯à¦¾à¦•à§à¦¤à¦¿à¦—ত উইনà§à¦¡à§‹
+Name[br]=Prenestr merdeiñ prevez nevez
+Name[bs]=Novi privatni prozor
+Name[ca]=Finestra privada nova
+Name[cs]=Nové anonymní okno
+Name[cy]=Ffenestr breifat newydd
+Name[da]=Nyt privat vindue
+Name[de]=Neues privates Fenster öffnen
+Name[dsb]=Nowe priwatne wokno
+Name[el]=Îέο παÏάθυÏο ιδιωτικής πεÏιήγησης
+Name[eo]=Nova privata fenestro
+Name[es_AR]=Nueva ventana privada
+Name[es_CL]=Nueva ventana privada
+Name[es_ES]=Nueva ventana privada
+Name[es_MX]=Nueva ventana privada
+Name[et]=Uus privaatne aken
+Name[eu]=Leiho pribatu berria
+Name[ff]=Henorde suturo hesere
+Name[fi]=Uusi yksityinen ikkuna
+Name[fr]=Nouvelle fenêtre de navigation privée
+Name[fy_NL]=Nij priveefinster
+Name[ga_IE]=Fuinneog nua phríobháideach
+Name[gd]=Uinneag phrìobhaideach ùr
+Name[gl]=Nova xanela privada
+Name[gu_IN]=નવી ખાનગી વિનà«àª¡à«‹
+Name[he]=חלון פרטי חדש
+Name[hi_IN]=नया निजी विंडो
+Name[hr]=Novi privatni prozor
+Name[hsb]=Nowe priwatne wokno
+Name[hu]=Új privát ablak
+Name[hy_AM]=Ô³Õ¡Õ²Õ¿Õ¶Õ« Õ¤Õ«Õ¿Õ¡Ö€Õ¯Õ¸Ö‚Õ´
+Name[id]=Jendela mode pribadi baru
+Name[is]=Nýr einkagluggi
+Name[it]=Nuova finestra anonima
+Name[ja]=æ–°ã—ã„プライベートウィンドウ
+Name[kk]=Жаңа жекелік терезе
+Name[kn]=ಹೊಸ ಖಾಸಗಿ ಕಿಟಕಿ
+Name[ko]=새 사ìƒí™œ 보호 ì°½
+Name[lij]=Nêuvo barcón privòu
+Name[lt]=Atverti privaÄiojo narÅ¡ymo langÄ…
+Name[mai]=नव निज विंडो
+Name[mk]=Ðов прозорец за приватно Ñурфање
+Name[ml]=à´ªàµà´¤à´¿à´¯ à´¸àµà´µà´•à´¾à´°àµà´¯ ജാലകം
+Name[mr]=नवीन वैयकà¥à¤¤à¤¿à¤• पटल
+Name[ms]=Tetingkap peribadi baharu
+Name[nb_NO]=Nytt privat vindu
+Name[nl]=Nieuw privévenster
+Name[nn_NO]=Nytt privat vindauge
+Name[or]=ନୂତନ ବà­à­Ÿà¬•à­à¬¤à¬¿à¬—ତ ୱିଣà­à¬¡à­‹
+Name[pa_IN]=ਨਵੀਂ ਪà©à¨°à¨¾à¨ˆà¨µà©‡à¨Ÿ ਵਿੰਡੋ
+Name[pl]=Nowe okno w trybie prywatnym
+Name[pt_BR]=Nova janela privativa
+Name[pt_PT]=Nova janela privada
+Name[rm]=Nova fanestra privata
+Name[ro]=Fereastră fără urme nouă
+Name[ru]=Ðовое приватное окно
+Name[si]=නව පුද්ගලික කවුළුව
+Name[sk]=Nové okno v režime Súkromné prehliadanie
+Name[sl]=Novo zasebno okno
+Name[son]=Sutura zanfun taaga
+Name[sq]=Dritare e re private
+Name[sr]=Ðови приватни прозор
+Name[sv_SE]=Nytt privat fönster
+Name[ta]=பà¯à®¤à®¿à®¯ தனிபà¯à®ªà®Ÿà¯à®Ÿ சாளரமà¯
+Name[te]=కొతà±à°¤ ఆంతరంగిక విండో
+Name[th]=หน้าต่างท่องเว็บà¹à¸šà¸šà¸ªà¹ˆà¸§à¸™à¸•à¸±à¸§à¹ƒà¸«à¸¡à¹ˆ
+Name[tr]=Yeni gizli pencere
+Name[uk]=Ðове приватне вікно
+Name[uz]=Yangi shaxsiy oyna
+Name[vi]=Cửa sổ riêng tư mới
+Name[xh]=Ifestile yangasese entsha
+Name[zh_CN]=新建éšç§æµè§ˆçª—å£
+Name[zh_TW]=新增隱ç§è¦–窗
+Exec=palemoon -private-window
diff --git a/branding/official/pref/palemoon-branding.js b/branding/official/pref/palemoon-branding.js
new file mode 100644
index 0000000..02e75b6
--- /dev/null
+++ b/branding/official/pref/palemoon-branding.js
@@ -0,0 +1,35 @@
+#filter substitution
+#filter emptyLines
+#include ../../shared/pref/preferences.inc
+#include ../../shared/pref/uaoverrides.inc
+
+pref("startup.homepage_override_url","http://www.palemoon.org/releasenotes.shtml");
+pref("app.releaseNotesURL", "http://www.palemoon.org/releasenotes.shtml");
+
+// Enable Firefox compatmode by default.
+pref("general.useragent.compatMode", 2);
+pref("general.useragent.compatMode.gecko", true);
+pref("general.useragent.compatMode.firefox", true);
+
+// ========================= updates ========================
+#if defined(XP_WIN) || defined(XP_LINUX)
+// Updates enabled
+pref("app.update.enabled", true);
+pref("app.update.cert.checkAttributes", true);
+
+// Interval: Time between checks for a new version (in seconds) -- 2 days for Pale Moon
+pref("app.update.interval", 172800);
+pref("app.update.promptWaitTime", 86400);
+
+// URL user can browse to manually if for some reason all update
+// installation attempts fail.
+pref("app.update.url.manual", "http://www.palemoon.org/");
+
+// A default value for the "More information about this update" link
+// supplied in the "An update is available" page of the update wizard.
+pref("app.update.url.details", "http://www.palemoon.org/releasenotes.shtml");
+#else
+// Updates disabled (Mac, etc.)
+pref("app.update.enabled", false);
+pref("app.update.url", "");
+#endif
diff --git a/branding/official/wizHeader.bmp b/branding/official/wizHeader.bmp
new file mode 100644
index 0000000..c023036
--- /dev/null
+++ b/branding/official/wizHeader.bmp
Binary files differ
diff --git a/branding/official/wizHeaderRTL.bmp b/branding/official/wizHeaderRTL.bmp
new file mode 100644
index 0000000..11edaa8
--- /dev/null
+++ b/branding/official/wizHeaderRTL.bmp
Binary files differ
diff --git a/branding/official/wizWatermark.bmp b/branding/official/wizWatermark.bmp
new file mode 100644
index 0000000..ad7703d
--- /dev/null
+++ b/branding/official/wizWatermark.bmp
Binary files differ
diff --git a/branding/shared/background.png b/branding/shared/background.png
new file mode 100644
index 0000000..3594557
--- /dev/null
+++ b/branding/shared/background.png
Binary files differ
diff --git a/branding/shared/branding.mozbuild b/branding/shared/branding.mozbuild
new file mode 100644
index 0000000..284520a
--- /dev/null
+++ b/branding/shared/branding.mozbuild
@@ -0,0 +1,58 @@
+# -*- 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/.
+
+@template
+def ApplicationBranding():
+ JS_PREFERENCE_PP_FILES += [
+ 'pref/palemoon-branding.js',
+ ]
+
+ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ FINAL_TARGET_FILES['..'] += [
+ 'palemoon.VisualElementsManifest.xml',
+ ]
+ FINAL_TARGET_FILES.VisualElements += [
+ 'VisualElements_150.png',
+ 'VisualElements_70.png',
+ ]
+ BRANDING_FILES += [
+ '../shared/newtab.ico',
+ '../shared/newwindow.ico',
+ '../shared/pbmode.ico',
+ 'appname.bmp',
+ 'branding.nsi',
+ 'document.ico',
+ 'firefox.ico',
+ 'wizHeader.bmp',
+ 'wizHeaderRTL.bmp',
+ 'wizWatermark.bmp',
+ ]
+ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ BRANDING_FILES += [
+ '../shared/background.png',
+ 'disk.icns',
+ 'document.icns',
+ 'dsstore',
+ 'firefox.icns',
+ ]
+ elif 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ BRANDING_FILES += [
+ 'default16.png',
+ 'default32.png',
+ 'default48.png',
+ 'mozicon128.png',
+ ]
+ FINAL_TARGET_FILES.icons += ['mozicon128.png']
+ FINAL_TARGET_FILES.chrome.icons.default += [
+ 'default16.png',
+ 'default32.png',
+ 'default48.png',
+ ]
+
+ DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
+ DEFINES['MOZ_BRANDING_DIRECTORY'] = CONFIG['MOZ_BRANDING_DIRECTORY']
+ DEFINES['MOZILLA_UAVERSION_U'] = CONFIG['MOZILLA_UAVERSION_U']
+ DEFINES['MOZILLA_COMPATVERSION_U'] = "52.9"
diff --git a/branding/shared/locales/browserconfig.properties b/branding/shared/locales/browserconfig.properties
new file mode 100644
index 0000000..139e884
--- /dev/null
+++ b/branding/shared/locales/browserconfig.properties
@@ -0,0 +1,2 @@
+browser.startup.homepage=https://wiby.me/
+browser.startup.homepage_reset=https://wiby.me/
diff --git a/branding/shared/newtab.ico b/branding/shared/newtab.ico
new file mode 100644
index 0000000..6e3fee6
--- /dev/null
+++ b/branding/shared/newtab.ico
Binary files differ
diff --git a/branding/shared/newwindow.ico b/branding/shared/newwindow.ico
new file mode 100644
index 0000000..a300935
--- /dev/null
+++ b/branding/shared/newwindow.ico
Binary files differ
diff --git a/branding/shared/pbmode.ico b/branding/shared/pbmode.ico
new file mode 100644
index 0000000..d217994
--- /dev/null
+++ b/branding/shared/pbmode.ico
Binary files differ
diff --git a/branding/shared/pref/preferences.inc b/branding/shared/pref/preferences.inc
new file mode 100644
index 0000000..5b4c031
--- /dev/null
+++ b/branding/shared/pref/preferences.inc
@@ -0,0 +1,107 @@
+// ===| General |==============================================================
+
+pref("startup.homepage_welcome_url", "");
+
+//pref("app.vendorURL", "http://www.palemoon.org/");
+
+
+// User Interface
+pref("browser.identity.ssl_domain_display", 1); //show domain verified SSL (blue)
+
+// ============================================================================
+
+// ===| Application Update Service |===========================================
+
+// Disable application auto-update
+pref("app.updated.enabled", false);
+
+// The time interval between the downloading of mar file chunks in the
+// background (in seconds)
+//pref("app.update.download.backgroundInterval", 600);
+
+// Give the user x seconds to react before showing the big UI. default=48 hours
+//pref("app.update.promptWaitTime", 172800);
+
+// ============================================================================
+
+// ===| Add-ons Manager |======================================================
+
+// Add-on window fixes
+pref("extensions.getMoreThemesURL", "https://addons.palemoon.org/themes/");
+
+pref("extensions.update.autoUpdateDefault", true); // Automatically update extensions by default
+pref("extensions.getAddons.maxResults", 10);
+pref("extensions.getAddons.cache.enabled", false);
+
+// ============================================================================
+
+// ===| DOM |==================================================================
+
+// Set max script runtimes to sane values
+pref("dom.max_chrome_script_run_time", 90); //Some addons need ample time!
+pref("dom.max_script_run_time", 20); //Should be plenty for a page script to do what it needs
+
+// ============================================================================
+
+// ===| Plugins |==============================================================
+
+pref("plugin.default.state", 2); //Allow plugins to run by default
+pref("plugin.expose_full_path", true); //Security: expose the full path to the plugin
+pref("dom.ipc.plugins.timeoutSecs", 20);
+
+// ============================================================================
+
+// ===| Graphics |=============================================================
+
+pref("nglayout.initialpaint.delay", 300);
+
+// ============================================================================
+
+// ===| Image |================================================================
+
+pref("image.mem.max_ms_before_yield", 50);
+pref("image.mem.decode_bytes_at_a_time", 65536); //larger chunks
+
+// ============================================================================
+
+// ===| Sync |=================================================================
+
+// Pale Moon Sync server URLs
+//pref("services.sync.serverURL","https://pmsync.palemoon.org/sync/index.php/");
+//pref("services.sync.jpake.serverURL","https://keyserver.palemoon.org/");
+//pref("services.sync.termsURL", "http://www.palemoon.org/sync/terms.shtml");
+//pref("services.sync.privacyURL", "http://www.palemoon.org/sync/privacy.shtml");
+//pref("services.sync.statusURL", "https://pmsync.palemoon.org/status/");
+//pref("services.sync.syncKeyHelpURL", "http://www.palemoon.org/sync/help/recoverykey.shtml");
+//
+//pref("services.sync.APILevel", 1); // FSyncMS doesn't support 'info/configuration' requests
+
+// ============================================================================
+
+// ===| Misc. |================================================================
+
+// Make sure we shortcut out of a11y to save walking unnecessary code
+pref("accessibility.force_disabled", 1);
+
+// Disable OCSP Stapling which sends every website visited to the CA's server
+// and is easily defeatable by sending a '3' response code making the whole
+// standard meaningless to protect against MITM attacks with stolen privkeys.
+// see https://tools.ietf.org/html/rfc6960#section-4.2
+pref("security.ssl.enable_stapling", false);
+pref("security.OCSP.enabled", 0);
+
+// Force a successful staple if user turns OCSP back to prevent '3' response
+// code bypass. keep in mind you'll be sending all the domains you vist to the
+// CA's OCSP endpoint.
+pref("security.ssl.must_staple", true);
+pref("security.OCSP.require", true);
+
+// ============================================================================
+
+// ===| DevTools |=============================================================
+
+// Number of usages of the web console or scratchpad.
+// If this is less than 5, then pasting code into the web console or scratchpad is disabled
+pref("devtools.selfxss.count", 100);
+
+// ============================================================================
diff --git a/branding/shared/pref/uaoverrides.inc b/branding/shared/pref/uaoverrides.inc
new file mode 100644
index 0000000..c3286f7
--- /dev/null
+++ b/branding/shared/pref/uaoverrides.inc
@@ -0,0 +1,83 @@
+// ===| Site Specific User Agent Overrides |===================================
+
+#define GUAO_PREF general.useragent.override
+
+#define GRE_VERSION @MOZILLA_UAVERSION_U@
+#define GRE_VERSION_SLICE Goanna/@GRE_VERSION@
+#define GRE_DATE_SLICE Goanna/20170101
+#define PM_SLICE WebBrowser/@MOZ_APP_VERSION@
+
+#define GK_VERSION @MOZILLA_COMPATVERSION_U@
+#define GK_SLICE Gecko/20100101
+#define FX_SLICE Firefox/@GK_VERSION@
+
+#ifdef XP_UNIX
+#ifndef XP_MACOSX
+#define OS_SLICE X11; Linux x86_64;
+#else
+#define OS_SLICE Macintosh; Intel Mac OS X 10.11;
+#endif
+#else
+#define OS_SLICE Windows NT 6.1; WOW64;
+#endif
+
+// Special-case AMO
+// We send the native UA slice now, since they no longer offer any compatible extensions for us.
+// This will result in an "only with Firefox" message which suits us fine, because it's the truth.
+pref("@GUAO_PREF@.addons.mozilla.org","Mozilla/5.0 (@OS_SLICE@ rv:@GRE_VERSION@) @GRE_DATE_SLICE@ @PM_SLICE@");
+
+// Required for domains that have proven unresponsive to requests from users
+pref("@GUAO_PREF@.live.com","Mozilla/5.0 (@OS_SLICE@ rv:@GK_VERSION@) @GK_SLICE@ @FX_SLICE@ (Pale Moon)");
+pref("@GUAO_PREF@.msn.com","Mozilla/5.0 (@OS_SLICE@ rv:@GK_VERSION@) @GK_SLICE@ @FX_SLICE@ (Pale Moon)");
+pref("@GUAO_PREF@.bing.com","Mozilla/5.0 (@OS_SLICE@ rv:@GK_VERSION@) @GK_SLICE@ @FX_SLICE@ (Pale Moon)");
+pref("@GUAO_PREF@.outlook.com","Mozilla/5.0 (@OS_SLICE@ rv:@GK_VERSION@) @GK_SLICE@ @FX_SLICE@ (Pale Moon)");
+pref("@GUAO_PREF@.web.de","Mozilla/5.0 (@OS_SLICE@ rv:@GK_VERSION@) @GK_SLICE@ @FX_SLICE@ (Pale Moon)");
+pref("@GUAO_PREF@.aol.com","Mozilla/5.0 (@OS_SLICE@ rv:@GK_VERSION@) @GK_SLICE@ @FX_SLICE@ (Pale Moon)");
+pref("@GUAO_PREF@.calendar.yahoo.com","Mozilla/5.0 (@OS_SLICE@ rv:@GK_VERSION@) @GK_SLICE@ @FX_SLICE@ (Pale Moon)");
+pref("@GUAO_PREF@.google.com","Mozilla/5.0 (@OS_SLICE@ rv:52.9) @GK_SLICE@ @GRE_VERSION_SLICE@ Firefox/52.9 @PM_SLICE@");
+pref("@GUAO_PREF@.googlevideos.com","Mozilla/5.0 (@OS_SLICE@ rv:38.9) @GK_SLICE@ @GRE_VERSION_SLICE@ Firefox/38.9 @PM_SLICE@");
+pref("@GUAO_PREF@.gstatic.com","Mozilla/5.0 (@OS_SLICE@ rv:31.9) @GK_SLICE@ @GRE_VERSION_SLICE@ Firefox/31.9 @PM_SLICE@");
+pref("@GUAO_PREF@.yahoo.com","Mozilla/5.0 (@OS_SLICE@ rv:99.9) @GK_SLICE@ Firefox/99.9 (Pale Moon)");
+pref("@GUAO_PREF@.youtube.com","Mozilla/5.0 (@OS_SLICE@ rv:42.0) @GK_SLICE@ Firefox/42.0 @PM_SLICE@");
+pref("@GUAO_PREF@.gaming.youtube.com","Mozilla/5.0 (@OS_SLICE@ rv:42.0) @GK_SLICE@ Firefox/42.0");
+pref("@GUAO_PREF@.dropbox.com","Mozilla/5.0 (@OS_SLICE@ rv:99.9) @GK_SLICE@ Firefox/99.9 (Pale Moon)");
+
+pref("@GUAO_PREF@.players.brightcove.net","Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko");
+
+// The never-ending Facebook debacle...
+pref("@GUAO_PREF@.facebook.com","Mozilla/5.0 (@OS_SLICE@ rv:99.9) @GK_SLICE@ Firefox/99.9 (Pale Moon)");
+pref("@GUAO_PREF@.fbcdn.net","Mozilla/5.0 (@OS_SLICE@ rv:99.9) @GK_SLICE@ Firefox/99.9 (Pale Moon)");
+
+
+// UA-Sniffing domains below are pending responses from their operators - temp workaround
+pref("@GUAO_PREF@.chase.com","Mozilla/5.0 (@OS_SLICE@ rv:@GK_VERSION@) @GK_SLICE@ @FX_SLICE@");
+// For Amazon Prime videos
+pref("@GUAO_PREF@.www.amazon.com","Mozilla/5.0 (@OS_SLICE@ rv:45.9) @GK_SLICE@ Firefox/45.9 (Pale Moon)");
+// Soundcloud uses Firefox-exclusive combinations of code. Never pass Firefox slice.
+pref("@GUAO_PREF@.soundcloud.com","Mozilla/5.0 (@OS_SLICE@ rv:@GRE_VERSION@) @GRE_DATE_SLICE@ @PM_SLICE@");
+// Daily motion only likes strict Firefox UAs
+pref("@GUAO_PREF@.dailymotion.com","Mozilla/5.0 (@OS_SLICE@ rv:52.0) @GK_SLICE@ Firefox/52.0");
+
+
+// The following requires native mode. Or it blocks.. "too old firefox", breakage, etc.
+pref("@GUAO_PREF@.deviantart.com","Mozilla/5.0 (@OS_SLICE@ rv:@GRE_VERSION@) @GRE_DATE_SLICE@ @PM_SLICE@");
+pref("@GUAO_PREF@.deviantart.net","Mozilla/5.0 (@OS_SLICE@ rv:@GRE_VERSION@) @GRE_DATE_SLICE@ @PM_SLICE@");
+pref("@GUAO_PREF@.altibox.dk","Mozilla/5.0 (@OS_SLICE@ rv:@GRE_VERSION@) @GRE_DATE_SLICE@ @PM_SLICE@");
+pref("@GUAO_PREF@.altibox.no","Mozilla/5.0 (@OS_SLICE@ rv:@GRE_VERSION@) @GRE_DATE_SLICE@ @PM_SLICE@");
+pref("@GUAO_PREF@.firefox.com","Mozilla/5.0 (@OS_SLICE@ rv:@GRE_VERSION@) @GRE_DATE_SLICE@ @PM_SLICE@");
+
+// UA-Sniffing domains below have indicated no interest in supporting Pale Moon (BOO!)
+pref("@GUAO_PREF@.humblebundle.com","Mozilla/5.0 (@OS_SLICE@ rv:@GK_VERSION@) @GK_SLICE@ @FX_SLICE@ (Pale Moon)");
+pref("@GUAO_PREF@.privat24.ua","Mozilla/5.0 (@OS_SLICE@ rv:38.0) @GK_SLICE@ Firefox/38.0");
+pref("@GUAO_PREF@.citi.com","Mozilla/5.0 (@OS_SLICE@ rv:57.0) @GK_SLICE@ Firefox/57.0 (Pale Moon)");
+pref("@GUAO_PREF@.netflix.com","Mozilla/5.0 (@OS_SLICE@ rv:45.9) @GK_SLICE@ Firefox/45.9");
+pref("@GUAO_PREF@.netflximg.net","Mozilla/5.0 (@OS_SLICE@ rv:45.9) @GK_SLICE@ Firefox/45.9");
+
+// UA-sniffing domains that are "app/vendor-specific" and do not like Pale Moon
+pref("@GUAO_PREF@.web.whatsapp.com","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36");
+
+// The following domains do not like the Goanna slice
+pref("@GUAO_PREF@.hitbox.tv","Mozilla/5.0 (@OS_SLICE@ rv:@GK_VERSION@) @GK_SLICE@ @FX_SLICE@");
+pref("@GUAO_PREF@.yuku.com","Mozilla/5.0 (@OS_SLICE@ rv:@GK_VERSION@) @GK_SLICE@ @FX_SLICE@ @PM_SLICE@");
+
+// ============================================================================
diff --git a/branding/unofficial/VisualElements_150.png b/branding/unofficial/VisualElements_150.png
new file mode 100644
index 0000000..3fc7deb
--- /dev/null
+++ b/branding/unofficial/VisualElements_150.png
Binary files differ
diff --git a/branding/unofficial/VisualElements_70.png b/branding/unofficial/VisualElements_70.png
new file mode 100644
index 0000000..be9f667
--- /dev/null
+++ b/branding/unofficial/VisualElements_70.png
Binary files differ
diff --git a/branding/unofficial/appname.bmp b/branding/unofficial/appname.bmp
new file mode 100644
index 0000000..e12534a
--- /dev/null
+++ b/branding/unofficial/appname.bmp
Binary files differ
diff --git a/branding/unofficial/branding.nsi b/branding/unofficial/branding.nsi
new file mode 100644
index 0000000..e863d85
--- /dev/null
+++ b/branding/unofficial/branding.nsi
@@ -0,0 +1,12 @@
+# NSIS branding defines for unofficial builds.
+# The official release build branding.nsi is located in other-license/branding/firefox/
+# The nightly build branding.nsi is located in browser/installer/windows/nsis/
+
+# BrandFullNameInternal is used for some registry and file system values
+# instead of BrandFullName and typically should not be modified.
+!define BrandFullNameInternal "Web Browser"
+!define CompanyName "Thomas"
+!define URLInfoAbout "http://localhost"
+!define URLUpdateInfo "http://localhost"
+!define HelpLink "http://localhost"
+!define URLSystemRequirements "http://localhost/download.shtml"
diff --git a/branding/unofficial/configure.sh b/branding/unofficial/configure.sh
new file mode 100644
index 0000000..07ddffb
--- /dev/null
+++ b/branding/unofficial/configure.sh
@@ -0,0 +1,5 @@
+# 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/.
+
+MOZ_APP_DISPLAYNAME="Web Browser"
diff --git a/branding/unofficial/content/about-background.png b/branding/unofficial/content/about-background.png
new file mode 100644
index 0000000..c034041
--- /dev/null
+++ b/branding/unofficial/content/about-background.png
Binary files differ
diff --git a/branding/unofficial/content/about-logo.png b/branding/unofficial/content/about-logo.png
new file mode 100644
index 0000000..4c05766
--- /dev/null
+++ b/branding/unofficial/content/about-logo.png
Binary files differ
diff --git a/branding/unofficial/content/about-logo@2x.png b/branding/unofficial/content/about-logo@2x.png
new file mode 100644
index 0000000..db6b4d8
--- /dev/null
+++ b/branding/unofficial/content/about-logo@2x.png
Binary files differ
diff --git a/branding/unofficial/content/about.png b/branding/unofficial/content/about.png
new file mode 100644
index 0000000..7457f5e
--- /dev/null
+++ b/branding/unofficial/content/about.png
Binary files differ
diff --git a/branding/unofficial/content/aboutDialog.css b/branding/unofficial/content/aboutDialog.css
new file mode 100644
index 0000000..5cc6b42
--- /dev/null
+++ b/branding/unofficial/content/aboutDialog.css
@@ -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/. */
+
+#aboutPMDialogContainer {
+ background-image: url("chrome://branding/content/about-background.png");
+ background-repeat: no-repeat;
+ background-color: #000;
+ color: #fff;
+}
+
+#aboutVersionBox {
+ /* No wordmark: leave empty space */
+ margin-top: 20px;
+}
+
+#aboutLinkBox {
+ padding: 15px 10px 20px;
+}
diff --git a/branding/unofficial/content/icon48.png b/branding/unofficial/content/icon48.png
new file mode 100644
index 0000000..46b752d
--- /dev/null
+++ b/branding/unofficial/content/icon48.png
Binary files differ
diff --git a/branding/unofficial/content/icon64.png b/branding/unofficial/content/icon64.png
new file mode 100644
index 0000000..23aabb4
--- /dev/null
+++ b/branding/unofficial/content/icon64.png
Binary files differ
diff --git a/branding/unofficial/content/jar.mn b/branding/unofficial/content/jar.mn
new file mode 100644
index 0000000..3536957
--- /dev/null
+++ b/branding/unofficial/content/jar.mn
@@ -0,0 +1,15 @@
+# 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 branding %content/branding/ contentaccessible=yes
+ content/branding/about.png (about.png)
+ content/branding/about-background.png (about-background.png)
+ content/branding/about-logo.png (about-logo.png)
+ content/branding/about-logo@2x.png (about-logo@2x.png)
+ content/branding/icon48.png (icon48.png)
+ content/branding/icon64.png (icon64.png)
+ content/branding/icon16.png (../default16.png)
+ content/branding/icon32.png (../default32.png)
+ content/branding/aboutDialog.css (aboutDialog.css)
diff --git a/branding/unofficial/content/moz.build b/branding/unofficial/content/moz.build
new file mode 100644
index 0000000..c97072b
--- /dev/null
+++ b/branding/unofficial/content/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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/branding/unofficial/default16.png b/branding/unofficial/default16.png
new file mode 100644
index 0000000..d115f71
--- /dev/null
+++ b/branding/unofficial/default16.png
Binary files differ
diff --git a/branding/unofficial/default32.png b/branding/unofficial/default32.png
new file mode 100644
index 0000000..8bc578f
--- /dev/null
+++ b/branding/unofficial/default32.png
Binary files differ
diff --git a/branding/unofficial/default48.png b/branding/unofficial/default48.png
new file mode 100644
index 0000000..a9ae407
--- /dev/null
+++ b/branding/unofficial/default48.png
Binary files differ
diff --git a/branding/unofficial/disk.icns b/branding/unofficial/disk.icns
new file mode 100644
index 0000000..e97e490
--- /dev/null
+++ b/branding/unofficial/disk.icns
Binary files differ
diff --git a/branding/unofficial/document.icns b/branding/unofficial/document.icns
new file mode 100644
index 0000000..dd5f7aa
--- /dev/null
+++ b/branding/unofficial/document.icns
Binary files differ
diff --git a/branding/unofficial/document.ico b/branding/unofficial/document.ico
new file mode 100644
index 0000000..44a707b
--- /dev/null
+++ b/branding/unofficial/document.ico
Binary files differ
diff --git a/branding/unofficial/dsstore b/branding/unofficial/dsstore
new file mode 100644
index 0000000..bbba9ec
--- /dev/null
+++ b/branding/unofficial/dsstore
Binary files differ
diff --git a/branding/unofficial/firefox.icns b/branding/unofficial/firefox.icns
new file mode 100644
index 0000000..117ddb1
--- /dev/null
+++ b/branding/unofficial/firefox.icns
Binary files differ
diff --git a/branding/unofficial/firefox.ico b/branding/unofficial/firefox.ico
new file mode 100644
index 0000000..e4d3195
--- /dev/null
+++ b/branding/unofficial/firefox.ico
Binary files differ
diff --git a/branding/unofficial/locales/browserconfig.properties b/branding/unofficial/locales/browserconfig.properties
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/branding/unofficial/locales/browserconfig.properties
diff --git a/branding/unofficial/locales/en-US/brand.dtd b/branding/unofficial/locales/en-US/brand.dtd
new file mode 100644
index 0000000..bd195c8
--- /dev/null
+++ b/branding/unofficial/locales/en-US/brand.dtd
@@ -0,0 +1,4 @@
+<!ENTITY brandShortName "Web Browser">
+<!ENTITY brandFullName "Web Browser">
+<!ENTITY vendorShortName "an individual programmer">
+<!ENTITY trademarkInfo.part1 " ">
diff --git a/branding/unofficial/locales/en-US/brand.properties b/branding/unofficial/locales/en-US/brand.properties
new file mode 100644
index 0000000..2c48195
--- /dev/null
+++ b/branding/unofficial/locales/en-US/brand.properties
@@ -0,0 +1,5 @@
+brandShortName=Web Browser
+brandFullName=Web Browser
+vendorShortName=Thomas
+
+syncBrandShortName=Sync
diff --git a/branding/unofficial/locales/jar.mn b/branding/unofficial/locales/jar.mn
new file mode 100644
index 0000000..9de6cfc
--- /dev/null
+++ b/branding/unofficial/locales/jar.mn
@@ -0,0 +1,12 @@
+#filter substitution
+# 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/.
+
+
+@AB_CD@.jar:
+% locale branding @AB_CD@ %locale/branding/
+# Unofficial branding only exists in en-US
+ locale/branding/brand.dtd (en-US/brand.dtd)
+ locale/branding/brand.properties (en-US/brand.properties)
+ locale/branding/browserconfig.properties (../../shared/locales/browserconfig.properties)
diff --git a/branding/unofficial/locales/moz.build b/branding/unofficial/locales/moz.build
new file mode 100644
index 0000000..dca25bf
--- /dev/null
+++ b/branding/unofficial/locales/moz.build
@@ -0,0 +1,3 @@
+DEFINES['MOZ_DISTRIBUTION_ID_UNQUOTED'] = CONFIG['MOZ_DISTRIBUTION_ID']
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/branding/unofficial/moz.build b/branding/unofficial/moz.build
new file mode 100644
index 0000000..8cb9013
--- /dev/null
+++ b/branding/unofficial/moz.build
@@ -0,0 +1,13 @@
+# -*- 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 += ['content', 'locales']
+
+DIST_SUBDIR = 'browser'
+export('DIST_SUBDIR')
+
+include('../shared/branding.mozbuild')
+ApplicationBranding()
diff --git a/branding/unofficial/mozicon128.png b/branding/unofficial/mozicon128.png
new file mode 100644
index 0000000..4617ee6
--- /dev/null
+++ b/branding/unofficial/mozicon128.png
Binary files differ
diff --git a/branding/unofficial/pref/palemoon-branding.js b/branding/unofficial/pref/palemoon-branding.js
new file mode 100644
index 0000000..f32870b
--- /dev/null
+++ b/branding/unofficial/pref/palemoon-branding.js
@@ -0,0 +1,9 @@
+#filter substitution
+#filter emptyLines
+#include ../../shared/pref/preferences.inc
+#include ../../shared/pref/uaoverrides.inc
+// Updates disabled
+pref("app.update.enabled", false);
+pref("app.update.url", "");
+
+pref("app.releaseNotesURL", "http://www.palemoon.org/releasenotes.shtml");
diff --git a/branding/unofficial/webbrowser.VisualElementsManifest.xml b/branding/unofficial/webbrowser.VisualElementsManifest.xml
new file mode 100644
index 0000000..070bfc9
--- /dev/null
+++ b/branding/unofficial/webbrowser.VisualElementsManifest.xml
@@ -0,0 +1,8 @@
+<Application xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
+ <VisualElements
+ ShowNameOnSquare150x150Logo='on'
+ Square150x150Logo='browser\VisualElements\VisualElements_150.png'
+ Square70x70Logo='browser\VisualElements\VisualElements_70.png'
+ ForegroundText='light'
+ BackgroundColor='#171717'/>
+</Application>
diff --git a/branding/unofficial/webbrowser.desktop b/branding/unofficial/webbrowser.desktop
new file mode 100644
index 0000000..4981599
--- /dev/null
+++ b/branding/unofficial/webbrowser.desktop
@@ -0,0 +1,353 @@
+[Desktop Entry]
+Name=Web Browser
+GenericName=Web Browser
+GenericName[ar]=متصÙØ­ ويب
+GenericName[ast]=Restolador Web
+GenericName[bn]=ওয়েব বà§à¦°à¦¾à¦‰à¦œà¦¾à¦°
+GenericName[ca]=Navegador web
+GenericName[cs]=Webový prohlížeÄ
+GenericName[da]=Webbrowser
+GenericName[el]=ΠεÏιηγητής διαδικτÏου
+GenericName[es]=Navegador web
+GenericName[et]=Veebibrauser
+GenericName[fa]=مرورگر اینترنتی
+GenericName[fi]=WWW-selain
+GenericName[fr]=Navigateur Web
+GenericName[gl]=Navegador Web
+GenericName[he]=דפדפן ×ינטרנט
+GenericName[hr]=Web preglednik
+GenericName[hu]=Webböngésző
+GenericName[it]=Browser web
+GenericName[ja]=ウェブ・ブラウザ
+GenericName[ko]=웹 브ë¼ìš°ì €
+GenericName[ku]=Geroka torê
+GenericName[lt]=Interneto naršyklė
+GenericName[nb]=Nettleser
+GenericName[nl]=Webbrowser
+GenericName[nn]=Nettlesar
+GenericName[no]=Nettleser
+GenericName[pl]=PrzeglÄ…darka WWW
+GenericName[pt]=Navegador Web
+GenericName[pt_BR]=Navegador Web
+GenericName[ro]=Navigator Internet
+GenericName[ru]=Веб-браузер
+GenericName[sk]=Internetový prehliadaÄ
+GenericName[sl]=Spletni brskalnik
+GenericName[sv]=Webbläsare
+GenericName[tr]=Web Tarayıcı
+GenericName[ug]=توركۆرگۈ
+GenericName[uk]=Веб-браузер
+GenericName[vi]=Trình duyệt Web
+GenericName[zh_CN]=网络æµè§ˆå™¨
+GenericName[zh_TW]=網路ç€è¦½å™¨
+Comment=Browse the World Wide Web
+Comment[ar]=تصÙØ­ الشبكة العنكبوتية العالمية
+Comment[ast]=Restola pela Rede
+Comment[bn]=ইনà§à¦Ÿà¦¾à¦°à¦¨à§‡à¦Ÿ বà§à¦°à¦¾à¦‰à¦œ করà§à¦¨
+Comment[ca]=Navegueu per la web
+Comment[cs]=Prohlížení stránek World Wide Webu
+Comment[da]=Surf på internettet
+Comment[de]=Im Internet surfen
+Comment[el]=ΜποÏείτε να πεÏιηγηθείτε στο διαδίκτυο (Web)
+Comment[es]=Navegue por la web
+Comment[et]=Lehitse veebi
+Comment[fa]=صÙحات شبکه جهانی اینترنت را مرور نمایید
+Comment[fi]=Selaa Internetin WWW-sivuja
+Comment[fr]=Naviguer sur le Web
+Comment[gl]=Navegar pola rede
+Comment[he]=גלישה ברחבי ×”×ינטרנט
+Comment[hr]=Pretražite web
+Comment[hu]=A világháló böngészése
+Comment[it]=Esplora il web
+Comment[ja]=ウェブを閲覧ã—ã¾ã™
+Comment[ko]=ì›¹ì„ ëŒì•„ 다닙니다
+Comment[ku]=Li torê bigere
+Comment[lt]=Naršykite internete
+Comment[nb]=Surf på nettet
+Comment[nl]=Verken het internet
+Comment[nn]=Surf på nettet
+Comment[no]=Surf på nettet
+Comment[pl]=PrzeglÄ…danie stron WWW
+Comment[pt]=Navegue na Internet
+Comment[pt_BR]=Navegue na Internet
+Comment[ro]=Navigați pe Internet
+Comment[ru]=ДоÑтуп в Интернет
+Comment[sk]=Prehliadanie internetu
+Comment[sl]=Brskajte po spletu
+Comment[sv]=Surfa på webben
+Comment[tr]=Ä°nternet'te Gezinin
+Comment[ug]=دۇنيادىكى توربەتلەرنى كۆرگىلى بولىدۇ
+Comment[uk]=ПереглÑд Ñторінок Інтернету
+Comment[vi]=Äể duyệt các trang web
+Comment[zh_CN]=æµè§ˆäº’è”网
+Comment[zh_TW]=ç€è¦½ç¶²éš›ç¶²è·¯
+Exec=webbrowser %u
+Terminal=false
+Type=Application
+Icon=webbrowser
+Categories=Network;WebBrowser;
+MimeType=text/html;text/xml;application/xhtml+xml;application/vnd.mozilla.xul+xml;text/mml;x-scheme-handler/http;x-scheme-handler/https;x-scheme-handler/ftp;
+StartupNotify=false
+Actions=NewTab;NewWindow;NewPrivateWindow;
+StartupWMClass="Web Browser"
+
+[Desktop Action NewTab]
+Name=Open new tab
+Name[ach]=Yab dirica matidi manyen
+Name[af]=Open nuwe oortjie
+Name[an]=Ubrir una pestanya nueva
+Name[ar]=اÙتح لسانًا جديدًا
+Name[as]=নতà§à¦¨ টেব খোলক
+Name[ast]=Abrir llingüeta nueva
+Name[az]=Yeni vərəq aç
+Name[be]=Ðдкрыць новую ÑžÑтаўку
+Name[bg]=ОтварÑне на нов подпрозорец
+Name[bn_BD]=নতà§à¦¨ টà§à¦¯à¦¾à¦¬ খà§à¦²à§à¦¨
+Name[bn_IN]=নতà§à¦¨ টà§à¦¯à¦¾à¦¬ খà§à¦²à§à¦¨
+Name[br]=Digeriñ un ivinell nevez
+Name[bs]=Otvori novi tab
+Name[ca]=Obre una pestanya nova
+Name[cs]=Otevřít nový panel
+Name[cy]=Agor tab newydd
+Name[da]=Ã…bn nyt faneblad
+Name[de]=Neuen Tab öffnen
+Name[dsb]=Nowy rejtark wócyniś
+Name[el]=Άνοιγμα νέας καÏτέλας
+Name[eo]=Malfermi novan langeton
+Name[es_AR]=Abrir nueva pestaña
+Name[es_CL]=Abrir nueva pestaña
+Name[es_ES]=Abrir pestaña nueva
+Name[es_MX]=Abrir una pestaña nueva
+Name[et]=Ava uus kaart
+Name[eu]=Ireki fitxa berria
+Name[ff]=Uddit tabbere hesere
+Name[fi]=Avaa uusi välilehti
+Name[fr]=Ouvrir un nouvel onglet
+Name[fy_NL]=Iepenje nij ljepblêd
+Name[ga_IE]=Oscail i gcluaisín nua
+Name[gd]=Fosgail taba ùr
+Name[gl]=Abrir unha nova lapela
+Name[gu_IN]=નવી ટૅબને ખોલો
+Name[he]=פתיחת לשונית חדשה
+Name[hi_IN]=नया टैब खोलें
+Name[hr]=Otvori novu karticu
+Name[hsb]=Nowy rajtark woÄinić
+Name[hu]=Új lap megnyitása
+Name[hy_AM]=Ô²Õ¡ÖÕ¥Õ¬ Õ¶Õ¸Ö€ Õ¶Õ¥Ö€Õ¤Õ«Ö€
+Name[id]=Buka tab baru
+Name[is]=Opna nýjan flipa
+Name[it]=Apri nuova scheda
+Name[ja]=æ–°ã—ã„タブ
+Name[kk]=Жаңа бетті ашу
+Name[kn]=ಹೊಸ ಹಾಳೆಯನà³à²¨à³ ತೆರೆ
+Name[ko]=새 탭 열기
+Name[lij]=Àrvi nêuvo féuggio
+Name[lt]=Atverti naujÄ… kortelÄ™
+Name[mai]=नव टैब खोलू
+Name[mk]=Отвори ново јазиче
+Name[ml]=à´ªàµà´¤à´¿à´¯ à´±àµà´±à´¾à´¬àµ à´¤àµà´±à´•àµà´•àµà´•
+Name[mr]=नवीन टॅब उघडा
+Name[ms]=Buka tab baru
+Name[nb_NO]=Ã…pne ny fane
+Name[nl]=Nieuw tabblad openen
+Name[nn_NO]=Opna ny fane
+Name[or]=ନୂତନ ଟà­à­Ÿà¬¾à¬¬ ଖୋଲନà­à¬¤à­
+Name[pa_IN]=ਨਵੀਂ ਟੈਬ ਖੋਲà©à¨¹à©‹
+Name[pl]=Otwórz nową kartę
+Name[pt_BR]=Nova aba
+Name[pt_PT]=Abrir novo separador
+Name[rm]=Avrir in nov tab
+Name[ro]=Deschide o filă nouă
+Name[ru]=Открыть новую вкладку
+Name[si]=නව ටà·à¶¶à¶º විවෘත කරන්න
+Name[sk]=Otvoriť novú kartu
+Name[sl]=Odpri nov zavihek
+Name[son]=Nor loku taaga feeri
+Name[sq]=Hap skedë të re
+Name[sr]=Отвори нови језичак
+Name[sv_SE]=Öppna ny flik
+Name[ta]=பà¯à®¤à®¿à®¯ கீறà¯à®±à¯ˆà®¤à¯ திற
+Name[te]=కొతà±à°¤ టాబౠతెరà±à°µà±à°®à±
+Name[th]=เปิดà¹à¸—็บใหม่
+Name[tr]=Yeni sekme aç
+Name[uk]=Відкрити нову вкладку
+Name[uz]=Yangi ichki oyna ochish
+Name[vi]=Mở thẻ mới
+Name[xh]=Vula ithebhu entsha
+Name[zh_CN]=打开新标签页
+Name[zh_TW]=開啟新分é 
+Exec=webbrowser -new-tab
+
+[Desktop Action NewWindow]
+Name=Open new window
+Name[ach]=Yab dirica manyen
+Name[af]=Open nuwe venster
+Name[an]=Ubrir una nueva finestra
+Name[ar]=اÙتح ناÙذة جديدة
+Name[as]=নতà§à¦¨ উইনà§à¦¡à§‹ খোলক
+Name[ast]=Abrir ventana nueva
+Name[az]=Yeni pəncərə aç
+Name[be]=Ðдкрыць новае акно
+Name[bg]=ОтварÑне на нов прозорец
+Name[bn_BD]=নতà§à¦¨ উইনà§à¦¡à§‹ খà§à¦²à§à¦¨
+Name[bn_IN]=নতà§à¦¨ উইনà§à¦¡à§‹ খà§à¦²à§à¦¨
+Name[br]=Digeriñ ur prenestr nevez
+Name[bs]=Otvori novi prozor
+Name[ca]=Obre una finestra nova
+Name[cs]=Otevřít nové okno
+Name[cy]=Agor ffenestr newydd
+Name[da]=Ã…bn nyt vindue
+Name[de]=Neues Fenster öffnen
+Name[dsb]=Nowe wokno wócyniś
+Name[el]=Άνοιγμα νέου παÏαθÏÏου
+Name[eo]=Malfermi novan fenestron
+Name[es_AR]=Abrir nueva ventana
+Name[es_CL]=Abrir nueva ventana
+Name[es_ES]=Abrir nueva ventana
+Name[es_MX]=Abrir nueva ventana
+Name[et]=Ava uus aken
+Name[eu]=Ireki leiho berria
+Name[ff]=Uddit henorde hesere
+Name[fi]=Avaa uusi ikkuna
+Name[fr]=Ouvrir une nouvelle fenêtre
+Name[fy_NL]=Iepenje nij finster
+Name[ga_IE]=Oscail fuinneog nua
+Name[gd]=Fosgail uinneag ùr
+Name[gl]=Abrir unha nova xanela
+Name[gu_IN]=નવી વિનà«àª¡à«‹àª¨à«‡ ખોલો
+Name[he]=פתח חלון חדש
+Name[hi_IN]=नई विंडो खोलें
+Name[hr]=Otvori novi prozor
+Name[hsb]=Nowe wokno woÄinić
+Name[hu]=Új ablak megnyitása
+Name[hy_AM]=Ô²Õ¡ÖÕ¥Õ¬ Õ¶Õ¸Ö€ ÕºÕ¡Õ¿Õ¸Ö‚Õ°Õ¡Õ¶
+Name[id]=Buka jendela baru
+Name[is]=Opna nýjan glugga
+Name[it]=Apri nuova finestra
+Name[ja]=æ–°ã—ã„ウィンドウ
+Name[kk]=Жаңа терезені ашу
+Name[kn]=ಹೊಸ ವಿಂಡೊವನà³à²¨à³ ತೆರೆ
+Name[ko]=새 창 열기
+Name[lij]=Àrvi nêuvo barcón
+Name[lt]=Atverti naujÄ… langÄ…
+Name[mai]=नई विंडो खोलू
+Name[mk]=Отвори нов прозорец
+Name[ml]=à´ªàµà´¤à´¿à´¯ ജാലകം à´¤àµà´±à´•àµà´•àµà´•
+Name[mr]=नवीन पटल उघडा
+Name[ms]=Buka tetingkap baru
+Name[nb_NO]=Ã…pne nytt vindu
+Name[nl]=Een nieuw venster openen
+Name[nn_NO]=Opna nytt vindauge
+Name[or]=ନୂତନ ୱିଣà­à¬¡à­‹ ଖୋଲନà­à¬¤à­
+Name[pa_IN]=ਨਵੀਂ ਵਿੰਡੋ ਖੋਲà©à¨¹à©‹
+Name[pl]=Otwórz nowe okno
+Name[pt_BR]=Nova janela
+Name[pt_PT]=Abrir nova janela
+Name[rm]=Avrir ina nova fanestra
+Name[ro]=Deschide o nouă fereastră
+Name[ru]=Открыть новое окно
+Name[si]=නව කවුළුවක් විවෘත කරන්න
+Name[sk]=Otvoriť nové okno
+Name[sl]=Odpri novo okno
+Name[son]=Zanfun taaga feeri
+Name[sq]=Hap dritare të re
+Name[sr]=Отвори нови прозор
+Name[sv_SE]=Öppna nytt fönster
+Name[ta]=பà¯à®¤à®¿à®¯ சாளரதà¯à®¤à¯ˆ திற
+Name[te]=కొతà±à°¤ విండో తెరà±à°µà±à°®à±
+Name[th]=เปิดหน้าต่างใหม่
+Name[tr]=Yeni pencere aç
+Name[uk]=Відкрити нове вікно
+Name[uz]=Yangi oyna ochish
+Name[vi]=Mở cửa sổ mới
+Name[xh]=Vula iwindow entsha
+Name[zh_CN]=打开新窗å£
+Name[zh_TW]=開啟新視窗
+Exec=webbrowser -new-window
+
+[Desktop Action NewPrivateWindow]
+Name=New private window
+Name[ach]=Dirica manyen me mung
+Name[af]=Nuwe privaatvenster
+Name[an]=Nueva finestra de navegación privada
+Name[ar]=ناÙذة خاصة جديدة
+Name[as]=নতà§à¦¨ বà§à¦¯à¦•à§à¦¤à¦¿à¦—ত উইনà§à¦¡à§‹
+Name[ast]=Ventana privada nueva
+Name[az]=Yeni məxfi pəncərə
+Name[be]=Ðовае акно адаÑабленнÑ
+Name[bg]=Ðов прозорец за поверително Ñърфиране
+Name[bn_BD]=নতà§à¦¨ বà§à¦¯à¦•à§à¦¤à¦¿à¦—ত উইনà§à¦¡à§‹
+Name[bn_IN]=নতà§à¦¨ বà§à¦¯à¦¾à¦•à§à¦¤à¦¿à¦—ত উইনà§à¦¡à§‹
+Name[br]=Prenestr merdeiñ prevez nevez
+Name[bs]=Novi privatni prozor
+Name[ca]=Finestra privada nova
+Name[cs]=Nové anonymní okno
+Name[cy]=Ffenestr breifat newydd
+Name[da]=Nyt privat vindue
+Name[de]=Neues privates Fenster öffnen
+Name[dsb]=Nowe priwatne wokno
+Name[el]=Îέο παÏάθυÏο ιδιωτικής πεÏιήγησης
+Name[eo]=Nova privata fenestro
+Name[es_AR]=Nueva ventana privada
+Name[es_CL]=Nueva ventana privada
+Name[es_ES]=Nueva ventana privada
+Name[es_MX]=Nueva ventana privada
+Name[et]=Uus privaatne aken
+Name[eu]=Leiho pribatu berria
+Name[ff]=Henorde suturo hesere
+Name[fi]=Uusi yksityinen ikkuna
+Name[fr]=Nouvelle fenêtre de navigation privée
+Name[fy_NL]=Nij priveefinster
+Name[ga_IE]=Fuinneog nua phríobháideach
+Name[gd]=Uinneag phrìobhaideach ùr
+Name[gl]=Nova xanela privada
+Name[gu_IN]=નવી ખાનગી વિનà«àª¡à«‹
+Name[he]=חלון פרטי חדש
+Name[hi_IN]=नया निजी विंडो
+Name[hr]=Novi privatni prozor
+Name[hsb]=Nowe priwatne wokno
+Name[hu]=Új privát ablak
+Name[hy_AM]=Ô³Õ¡Õ²Õ¿Õ¶Õ« Õ¤Õ«Õ¿Õ¡Ö€Õ¯Õ¸Ö‚Õ´
+Name[id]=Jendela mode pribadi baru
+Name[is]=Nýr einkagluggi
+Name[it]=Nuova finestra anonima
+Name[ja]=æ–°ã—ã„プライベートウィンドウ
+Name[kk]=Жаңа жекелік терезе
+Name[kn]=ಹೊಸ ಖಾಸಗಿ ಕಿಟಕಿ
+Name[ko]=새 사ìƒí™œ 보호 ì°½
+Name[lij]=Nêuvo barcón privòu
+Name[lt]=Atverti privaÄiojo narÅ¡ymo langÄ…
+Name[mai]=नव निज विंडो
+Name[mk]=Ðов прозорец за приватно Ñурфање
+Name[ml]=à´ªàµà´¤à´¿à´¯ à´¸àµà´µà´•à´¾à´°àµà´¯ ജാലകം
+Name[mr]=नवीन वैयकà¥à¤¤à¤¿à¤• पटल
+Name[ms]=Tetingkap peribadi baharu
+Name[nb_NO]=Nytt privat vindu
+Name[nl]=Nieuw privévenster
+Name[nn_NO]=Nytt privat vindauge
+Name[or]=ନୂତନ ବà­à­Ÿà¬•à­à¬¤à¬¿à¬—ତ ୱିଣà­à¬¡à­‹
+Name[pa_IN]=ਨਵੀਂ ਪà©à¨°à¨¾à¨ˆà¨µà©‡à¨Ÿ ਵਿੰਡੋ
+Name[pl]=Nowe okno w trybie prywatnym
+Name[pt_BR]=Nova janela privativa
+Name[pt_PT]=Nova janela privada
+Name[rm]=Nova fanestra privata
+Name[ro]=Fereastră fără urme nouă
+Name[ru]=Ðовое приватное окно
+Name[si]=නව පුද්ගලික කවුළුව
+Name[sk]=Nové okno v režime Súkromné prehliadanie
+Name[sl]=Novo zasebno okno
+Name[son]=Sutura zanfun taaga
+Name[sq]=Dritare e re private
+Name[sr]=Ðови приватни прозор
+Name[sv_SE]=Nytt privat fönster
+Name[ta]=பà¯à®¤à®¿à®¯ தனிபà¯à®ªà®Ÿà¯à®Ÿ சாளரமà¯
+Name[te]=కొతà±à°¤ ఆంతరంగిక విండో
+Name[th]=หน้าต่างท่องเว็บà¹à¸šà¸šà¸ªà¹ˆà¸§à¸™à¸•à¸±à¸§à¹ƒà¸«à¸¡à¹ˆ
+Name[tr]=Yeni gizli pencere
+Name[uk]=Ðове приватне вікно
+Name[uz]=Yangi shaxsiy oyna
+Name[vi]=Cửa sổ riêng tư mới
+Name[xh]=Ifestile yangasese entsha
+Name[zh_CN]=新建éšç§æµè§ˆçª—å£
+Name[zh_TW]=新增隱ç§è¦–窗
+Exec=webbrowser -private-window
diff --git a/branding/unofficial/wizHeader.bmp b/branding/unofficial/wizHeader.bmp
new file mode 100644
index 0000000..a566996
--- /dev/null
+++ b/branding/unofficial/wizHeader.bmp
Binary files differ
diff --git a/branding/unofficial/wizHeaderRTL.bmp b/branding/unofficial/wizHeaderRTL.bmp
new file mode 100644
index 0000000..137fe5b
--- /dev/null
+++ b/branding/unofficial/wizHeaderRTL.bmp
Binary files differ
diff --git a/branding/unofficial/wizWatermark.bmp b/branding/unofficial/wizWatermark.bmp
new file mode 100644
index 0000000..b229261
--- /dev/null
+++ b/branding/unofficial/wizWatermark.bmp
Binary files differ
diff --git a/branding/unstable/VisualElements_150.png b/branding/unstable/VisualElements_150.png
new file mode 100644
index 0000000..320623d
--- /dev/null
+++ b/branding/unstable/VisualElements_150.png
Binary files differ
diff --git a/branding/unstable/VisualElements_70.png b/branding/unstable/VisualElements_70.png
new file mode 100644
index 0000000..cb4c868
--- /dev/null
+++ b/branding/unstable/VisualElements_70.png
Binary files differ
diff --git a/branding/unstable/appname.bmp b/branding/unstable/appname.bmp
new file mode 100644
index 0000000..78e227a
--- /dev/null
+++ b/branding/unstable/appname.bmp
Binary files differ
diff --git a/branding/unstable/branding.nsi b/branding/unstable/branding.nsi
new file mode 100644
index 0000000..535cfde
--- /dev/null
+++ b/branding/unstable/branding.nsi
@@ -0,0 +1,16 @@
+# 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/.
+
+# NSIS branding defines for official release builds.
+# The nightly build branding.nsi is located in browser/installer/windows/nsis/
+# The unofficial build branding.nsi is located in browser/branding/unofficial/
+
+# BrandFullNameInternal is used for some registry and file system values
+# instead of BrandFullName and typically should not be modified.
+!define BrandFullNameInternal "Pale Moon"
+!define CompanyName "Moonchild Productions"
+!define URLInfoAbout "http://www.palemoon.org/"
+!define URLUpdateInfo "http://www.palemoon.org/unstable/"
+!define HelpLink "http://www.palemoon.org/unstable/"
+!define URLSystemRequirements "http://www.palemoon.org/download.shtml"
diff --git a/branding/unstable/configure.sh b/branding/unstable/configure.sh
new file mode 100644
index 0000000..8943f58
--- /dev/null
+++ b/branding/unstable/configure.sh
@@ -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/.
+
+MOZ_APP_DISPLAYNAME="Pale Moon"
+# MOZ_UA_BUILDID=20100101
diff --git a/branding/unstable/content/about-background.jpg b/branding/unstable/content/about-background.jpg
new file mode 100644
index 0000000..a33b331
--- /dev/null
+++ b/branding/unstable/content/about-background.jpg
Binary files differ
diff --git a/branding/unstable/content/about-logo.png b/branding/unstable/content/about-logo.png
new file mode 100644
index 0000000..aa79de6
--- /dev/null
+++ b/branding/unstable/content/about-logo.png
Binary files differ
diff --git a/branding/unstable/content/about-logo@2x.png b/branding/unstable/content/about-logo@2x.png
new file mode 100644
index 0000000..5d507a6
--- /dev/null
+++ b/branding/unstable/content/about-logo@2x.png
Binary files differ
diff --git a/branding/unstable/content/about-wordmark.png b/branding/unstable/content/about-wordmark.png
new file mode 100644
index 0000000..bf09f15
--- /dev/null
+++ b/branding/unstable/content/about-wordmark.png
Binary files differ
diff --git a/branding/unstable/content/about.png b/branding/unstable/content/about.png
new file mode 100644
index 0000000..d158863
--- /dev/null
+++ b/branding/unstable/content/about.png
Binary files differ
diff --git a/branding/unstable/content/aboutDialog.css b/branding/unstable/content/aboutDialog.css
new file mode 100644
index 0000000..de71f25
--- /dev/null
+++ b/branding/unstable/content/aboutDialog.css
@@ -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/. */
+
+#aboutPMDialogContainer {
+ background-image: url("chrome://branding/content/about-background.jpg");
+ background-repeat: no-repeat;
+ background-color: #D5BC9A;
+ color: #202010;
+}
+
+#aboutHeaderBox {
+ background-image: url("chrome://branding/content/about-wordmark.png");
+ background-repeat: no-repeat;
+ background-position: center center;
+ height: 44px;
+}
+
+#aboutVersionBox {
+ text-shadow: 1px 1px 0px #D5BC9A;
+}
+
+#aboutTextBox {
+ animation: 3s fadeIn;
+ animation-fill-mode: forwards;
+ text-shadow: 1px 1px 0px #D5BC9A;
+ color: #202010;
+}
+
+@keyframes fadeIn {
+ 0% {
+ opacity: 0;
+ }
+ 50% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
+}
+
+#aboutLinkBox {
+ padding: 15px 10px 0;
+}
+
+#aboutPMtrademark {
+ font-size: 10px;
+ text-align: center;
+ color: #C0C0C0;
+ text-shadow: 1px 1px 0px #000000;
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
diff --git a/branding/unstable/content/icon48.png b/branding/unstable/content/icon48.png
new file mode 100644
index 0000000..9572134
--- /dev/null
+++ b/branding/unstable/content/icon48.png
Binary files differ
diff --git a/branding/unstable/content/icon64.png b/branding/unstable/content/icon64.png
new file mode 100644
index 0000000..c370f33
--- /dev/null
+++ b/branding/unstable/content/icon64.png
Binary files differ
diff --git a/branding/unstable/content/jar.mn b/branding/unstable/content/jar.mn
new file mode 100644
index 0000000..fcb7890
--- /dev/null
+++ b/branding/unstable/content/jar.mn
@@ -0,0 +1,16 @@
+# 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 branding %content/branding/ contentaccessible=yes
+ content/branding/about.png (about.png)
+ content/branding/about-background.jpg (about-background.jpg)
+ content/branding/about-logo.png (about-logo.png)
+ content/branding/about-logo@2x.png (about-logo@2x.png)
+ content/branding/about-wordmark.png (about-wordmark.png)
+ content/branding/icon48.png (icon48.png)
+ content/branding/icon64.png (icon64.png)
+ content/branding/icon16.png (../default16.png)
+ content/branding/icon32.png (../default32.png)
+ content/branding/aboutDialog.css (aboutDialog.css)
diff --git a/branding/unstable/content/moz.build b/branding/unstable/content/moz.build
new file mode 100644
index 0000000..c97072b
--- /dev/null
+++ b/branding/unstable/content/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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/branding/unstable/default16.png b/branding/unstable/default16.png
new file mode 100644
index 0000000..d239402
--- /dev/null
+++ b/branding/unstable/default16.png
Binary files differ
diff --git a/branding/unstable/default32.png b/branding/unstable/default32.png
new file mode 100644
index 0000000..05ccab8
--- /dev/null
+++ b/branding/unstable/default32.png
Binary files differ
diff --git a/branding/unstable/default48.png b/branding/unstable/default48.png
new file mode 100644
index 0000000..9572134
--- /dev/null
+++ b/branding/unstable/default48.png
Binary files differ
diff --git a/branding/unstable/disk.icns b/branding/unstable/disk.icns
new file mode 100644
index 0000000..e97e490
--- /dev/null
+++ b/branding/unstable/disk.icns
Binary files differ
diff --git a/branding/unstable/document.icns b/branding/unstable/document.icns
new file mode 100644
index 0000000..dd5f7aa
--- /dev/null
+++ b/branding/unstable/document.icns
Binary files differ
diff --git a/branding/unstable/document.ico b/branding/unstable/document.ico
new file mode 100644
index 0000000..d5c730b
--- /dev/null
+++ b/branding/unstable/document.ico
Binary files differ
diff --git a/branding/unstable/dsstore b/branding/unstable/dsstore
new file mode 100644
index 0000000..bbba9ec
--- /dev/null
+++ b/branding/unstable/dsstore
Binary files differ
diff --git a/branding/unstable/firefox.icns b/branding/unstable/firefox.icns
new file mode 100644
index 0000000..3df606a
--- /dev/null
+++ b/branding/unstable/firefox.icns
Binary files differ
diff --git a/branding/unstable/firefox.ico b/branding/unstable/firefox.ico
new file mode 100644
index 0000000..605c876
--- /dev/null
+++ b/branding/unstable/firefox.ico
Binary files differ
diff --git a/branding/unstable/locales/en-US/brand.dtd b/branding/unstable/locales/en-US/brand.dtd
new file mode 100644
index 0000000..9a26025
--- /dev/null
+++ b/branding/unstable/locales/en-US/brand.dtd
@@ -0,0 +1,4 @@
+<!ENTITY brandShortName "Pale Moon">
+<!ENTITY brandFullName "Pale Moon">
+<!ENTITY vendorShortName "Moonchild">
+<!ENTITY trademarkInfo.part1 "The Pale Moon logo and project names are trademarks of Moonchild Productions (M.C. Straver BASc). All rights reserved.">
diff --git a/branding/unstable/locales/en-US/brand.properties b/branding/unstable/locales/en-US/brand.properties
new file mode 100644
index 0000000..7d4b469
--- /dev/null
+++ b/branding/unstable/locales/en-US/brand.properties
@@ -0,0 +1,5 @@
+brandShortName=Pale Moon
+brandFullName=Pale Moon
+vendorShortName=Moonchild
+
+syncBrandShortName=Sync
diff --git a/branding/unstable/locales/jar.mn b/branding/unstable/locales/jar.mn
new file mode 100644
index 0000000..9de6cfc
--- /dev/null
+++ b/branding/unstable/locales/jar.mn
@@ -0,0 +1,12 @@
+#filter substitution
+# 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/.
+
+
+@AB_CD@.jar:
+% locale branding @AB_CD@ %locale/branding/
+# Unofficial branding only exists in en-US
+ locale/branding/brand.dtd (en-US/brand.dtd)
+ locale/branding/brand.properties (en-US/brand.properties)
+ locale/branding/browserconfig.properties (../../shared/locales/browserconfig.properties)
diff --git a/branding/unstable/locales/moz.build b/branding/unstable/locales/moz.build
new file mode 100644
index 0000000..3a54c0c
--- /dev/null
+++ b/branding/unstable/locales/moz.build
@@ -0,0 +1,9 @@
+# -*- 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/.
+
+DEFINES['MOZ_DISTRIBUTION_ID_UNQUOTED'] = CONFIG['MOZ_DISTRIBUTION_ID']
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/branding/unstable/moz.build b/branding/unstable/moz.build
new file mode 100644
index 0000000..8cb9013
--- /dev/null
+++ b/branding/unstable/moz.build
@@ -0,0 +1,13 @@
+# -*- 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 += ['content', 'locales']
+
+DIST_SUBDIR = 'browser'
+export('DIST_SUBDIR')
+
+include('../shared/branding.mozbuild')
+ApplicationBranding()
diff --git a/branding/unstable/mozicon128.png b/branding/unstable/mozicon128.png
new file mode 100644
index 0000000..fa8a685
--- /dev/null
+++ b/branding/unstable/mozicon128.png
Binary files differ
diff --git a/branding/unstable/palemoon.VisualElementsManifest.xml b/branding/unstable/palemoon.VisualElementsManifest.xml
new file mode 100644
index 0000000..3bdebe2
--- /dev/null
+++ b/branding/unstable/palemoon.VisualElementsManifest.xml
@@ -0,0 +1,8 @@
+<Application xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>
+ <VisualElements
+ ShowNameOnSquare150x150Logo='on'
+ Square150x150Logo='browser\VisualElements\VisualElements_150.png'
+ Square70x70Logo='browser\VisualElements\VisualElements_70.png'
+ ForegroundText='light'
+ BackgroundColor='#14141A'/>
+</Application>
diff --git a/branding/unstable/pref/palemoon-branding.js b/branding/unstable/pref/palemoon-branding.js
new file mode 100644
index 0000000..2cd64fa
--- /dev/null
+++ b/branding/unstable/pref/palemoon-branding.js
@@ -0,0 +1,46 @@
+#filter substitution
+#filter emptyLines
+#include ../../shared/pref/preferences.inc
+#include ../../shared/pref/uaoverrides.inc
+
+pref("startup.homepage_override_url","http://www.palemoon.org/unstable/releasenotes.shtml");
+pref("app.releaseNotesURL", "http://www.palemoon.org/unstable/releasenotes.shtml");
+
+// Enable Firefox compatmode by default.
+pref("general.useragent.compatMode", 2);
+pref("general.useragent.compatMode.gecko", true);
+pref("general.useragent.compatMode.firefox", true);
+
+// ========================= updates ========================
+#if defined(XP_WIN) || defined(XP_LINUX)
+// Enable auto-updates for this channel
+pref("app.update.auto", true);
+
+// Updates enabled
+pref("app.update.enabled", true);
+pref("app.update.cert.checkAttributes", true);
+
+// Interval: Time between checks for a new version (in seconds) -- 6 hours for unstable
+pref("app.update.interval", 21600);
+pref("app.update.promptWaitTime", 86400);
+
+// URL user can browse to manually if for some reason all update installation
+// attempts fail.
+#ifndef XP_LINUX
+pref("app.update.url.manual", "http://www.palemoon.org/unstable/");
+#else
+pref("app.update.url.manual", "http://linux.palemoon.org/download/unstable/");
+#endif
+// A default value for the "More information about this update" link
+// supplied in the "An update is available" page of the update wizard.
+#ifndef XP_LINUX
+pref("app.update.url.details", "http://www.palemoon.org/unstable/");
+#else
+pref("app.update.url.details", "http://linux.palemoon.org/download/unstable/");
+#endif
+
+#else
+// Updates disabled (Mac, etc.)
+pref("app.update.enabled", false);
+pref("app.update.url", "");
+#endif
diff --git a/branding/unstable/wizHeader.bmp b/branding/unstable/wizHeader.bmp
new file mode 100644
index 0000000..12e08e8
--- /dev/null
+++ b/branding/unstable/wizHeader.bmp
Binary files differ
diff --git a/branding/unstable/wizHeaderRTL.bmp b/branding/unstable/wizHeaderRTL.bmp
new file mode 100644
index 0000000..eb8b9f4
--- /dev/null
+++ b/branding/unstable/wizHeaderRTL.bmp
Binary files differ
diff --git a/branding/unstable/wizWatermark.bmp b/branding/unstable/wizWatermark.bmp
new file mode 100644
index 0000000..8c39d51
--- /dev/null
+++ b/branding/unstable/wizWatermark.bmp
Binary files differ
diff --git a/build.mk b/build.mk
new file mode 100644
index 0000000..cc0f345
--- /dev/null
+++ b/build.mk
@@ -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/.
+
+installer:
+ @$(MAKE) -C application/webbrowser/installer installer
+
+package:
+ @$(MAKE) -C application/webbrowser/installer
+
+package-compare:
+ @$(MAKE) -C application/webbrowser/installer package-compare
+
+stage-package:
+ @$(MAKE) -C application/webbrowser/installer stage-package
+
+install::
+ @$(MAKE) -C application/webbrowser/installer install
+
+clean::
+ @$(MAKE) -C application/webbrowser/installer clean
+
+distclean::
+ @$(MAKE) -C application/webbrowser/installer distclean
+
+source-package::
+ @$(MAKE) -C application/webbrowser/installer source-package
+
+upload::
+ @$(MAKE) -C application/webbrowser/installer upload
+
+source-upload::
+ @$(MAKE) -C application/webbrowser/installer source-upload
+
+hg-bundle::
+ @$(MAKE) -C application/webbrowser/installer hg-bundle
+
+l10n-check::
+ @$(MAKE) -C application/webbrowser/locales l10n-check
+
+ifdef ENABLE_TESTS
+# Implemented in testing/testsuite-targets.mk
+
+mochitest-browser-chrome:
+ $(RUN_MOCHITEST) --browser-chrome
+ $(CHECK_TEST_ERROR)
+
+mochitest:: mochitest-browser-chrome
+
+.PHONY: mochitest-browser-chrome
+
+mochitest-metro-chrome:
+ $(RUN_MOCHITEST) --metro-immersive --browser-chrome
+ $(CHECK_TEST_ERROR)
+
+
+endif
diff --git a/components/BrowserComponents.manifest b/components/BrowserComponents.manifest
new file mode 100644
index 0000000..0ff14d0
--- /dev/null
+++ b/components/BrowserComponents.manifest
@@ -0,0 +1,64 @@
+# nsAboutRedirector.js
+component {8cc51368-6aa0-43e8-b762-bde9b9fd828c} nsAboutRedirector.js
+# Each entry here should be coupled with an entry in nsAboutRedirector.js
+contract @mozilla.org/network/protocol/about;1?what=certerror {8cc51368-6aa0-43e8-b762-bde9b9fd828c}
+contract @mozilla.org/network/protocol/about;1?what=downloads {8cc51368-6aa0-43e8-b762-bde9b9fd828c}
+contract @mozilla.org/network/protocol/about;1?what=feeds {8cc51368-6aa0-43e8-b762-bde9b9fd828c}
+contract @mozilla.org/network/protocol/about;1?what=home {8cc51368-6aa0-43e8-b762-bde9b9fd828c}
+contract @mozilla.org/network/protocol/about;1?what=newtab {8cc51368-6aa0-43e8-b762-bde9b9fd828c}
+contract @mozilla.org/network/protocol/about;1?what=palemoon {8cc51368-6aa0-43e8-b762-bde9b9fd828c}
+contract @mozilla.org/network/protocol/about;1?what=permissions {8cc51368-6aa0-43e8-b762-bde9b9fd828c}
+contract @mozilla.org/network/protocol/about;1?what=privatebrowsing {8cc51368-6aa0-43e8-b762-bde9b9fd828c}
+contract @mozilla.org/network/protocol/about;1?what=rights {8cc51368-6aa0-43e8-b762-bde9b9fd828c}
+contract @mozilla.org/network/protocol/about;1?what=sessionrestore {8cc51368-6aa0-43e8-b762-bde9b9fd828c}
+#ifdef MOZ_SERVICES_SYNC
+contract @mozilla.org/network/protocol/about;1?what=sync-progress {8cc51368-6aa0-43e8-b762-bde9b9fd828c}
+contract @mozilla.org/network/protocol/about;1?what=sync-tabs {8cc51368-6aa0-43e8-b762-bde9b9fd828c}
+#endif
+
+# nsBrowserContentHandler.js
+component {5d0ce354-df01-421a-83fb-7ead0990c24e} nsBrowserContentHandler.js application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
+contract @mozilla.org/browser/clh;1 {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
+component {47cd0651-b1be-4a0f-b5c4-10e5a573ef71} nsBrowserContentHandler.js application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
+contract @mozilla.org/browser/final-clh;1 {47cd0651-b1be-4a0f-b5c4-10e5a573ef71} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
+contract @mozilla.org/uriloader/content-handler;1?type=text/html {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
+contract @mozilla.org/uriloader/content-handler;1?type=application/vnd.mozilla.xul+xml {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
+contract @mozilla.org/uriloader/content-handler;1?type=image/svg+xml {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
+contract @mozilla.org/uriloader/content-handler;1?type=text/rdf {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
+contract @mozilla.org/uriloader/content-handler;1?type=text/xml {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
+contract @mozilla.org/uriloader/content-handler;1?type=application/xhtml+xml {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
+contract @mozilla.org/uriloader/content-handler;1?type=text/css {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
+contract @mozilla.org/uriloader/content-handler;1?type=text/plain {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
+contract @mozilla.org/uriloader/content-handler;1?type=image/gif {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
+contract @mozilla.org/uriloader/content-handler;1?type=image/jpeg {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
+contract @mozilla.org/uriloader/content-handler;1?type=image/jpg {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
+contract @mozilla.org/uriloader/content-handler;1?type=image/png {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
+contract @mozilla.org/uriloader/content-handler;1?type=image/bmp {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
+contract @mozilla.org/uriloader/content-handler;1?type=image/x-icon {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
+contract @mozilla.org/uriloader/content-handler;1?type=image/vnd.microsoft.icon {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
+contract @mozilla.org/uriloader/content-handler;1?type=image/webp {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
+contract @mozilla.org/uriloader/content-handler;1?type=application/http-index-format {5d0ce354-df01-421a-83fb-7ead0990c24e} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
+category command-line-handler m-browser @mozilla.org/browser/clh;1 application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
+category command-line-handler x-default @mozilla.org/browser/final-clh;1 application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
+category command-line-validator b-browser @mozilla.org/browser/clh;1 application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
+
+# nsBrowserGlue.js
+
+# WebappRT doesn't need these instructions, and they don't necessarily work
+# with it, but it does use a GRE directory that the GRE shares with Firefox,
+# so in order to prevent the instructions from being processed for WebappRT,
+# we need to restrict them to the applications that depend on them, i.e.:
+#
+# b2g: {3c2e2abc-06d4-11e1-ac3b-374f68613e61}
+# browser: {8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
+# mobile/android: {aa3c5121-dab2-40e2-81ca-7ea25febc110}
+# mobile/xul: {a23983c0-fd0e-11dc-95ff-0800200c9a66}
+#
+# In theory we should do this for all these instructions, but in practice it is
+# sufficient to do it for the app-startup one, and the file is simpler that way.
+
+component {eab9012e-5f74-4cbc-b2b5-a590235513cc} nsBrowserGlue.js
+contract @mozilla.org/browser/browserglue;1 {eab9012e-5f74-4cbc-b2b5-a590235513cc}
+category app-startup nsBrowserGlue service,@mozilla.org/browser/browserglue;1 application={3c2e2abc-06d4-11e1-ac3b-374f68613e61} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4} application={aa3c5121-dab2-40e2-81ca-7ea25febc110} application={a23983c0-fd0e-11dc-95ff-0800200c9a66}
+component {d8903bf6-68d5-4e97-bcd1-e4d3012f721a} nsBrowserGlue.js
+contract @mozilla.org/content-permission/prompt;1 {d8903bf6-68d5-4e97-bcd1-e4d3012f721a}
diff --git a/components/abouthome/aboutHome.css b/components/abouthome/aboutHome.css
new file mode 100644
index 0000000..2b062e8
--- /dev/null
+++ b/components/abouthome/aboutHome.css
@@ -0,0 +1,343 @@
+%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 {
+ 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;
+}
+
+#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");
+}
+
+#addons::before {
+ content: url("chrome://browser/content/abouthome/addons.png");
+}
+
+%ifdef MOZ_SERVICES_SYNC
+#sync::before {
+ content: url("chrome://browser/content/abouthome/sync.png");
+}
+%endif
+
+#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;
+}
+
+/* [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");
+ }
+
+ .launchButton::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");
+ }
+
+ #addons::before {
+ content: url("chrome://browser/content/abouthome/addons@2x.png");
+ }
+
+%ifdef MOZ_SERVICES_SYNC
+ #sync::before {
+ content: url("chrome://browser/content/abouthome/sync@2x.png");
+ }
+%endif
+
+ #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");
+ }
+}
+
diff --git a/components/abouthome/aboutHome.js b/components/abouthome/aboutHome.js
new file mode 100644
index 0000000..6ff8eee
--- /dev/null
+++ b/components/abouthome/aboutHome.js
@@ -0,0 +1,227 @@
+/* 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 = {
+ "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=="
+ }
+};
+
+// This global tracks if the page has been set up before, to prevent double inits
+var gInitialized = false;
+var gObserver = new MutationObserver(function (mutations) {
+ for (let mutation of mutations) {
+ if (mutation.attributeName == "searchEngineURL") {
+ setupSearchEngine();
+ if (!gInitialized) {
+ 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);
+});
+
+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;
+ }
+
+}
+
+function fitToWidth() {
+ if (window.scrollMaxX) {
+ document.body.setAttribute("narrow", "true");
+ } else if (document.body.hasAttribute("narrow")) {
+ document.body.removeAttribute("narrow");
+ fitToWidth();
+ }
+}
diff --git a/components/abouthome/aboutHome.xhtml b/components/abouthome/aboutHome.xhtml
new file mode 100644
index 0000000..d72ec49
--- /dev/null
+++ b/components/abouthome/aboutHome.xhtml
@@ -0,0 +1,62 @@
+<?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>
+ <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="addons">&abouthome.addonsButton.label;</button>
+#ifdef MOZ_SERVICES_SYNC
+ <button class="launchButton" id="sync">&abouthome.syncButton.label;</button>
+#endif
+ <button class="launchButton" id="settings">&abouthome.settingsButton.label;</button>
+ <div id="restorePreviousSessionSeparator"/>
+ <button class="launchButton" id="restorePreviousSession">&historyRestoreLastSession.label;</button>
+ </div>
+ </body>
+</html>
diff --git a/components/abouthome/addons.png b/components/abouthome/addons.png
new file mode 100644
index 0000000..41519ce
--- /dev/null
+++ b/components/abouthome/addons.png
Binary files differ
diff --git a/components/abouthome/addons@2x.png b/components/abouthome/addons@2x.png
new file mode 100644
index 0000000..d4d04ee
--- /dev/null
+++ b/components/abouthome/addons@2x.png
Binary files differ
diff --git a/components/abouthome/bookmarks.png b/components/abouthome/bookmarks.png
new file mode 100644
index 0000000..5c7e194
--- /dev/null
+++ b/components/abouthome/bookmarks.png
Binary files differ
diff --git a/components/abouthome/bookmarks@2x.png b/components/abouthome/bookmarks@2x.png
new file mode 100644
index 0000000..7ede007
--- /dev/null
+++ b/components/abouthome/bookmarks@2x.png
Binary files differ
diff --git a/components/abouthome/downloads.png b/components/abouthome/downloads.png
new file mode 100644
index 0000000..3d4d10e
--- /dev/null
+++ b/components/abouthome/downloads.png
Binary files differ
diff --git a/components/abouthome/downloads@2x.png b/components/abouthome/downloads@2x.png
new file mode 100644
index 0000000..d384a22
--- /dev/null
+++ b/components/abouthome/downloads@2x.png
Binary files differ
diff --git a/components/abouthome/history.png b/components/abouthome/history.png
new file mode 100644
index 0000000..ae742b1
--- /dev/null
+++ b/components/abouthome/history.png
Binary files differ
diff --git a/components/abouthome/history@2x.png b/components/abouthome/history@2x.png
new file mode 100644
index 0000000..696902e
--- /dev/null
+++ b/components/abouthome/history@2x.png
Binary files differ
diff --git a/components/abouthome/jar.mn b/components/abouthome/jar.mn
new file mode 100644
index 0000000..e1ae4ac
--- /dev/null
+++ b/components/abouthome/jar.mn
@@ -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/.
+
+browser.jar:
+* content/browser/abouthome/aboutHome.xhtml
+ content/browser/abouthome/aboutHome.js
+* content/browser/abouthome/aboutHome.css
+ content/browser/abouthome/noise.png
+ content/browser/abouthome/snippet1.png
+ content/browser/abouthome/snippet2.png
+ content/browser/abouthome/downloads.png
+ content/browser/abouthome/bookmarks.png
+ content/browser/abouthome/history.png
+ content/browser/abouthome/addons.png
+#ifdef MOZ_SERVICES_SYNC
+ content/browser/abouthome/sync.png
+#endif
+ content/browser/abouthome/settings.png
+ content/browser/abouthome/restore.png
+ content/browser/abouthome/restore-large.png
+ content/browser/abouthome/snippet1@2x.png
+ content/browser/abouthome/snippet2@2x.png
+ content/browser/abouthome/downloads@2x.png
+ content/browser/abouthome/bookmarks@2x.png
+ content/browser/abouthome/history@2x.png
+ content/browser/abouthome/addons@2x.png
+#ifdef MOZ_SERVICES_SYNC
+ content/browser/abouthome/sync@2x.png
+#endif
+ content/browser/abouthome/settings@2x.png
+ content/browser/abouthome/restore@2x.png
+ content/browser/abouthome/restore-large@2x.png \ No newline at end of file
diff --git a/components/abouthome/moz.build b/components/abouthome/moz.build
new file mode 100644
index 0000000..2d64d50
--- /dev/null
+++ b/components/abouthome/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/.
+
+JAR_MANIFESTS += ['jar.mn']
+
diff --git a/components/abouthome/noise.png b/components/abouthome/noise.png
new file mode 100644
index 0000000..3467cf4
--- /dev/null
+++ b/components/abouthome/noise.png
Binary files differ
diff --git a/components/abouthome/restore-large.png b/components/abouthome/restore-large.png
new file mode 100644
index 0000000..ef593e6
--- /dev/null
+++ b/components/abouthome/restore-large.png
Binary files differ
diff --git a/components/abouthome/restore-large@2x.png b/components/abouthome/restore-large@2x.png
new file mode 100644
index 0000000..d5c71d0
--- /dev/null
+++ b/components/abouthome/restore-large@2x.png
Binary files differ
diff --git a/components/abouthome/restore.png b/components/abouthome/restore.png
new file mode 100644
index 0000000..5c3d6f4
--- /dev/null
+++ b/components/abouthome/restore.png
Binary files differ
diff --git a/components/abouthome/restore@2x.png b/components/abouthome/restore@2x.png
new file mode 100644
index 0000000..5acb630
--- /dev/null
+++ b/components/abouthome/restore@2x.png
Binary files differ
diff --git a/components/abouthome/settings.png b/components/abouthome/settings.png
new file mode 100644
index 0000000..4b0c309
--- /dev/null
+++ b/components/abouthome/settings.png
Binary files differ
diff --git a/components/abouthome/settings@2x.png b/components/abouthome/settings@2x.png
new file mode 100644
index 0000000..c77cb9a
--- /dev/null
+++ b/components/abouthome/settings@2x.png
Binary files differ
diff --git a/components/abouthome/snippet1.png b/components/abouthome/snippet1.png
new file mode 100644
index 0000000..ce2ec55
--- /dev/null
+++ b/components/abouthome/snippet1.png
Binary files differ
diff --git a/components/abouthome/snippet1@2x.png b/components/abouthome/snippet1@2x.png
new file mode 100644
index 0000000..f57cd0a
--- /dev/null
+++ b/components/abouthome/snippet1@2x.png
Binary files differ
diff --git a/components/abouthome/snippet2.png b/components/abouthome/snippet2.png
new file mode 100644
index 0000000..e0724fb
--- /dev/null
+++ b/components/abouthome/snippet2.png
Binary files differ
diff --git a/components/abouthome/snippet2@2x.png b/components/abouthome/snippet2@2x.png
new file mode 100644
index 0000000..40577f5
--- /dev/null
+++ b/components/abouthome/snippet2@2x.png
Binary files differ
diff --git a/components/abouthome/sync.png b/components/abouthome/sync.png
new file mode 100644
index 0000000..11e40cc
--- /dev/null
+++ b/components/abouthome/sync.png
Binary files differ
diff --git a/components/abouthome/sync@2x.png b/components/abouthome/sync@2x.png
new file mode 100644
index 0000000..6354f5b
--- /dev/null
+++ b/components/abouthome/sync@2x.png
Binary files differ
diff --git a/components/build/Makefile.in b/components/build/Makefile.in
new file mode 100644
index 0000000..2387227
--- /dev/null
+++ b/components/build/Makefile.in
@@ -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/.
+
+include $(topsrcdir)/config/rules.mk
+
+# Ensure that we don't embed a manifest referencing the CRT.
+EMBED_MANIFEST_AT =
diff --git a/components/build/moz.build b/components/build/moz.build
new file mode 100644
index 0000000..ea1f771
--- /dev/null
+++ b/components/build/moz.build
@@ -0,0 +1,36 @@
+# -*- 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/.
+
+EXPORTS += ['nsBrowserCompsCID.h']
+
+SOURCES += ['nsModule.cpp']
+
+XPCOMBinaryComponent('browsercomps')
+
+LOCAL_INCLUDES += [
+ '../dirprovider',
+ '../feeds',
+ '../shell',
+]
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ OS_LIBS += [
+ 'esent',
+ 'netapi32',
+ 'ole32',
+ 'shell32',
+ 'shlwapi',
+ 'version',
+ ]
+ DELAYLOAD_DLLS += [
+ 'esent.dll',
+ 'netapi32.dll',
+ ]
+
+# Mac: Need to link with CoreFoundation for Mac Migrators (PList reading code)
+# GTK2: Need to link with glib for GNOME shell service
+if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('cocoa', 'gtk2', 'gtk3'):
+ OS_LIBS += CONFIG['TK_LIBS']
diff --git a/components/build/nsBrowserCompsCID.h b/components/build/nsBrowserCompsCID.h
new file mode 100644
index 0000000..bbaa9ab
--- /dev/null
+++ b/components/build/nsBrowserCompsCID.h
@@ -0,0 +1,31 @@
+/* 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 NS_SHELLSERVICE_CID \
+{ 0x63c7b9f4, 0xcc8, 0x43f8, { 0xb6, 0x66, 0xa, 0x66, 0x16, 0x55, 0xcb, 0x73 } }
+
+#define NS_SHELLSERVICE_CONTRACTID \
+ "@mozilla.org/browser/shell-service;1"
+
+#define NS_RDF_FORWARDPROXY_INFER_DATASOURCE_CID \
+{ 0x7a024bcf, 0xedd5, 0x4d9a, { 0x86, 0x14, 0xd4, 0x4b, 0xe1, 0xda, 0xda, 0xd3 } }
+
+#define NS_FEEDSNIFFER_CID \
+{ 0x6893e69, 0x71d8, 0x4b23, { 0x81, 0xeb, 0x80, 0x31, 0x4d, 0xaf, 0x3e, 0x66 } }
+
+#define NS_FEEDSNIFFER_CONTRACTID \
+ "@mozilla.org/browser/feeds/sniffer;1"
+
+#define NS_ABOUTFEEDS_CID \
+{ 0x12ff56ec, 0x58be, 0x402c, { 0xb0, 0x57, 0x1, 0xf9, 0x61, 0xde, 0x96, 0x9b } }
+
+// 136e2c4d-c5a4-477c-b131-d93d7d704f64
+#define NS_PRIVATE_BROWSING_SERVICE_WRAPPER_CID \
+{ 0x136e2c4d, 0xc5a4, 0x477c, { 0xb1, 0x31, 0xd9, 0x3d, 0x7d, 0x70, 0x4f, 0x64 } }
+
+// {6DEB193C-F87D-4078-BC78-5E64655B4D62}
+#define NS_BROWSERDIRECTORYPROVIDER_CID \
+{ 0x6deb193c, 0xf87d, 0x4078, { 0xbc, 0x78, 0x5e, 0x64, 0x65, 0x5b, 0x4d, 0x62 } }
diff --git a/components/build/nsModule.cpp b/components/build/nsModule.cpp
new file mode 100644
index 0000000..f98fc08
--- /dev/null
+++ b/components/build/nsModule.cpp
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; 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/. */
+
+#include "mozilla/ModuleUtils.h"
+
+#include "nsBrowserCompsCID.h"
+#include "DirectoryProvider.h"
+
+#if defined(XP_WIN)
+#include "nsWindowsShellService.h"
+#elif defined(XP_MACOSX)
+#include "nsMacShellService.h"
+#elif defined(MOZ_WIDGET_GTK)
+#include "nsGNOMEShellService.h"
+#endif
+
+#include "rdf.h"
+#include "nsFeedSniffer.h"
+
+#include "nsNetCID.h"
+
+using namespace mozilla::browser;
+
+/////////////////////////////////////////////////////////////////////////////
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(DirectoryProvider)
+#if defined(XP_WIN)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsWindowsShellService)
+#elif defined(XP_MACOSX)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsMacShellService)
+#elif defined(MOZ_WIDGET_GTK)
+NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsGNOMEShellService, Init)
+#endif
+
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsFeedSniffer)
+
+NS_DEFINE_NAMED_CID(NS_BROWSERDIRECTORYPROVIDER_CID);
+#if defined(XP_WIN)
+NS_DEFINE_NAMED_CID(NS_SHELLSERVICE_CID);
+#elif defined(MOZ_WIDGET_GTK)
+NS_DEFINE_NAMED_CID(NS_SHELLSERVICE_CID);
+#endif
+NS_DEFINE_NAMED_CID(NS_FEEDSNIFFER_CID);
+#ifdef XP_MACOSX
+NS_DEFINE_NAMED_CID(NS_SHELLSERVICE_CID);
+#endif
+
+static const mozilla::Module::CIDEntry kBrowserCIDs[] = {
+ { &kNS_BROWSERDIRECTORYPROVIDER_CID, false, nullptr, DirectoryProviderConstructor },
+#if defined(XP_WIN)
+ { &kNS_SHELLSERVICE_CID, false, nullptr, nsWindowsShellServiceConstructor },
+#elif defined(MOZ_WIDGET_GTK)
+ { &kNS_SHELLSERVICE_CID, false, nullptr, nsGNOMEShellServiceConstructor },
+#endif
+ { &kNS_FEEDSNIFFER_CID, false, nullptr, nsFeedSnifferConstructor },
+#ifdef XP_MACOSX
+ { &kNS_SHELLSERVICE_CID, false, nullptr, nsMacShellServiceConstructor },
+#endif
+ { nullptr }
+};
+
+static const mozilla::Module::ContractIDEntry kBrowserContracts[] = {
+ { NS_BROWSERDIRECTORYPROVIDER_CONTRACTID, &kNS_BROWSERDIRECTORYPROVIDER_CID },
+#if defined(XP_WIN)
+ { NS_SHELLSERVICE_CONTRACTID, &kNS_SHELLSERVICE_CID },
+#elif defined(MOZ_WIDGET_GTK)
+ { NS_SHELLSERVICE_CONTRACTID, &kNS_SHELLSERVICE_CID },
+#endif
+ { NS_FEEDSNIFFER_CONTRACTID, &kNS_FEEDSNIFFER_CID },
+#ifdef XP_MACOSX
+ { NS_SHELLSERVICE_CONTRACTID, &kNS_SHELLSERVICE_CID },
+#endif
+ { nullptr }
+};
+
+static const mozilla::Module::CategoryEntry kBrowserCategories[] = {
+ { XPCOM_DIRECTORY_PROVIDER_CATEGORY, "browser-directory-provider", NS_BROWSERDIRECTORYPROVIDER_CONTRACTID },
+ { NS_CONTENT_SNIFFER_CATEGORY, "Feed Sniffer", NS_FEEDSNIFFER_CONTRACTID },
+ { nullptr }
+};
+
+static const mozilla::Module kBrowserModule = {
+ mozilla::Module::kVersion,
+ kBrowserCIDs,
+ kBrowserContracts,
+ kBrowserCategories
+};
+
+NSMODULE_DEFN(nsBrowserCompsModule) = &kBrowserModule;
diff --git a/components/certerror/content/aboutCertError.css b/components/certerror/content/aboutCertError.css
new file mode 100644
index 0000000..059d812
--- /dev/null
+++ b/components/certerror/content/aboutCertError.css
@@ -0,0 +1,17 @@
+/* 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/. */
+
+/* Logical CSS rules belong here, but presentation & theming rules
+ should live in the CSS of the appropriate theme */
+
+#technicalContentText {
+ overflow: auto;
+ white-space: pre-wrap;
+}
+
+.expander[hidden],
+.expander[hidden] + *,
+.expander[collapsed] + * {
+ display: none;
+}
diff --git a/components/certerror/content/aboutCertError.xhtml b/components/certerror/content/aboutCertError.xhtml
new file mode 100644
index 0000000..c8a7e44
--- /dev/null
+++ b/components/certerror/content/aboutCertError.xhtml
@@ -0,0 +1,247 @@
+<?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 % certerrorDTD
+ SYSTEM "chrome://browser/locale/aboutCertError.dtd">
+ %certerrorDTD;
+]>
+
+<!-- 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">
+ <head>
+ <title>&certerror.pagetitle;</title>
+ <link rel="stylesheet" href="chrome://browser/skin/aboutCertError.css" type="text/css" media="all" />
+ <link rel="stylesheet" href="chrome://browser/content/certerror/aboutCertError.css" type="text/css" media="all" />
+ <!-- This page currently uses the same favicon as neterror.xhtml.
+ If the location of the favicon is changed for both pages, the
+ FAVICON_ERRORPAGE_URL symbol in toolkit/components/places/src/nsFaviconService.h
+ should be updated. If this page starts using a different favicon
+ than neterror.xhtml nsFaviconService->SetAndLoadFaviconForPage
+ should be updated to ignore this one as well. -->
+ <link rel="icon" type="image/png" id="favicon" href="chrome://global/skin/icons/warning-16.png"/>
+
+ <script type="application/javascript"><![CDATA[
+ // Error url MUST be formatted like this:
+ // about:certerror?e=error&u=url&d=desc
+
+ // 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 getCSSClass()
+ {
+ var url = document.documentURI;
+ var matches = url.match(/s\=([^&]+)\&/);
+ // s is optional, if no match just return nothing
+ if (!matches || matches.length < 2)
+ return "";
+
+ // parenthetical match is the second entry
+ return decodeURIComponent(matches[1]);
+ }
+
+ function getDescription()
+ {
+ var url = document.documentURI;
+ var desc = url.search(/d\=/);
+
+ // desc == -1 if not found; if so, return an empty string
+ // instead of what would turn out to be portions of the URI
+ if (desc == -1)
+ return "";
+
+ return decodeURIComponent(url.slice(desc + 2));
+ }
+
+ function initPage()
+ {
+ // Replace the "#1" string in the intro with the hostname. Trickier
+ // than it might seem since we want to preserve the <b> tags, but
+ // not allow for any injection by just using innerHTML. Instead,
+ // just find the right target text node.
+ var intro = document.getElementById('introContentP1');
+ function replaceWithHost(node) {
+ if (node.textContent == "#1")
+ node.textContent = location.host;
+ else
+ for(var i = 0; i < node.childNodes.length; i++)
+ replaceWithHost(node.childNodes[i]);
+ };
+ replaceWithHost(intro);
+
+ if (getCSSClass() == "expertBadCert") {
+ toggle('technicalContent');
+ toggle('expertContent');
+ }
+
+ // Disallow overrides if this is a Strict-Transport-Security
+ // host and the cert is bad (STS Spec section 7.3) or if the
+ // certerror is in a frame (bug 633691).
+ if (getCSSClass() == "badStsCert" || window != top)
+ document.getElementById("expertContent").setAttribute("hidden", "true");
+
+ var tech = document.getElementById("technicalContentText");
+ if (tech)
+ tech.textContent = getDescription();
+
+ addDomainErrorLink();
+ }
+
+ /* In the case of SSL error pages about domain mismatch, see if
+ we can hyperlink the user to the correct site. We don't want
+ to do this generically since it allows MitM attacks to redirect
+ users to a site under attacker control, but in certain cases
+ it is safe (and helpful!) to do so. Bug 402210
+ */
+ function addDomainErrorLink() {
+ // Rather than textContent, we need to treat description as HTML
+ var sd = document.getElementById("technicalContentText");
+ if (sd) {
+ var desc = getDescription();
+
+ // sanitize description text - see bug 441169
+
+ // First, find the index of the <a> tag we care about, being careful not to
+ // use an over-greedy regex
+ var re = /<a id="cert_domain_link" title="([^"]+)">/;
+ var result = re.exec(desc);
+ if(!result)
+ return;
+
+ // Remove sd's existing children
+ sd.textContent = "";
+
+ // Everything up to the link should be text content
+ sd.appendChild(document.createTextNode(desc.slice(0, result.index)));
+
+ // Now create the link itself
+ var anchorEl = document.createElement("a");
+ anchorEl.setAttribute("id", "cert_domain_link");
+ anchorEl.setAttribute("title", result[1]);
+ anchorEl.appendChild(document.createTextNode(result[1]));
+ sd.appendChild(anchorEl);
+
+ // Finally, append text for anything after the closing </a>
+ sd.appendChild(document.createTextNode(desc.slice(desc.indexOf("</a>") + "</a>".length)));
+ }
+
+ var link = document.getElementById('cert_domain_link');
+ if (!link)
+ return;
+
+ var okHost = link.getAttribute("title");
+ var thisHost = document.location.hostname;
+ var proto = document.location.protocol;
+
+ // If okHost is a wildcard domain ("*.example.com") let's
+ // use "www" instead. "*.example.com" isn't going to
+ // get anyone anywhere useful. bug 432491
+ okHost = okHost.replace(/^\*\./, "www.");
+
+ /* case #1:
+ * example.com uses an invalid security certificate.
+ *
+ * The certificate is only valid for www.example.com
+ *
+ * Make sure to include the "." ahead of thisHost so that
+ * a MitM attack on paypal.com doesn't hyperlink to "notpaypal.com"
+ *
+ * We'd normally just use a RegExp here except that we lack a
+ * library function to escape them properly (bug 248062), and
+ * domain names are famous for having '.' characters in them,
+ * which would allow spurious and possibly hostile matches.
+ */
+ if (endsWith(okHost, "." + thisHost))
+ link.href = proto + okHost;
+
+ /* case #2:
+ * browser.garage.maemo.org uses an invalid security certificate.
+ *
+ * The certificate is only valid for garage.maemo.org
+ */
+ if (endsWith(thisHost, "." + okHost))
+ link.href = proto + okHost;
+
+ // If we set a link, meaning there's something helpful for
+ // the user here, expand the section by default
+ if (link.href && getCSSClass() != "expertBadCert")
+ toggle("technicalContent");
+ }
+
+ function endsWith(haystack, needle) {
+ return haystack.slice(-needle.length) == needle;
+ }
+
+ function toggle(id) {
+ var el = document.getElementById(id);
+ if (el.getAttribute("collapsed"))
+ el.removeAttribute("collapsed");
+ else
+ el.setAttribute("collapsed", true);
+ }
+ ]]></script>
+ </head>
+
+ <body dir="&locale.dir;">
+
+ <!-- PAGE CONTAINER (for styling purposes only) -->
+ <div id="errorPageContainer">
+
+ <!-- Error Title -->
+ <div id="errorTitle">
+ <h1 id="errorTitleText">&certerror.longpagetitle;</h1>
+ </div>
+
+ <!-- LONG CONTENT (the section most likely to require scrolling) -->
+ <div id="errorLongContent">
+ <div id="introContent">
+ <p id="introContentP1">&certerror.introPara1;</p>
+ <p>&certerror.introPara2;</p>
+ </div>
+
+ <div id="whatShouldIDoContent">
+ <h2>&certerror.whatShouldIDo.heading;</h2>
+ <div id="whatShouldIDoContentText">
+ <p>&certerror.whatShouldIDo.content;</p>
+ <button id='getMeOutOfHereButton'>&certerror.getMeOutOfHere.label;</button>
+ </div>
+ </div>
+
+ <!-- The following sections can be unhidden by default by setting the
+ "browser.xul.error_pages.expert_bad_cert" pref to true -->
+ <h2 id="technicalContent" class="expander" collapsed="true">
+ <button onclick="toggle('technicalContent');">&certerror.technical.heading;</button>
+ </h2>
+ <p id="technicalContentText"/>
+
+ <h2 id="expertContent" class="expander" collapsed="true">
+ <button onclick="toggle('expertContent');">&certerror.expert.heading;</button>
+ </h2>
+ <div>
+ <p>&certerror.expert.content;</p>
+ <p>&certerror.expert.contentPara2;</p>
+ <button id='exceptionDialogButton'>&certerror.addException.label;</button>
+ </div>
+ </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/components/certerror/jar.mn b/components/certerror/jar.mn
new file mode 100644
index 0000000..08e0710
--- /dev/null
+++ b/components/certerror/jar.mn
@@ -0,0 +1,7 @@
+# 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/certerror/aboutCertError.xhtml (content/aboutCertError.xhtml)
+ content/browser/certerror/aboutCertError.css (content/aboutCertError.css)
diff --git a/components/certerror/moz.build b/components/certerror/moz.build
new file mode 100644
index 0000000..c97072b
--- /dev/null
+++ b/components/certerror/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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/components/dirprovider/DirectoryProvider.cpp b/components/dirprovider/DirectoryProvider.cpp
new file mode 100644
index 0000000..85728b3
--- /dev/null
+++ b/components/dirprovider/DirectoryProvider.cpp
@@ -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/. */
+
+#include "nsIDirectoryService.h"
+#include "DirectoryProvider.h"
+
+#include "nsIFile.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+
+#include "nsArrayEnumerator.h"
+#include "nsEnumeratorUtils.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsCategoryManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsCOMArray.h"
+#include "nsDirectoryServiceUtils.h"
+#include "mozilla/ModuleUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsStringAPI.h"
+#include "nsXULAppAPI.h"
+#include "nsIPrefLocalizedString.h"
+
+namespace mozilla {
+namespace browser {
+
+NS_IMPL_ISUPPORTS(DirectoryProvider,
+ nsIDirectoryServiceProvider,
+ nsIDirectoryServiceProvider2)
+
+NS_IMETHODIMP
+DirectoryProvider::GetFile(const char *aKey, bool *aPersist, nsIFile* *aResult)
+{
+ return NS_ERROR_FAILURE;
+}
+
+static void
+AppendFileKey(const char *key, nsIProperties* aDirSvc,
+ nsCOMArray<nsIFile> &array)
+{
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = aDirSvc->Get(key, NS_GET_IID(nsIFile), getter_AddRefs(file));
+ if (NS_FAILED(rv))
+ return;
+
+ bool exists;
+ rv = file->Exists(&exists);
+ if (NS_FAILED(rv) || !exists)
+ return;
+
+ array.AppendObject(file);
+}
+
+// Appends the distribution-specific search engine directories to the
+// array. The directory structure is as follows:
+
+// appdir/
+// \- distribution/
+// \- searchplugins/
+// |- common/
+// \- locale/
+// |- <locale 1>/
+// ...
+// \- <locale N>/
+
+// common engines are loaded for all locales. If there is no locale
+// directory for the current locale, there is a pref:
+// "distribution.searchplugins.defaultLocale"
+// which specifies a default locale to use.
+
+static void
+AppendDistroSearchDirs(nsIProperties* aDirSvc, nsCOMArray<nsIFile> &array)
+{
+ nsCOMPtr<nsIFile> searchPlugins;
+ nsresult rv = aDirSvc->Get(XRE_APP_DISTRIBUTION_DIR,
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(searchPlugins));
+ if (NS_FAILED(rv))
+ return;
+ searchPlugins->AppendNative(NS_LITERAL_CSTRING("searchplugins"));
+
+ bool exists;
+ rv = searchPlugins->Exists(&exists);
+ if (NS_FAILED(rv) || !exists)
+ return;
+
+ nsCOMPtr<nsIFile> commonPlugins;
+ rv = searchPlugins->Clone(getter_AddRefs(commonPlugins));
+ if (NS_SUCCEEDED(rv)) {
+ commonPlugins->AppendNative(NS_LITERAL_CSTRING("common"));
+ rv = commonPlugins->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && exists)
+ array.AppendObject(commonPlugins);
+ }
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs) {
+
+ nsCOMPtr<nsIFile> localePlugins;
+ rv = searchPlugins->Clone(getter_AddRefs(localePlugins));
+ if (NS_FAILED(rv))
+ return;
+
+ localePlugins->AppendNative(NS_LITERAL_CSTRING("locale"));
+
+ nsCString locale;
+ nsCOMPtr<nsIPrefLocalizedString> prefString;
+ rv = prefs->GetComplexValue("general.useragent.locale",
+ NS_GET_IID(nsIPrefLocalizedString),
+ getter_AddRefs(prefString));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoString wLocale;
+ prefString->GetData(getter_Copies(wLocale));
+ CopyUTF16toUTF8(wLocale, locale);
+ } else {
+ rv = prefs->GetCharPref("general.useragent.locale", getter_Copies(locale));
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+
+ nsCOMPtr<nsIFile> curLocalePlugins;
+ rv = localePlugins->Clone(getter_AddRefs(curLocalePlugins));
+ if (NS_SUCCEEDED(rv)) {
+
+ curLocalePlugins->AppendNative(locale);
+ rv = curLocalePlugins->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && exists) {
+ array.AppendObject(curLocalePlugins);
+ return; // all done
+ }
+ }
+ }
+
+ // we didn't append the locale dir - try the default one
+ nsCString defLocale;
+ rv = prefs->GetCharPref("distribution.searchplugins.defaultLocale",
+ getter_Copies(defLocale));
+ if (NS_SUCCEEDED(rv)) {
+
+ nsCOMPtr<nsIFile> defLocalePlugins;
+ rv = localePlugins->Clone(getter_AddRefs(defLocalePlugins));
+ if (NS_SUCCEEDED(rv)) {
+
+ defLocalePlugins->AppendNative(defLocale);
+ rv = defLocalePlugins->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && exists)
+ array.AppendObject(defLocalePlugins);
+ }
+ }
+ }
+}
+
+NS_IMETHODIMP
+DirectoryProvider::GetFiles(const char *aKey, nsISimpleEnumerator* *aResult)
+{
+ nsresult rv;
+
+ if (!strcmp(aKey, NS_APP_SEARCH_DIR_LIST)) {
+ nsCOMPtr<nsIProperties> dirSvc
+ (do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID));
+ if (!dirSvc)
+ return NS_ERROR_FAILURE;
+
+ nsCOMArray<nsIFile> baseFiles;
+
+ /**
+ * We want to preserve the following order, since the search service loads
+ * engines in first-loaded-wins order.
+ * - extension search plugin locations (prepended below using
+ * NS_NewUnionEnumerator)
+ * - distro search plugin locations
+ * - user search plugin locations (profile)
+ * - app search plugin location (shipped engines)
+ */
+ AppendDistroSearchDirs(dirSvc, baseFiles);
+ AppendFileKey(NS_APP_USER_SEARCH_DIR, dirSvc, baseFiles);
+ AppendFileKey(NS_APP_SEARCH_DIR, dirSvc, baseFiles);
+
+ nsCOMPtr<nsISimpleEnumerator> baseEnum;
+ rv = NS_NewArrayEnumerator(getter_AddRefs(baseEnum), baseFiles);
+ if (NS_FAILED(rv))
+ return rv;
+
+ nsCOMPtr<nsISimpleEnumerator> list;
+ rv = dirSvc->Get(XRE_EXTENSIONS_DIR_LIST,
+ NS_GET_IID(nsISimpleEnumerator), getter_AddRefs(list));
+ if (NS_FAILED(rv))
+ return rv;
+
+ static char const *const kAppendSPlugins[] = {"searchplugins", nullptr};
+
+ nsCOMPtr<nsISimpleEnumerator> extEnum =
+ new AppendingEnumerator(list, kAppendSPlugins);
+ if (!extEnum)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ return NS_NewUnionEnumerator(aResult, extEnum, baseEnum);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMPL_ISUPPORTS(DirectoryProvider::AppendingEnumerator, nsISimpleEnumerator)
+
+NS_IMETHODIMP
+DirectoryProvider::AppendingEnumerator::HasMoreElements(bool *aResult)
+{
+ *aResult = mNext ? true : false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+DirectoryProvider::AppendingEnumerator::GetNext(nsISupports* *aResult)
+{
+ if (aResult)
+ NS_ADDREF(*aResult = mNext);
+
+ mNext = nullptr;
+
+ nsresult rv;
+
+ // Ignore all errors
+
+ bool more;
+ while (NS_SUCCEEDED(mBase->HasMoreElements(&more)) && more) {
+ nsCOMPtr<nsISupports> nextbasesupp;
+ mBase->GetNext(getter_AddRefs(nextbasesupp));
+
+ nsCOMPtr<nsIFile> nextbase(do_QueryInterface(nextbasesupp));
+ if (!nextbase)
+ continue;
+
+ nextbase->Clone(getter_AddRefs(mNext));
+ if (!mNext)
+ continue;
+
+ char const *const * i = mAppendList;
+ while (*i) {
+ mNext->AppendNative(nsDependentCString(*i));
+ ++i;
+ }
+
+ bool exists;
+ rv = mNext->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && exists)
+ break;
+
+ mNext = nullptr;
+ }
+
+ return NS_OK;
+}
+
+DirectoryProvider::AppendingEnumerator::AppendingEnumerator
+ (nsISimpleEnumerator* aBase,
+ char const *const *aAppendList) :
+ mBase(aBase),
+ mAppendList(aAppendList)
+{
+ // Initialize mNext to begin.
+ GetNext(nullptr);
+}
+
+} // namespace browser
+} // namespace mozilla
diff --git a/components/dirprovider/DirectoryProvider.h b/components/dirprovider/DirectoryProvider.h
new file mode 100644
index 0000000..43fa85a
--- /dev/null
+++ b/components/dirprovider/DirectoryProvider.h
@@ -0,0 +1,51 @@
+/* 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/. */
+
+#ifndef DirectoryProvider_h__
+#define DirectoryProvider_h__
+
+#include "nsIDirectoryService.h"
+#include "nsComponentManagerUtils.h"
+#include "nsISimpleEnumerator.h"
+#include "nsIFile.h"
+#include "mozilla/Attributes.h"
+
+#define NS_BROWSERDIRECTORYPROVIDER_CONTRACTID \
+ "@mozilla.org/browser/directory-provider;1"
+
+namespace mozilla {
+namespace browser {
+
+class DirectoryProvider final : public nsIDirectoryServiceProvider2
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDIRECTORYSERVICEPROVIDER
+ NS_DECL_NSIDIRECTORYSERVICEPROVIDER2
+
+private:
+ ~DirectoryProvider() {}
+
+ class AppendingEnumerator final : public nsISimpleEnumerator
+ {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ AppendingEnumerator(nsISimpleEnumerator* aBase,
+ char const *const *aAppendList);
+
+ private:
+ ~AppendingEnumerator() {}
+
+ nsCOMPtr<nsISimpleEnumerator> mBase;
+ char const *const *const mAppendList;
+ nsCOMPtr<nsIFile> mNext;
+ };
+};
+
+} // namespace browser
+} // namespace mozilla
+
+#endif // DirectoryProvider_h__
diff --git a/components/dirprovider/moz.build b/components/dirprovider/moz.build
new file mode 100644
index 0000000..b01c4a3
--- /dev/null
+++ b/components/dirprovider/moz.build
@@ -0,0 +1,13 @@
+# -*- 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/.
+
+EXPORTS.mozilla.browser += ['DirectoryProvider.h']
+
+SOURCES += ['DirectoryProvider.cpp']
+
+FINAL_LIBRARY = 'browsercomps'
+
+LOCAL_INCLUDES += ['../build']
diff --git a/components/distribution.js b/components/distribution.js
new file mode 100644
index 0000000..121e55b
--- /dev/null
+++ b/components/distribution.js
@@ -0,0 +1,345 @@
+/* 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.EXPORTED_SYMBOLS = [ "DistributionCustomizer" ];
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+const DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC =
+ "distribution-customization-complete";
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+
+this.DistributionCustomizer = function DistributionCustomizer() {
+ let dirSvc = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties);
+ let iniFile = dirSvc.get("XREExeF", Ci.nsIFile);
+ iniFile.leafName = "distribution";
+ iniFile.append("distribution.ini");
+ if (iniFile.exists())
+ this._iniFile = iniFile;
+}
+
+DistributionCustomizer.prototype = {
+ _iniFile: null,
+
+ get _ini() {
+ let ini = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].
+ getService(Ci.nsIINIParserFactory).
+ createINIParser(this._iniFile);
+ this.__defineGetter__("_ini", function() ini);
+ return this._ini;
+ },
+
+ get _locale() {
+ let locale = this._prefs.getCharPref("general.useragent.locale", "en-US");
+ this.__defineGetter__("_locale", function() locale);
+ return this._locale;
+ },
+
+ get _prefSvc() {
+ let svc = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService);
+ this.__defineGetter__("_prefSvc", function() svc);
+ return this._prefSvc;
+ },
+
+ get _prefs() {
+ let branch = this._prefSvc.getBranch(null);
+ this.__defineGetter__("_prefs", function() branch);
+ return this._prefs;
+ },
+
+ get _ioSvc() {
+ let svc = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ this.__defineGetter__("_ioSvc", function() svc);
+ return this._ioSvc;
+ },
+
+ _makeURI: function DIST__makeURI(spec) {
+ return this._ioSvc.newURI(spec, null, null);
+ },
+
+ _parseBookmarksSection:
+ function DIST_parseBookmarksSection(parentId, section) {
+ let keys = [];
+ for (let i in enumerate(this._ini.getKeys(section)))
+ keys.push(i);
+ keys.sort();
+
+ let items = {};
+ let defaultItemId = -1;
+ let maxItemId = -1;
+
+ for (let i = 0; i < keys.length; i++) {
+ let m = /^item\.(\d+)\.(\w+)\.?(\w*)/.exec(keys[i]);
+ if (m) {
+ let [foo, iid, iprop, ilocale] = m;
+ iid = parseInt(iid);
+
+ if (ilocale)
+ continue;
+
+ if (!items[iid])
+ items[iid] = {};
+ if (keys.indexOf(keys[i] + "." + this._locale) >= 0) {
+ items[iid][iprop] = this._ini.getString(section, keys[i] + "." +
+ this._locale);
+ } else {
+ items[iid][iprop] = this._ini.getString(section, keys[i]);
+ }
+
+ if (iprop == "type" && items[iid]["type"] == "default")
+ defaultItemId = iid;
+
+ if (maxItemId < iid)
+ maxItemId = iid;
+ } else {
+ dump("Key did not match: " + keys[i] + "\n");
+ }
+ }
+
+ let prependIndex = 0;
+ for (let iid = 0; iid <= maxItemId; iid++) {
+ if (!items[iid])
+ continue;
+
+ let index = PlacesUtils.bookmarks.DEFAULT_INDEX;
+ let newId;
+
+ switch (items[iid]["type"]) {
+ case "default":
+ break;
+
+ case "folder":
+ if (iid < defaultItemId)
+ index = prependIndex++;
+
+ newId = PlacesUtils.bookmarks.createFolder(parentId,
+ items[iid]["title"],
+ index);
+
+ this._parseBookmarksSection(newId, "BookmarksFolder-" +
+ items[iid]["folderId"]);
+
+ if (items[iid]["description"])
+ PlacesUtils.annotations.setItemAnnotation(newId,
+ "bookmarkProperties/description",
+ items[iid]["description"], 0,
+ PlacesUtils.annotations.EXPIRE_NEVER);
+
+ break;
+
+ case "separator":
+ if (iid < defaultItemId)
+ index = prependIndex++;
+ PlacesUtils.bookmarks.insertSeparator(parentId, index);
+ break;
+
+ case "livemark":
+ if (iid < defaultItemId)
+ index = prependIndex++;
+
+ // Don't bother updating the livemark contents on creation.
+ PlacesUtils.livemarks.addLivemark({ title: items[iid]["title"]
+ , parentId: parentId
+ , index: index
+ , feedURI: this._makeURI(items[iid]["feedLink"])
+ , siteURI: this._makeURI(items[iid]["siteLink"])
+ }).then(null, Cu.reportError);
+ break;
+
+ case "bookmark":
+ default:
+ if (iid < defaultItemId)
+ index = prependIndex++;
+
+ newId = PlacesUtils.bookmarks.insertBookmark(parentId,
+ this._makeURI(items[iid]["link"]),
+ index, items[iid]["title"]);
+
+ if (items[iid]["description"])
+ PlacesUtils.annotations.setItemAnnotation(newId,
+ "bookmarkProperties/description",
+ items[iid]["description"], 0,
+ PlacesUtils.annotations.EXPIRE_NEVER);
+
+ break;
+ }
+ }
+ },
+
+ _customizationsApplied: false,
+ applyCustomizations: function DIST_applyCustomizations() {
+ this._customizationsApplied = true;
+ if (!this._iniFile)
+ return this._checkCustomizationComplete();
+
+ // nsPrefService loads very early. Reload prefs so we can set
+ // distribution defaults during the prefservice:after-app-defaults
+ // notification (see applyPrefDefaults below)
+ this._prefSvc.QueryInterface(Ci.nsIObserver);
+ this._prefSvc.observe(null, "reload-default-prefs", null);
+ },
+
+ _bookmarksApplied: false,
+ applyBookmarks: function DIST_applyBookmarks() {
+ this._bookmarksApplied = true;
+ if (!this._iniFile)
+ return this._checkCustomizationComplete();
+
+ let sections = enumToObject(this._ini.getSections());
+
+ // The global section, and several of its fields, is required
+ // (we also check here to be consistent with applyPrefDefaults below)
+ if (!sections["Global"])
+ return this._checkCustomizationComplete();
+ let globalPrefs = enumToObject(this._ini.getKeys("Global"));
+ if (!(globalPrefs["id"] && globalPrefs["version"] && globalPrefs["about"]))
+ return this._checkCustomizationComplete();
+
+ let bmProcessedPref;
+ try {
+ bmProcessedPref = this._ini.getString("Global",
+ "bookmarks.initialized.pref");
+ }
+ catch (e) {
+ bmProcessedPref = "distribution." +
+ this._ini.getString("Global", "id") + ".bookmarksProcessed";
+ }
+
+ let bmProcessed = this._prefs.getBoolPref(bmProcessedPref, false);
+
+ if (!bmProcessed) {
+ if (sections["BookmarksMenu"])
+ this._parseBookmarksSection(PlacesUtils.bookmarksMenuFolderId,
+ "BookmarksMenu");
+ if (sections["BookmarksToolbar"])
+ this._parseBookmarksSection(PlacesUtils.toolbarFolderId,
+ "BookmarksToolbar");
+ this._prefs.setBoolPref(bmProcessedPref, true);
+ }
+ return this._checkCustomizationComplete();
+ },
+
+ _prefDefaultsApplied: false,
+ applyPrefDefaults: function DIST_applyPrefDefaults() {
+ this._prefDefaultsApplied = true;
+ if (!this._iniFile)
+ return this._checkCustomizationComplete();
+
+ let sections = enumToObject(this._ini.getSections());
+
+ // The global section, and several of its fields, is required
+ if (!sections["Global"])
+ return this._checkCustomizationComplete();
+ let globalPrefs = enumToObject(this._ini.getKeys("Global"));
+ if (!(globalPrefs["id"] && globalPrefs["version"] && globalPrefs["about"]))
+ return this._checkCustomizationComplete();
+
+ let defaults = this._prefSvc.getDefaultBranch(null);
+
+ // Global really contains info we set as prefs. They're only
+ // separate because they are "special" (read: required)
+
+ defaults.setCharPref("distribution.id", this._ini.getString("Global", "id"));
+ defaults.setCharPref("distribution.version",
+ this._ini.getString("Global", "version"));
+
+ let partnerAbout = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ try {
+ if (globalPrefs["about." + this._locale]) {
+ partnerAbout.data = this._ini.getString("Global", "about." + this._locale);
+ } else {
+ partnerAbout.data = this._ini.getString("Global", "about");
+ }
+ defaults.setComplexValue("distribution.about",
+ Ci.nsISupportsString, partnerAbout);
+ } catch (e) {
+ /* ignore bad prefs due to bug 895473 and move on */
+ Cu.reportError(e);
+ }
+
+ if (sections["Preferences"]) {
+ for (let key in enumerate(this._ini.getKeys("Preferences"))) {
+ try {
+ let value = eval(this._ini.getString("Preferences", key));
+ switch (typeof value) {
+ case "boolean":
+ defaults.setBoolPref(key, value);
+ break;
+ case "number":
+ defaults.setIntPref(key, value);
+ break;
+ case "string":
+ defaults.setCharPref(key, value);
+ break;
+ case "undefined":
+ defaults.setCharPref(key, value);
+ break;
+ }
+ } catch (e) { /* ignore bad prefs and move on */ }
+ }
+ }
+
+ // We eval() the localizable prefs as well (even though they'll
+ // always get set as a string) to keep the INI format consistent:
+ // string prefs always need to be in quotes
+
+ let localizedStr = Cc["@mozilla.org/pref-localizedstring;1"].
+ createInstance(Ci.nsIPrefLocalizedString);
+
+ if (sections["LocalizablePreferences"]) {
+ for (let key in enumerate(this._ini.getKeys("LocalizablePreferences"))) {
+ try {
+ let value = eval(this._ini.getString("LocalizablePreferences", key));
+ value = value.replace("%LOCALE%", this._locale, "g");
+ localizedStr.data = "data:text/plain," + key + "=" + value;
+ defaults.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr);
+ } catch (e) { /* ignore bad prefs and move on */ }
+ }
+ }
+
+ if (sections["LocalizablePreferences-" + this._locale]) {
+ for (let key in enumerate(this._ini.getKeys("LocalizablePreferences-" + this._locale))) {
+ try {
+ let value = eval(this._ini.getString("LocalizablePreferences-" + this._locale, key));
+ localizedStr.data = "data:text/plain," + key + "=" + value;
+ defaults.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedStr);
+ } catch (e) { /* ignore bad prefs and move on */ }
+ }
+ }
+
+ return this._checkCustomizationComplete();
+ },
+
+ _checkCustomizationComplete: function DIST__checkCustomizationComplete() {
+ let prefDefaultsApplied = this._prefDefaultsApplied || !this._iniFile;
+ if (this._customizationsApplied && this._bookmarksApplied &&
+ prefDefaultsApplied) {
+ let os = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ os.notifyObservers(null, DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC, null);
+ }
+ }
+};
+
+function enumerate(UTF8Enumerator) {
+ while (UTF8Enumerator.hasMore())
+ yield UTF8Enumerator.getNext();
+}
+
+function enumToObject(UTF8Enumerator) {
+ let ret = {};
+ for (let i in enumerate(UTF8Enumerator))
+ ret[i] = 1;
+ return ret;
+}
diff --git a/components/downloads/BrowserDownloads.manifest b/components/downloads/BrowserDownloads.manifest
new file mode 100644
index 0000000..1881ca1
--- /dev/null
+++ b/components/downloads/BrowserDownloads.manifest
@@ -0,0 +1,4 @@
+component {49507fe5-2cee-4824-b6a3-e999150ce9b8} DownloadsStartup.js
+contract @mozilla.org/browser/downloadsstartup;1 {49507fe5-2cee-4824-b6a3-e999150ce9b8}
+category profile-after-change DownloadsStartup @mozilla.org/browser/downloadsstartup;1
+component {4d99321e-d156-455b-81f7-e7aa2308134f} DownloadsUI.js
diff --git a/components/downloads/DownloadsCommon.jsm b/components/downloads/DownloadsCommon.jsm
new file mode 100644
index 0000000..efe31ce
--- /dev/null
+++ b/components/downloads/DownloadsCommon.jsm
@@ -0,0 +1,1920 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
+/* 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";
+
+this.EXPORTED_SYMBOLS = [
+ "DownloadsCommon",
+];
+
+/**
+ * Handles the Downloads panel shared methods and data access.
+ *
+ * This file includes the following constructors and global objects:
+ *
+ * DownloadsCommon
+ * This object is exposed directly to the consumers of this JavaScript module,
+ * and provides shared methods for all the instances of the user interface.
+ *
+ * DownloadsData
+ * Retrieves the list of past and completed downloads from the underlying
+ * Downloads API data, and provides asynchronous notifications allowing
+ * to build a consistent view of the available data.
+ *
+ * DownloadsIndicatorData
+ * This object registers itself with DownloadsData as a view, and transforms the
+ * notifications it receives into overall status data, that is then broadcast to
+ * the registered download status indicators.
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+//// Globals
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+ "resource://gre/modules/PluralForm.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
+ "resource://gre/modules/Downloads.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadUIHelper",
+ "resource://gre/modules/DownloadUIHelper.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils",
+ "resource://gre/modules/DownloadUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm")
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
+ "resource:///modules/RecentWindow.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadsLogger",
+ "resource:///modules/DownloadsLogger.jsm");
+
+const nsIDM = Ci.nsIDownloadManager;
+
+const kDownloadsStringBundleUrl =
+ "chrome://browser/locale/downloads/downloads.properties";
+
+const kPrefConfirmOpenExe = "browser.download.confirmOpenExecutable";
+
+const kDownloadsStringsRequiringFormatting = {
+ sizeWithUnits: true,
+ shortTimeLeftSeconds: true,
+ shortTimeLeftMinutes: true,
+ shortTimeLeftHours: true,
+ shortTimeLeftDays: true,
+ statusSeparator: true,
+ statusSeparatorBeforeNumber: true,
+ fileExecutableSecurityWarning: true
+};
+
+const kDownloadsStringsRequiringPluralForm = {
+ otherDownloads2: true
+};
+
+const kPartialDownloadSuffix = ".part";
+
+const kPrefBranch = Services.prefs.getBranch("browser.download.");
+
+var PrefObserver = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]),
+ getPref: function PO_getPref(name) {
+ try {
+ switch (typeof this.prefs[name]) {
+ case "boolean":
+ return kPrefBranch.getBoolPref(name);
+ }
+ } catch (ex) { }
+ return this.prefs[name];
+ },
+ observe: function PO_observe(aSubject, aTopic, aData) {
+ if (this.prefs.hasOwnProperty(aData)) {
+ return this[aData] = this.getPref(aData);
+ }
+ },
+ register: function PO_register(prefs) {
+ this.prefs = prefs;
+ kPrefBranch.addObserver("", this, true);
+ for (let key in prefs) {
+ let name = key;
+ XPCOMUtils.defineLazyGetter(this, name, function () {
+ return PrefObserver.getPref(name);
+ });
+ }
+ },
+};
+
+PrefObserver.register({
+ // prefName: defaultValue
+ debug: false,
+ animateNotifications: true
+});
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsCommon
+
+/**
+ * This object is exposed directly to the consumers of this JavaScript module,
+ * and provides shared methods for all the instances of the user interface.
+ */
+this.DownloadsCommon = {
+ log: function DC_log(...aMessageArgs) {
+ delete this.log;
+ this.log = function DC_log(...aMessageArgs) {
+ if (!PrefObserver.debug) {
+ return;
+ }
+ DownloadsLogger.log.apply(DownloadsLogger, aMessageArgs);
+ }
+ this.log.apply(this, aMessageArgs);
+ },
+
+ error: function DC_error(...aMessageArgs) {
+ delete this.error;
+ this.error = function DC_error(...aMessageArgs) {
+ if (!PrefObserver.debug) {
+ return;
+ }
+ DownloadsLogger.reportError.apply(DownloadsLogger, aMessageArgs);
+ }
+ this.error.apply(this, aMessageArgs);
+ },
+ /**
+ * Returns an object whose keys are the string names from the downloads string
+ * bundle, and whose values are either the translated strings or functions
+ * returning formatted strings.
+ */
+ get strings()
+ {
+ let strings = {};
+ let sb = Services.strings.createBundle(kDownloadsStringBundleUrl);
+ let enumerator = sb.getSimpleEnumeration();
+ while (enumerator.hasMoreElements()) {
+ let string = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement);
+ let stringName = string.key;
+ if (stringName in kDownloadsStringsRequiringFormatting) {
+ strings[stringName] = function () {
+ // Convert "arguments" to a real array before calling into XPCOM.
+ return sb.formatStringFromName(stringName,
+ Array.slice(arguments, 0),
+ arguments.length);
+ };
+ } else if (stringName in kDownloadsStringsRequiringPluralForm) {
+ strings[stringName] = function (aCount) {
+ // Convert "arguments" to a real array before calling into XPCOM.
+ let formattedString = sb.formatStringFromName(stringName,
+ Array.slice(arguments, 0),
+ arguments.length);
+ return PluralForm.get(aCount, formattedString);
+ };
+ } else {
+ strings[stringName] = string.value;
+ }
+ }
+ delete this.strings;
+ return this.strings = strings;
+ },
+
+ /**
+ * Generates a very short string representing the given time left.
+ *
+ * @param aSeconds
+ * Value to be formatted. It represents the number of seconds, it must
+ * be positive but does not need to be an integer.
+ *
+ * @return Formatted string, for example "30s" or "2h". The returned value is
+ * maximum three characters long, at least in English.
+ */
+ formatTimeLeft: function DC_formatTimeLeft(aSeconds)
+ {
+ // Decide what text to show for the time
+ let seconds = Math.round(aSeconds);
+ if (!seconds) {
+ return "";
+ } else if (seconds <= 30) {
+ return DownloadsCommon.strings["shortTimeLeftSeconds"](seconds);
+ }
+ let minutes = Math.round(aSeconds / 60);
+ if (minutes < 60) {
+ return DownloadsCommon.strings["shortTimeLeftMinutes"](minutes);
+ }
+ let hours = Math.round(minutes / 60);
+ if (hours < 48) { // two days
+ return DownloadsCommon.strings["shortTimeLeftHours"](hours);
+ }
+ let days = Math.round(hours / 24);
+ return DownloadsCommon.strings["shortTimeLeftDays"](Math.min(days, 99));
+ },
+
+ /**
+ * Indicates whether we should show the full Download Manager window interface
+ * instead of the simplified panel interface. The behavior of downloads
+ * across browsing session is consistent with the selected interface.
+ */
+ get useToolkitUI()
+ {
+ /* Toolkit UI is currently incompatible.
+ * FIXME: Either fix the toolkitUI (make DBConnection work) or remove
+ * the unused code altogether
+ */
+ //try {
+ // return Services.prefs.getBoolPref("browser.download.useToolkitUI");
+ //} catch (ex) { }
+ return false;
+ },
+
+ /**
+ * Indicates whether we should show visual notification on the indicator
+ * when a download event is triggered.
+ */
+ get animateNotifications()
+ {
+ return PrefObserver.animateNotifications;
+ },
+
+ /**
+ * Get access to one of the DownloadsData or PrivateDownloadsData objects,
+ * depending on the privacy status of the window in question.
+ *
+ * @param aWindow
+ * The browser window which owns the download button.
+ */
+ getData: function DC_getData(aWindow) {
+ if (PrivateBrowsingUtils.isWindowPrivate(aWindow)) {
+ return PrivateDownloadsData;
+ } else {
+ return DownloadsData;
+ }
+ },
+
+ /**
+ * Initializes the data link for both the private and non-private downloads
+ * data objects.
+ *
+ * @param aDownloadManagerService
+ * Reference to the service implementing nsIDownloadManager. We need
+ * this because getService isn't available for us when this method is
+ * called, and we must ensure to register our listeners before the
+ * getService call for the Download Manager returns.
+ */
+ initializeAllDataLinks: function DC_initializeAllDataLinks(aDownloadManagerService) {
+ DownloadsData.initializeDataLink(aDownloadManagerService);
+ PrivateDownloadsData.initializeDataLink(aDownloadManagerService);
+ },
+
+ /**
+ * Terminates the data link for both the private and non-private downloads
+ * data objects.
+ */
+ terminateAllDataLinks: function DC_terminateAllDataLinks() {
+ DownloadsData.terminateDataLink();
+ PrivateDownloadsData.terminateDataLink();
+ },
+
+ /**
+ * Reloads the specified kind of downloads from the non-private store.
+ * This method must only be called when Private Browsing Mode is disabled.
+ *
+ * @param aActiveOnly
+ * True to load only active downloads from the database.
+ */
+ ensureAllPersistentDataLoaded:
+ function DC_ensureAllPersistentDataLoaded(aActiveOnly) {
+ DownloadsData.ensurePersistentDataLoaded(aActiveOnly);
+ },
+
+ /**
+ * Get access to one of the DownloadsIndicatorData or
+ * PrivateDownloadsIndicatorData objects, depending on the privacy status of
+ * the window in question.
+ */
+ getIndicatorData: function DC_getIndicatorData(aWindow) {
+ if (PrivateBrowsingUtils.isWindowPrivate(aWindow)) {
+ return PrivateDownloadsIndicatorData;
+ } else {
+ return DownloadsIndicatorData;
+ }
+ },
+
+ /**
+ * Returns a reference to the DownloadsSummaryData singleton - creating one
+ * in the process if one hasn't been instantiated yet.
+ *
+ * @param aWindow
+ * The browser window which owns the download button.
+ * @param aNumToExclude
+ * The number of items on the top of the downloads list to exclude
+ * from the summary.
+ */
+ getSummary: function DC_getSummary(aWindow, aNumToExclude)
+ {
+ if (PrivateBrowsingUtils.isWindowPrivate(aWindow)) {
+ if (this._privateSummary) {
+ return this._privateSummary;
+ }
+ return this._privateSummary = new DownloadsSummaryData(true, aNumToExclude);
+ } else {
+ if (this._summary) {
+ return this._summary;
+ }
+ return this._summary = new DownloadsSummaryData(false, aNumToExclude);
+ }
+ },
+ _summary: null,
+ _privateSummary: null,
+
+ /**
+ * Returns the legacy state integer value for the provided Download object.
+ */
+ stateOfDownload(download) {
+ // Collapse state using the correct priority.
+ if (!download.stopped) {
+ return nsIDM.DOWNLOAD_DOWNLOADING;
+ }
+ if (download.succeeded) {
+ return nsIDM.DOWNLOAD_FINISHED;
+ }
+ if (download.error) {
+ if (download.error.becauseBlockedByParentalControls) {
+ return nsIDM.DOWNLOAD_BLOCKED_PARENTAL;
+ }
+ if (download.error.becauseBlockedByReputationCheck) {
+ return nsIDM.DOWNLOAD_DIRTY;
+ }
+ return nsIDM.DOWNLOAD_FAILED;
+ }
+ if (download.canceled) {
+ if (download.hasPartialData) {
+ return nsIDM.DOWNLOAD_PAUSED;
+ }
+ return nsIDM.DOWNLOAD_CANCELED;
+ }
+ return nsIDM.DOWNLOAD_NOTSTARTED;
+ },
+
+ /**
+ * Helper function required because the Downloads Panel and the Downloads View
+ * don't share the controller yet.
+ */
+ removeAndFinalizeDownload(download) {
+ Downloads.getList(Downloads.ALL)
+ .then(list => list.remove(download))
+ .then(() => download.finalize(true))
+ .catch(Cu.reportError);
+ },
+
+ /**
+ * Given an iterable collection of Download objects, generates and returns
+ * statistics about that collection.
+ *
+ * @param downloads An iterable collection of Download objects.
+ *
+ * @return Object whose properties are the generated statistics. Currently,
+ * we return the following properties:
+ *
+ * numActive : The total number of downloads.
+ * numPaused : The total number of paused downloads.
+ * numDownloading : The total number of downloads being downloaded.
+ * totalSize : The total size of all downloads once completed.
+ * totalTransferred: The total amount of transferred data for these
+ * downloads.
+ * slowestSpeed : The slowest download rate.
+ * rawTimeLeft : The estimated time left for the downloads to
+ * complete.
+ * percentComplete : The percentage of bytes successfully downloaded.
+ */
+ summarizeDownloads(downloads) {
+ let summary = {
+ numActive: 0,
+ numPaused: 0,
+ numDownloading: 0,
+ totalSize: 0,
+ totalTransferred: 0,
+ // slowestSpeed is Infinity so that we can use Math.min to
+ // find the slowest speed. We'll set this to 0 afterwards if
+ // it's still at Infinity by the time we're done iterating all
+ // download.
+ slowestSpeed: Infinity,
+ rawTimeLeft: -1,
+ percentComplete: -1
+ }
+
+ for (let download of downloads) {
+ summary.numActive++;
+
+ if (!download.stopped) {
+ summary.numDownloading++;
+ if (download.hasProgress && download.speed > 0) {
+ let sizeLeft = download.totalBytes - download.currentBytes;
+ summary.rawTimeLeft = Math.max(summary.rawTimeLeft,
+ sizeLeft / download.speed);
+ summary.slowestSpeed = Math.min(summary.slowestSpeed,
+ download.speed);
+ }
+ } else if (download.canceled && download.hasPartialData) {
+ summary.numPaused++;
+ }
+ // Only add to total values if we actually know the download size.
+ if (download.succeeded) {
+ summary.totalSize += download.target.size;
+ summary.totalTransferred += download.target.size;
+ } else if (download.hasProgress) {
+ summary.totalSize += download.totalBytes;
+ summary.totalTransferred += download.currentBytes;
+ }
+ }
+
+ if (summary.totalSize != 0) {
+ summary.percentComplete = (summary.totalTransferred /
+ summary.totalSize) * 100;
+ }
+
+ if (summary.slowestSpeed == Infinity) {
+ summary.slowestSpeed = 0;
+ }
+
+ return summary;
+ },
+
+ /**
+ * If necessary, smooths the estimated number of seconds remaining for one
+ * or more downloads to complete.
+ *
+ * @param aSeconds
+ * Current raw estimate on number of seconds left for one or more
+ * downloads. This is a floating point value to help get sub-second
+ * accuracy for current and future estimates.
+ */
+ smoothSeconds: function DC_smoothSeconds(aSeconds, aLastSeconds)
+ {
+ // We apply an algorithm similar to the DownloadUtils.getTimeLeft function,
+ // though tailored to a single time estimation for all downloads. We never
+ // apply something if the new value is less than half the previous value.
+ let shouldApplySmoothing = aLastSeconds >= 0 &&
+ aSeconds > aLastSeconds / 2;
+ if (shouldApplySmoothing) {
+ // Apply hysteresis to favor downward over upward swings. Trust only 30%
+ // of the new value if lower, and 10% if higher (exponential smoothing).
+ let diff = aSeconds - aLastSeconds;
+ aSeconds = aLastSeconds + (diff < 0 ? .3 : .1) * diff;
+
+ // If the new time is similar, reuse something close to the last time
+ // left, but subtract a little to provide forward progress.
+ diff = aSeconds - aLastSeconds;
+ let diffPercent = diff / aLastSeconds * 100;
+ if (Math.abs(diff) < 5 || Math.abs(diffPercent) < 5) {
+ aSeconds = aLastSeconds - (diff < 0 ? .4 : .2);
+ }
+ }
+
+ // In the last few seconds of downloading, we are always subtracting and
+ // never adding to the time left. Ensure that we never fall below one
+ // second left until all downloads are actually finished.
+ return aLastSeconds = Math.max(aSeconds, 1);
+ },
+
+ /**
+ * Opens a downloaded file.
+ *
+ * @param aFile
+ * the downloaded file to be opened.
+ * @param aMimeInfo
+ * the mime type info object. May be null.
+ * @param aOwnerWindow
+ * the window with which this action is associated.
+ */
+ openDownloadedFile: function DC_openDownloadedFile(aFile, aMimeInfo, aOwnerWindow) {
+ if (!(aFile instanceof Ci.nsIFile))
+ throw new Error("aFile must be a nsIFile object");
+ if (aMimeInfo && !(aMimeInfo instanceof Ci.nsIMIMEInfo))
+ throw new Error("Invalid value passed for aMimeInfo");
+ if (!(aOwnerWindow instanceof Ci.nsIDOMWindow))
+ throw new Error("aOwnerWindow must be a dom-window object");
+
+#ifdef XP_WIN
+ // On Windows, the system will provide a native confirmation prompt
+ // for .exe files. Exclude this from our prompt, but prompt on other
+ // executable types.
+ let isWindowsExe = aFile.leafName.toLowerCase().endsWith(".exe");
+#else
+ let isWindowsExe = false;
+#endif
+
+ // Confirm opening executable files if required.
+ if (aFile.isExecutable() && !isWindowsExe) {
+ let showAlert = true;
+ try {
+ showAlert = Services.prefs.getBoolPref(kPrefConfirmOpenExe);
+ } catch (ex) {
+ // If the preference does not exist, continue with the prompt.
+ }
+
+ if (showAlert) {
+ let name = aFile.leafName;
+ let message =
+ DownloadsCommon.strings.fileExecutableSecurityWarning(name, name);
+ let title =
+ DownloadsCommon.strings.fileExecutableSecurityWarningTitle;
+
+ let open = Services.prompt.confirm(aOwnerWindow, title, message);
+ if (!open) {
+ return;
+ }
+ }
+ }
+
+ // Actually open the file.
+ try {
+ if (aMimeInfo && aMimeInfo.preferredAction == aMimeInfo.useHelperApp) {
+ aMimeInfo.launchWithFile(aFile);
+ return;
+ }
+ }
+ catch(ex) { }
+
+ // If either we don't have the mime info, or the preferred action failed,
+ // attempt to launch the file directly.
+ try {
+ aFile.launch();
+ }
+ catch(ex) {
+ // If launch fails, try sending it through the system's external "file:"
+ // URL handler.
+ Cc["@mozilla.org/uriloader/external-protocol-service;1"]
+ .getService(Ci.nsIExternalProtocolService)
+ .loadUrl(NetUtil.newURI(aFile));
+ }
+ },
+
+ /**
+ * Show a downloaded file in the system file manager.
+ *
+ * @param aFile
+ * a downloaded file.
+ */
+ showDownloadedFile: function DC_showDownloadedFile(aFile) {
+ if (!(aFile instanceof Ci.nsIFile))
+ throw new Error("aFile must be a nsIFile object");
+ try {
+ // Show the directory containing the file and select the file.
+ aFile.reveal();
+ } catch (ex) {
+ // If reveal fails for some reason (e.g., it's not implemented on unix
+ // or the file doesn't exist), try using the parent if we have it.
+ let parent = aFile.parent;
+ if (parent) {
+ try {
+ // Open the parent directory to show where the file should be.
+ parent.launch();
+ } catch (ex) {
+ // If launch also fails (probably because it's not implemented), let
+ // the OS handler try to open the parent.
+ Cc["@mozilla.org/uriloader/external-protocol-service;1"]
+ .getService(Ci.nsIExternalProtocolService)
+ .loadUrl(NetUtil.newURI(parent));
+ }
+ }
+ }
+ }
+};
+
+/**
+ * Returns true if we are executing on Windows Vista or a later version.
+ */
+XPCOMUtils.defineLazyGetter(DownloadsCommon, "isWinVistaOrHigher", function () {
+ let os = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
+ if (os != "WINNT") {
+ return false;
+ }
+ let sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2);
+ return parseFloat(sysInfo.getProperty("version")) >= 6;
+});
+
+/**
+ * Returns true to indicate that we should hook the panel to the JavaScript API
+ * for downloads instead of the nsIDownloadManager back-end.
+ * This is kept for compatibility/leftovers and should be removed later.
+ */
+XPCOMUtils.defineLazyGetter(DownloadsCommon, "useJSTransfer", function () {
+ return true;
+});
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsData
+
+/**
+ * Retrieves the list of past and completed downloads from the underlying
+ * Download Manager data, and provides asynchronous notifications allowing to
+ * build a consistent view of the available data.
+ *
+ * This object responds to real-time changes in the underlying Download Manager
+ * data. For example, the deletion of one or more downloads is notified through
+ * the nsIObserver interface, while any state or progress change is notified
+ * through the nsIDownloadProgressListener interface.
+ *
+ * Note that using this object does not automatically start the Download Manager
+ * service. Consumers will see an empty list of downloads until the service is
+ * actually started. This is useful to display a neutral progress indicator in
+ * the main browser window until the autostart timeout elapses.
+ *
+ * Note that DownloadsData and PrivateDownloadsData are two equivalent singleton
+ * objects, one accessing non-private downloads, and the other accessing private
+ * ones.
+ */
+function DownloadsDataCtor(aPrivate) {
+ this._isPrivate = aPrivate;
+
+ // Contains all the available Download objects and their integer state.
+ this.oldDownloadStates = new Map();
+
+ // Array of view objects that should be notified when the available download
+ // data changes.
+ this._views = [];
+}
+
+DownloadsDataCtor.prototype = {
+ /**
+ * Starts receiving events for current downloads.
+ */
+ initializeDataLink() {
+ if (!this._dataLinkInitialized) {
+ let promiseList = Downloads.getList(this._isPrivate ? Downloads.PRIVATE
+ : Downloads.PUBLIC);
+ promiseList.then(list => list.addView(this)).then(null, Cu.reportError);
+ this._dataLinkInitialized = true;
+ }
+ },
+ _dataLinkInitialized: false,
+
+ /**
+ * Stops receiving events for current downloads and cancels any pending read.
+ */
+ terminateDataLink: function DD_terminateDataLink()
+ {
+ Cu.reportError("terminateDataLink not applicable with JS Transfers");
+ return;
+ },
+
+ /**
+ * Iterator for all the available Download objects. This is empty until the
+ * data has been loaded using the JavaScript API for downloads.
+ */
+ get downloads() this.oldDownloadStates.keys(),
+
+ /**
+ * True if there are finished downloads that can be removed from the list.
+ */
+ get canRemoveFinished()
+ {
+ for (let download of this.downloads) {
+ // Stopped, paused, and failed downloads with partial data are removed.
+ if (download.stopped && !(download.canceled && download.hasPartialData)) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ /**
+ * Asks the back-end to remove finished downloads from the list.
+ */
+ removeFinished: function DD_removeFinished()
+ {
+ let promiseList = Downloads.getList(this._isPrivate ? Downloads.PRIVATE
+ : Downloads.PUBLIC);
+ promiseList.then(list => list.removeFinished())
+ .then(null, Cu.reportError);
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Integration with the asynchronous Downloads back-end
+
+ onDownloadAdded(download) {
+ // Download objects do not store the end time of downloads, as the Downloads
+ // API does not need to persist this information for all platforms. Once a
+ // download terminates on a Desktop browser, it becomes a history download,
+ // for which the end time is stored differently, as a Places annotation.
+ download.endTime = Date.now();
+
+ this.oldDownloadStates.set(download,
+ DownloadsCommon.stateOfDownload(download));
+
+ for (let view of this._views) {
+ view.onDownloadAdded(download, true);
+ }
+ },
+
+ onDownloadChanged(download) {
+ let oldState = this.oldDownloadStates.get(download);
+ let newState = DownloadsCommon.stateOfDownload(download);
+ this.oldDownloadStates.set(download, newState);
+
+ if (oldState != newState) {
+ if (download.succeeded ||
+ (download.canceled && !download.hasPartialData) ||
+ download.error) {
+ // Store the end time that may be displayed by the views.
+ download.endTime = Date.now();
+
+ // This state transition code should actually be located in a Downloads
+ // API module (bug 941009). Moreover, the fact that state is stored as
+ // annotations should be ideally hidden behind methods of
+ // nsIDownloadHistory (bug 830415).
+ if (!this._isPrivate) {
+ try {
+ let downloadMetaData = {
+ state: DownloadsCommon.stateOfDownload(download),
+ endTime: download.endTime,
+ };
+ if (download.succeeded) {
+ downloadMetaData.fileSize = download.target.size;
+ }
+
+ PlacesUtils.annotations.setPageAnnotation(
+ NetUtil.newURI(download.source.url),
+ "downloads/metaData",
+ JSON.stringify(downloadMetaData), 0,
+ PlacesUtils.annotations.EXPIRE_WITH_HISTORY);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+ }
+
+ for (let view of this._views) {
+ try {
+ view.onDownloadStateChanged(download);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+
+ if (download.succeeded ||
+ (download.error && download.error.becauseBlocked)) {
+ this._notifyDownloadEvent("finish");
+ }
+ }
+
+ if (!download.newDownloadNotified) {
+ download.newDownloadNotified = true;
+ this._notifyDownloadEvent("start");
+ }
+
+ for (let view of this._views) {
+ view.onDownloadChanged(download);
+ }
+ },
+
+ onDownloadRemoved(download) {
+ this.oldDownloadStates.delete(download);
+
+ for (let view of this._views) {
+ view.onDownloadRemoved(download);
+ }
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Registration of views
+
+ /**
+ * Adds an object to be notified when the available download data changes.
+ * The specified object is initialized with the currently available downloads.
+ *
+ * @param aView
+ * DownloadsView object to be added. This reference must be passed to
+ * removeView before termination.
+ */
+ addView: function DD_addView(aView)
+ {
+ this._views.push(aView);
+ this._updateView(aView);
+ },
+
+ /**
+ * Removes an object previously added using addView.
+ *
+ * @param aView
+ * DownloadsView object to be removed.
+ */
+ removeView: function DD_removeView(aView)
+ {
+ let index = this._views.indexOf(aView);
+ if (index != -1) {
+ this._views.splice(index, 1);
+ }
+ },
+
+ /**
+ * Ensures that the currently loaded data is added to the specified view.
+ *
+ * @param aView
+ * DownloadsView object to be initialized.
+ */
+ _updateView: function DD_updateView(aView)
+ {
+ // Indicate to the view that a batch loading operation is in progress.
+ aView.onDataLoadStarting();
+
+ // Sort backwards by start time, ensuring that the most recent
+ // downloads are added first regardless of their state.
+ // Tycho:
+ //let loadedItemsArray = [dataItem
+ // for each (dataItem in this.dataItems)
+ // if (dataItem)];
+ let downloadsArray = [...this.downloads];
+ downloadsArray.sort((a, b) => b.startTime - a.startTime);
+ downloadsArray.forEach(download => aView.onDownloadAdded(download, false));
+
+ // Notify the view that all data is available unless loading is in progress.
+ if (!this._pendingStatement) {
+ aView.onDataLoadCompleted();
+ }
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// In-memory downloads data store
+
+ /**
+ * Clears the loaded data.
+ */
+ clear: function DD_clear()
+ {
+ this._terminateDataAccess();
+ this.dataItems = {};
+ },
+
+ /**
+ * Returns the data item associated with the provided source object. The
+ * source can be a download object that we received from the Download Manager
+ * because of a real-time notification, or a row from the downloads database,
+ * during the asynchronous data load.
+ *
+ * In case we receive download status notifications while we are still
+ * populating the list of downloads from the database, we want the real-time
+ * status to take precedence over the state that is read from the database,
+ * which might be older. This is achieved by creating the download item if
+ * it's not already in the list, but never updating the returned object using
+ * the data from the database, if the object already exists.
+ *
+ * @param aSource
+ * Object containing the data with which the item should be initialized
+ * if it doesn't already exist in the list. This should implement
+ * either nsIDownload or mozIStorageRow. If the item exists, this
+ * argument is only used to retrieve the download identifier.
+ * @param aMayReuseGUID
+ * If false, indicates that the download should not be added if a
+ * download with the same identifier was removed in the meantime. This
+ * ensures that, while loading the list asynchronously, downloads that
+ * have been removed in the meantime do no reappear inadvertently.
+ *
+ * @return New or existing data item, or null if the item was deleted from the
+ * list of available downloads.
+ */
+ _getOrAddDataItem: function DD_getOrAddDataItem(aSource, aMayReuseGUID)
+ {
+ let downloadGuid = (aSource instanceof Ci.nsIDownload)
+ ? aSource.guid
+ : aSource.getResultByName("guid");
+ if (downloadGuid in this.dataItems) {
+ let existingItem = this.dataItems[downloadGuid];
+ if (existingItem || !aMayReuseGUID) {
+ // Returns null if the download was removed and we can't reuse the item.
+ return existingItem;
+ }
+ }
+ DownloadsCommon.log("Creating a new DownloadsDataItem with downloadGuid =",
+ downloadGuid);
+ let dataItem = new DownloadsDataItem(aSource);
+ this.dataItems[downloadGuid] = dataItem;
+
+ // Create the view items before returning.
+ let addToStartOfList = aSource instanceof Ci.nsIDownload;
+ this._views.forEach(
+ function (view) view.onDataItemAdded(dataItem, addToStartOfList)
+ );
+ return dataItem;
+ },
+
+ /**
+ * Removes the data item with the specified identifier.
+ *
+ * This method can be called at most once per download identifier.
+ */
+ _removeDataItem: function DD_removeDataItem(aDownloadId)
+ {
+ if (aDownloadId in this.dataItems) {
+ let dataItem = this.dataItems[aDownloadId];
+ this.dataItems[aDownloadId] = null;
+ this._views.forEach(
+ function (view) view.onDataItemRemoved(dataItem)
+ );
+ }
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Persistent data loading
+
+ /**
+ * Represents an executing statement, allowing its cancellation.
+ */
+ _pendingStatement: null,
+
+ /**
+ * Indicates which kind of items from the persistent downloads database have
+ * been fully loaded in memory and are available to the views. This can
+ * assume the value of one of the kLoad constants.
+ */
+ _loadState: 0,
+
+ /** No downloads have been fully loaded yet. */
+ get kLoadNone() 0,
+ /** All the active downloads in the database are loaded in memory. */
+ get kLoadActive() 1,
+ /** All the downloads in the database are loaded in memory. */
+ get kLoadAll() 2,
+
+ /**
+ * Reloads the specified kind of downloads from the persistent database. This
+ * method must only be called when Private Browsing Mode is disabled.
+ *
+ * @param aActiveOnly
+ * True to load only active downloads from the database.
+ */
+ ensurePersistentDataLoaded:
+ function DD_ensurePersistentDataLoaded(aActiveOnly)
+ {
+ if (this == PrivateDownloadsData) {
+ Cu.reportError("ensurePersistentDataLoaded should not be called on PrivateDownloadsData");
+ return;
+ }
+
+ if (this._pendingStatement) {
+ // We are already in the process of reloading all downloads.
+ return;
+ }
+
+ if (aActiveOnly) {
+ if (this._loadState == this.kLoadNone) {
+ DownloadsCommon.log("Loading only active downloads from the persistence database");
+ // Indicate to the views that a batch loading operation is in progress.
+ this._views.forEach(
+ function (view) view.onDataLoadStarting()
+ );
+
+ // Reload the list using the Download Manager service. The list is
+ // returned in no particular order.
+ let downloads = Services.downloads.activeDownloads;
+ while (downloads.hasMoreElements()) {
+ let download = downloads.getNext().QueryInterface(Ci.nsIDownload);
+ this._getOrAddDataItem(download, true);
+ }
+ this._loadState = this.kLoadActive;
+
+ // Indicate to the views that the batch loading operation is complete.
+ this._views.forEach(
+ function (view) view.onDataLoadCompleted()
+ );
+ DownloadsCommon.log("Active downloads done loading.");
+ }
+ } else {
+ if (this._loadState != this.kLoadAll) {
+ // Load only the relevant columns from the downloads database. The
+ // columns are read in the _initFromDataRow method of DownloadsDataItem.
+ // Order by descending download identifier so that the most recent
+ // downloads are notified first to the listening views.
+ DownloadsCommon.log("Loading all downloads from the persistence database.");
+ let dbConnection = Services.downloads.DBConnection;
+ let statement = dbConnection.createAsyncStatement(
+ "SELECT guid, target, name, source, referrer, state, "
+ + "startTime, endTime, currBytes, maxBytes "
+ + "FROM moz_downloads "
+ + "ORDER BY startTime DESC"
+ );
+ try {
+ this._pendingStatement = statement.executeAsync(this);
+ } finally {
+ statement.finalize();
+ }
+ }
+ }
+ },
+
+ /**
+ * Cancels any pending data access and ensures views are notified.
+ */
+ _terminateDataAccess: function DD_terminateDataAccess()
+ {
+ if (this._pendingStatement) {
+ this._pendingStatement.cancel();
+ this._pendingStatement = null;
+ }
+
+ // Close all the views on the current data. Create a copy of the array
+ // because some views might unregister while processing this event.
+ Array.slice(this._views, 0).forEach(
+ function (view) view.onDataInvalidated()
+ );
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// mozIStorageStatementCallback
+
+ handleResult: function DD_handleResult(aResultSet)
+ {
+ for (let row = aResultSet.getNextRow();
+ row;
+ row = aResultSet.getNextRow()) {
+ // Add the download to the list and initialize it with the data we read,
+ // unless we already received a notification providing more reliable
+ // information for this download.
+ this._getOrAddDataItem(row, false);
+ }
+ },
+
+ handleError: function DD_handleError(aError)
+ {
+ DownloadsCommon.error("Database statement execution error (",
+ aError.result, "): ", aError.message);
+ },
+
+ handleCompletion: function DD_handleCompletion(aReason)
+ {
+ DownloadsCommon.log("Loading all downloads from database completed with reason:",
+ aReason);
+ this._pendingStatement = null;
+
+ // To ensure that we don't inadvertently delete more downloads from the
+ // database than needed on shutdown, we should update the load state only if
+ // the operation completed successfully.
+ if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) {
+ this._loadState = this.kLoadAll;
+ }
+
+ // Indicate to the views that the batch loading operation is complete, even
+ // if the lookup failed or was canceled. The only possible glitch happens
+ // in case the database backend changes while loading data, when the views
+ // would open and immediately close. This case is rare enough not to need a
+ // special treatment.
+ this._views.forEach(
+ function (view) view.onDataLoadCompleted()
+ );
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsIObserver
+
+ observe: function DD_observe(aSubject, aTopic, aData)
+ {
+ switch (aTopic) {
+ case "download-manager-remove-download-guid":
+ // If a single download was removed, remove the corresponding data item.
+ if (aSubject) {
+ let downloadGuid = aSubject.QueryInterface(Ci.nsISupportsCString).data;
+ DownloadsCommon.log("A single download with id",
+ downloadGuid, "was removed.");
+ this._removeDataItem(downloadGuid);
+ break;
+ }
+
+ // Multiple downloads have been removed. Iterate over known downloads
+ // and remove those that don't exist anymore.
+ DownloadsCommon.log("Multiple downloads were removed.");
+ for each (let dataItem in this.dataItems) {
+ if (dataItem) {
+ // Bug 449811 - We have to bind to the dataItem because Javascript
+ // doesn't do fresh let-bindings per loop iteration.
+ let dataItemBinding = dataItem;
+ Services.downloads.getDownloadByGUID(dataItemBinding.downloadGuid,
+ function(aStatus, aResult) {
+ if (aStatus == Components.results.NS_ERROR_NOT_AVAILABLE) {
+ DownloadsCommon.log("Removing download with id",
+ dataItemBinding.downloadGuid);
+ this._removeDataItem(dataItemBinding.downloadGuid);
+ }
+ }.bind(this));
+ }
+ }
+ break;
+ }
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsIDownloadProgressListener
+
+ onDownloadStateChange: function DD_onDownloadStateChange(aOldState, aDownload)
+ {
+ if (aDownload.isPrivate != this._isPrivate) {
+ // Ignore the downloads with a privacy status other than what we are
+ // tracking.
+ return;
+ }
+
+ // When a new download is added, it may have the same identifier of a
+ // download that we previously deleted during this session, and we also
+ // want to provide a visible indication that the download started.
+ let isNew = aOldState == nsIDM.DOWNLOAD_NOTSTARTED ||
+ aOldState == nsIDM.DOWNLOAD_QUEUED;
+
+ let dataItem = this._getOrAddDataItem(aDownload, isNew);
+ if (!dataItem) {
+ return;
+ }
+
+ let wasInProgress = dataItem.inProgress;
+
+ DownloadsCommon.log("A download changed its state to:", aDownload.state);
+ dataItem.state = aDownload.state;
+ dataItem.referrer = aDownload.referrer && aDownload.referrer.spec;
+ dataItem.resumable = aDownload.resumable;
+ dataItem.startTime = Math.round(aDownload.startTime / 1000);
+ dataItem.currBytes = aDownload.amountTransferred;
+ dataItem.maxBytes = aDownload.size;
+
+ if (wasInProgress && !dataItem.inProgress) {
+ dataItem.endTime = Date.now();
+ }
+
+ // When a download is retried, we create a different download object from
+ // the database with the same ID as before. This means that the nsIDownload
+ // that the dataItem holds might now need updating.
+ //
+ // We only overwrite this in the event that _download exists, because if it
+ // doesn't, that means that no caller ever tried to get the nsIDownload,
+ // which means it was never retrieved and doesn't need to be overwritten.
+ if (dataItem._download) {
+ dataItem._download = aDownload;
+ }
+
+ for (let view of this._views) {
+ try {
+ view.getViewItem(dataItem).onStateChange(aOldState);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+
+ if (isNew && !dataItem.newDownloadNotified) {
+ dataItem.newDownloadNotified = true;
+ this._notifyDownloadEvent("start");
+ }
+
+ // This is a final state of which we are only notified once.
+ if (dataItem.done) {
+ this._notifyDownloadEvent("finish");
+ }
+
+ // TODO Bug 830415: this isn't the right place to set these annotation.
+ // It should be set it in places' nsIDownloadHistory implementation.
+ if (!this._isPrivate && !dataItem.inProgress) {
+ let downloadMetaData = { state: dataItem.state,
+ endTime: dataItem.endTime };
+ if (dataItem.done)
+ downloadMetaData.fileSize = dataItem.maxBytes;
+
+ try {
+ PlacesUtils.annotations.setPageAnnotation(
+ NetUtil.newURI(dataItem.uri), "downloads/metaData", JSON.stringify(downloadMetaData), 0,
+ PlacesUtils.annotations.EXPIRE_WITH_HISTORY);
+ }
+ catch(ex) {
+ Cu.reportError(ex);
+ }
+ }
+ },
+
+ onProgressChange: function DD_onProgressChange(aWebProgress, aRequest,
+ aCurSelfProgress,
+ aMaxSelfProgress,
+ aCurTotalProgress,
+ aMaxTotalProgress, aDownload)
+ {
+ if (aDownload.isPrivate != this._isPrivate) {
+ // Ignore the downloads with a privacy status other than what we are
+ // tracking.
+ return;
+ }
+
+ let dataItem = this._getOrAddDataItem(aDownload, false);
+ if (!dataItem) {
+ return;
+ }
+
+ dataItem.currBytes = aDownload.amountTransferred;
+ dataItem.maxBytes = aDownload.size;
+ dataItem.speed = aDownload.speed;
+ dataItem.percentComplete = aDownload.percentComplete;
+
+ this._views.forEach(
+ function (view) view.getViewItem(dataItem).onProgressChange()
+ );
+ },
+
+ onStateChange: function () { },
+
+ onSecurityChange: function () { },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Notifications sent to the most recent browser window only
+
+ /**
+ * Set to true after the first download causes the downloads panel to be
+ * displayed.
+ */
+ get panelHasShownBefore() {
+ try {
+ return Services.prefs.getBoolPref("browser.download.panel.shown");
+ } catch (ex) { }
+ return false;
+ },
+
+ set panelHasShownBefore(aValue) {
+ Services.prefs.setBoolPref("browser.download.panel.shown", aValue);
+ return aValue;
+ },
+
+ /**
+ * Displays a new or finished download notification in the most recent browser
+ * window, if one is currently available with the required privacy type.
+ *
+ * @param aType
+ * Set to "start" for new downloads, "finish" for completed downloads.
+ */
+ _notifyDownloadEvent: function DD_notifyDownloadEvent(aType)
+ {
+ DownloadsCommon.log("Attempting to notify that a new download has started or finished.");
+ if (DownloadsCommon.useToolkitUI) {
+ DownloadsCommon.log("Cancelling notification - we're using the toolkit downloads manager.");
+ return;
+ }
+
+ // Show the panel in the most recent browser window, if present.
+ let browserWin = RecentWindow.getMostRecentBrowserWindow({ private: this._isPrivate });
+ if (!browserWin) {
+ return;
+ }
+
+ if (this.panelHasShownBefore) {
+ // For new downloads after the first one, don't show the panel
+ // automatically, but provide a visible notification in the topmost
+ // browser window, if the status indicator is already visible.
+ DownloadsCommon.log("Showing new download notification.");
+ browserWin.DownloadsIndicatorView.showEventNotification(aType);
+ return;
+ }
+ this.panelHasShownBefore = true;
+ browserWin.DownloadsPanel.showPanel();
+ }
+};
+
+XPCOMUtils.defineLazyGetter(this, "PrivateDownloadsData", function() {
+ return new DownloadsDataCtor(true);
+});
+
+XPCOMUtils.defineLazyGetter(this, "DownloadsData", function() {
+ return new DownloadsDataCtor(false);
+});
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsViewPrototype
+
+/**
+ * A prototype for an object that registers itself with DownloadsData as soon
+ * as a view is registered with it.
+ */
+const DownloadsViewPrototype = {
+ //////////////////////////////////////////////////////////////////////////////
+ //// Registration of views
+
+ /**
+ * Array of view objects that should be notified when the available status
+ * data changes.
+ *
+ * SUBCLASSES MUST OVERRIDE THIS PROPERTY.
+ */
+ _views: null,
+
+ /**
+ * Determines whether this view object is over the private or non-private
+ * downloads.
+ *
+ * SUBCLASSES MUST OVERRIDE THIS PROPERTY.
+ */
+ _isPrivate: false,
+
+ /**
+ * Adds an object to be notified when the available status data changes.
+ * The specified object is initialized with the currently available status.
+ *
+ * @param aView
+ * View object to be added. This reference must be
+ * passed to removeView before termination.
+ */
+ addView: function DVP_addView(aView)
+ {
+ // Start receiving events when the first of our views is registered.
+ if (this._views.length == 0) {
+ if (this._isPrivate) {
+ PrivateDownloadsData.addView(this);
+ } else {
+ DownloadsData.addView(this);
+ }
+ }
+
+ this._views.push(aView);
+ this.refreshView(aView);
+ },
+
+ /**
+ * Updates the properties of an object previously added using addView.
+ *
+ * @param aView
+ * View object to be updated.
+ */
+ refreshView: function DVP_refreshView(aView)
+ {
+ // Update immediately even if we are still loading data asynchronously.
+ // Subclasses must provide these two functions!
+ this._refreshProperties();
+ this._updateView(aView);
+ },
+
+ /**
+ * Removes an object previously added using addView.
+ *
+ * @param aView
+ * View object to be removed.
+ */
+ removeView: function DVP_removeView(aView)
+ {
+ let index = this._views.indexOf(aView);
+ if (index != -1) {
+ this._views.splice(index, 1);
+ }
+
+ // Stop receiving events when the last of our views is unregistered.
+ if (this._views.length == 0) {
+ if (this._isPrivate) {
+ PrivateDownloadsData.removeView(this);
+ } else {
+ DownloadsData.removeView(this);
+ }
+ }
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Callback functions from DownloadsData
+
+ /**
+ * Indicates whether we are still loading downloads data asynchronously.
+ */
+ _loading: false,
+
+ /**
+ * Called before multiple downloads are about to be loaded.
+ */
+ onDataLoadStarting: function DVP_onDataLoadStarting()
+ {
+ this._loading = true;
+ },
+
+ /**
+ * Called after data loading finished.
+ */
+ onDataLoadCompleted: function DVP_onDataLoadCompleted()
+ {
+ this._loading = false;
+ },
+
+ /**
+ * Called when the downloads database becomes unavailable (for example, we
+ * entered Private Browsing Mode and the database backend changed).
+ * References to existing data should be discarded.
+ *
+ * @note Subclasses should override this.
+ */
+ onDataInvalidated: function DVP_onDataInvalidated()
+ {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ /**
+ * Called when a new download data item is available, either during the
+ * asynchronous data load or when a new download is started.
+ *
+ * @param download
+ * Download object that was just added.
+ * @param newest
+ * When true, indicates that this item is the most recent and should be
+ * added in the topmost position. This happens when a new download is
+ * started. When false, indicates that the item is the least recent
+ * with regard to the items that have been already added. The latter
+ * generally happens during the asynchronous data load.
+ *
+ * @note Subclasses should override this.
+ */
+ onDownloadAdded(download, newest) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ /**
+ * Called when the overall state of a Download has changed. In particular,
+ * this is called only once when the download succeeds or is blocked
+ * permanently, and is never called if only the current progress changed.
+ *
+ * The onDownloadChanged notification will always be sent afterwards.
+ *
+ * @note Subclasses should override this.
+ */
+ onDownloadStateChanged(download) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ /**
+ * Called every time any state property of a Download may have changed,
+ * including progress properties.
+ *
+ * Note that progress notification changes are throttled at the Downloads.jsm
+ * API level, and there is no throttling mechanism in the front-end.
+ *
+ * @note Subclasses should override this.
+ */
+ onDownloadChanged(download) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ /**
+ * Called when a data item is removed, ensures that the widget associated with
+ * the view item is removed from the user interface.
+ *
+ * @param download
+ * Download object that is being removed.
+ *
+ * @note Subclasses should override this.
+ */
+ onDownloadRemoved(download) {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ /**
+ * Private function used to refresh the internal properties being sent to
+ * each registered view.
+ *
+ * @note Subclasses should override this.
+ */
+ _refreshProperties: function DID_refreshProperties()
+ {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ /**
+ * Private function used to refresh an individual view.
+ *
+ * @note Subclasses should override this.
+ */
+ _updateView: function DID_updateView()
+ {
+ throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsIndicatorData
+
+/**
+ * This object registers itself with DownloadsData as a view, and transforms the
+ * notifications it receives into overall status data, that is then broadcast to
+ * the registered download status indicators.
+ *
+ * Note that using this object does not automatically start the Download Manager
+ * service. Consumers will see an empty list of downloads until the service is
+ * actually started. This is useful to display a neutral progress indicator in
+ * the main browser window until the autostart timeout elapses.
+ */
+function DownloadsIndicatorDataCtor(aPrivate) {
+ this._isPrivate = aPrivate;
+ this._views = [];
+}
+DownloadsIndicatorDataCtor.prototype = {
+ __proto__: DownloadsViewPrototype,
+
+ /**
+ * Removes an object previously added using addView.
+ *
+ * @param aView
+ * DownloadsIndicatorView object to be removed.
+ */
+ removeView: function DID_removeView(aView)
+ {
+ DownloadsViewPrototype.removeView.call(this, aView);
+
+ if (this._views.length == 0) {
+ this._itemCount = 0;
+ }
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Callback functions from DownloadsData
+
+ onDataLoadCompleted: function DID_onDataLoadCompleted()
+ {
+ DownloadsViewPrototype.onDataLoadCompleted.call(this);
+ this._updateViews();
+ },
+
+ /**
+ * Called when the downloads database becomes unavailable (for example, we
+ * entered Private Browsing Mode and the database backend changed).
+ * References to existing data should be discarded.
+ */
+ onDataInvalidated: function DID_onDataInvalidated()
+ {
+ this._itemCount = 0;
+ },
+
+ onDownloadAdded(download, newest) {
+ this._itemCount++;
+ this._updateViews();
+ },
+
+ onDownloadStateChanged(download) {
+ if (download.succeeded || download.error) {
+ this.attention = true;
+ }
+
+ // Since the state of a download changed, reset the estimated time left.
+ this._lastRawTimeLeft = -1;
+ this._lastTimeLeft = -1;
+ },
+
+ onDownloadChanged(download) {
+ this._updateViews();
+ },
+
+ onDownloadRemoved(download) {
+ this._itemCount--;
+ this._updateViews();
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Propagation of properties to our views
+
+ // The following properties are updated by _refreshProperties and are then
+ // propagated to the views. See _refreshProperties for details.
+ _hasDownloads: false,
+ _counter: "",
+ _percentComplete: -1,
+ _paused: false,
+
+ /**
+ * Indicates whether the download indicators should be highlighted.
+ */
+ set attention(aValue)
+ {
+ this._attention = aValue;
+ this._updateViews();
+ return aValue;
+ },
+ _attention: false,
+
+ /**
+ * Indicates whether the user is interacting with downloads, thus the
+ * attention indication should not be shown even if requested.
+ */
+ set attentionSuppressed(aValue)
+ {
+ this._attentionSuppressed = aValue;
+ this._attention = false;
+ this._updateViews();
+ return aValue;
+ },
+ _attentionSuppressed: false,
+
+ /**
+ * Computes aggregate values and propagates the changes to our views.
+ */
+ _updateViews: function DID_updateViews()
+ {
+ // Do not update the status indicators during batch loads of download items.
+ if (this._loading) {
+ return;
+ }
+
+ this._refreshProperties();
+ this._views.forEach(this._updateView, this);
+ },
+
+ /**
+ * Updates the specified view with the current aggregate values.
+ *
+ * @param aView
+ * DownloadsIndicatorView object to be updated.
+ */
+ _updateView: function DID_updateView(aView)
+ {
+ aView.hasDownloads = this._hasDownloads;
+ aView.counter = this._counter;
+ aView.percentComplete = this._percentComplete;
+ aView.paused = this._paused;
+ aView.attention = this._attention && !this._attentionSuppressed;
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Property updating based on current download status
+
+ /**
+ * Number of download items that are available to be displayed.
+ */
+ _itemCount: 0,
+
+ /**
+ * Floating point value indicating the last number of seconds estimated until
+ * the longest download will finish. We need to store this value so that we
+ * don't continuously apply smoothing if the actual download state has not
+ * changed. This is set to -1 if the previous value is unknown.
+ */
+ _lastRawTimeLeft: -1,
+
+ /**
+ * Last number of seconds estimated until all in-progress downloads with a
+ * known size and speed will finish. This value is stored to allow smoothing
+ * in case of small variations. This is set to -1 if the previous value is
+ * unknown.
+ */
+ _lastTimeLeft: -1,
+
+ /**
+ * A generator function for the Download objects this summary is currently
+ * interested in. This generator is passed off to summarizeDownloads in order
+ * to generate statistics about the downloads we care about - in this case,
+ * it's all active downloads.
+ */
+ * _activeDownloads() {
+ let downloads = this._isPrivate ? PrivateDownloadsData.downloads
+ : DownloadsData.downloads;
+ for (let download of downloads) {
+ if (!download.stopped || (download.canceled && download.hasPartialData)) {
+ yield download;
+ }
+ }
+ },
+
+ /**
+ * Computes aggregate values based on the current state of downloads.
+ */
+ _refreshProperties: function DID_refreshProperties()
+ {
+ let summary =
+ DownloadsCommon.summarizeDownloads(this._activeDownloads());
+
+ // Determine if the indicator should be shown or get attention.
+ this._hasDownloads = (this._itemCount > 0);
+
+ // If all downloads are paused, show the progress indicator as paused.
+ this._paused = summary.numActive > 0 &&
+ summary.numActive == summary.numPaused;
+
+ this._percentComplete = summary.percentComplete;
+
+ // Display the estimated time left, if present.
+ if (summary.rawTimeLeft == -1) {
+ // There are no downloads with a known time left.
+ this._lastRawTimeLeft = -1;
+ this._lastTimeLeft = -1;
+ this._counter = "";
+ } else {
+ // Compute the new time left only if state actually changed.
+ if (this._lastRawTimeLeft != summary.rawTimeLeft) {
+ this._lastRawTimeLeft = summary.rawTimeLeft;
+ this._lastTimeLeft = DownloadsCommon.smoothSeconds(summary.rawTimeLeft,
+ this._lastTimeLeft);
+ }
+ this._counter = DownloadsCommon.formatTimeLeft(this._lastTimeLeft);
+ }
+ }
+};
+
+XPCOMUtils.defineLazyGetter(this, "PrivateDownloadsIndicatorData", function() {
+ return new DownloadsIndicatorDataCtor(true);
+});
+
+XPCOMUtils.defineLazyGetter(this, "DownloadsIndicatorData", function() {
+ return new DownloadsIndicatorDataCtor(false);
+});
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsSummaryData
+
+/**
+ * DownloadsSummaryData is a view for DownloadsData that produces a summary
+ * of all downloads after a certain exclusion point aNumToExclude. For example,
+ * if there were 5 downloads in progress, and a DownloadsSummaryData was
+ * constructed with aNumToExclude equal to 3, then that DownloadsSummaryData
+ * would produce a summary of the last 2 downloads.
+ *
+ * @param aIsPrivate
+ * True if the browser window which owns the download button is a private
+ * window.
+ * @param aNumToExclude
+ * The number of items to exclude from the summary, starting from the
+ * top of the list.
+ */
+function DownloadsSummaryData(aIsPrivate, aNumToExclude) {
+ this._numToExclude = aNumToExclude;
+ // Since we can have multiple instances of DownloadsSummaryData, we
+ // override these values from the prototype so that each instance can be
+ // completely separated from one another.
+ this._loading = false;
+
+ this._downloads = [];
+
+ // Floating point value indicating the last number of seconds estimated until
+ // the longest download will finish. We need to store this value so that we
+ // don't continuously apply smoothing if the actual download state has not
+ // changed. This is set to -1 if the previous value is unknown.
+ this._lastRawTimeLeft = -1;
+
+ // Last number of seconds estimated until all in-progress downloads with a
+ // known size and speed will finish. This value is stored to allow smoothing
+ // in case of small variations. This is set to -1 if the previous value is
+ // unknown.
+ this._lastTimeLeft = -1;
+
+ // The following properties are updated by _refreshProperties and are then
+ // propagated to the views.
+ this._showingProgress = false;
+ this._details = "";
+ this._description = "";
+ this._numActive = 0;
+ this._percentComplete = -1;
+
+ this._isPrivate = aIsPrivate;
+ this._views = [];
+}
+
+DownloadsSummaryData.prototype = {
+ __proto__: DownloadsViewPrototype,
+
+ /**
+ * Removes an object previously added using addView.
+ *
+ * @param aView
+ * DownloadsSummary view to be removed.
+ */
+ removeView: function DSD_removeView(aView)
+ {
+ DownloadsViewPrototype.removeView.call(this, aView);
+
+ if (this._views.length == 0) {
+ // Clear out our collection of Download objects. If we ever have
+ // another view registered with us, this will get re-populated.
+ this._downloads = [];
+ }
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Callback functions from DownloadsData - see the documentation in
+ //// DownloadsViewPrototype for more information on what these functions
+ //// are used for.
+
+ onDataLoadCompleted: function DSD_onDataLoadCompleted()
+ {
+ DownloadsViewPrototype.onDataLoadCompleted.call(this);
+ this._updateViews();
+ },
+
+ onDataInvalidated: function DSD_onDataInvalidated()
+ {
+ this._dataItems = [];
+ },
+
+ onDownloadAdded(download, newest) {
+ if (newest) {
+ this._downloads.unshift(download);
+ } else {
+ this._downloads.push(download);
+ }
+
+ this._updateViews();
+ },
+
+ onDownloadStateChanged() {
+ // Since the state of a download changed, reset the estimated time left.
+ this._lastRawTimeLeft = -1;
+ this._lastTimeLeft = -1;
+ },
+
+ onDownloadChanged() {
+ this._updateViews();
+ },
+
+ onDownloadRemoved(download) {
+ let itemIndex = this._downloads.indexOf(download);
+ this._downloads.splice(itemIndex, 1);
+ this._updateViews();
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Propagation of properties to our views
+
+ /**
+ * Computes aggregate values and propagates the changes to our views.
+ */
+ _updateViews: function DSD_updateViews()
+ {
+ // Do not update the status indicators during batch loads of download items.
+ if (this._loading) {
+ return;
+ }
+
+ this._refreshProperties();
+ this._views.forEach(this._updateView, this);
+ },
+
+ /**
+ * Updates the specified view with the current aggregate values.
+ *
+ * @param aView
+ * DownloadsIndicatorView object to be updated.
+ */
+ _updateView: function DSD_updateView(aView)
+ {
+ aView.showingProgress = this._showingProgress;
+ aView.percentComplete = this._percentComplete;
+ aView.description = this._description;
+ aView.details = this._details;
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Property updating based on current download status
+
+ /**
+ * A generator function for the Download objects this summary is currently
+ * interested in. This generator is passed off to summarizeDownloads in order
+ * to generate statistics about the downloads we care about - in this case,
+ * it's the downloads in this._downloads after the first few to exclude,
+ * which was set when constructing this DownloadsSummaryData instance.
+ */
+ * _downloadsForSummary() {
+ if (this._downloads.length > 0) {
+ for (let i = this._numToExclude; i < this._downloads.length; ++i) {
+ yield this._downloads[i];
+ }
+ }
+ },
+
+ /**
+ * Computes aggregate values based on the current state of downloads.
+ */
+ _refreshProperties: function DSD_refreshProperties()
+ {
+ // Pre-load summary with default values.
+ let summary =
+ DownloadsCommon.summarizeDownloads(this._downloadsForSummary());
+
+ this._description = DownloadsCommon.strings
+ .otherDownloads2(summary.numActive);
+ this._percentComplete = summary.percentComplete;
+
+ // If all downloads are paused, show the progress indicator as paused.
+ this._showingProgress = summary.numDownloading > 0 ||
+ summary.numPaused > 0;
+
+ // Display the estimated time left, if present.
+ if (summary.rawTimeLeft == -1) {
+ // There are no downloads with a known time left.
+ this._lastRawTimeLeft = -1;
+ this._lastTimeLeft = -1;
+ this._details = "";
+ } else {
+ // Compute the new time left only if state actually changed.
+ if (this._lastRawTimeLeft != summary.rawTimeLeft) {
+ this._lastRawTimeLeft = summary.rawTimeLeft;
+ this._lastTimeLeft = DownloadsCommon.smoothSeconds(summary.rawTimeLeft,
+ this._lastTimeLeft);
+ }
+ [this._details] = DownloadUtils.getDownloadStatusNoRate(
+ summary.totalTransferred, summary.totalSize, summary.slowestSpeed,
+ this._lastTimeLeft);
+ }
+ }
+}
diff --git a/components/downloads/DownloadsLogger.jsm b/components/downloads/DownloadsLogger.jsm
new file mode 100644
index 0000000..1218539
--- /dev/null
+++ b/components/downloads/DownloadsLogger.jsm
@@ -0,0 +1,76 @@
+/* -*- Mode: js2; js2-basic-offset: 2; indent-tabs-mode: nil; -*- */
+/* vim: set ft=javascript 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/. */
+
+/**
+ * The contents of this file were copied almost entirely from
+ * toolkit/identity/LogUtils.jsm. Until we've got a more generalized logging
+ * mechanism for toolkit, I think this is going to be how we roll.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["DownloadsLogger"];
+const PREF_DEBUG = "browser.download.debug";
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+this.DownloadsLogger = {
+ _generateLogMessage: function _generateLogMessage(args) {
+ // create a string representation of a list of arbitrary things
+ let strings = [];
+
+ for (let arg of args) {
+ if (typeof arg === 'string') {
+ strings.push(arg);
+ } else if (arg === undefined) {
+ strings.push('undefined');
+ } else if (arg === null) {
+ strings.push('null');
+ } else {
+ try {
+ strings.push(JSON.stringify(arg, null, 2));
+ } catch(err) {
+ strings.push("<<something>>");
+ }
+ }
+ };
+ return 'Downloads: ' + strings.join(' ');
+ },
+
+ /**
+ * log() - utility function to print a list of arbitrary things
+ *
+ * Enable with about:config pref browser.download.debug
+ */
+ log: function DL_log(...args) {
+ let output = this._generateLogMessage(args);
+ dump(output + "\n");
+
+ // Additionally, make the output visible in the Error Console
+ Services.console.logStringMessage(output);
+ },
+
+ /**
+ * reportError() - report an error through component utils as well as
+ * our log function
+ */
+ reportError: function DL_reportError(...aArgs) {
+ // Report the error in the browser
+ let output = this._generateLogMessage(aArgs);
+ Cu.reportError(output);
+ dump("ERROR:" + output + "\n");
+ for (let frame = Components.stack.caller; frame; frame = frame.caller) {
+ dump("\t" + frame + "\n");
+ }
+ }
+
+};
diff --git a/components/downloads/DownloadsStartup.js b/components/downloads/DownloadsStartup.js
new file mode 100644
index 0000000..e1dd207
--- /dev/null
+++ b/components/downloads/DownloadsStartup.js
@@ -0,0 +1,278 @@
+/* -*- 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/. */
+
+/**
+ * This component listens to notifications for startup, shutdown and session
+ * restore, controlling which downloads should be loaded from the database.
+ *
+ * To avoid affecting startup performance, this component monitors the current
+ * session restore state, but defers the actual downloads data manipulation
+ * until the Download Manager service is loaded.
+ */
+
+"use strict";
+
+////////////////////////////////////////////////////////////////////////////////
+//// Globals
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
+ "resource:///modules/DownloadsCommon.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "gSessionStartup",
+ "@mozilla.org/browser/sessionstartup;1",
+ "nsISessionStartup");
+
+const kObservedTopics = [
+ "sessionstore-windows-restored",
+ "sessionstore-browser-state-restored",
+ "download-manager-initialized",
+ "download-manager-change-retention",
+ "last-pb-context-exited",
+ "browser-lastwindow-close-granted",
+ "quit-application",
+ "profile-change-teardown",
+];
+
+/**
+ * CID of our implementation of nsIDownloadManagerUI.
+ */
+const kDownloadsUICid = Components.ID("{4d99321e-d156-455b-81f7-e7aa2308134f}");
+
+/**
+ * Contract ID of the service implementing nsIDownloadManagerUI.
+ */
+const kDownloadsUIContractId = "@mozilla.org/download-manager-ui;1";
+
+/**
+ * CID of the JavaScript implementation of nsITransfer.
+ */
+const kTransferCid = Components.ID("{1b4c85df-cbdd-4bb6-b04e-613caece083c}");
+
+/**
+ * Contract ID of the service implementing nsITransfer.
+ */
+const kTransferContractId = "@mozilla.org/transfer;1";
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsStartup
+
+function DownloadsStartup() { }
+
+DownloadsStartup.prototype = {
+ classID: Components.ID("{49507fe5-2cee-4824-b6a3-e999150ce9b8}"),
+
+ _xpcom_factory: XPCOMUtils.generateSingletonFactory(DownloadsStartup),
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]),
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsIObserver
+
+ observe: function DS_observe(aSubject, aTopic, aData)
+ {
+ switch (aTopic) {
+ case "profile-after-change":
+ // Override Toolkit's nsIDownloadManagerUI implementation with our own.
+ // This must be done at application startup and not in the manifest to
+ // ensure that our implementation overrides the original one.
+ Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
+ .registerFactory(kDownloadsUICid, "",
+ kDownloadsUIContractId, null);
+
+ Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
+ .registerFactory(kTransferCid, "",
+ kTransferContractId, null);
+ break;
+
+ case "sessionstore-windows-restored":
+ case "sessionstore-browser-state-restored":
+ // Unless there is no saved session, there is a chance that we are
+ // starting up after a restart or a crash. We should check the disk
+ // database to see if there are completed downloads to recover and show
+ // in the panel, in addition to in-progress downloads.
+ if (gSessionStartup.sessionType != Ci.nsISessionStartup.NO_SESSION) {
+ this._restoringSession = true;
+ }
+ this._ensureDataLoaded();
+ break;
+
+ case "download-manager-initialized":
+ // Don't initialize the JavaScript data and user interface layer if we
+ // are initializing the Download Manager service during shutdown.
+ if (this._shuttingDown) {
+ break;
+ }
+
+ // Start receiving events for active and new downloads before we return
+ // from this observer function. We can't defer the execution of this
+ // step, to ensure that we don't lose events raised in the meantime.
+ DownloadsCommon.initializeAllDataLinks(
+ aSubject.QueryInterface(Ci.nsIDownloadManager));
+
+ this._downloadsServiceInitialized = true;
+
+ // Since this notification is generated during the getService call and
+ // we need to get the Download Manager service ourselves, we must post
+ // the handler on the event queue to be executed later.
+ Services.tm.mainThread.dispatch(this._ensureDataLoaded.bind(this),
+ Ci.nsIThread.DISPATCH_NORMAL);
+ break;
+
+ case "download-manager-change-retention":
+ // If we're using the Downloads Panel, we override the retention
+ // preference to always retain downloads on completion.
+ if (!DownloadsCommon.useToolkitUI) {
+ aSubject.QueryInterface(Ci.nsISupportsPRInt32).data = 2;
+ }
+ break;
+
+ case "browser-lastwindow-close-granted":
+ // When using the panel interface, downloads that are already completed
+ // should be removed when the last full browser window is closed. This
+ // event is invoked only if the application is not shutting down yet.
+ // If the Download Manager service is not initialized, we don't want to
+ // initialize it just to clean up completed downloads, because they can
+ // be present only in case there was a browser crash or restart.
+ if (this._downloadsServiceInitialized &&
+ !DownloadsCommon.useToolkitUI) {
+ Services.downloads.cleanUp();
+ }
+ break;
+
+ case "last-pb-context-exited":
+ // Similar to the above notification, but for private downloads.
+ if (this._downloadsServiceInitialized &&
+ !DownloadsCommon.useToolkitUI) {
+ Services.downloads.cleanUpPrivate();
+ }
+ break;
+
+ case "quit-application":
+ // When the application is shutting down, we must free all resources in
+ // addition to cleaning up completed downloads. If the Download Manager
+ // service is not initialized, we don't want to initialize it just to
+ // clean up completed downloads, because they can be present only in
+ // case there was a browser crash or restart.
+ this._shuttingDown = true;
+ if (!this._downloadsServiceInitialized) {
+ break;
+ }
+
+ DownloadsCommon.terminateAllDataLinks();
+
+ // When using the panel interface, downloads that are already completed
+ // should be removed when quitting the application.
+ if (!DownloadsCommon.useToolkitUI && aData != "restart") {
+ this._cleanupOnShutdown = true;
+ }
+ break;
+
+ case "profile-change-teardown":
+ // If we need to clean up, we must do it synchronously after all the
+ // "quit-application" listeners are invoked, so that the Download
+ // Manager service has a chance to pause or cancel in-progress downloads
+ // before we remove completed downloads from the list. Note that, since
+ // "quit-application" was invoked, we've already exited Private Browsing
+ // Mode, thus we are always working on the disk database.
+ if (this._cleanupOnShutdown) {
+ Services.downloads.cleanUp();
+ }
+
+ if (!DownloadsCommon.useToolkitUI) {
+ // If we got this far, that means that we finished our first session
+ // with the Downloads Panel without crashing. This means that we don't
+ // have to force displaying only active downloads on the next startup
+ // now.
+ this._firstSessionCompleted = true;
+ }
+ break;
+ }
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Private
+
+ /**
+ * Indicates whether we're restoring a previous session. This is used by
+ * _recoverAllDownloads to determine whether or not we should load and
+ * display all downloads data, or restrict it to only the active downloads.
+ */
+ _restoringSession: false,
+
+ /**
+ * Indicates whether the Download Manager service has been initialized. This
+ * flag is required because we want to avoid accessing the service immediately
+ * at browser startup. The service will start when the user first requests a
+ * download, or some time after browser startup.
+ */
+ _downloadsServiceInitialized: false,
+
+ /**
+ * True while we are processing the "quit-application" event, and later.
+ */
+ _shuttingDown: false,
+
+ /**
+ * True during shutdown if we need to remove completed downloads.
+ */
+ _cleanupOnShutdown: false,
+
+ /**
+ * True if we should display all downloads, as opposed to just active
+ * downloads. We decide to display all downloads if we're restoring a session,
+ * or if we're using the Downloads Panel anytime after the first session with
+ * it has completed.
+ */
+ get _recoverAllDownloads() {
+ return this._restoringSession ||
+ (!DownloadsCommon.useToolkitUI && this._firstSessionCompleted);
+ },
+
+ /**
+ * True if we've ever completed a session with the Downloads Panel enabled.
+ */
+ get _firstSessionCompleted() {
+ return Services.prefs
+ .getBoolPref("browser.download.panel.firstSessionCompleted");
+ },
+
+ set _firstSessionCompleted(aValue) {
+ Services.prefs.setBoolPref("browser.download.panel.firstSessionCompleted",
+ aValue);
+ return aValue;
+ },
+
+ /**
+ * Ensures that persistent download data is reloaded at the appropriate time.
+ */
+ _ensureDataLoaded: function DS_ensureDataLoaded()
+ {
+ if (!this._downloadsServiceInitialized) {
+ return;
+ }
+
+ // If the previous session has been already restored, then we ensure that
+ // all the downloads are loaded. Otherwise, we only ensure that the active
+ // downloads from the previous session are loaded.
+ DownloadsCommon.ensureAllPersistentDataLoaded(!this._recoverAllDownloads);
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// Module
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DownloadsStartup]);
diff --git a/components/downloads/DownloadsTaskbar.jsm b/components/downloads/DownloadsTaskbar.jsm
new file mode 100644
index 0000000..cf915ab
--- /dev/null
+++ b/components/downloads/DownloadsTaskbar.jsm
@@ -0,0 +1,177 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
+/* 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/. */
+
+/**
+ * Handles the download progress indicator in the taskbar.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+ "DownloadsTaskbar",
+];
+
+////////////////////////////////////////////////////////////////////////////////
+//// Globals
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Downloads",
+ "resource://gre/modules/Downloads.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
+ "resource:///modules/RecentWindow.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "gWinTaskbar", function () {
+ if (!("@mozilla.org/windows-taskbar;1" in Cc)) {
+ return null;
+ }
+ let winTaskbar = Cc["@mozilla.org/windows-taskbar;1"]
+ .getService(Ci.nsIWinTaskbar);
+ return winTaskbar.available && winTaskbar;
+});
+
+XPCOMUtils.defineLazyGetter(this, "gMacTaskbarProgress", function () {
+ return ("@mozilla.org/widget/macdocksupport;1" in Cc) &&
+ Cc["@mozilla.org/widget/macdocksupport;1"]
+ .getService(Ci.nsITaskbarProgress);
+});
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsTaskbar
+
+/**
+ * Handles the download progress indicator in the taskbar.
+ */
+this.DownloadsTaskbar = {
+ /**
+ * Underlying DownloadSummary providing the aggregate download information, or
+ * null if the indicator has never been initialized.
+ */
+ _summary: null,
+
+ /**
+ * nsITaskbarProgress object to which download information is dispatched.
+ * This can be null if the indicator has never been initialized or if the
+ * indicator is currently hidden on Windows.
+ */
+ _taskbarProgress: null,
+
+ /**
+ * This method is called after a new browser window is opened, and ensures
+ * that the download progress indicator is displayed in the taskbar.
+ *
+ * On Windows, the indicator is attached to the first browser window that
+ * calls this method. When the window is closed, the indicator is moved to
+ * another browser window, if available, in no particular order. When there
+ * are no browser windows visible, the indicator is hidden.
+ *
+ * On Mac OS X, the indicator is initialized globally when this method is
+ * called for the first time. Subsequent calls have no effect.
+ *
+ * @param aBrowserWindow
+ * nsIDOMWindow object of the newly opened browser window to which the
+ * indicator may be attached.
+ */
+ registerIndicator(aBrowserWindow) {
+ if (!this._taskbarProgress) {
+ if (gMacTaskbarProgress) {
+ // On Mac OS X, we have to register the global indicator only once.
+ this._taskbarProgress = gMacTaskbarProgress;
+ // Free the XPCOM reference on shutdown, to prevent detecting a leak.
+ Services.obs.addObserver(() => {
+ this._taskbarProgress = null;
+ gMacTaskbarProgress = null;
+ }, "quit-application-granted", false);
+ } else if (gWinTaskbar) {
+ // On Windows, the indicator is currently hidden because we have no
+ // previous browser window, thus we should attach the indicator now.
+ this._attachIndicator(aBrowserWindow);
+ } else {
+ // The taskbar indicator is not available on this platform.
+ return;
+ }
+ }
+
+ // Ensure that the DownloadSummary object will be created asynchronously.
+ if (!this._summary) {
+ Downloads.getSummary(Downloads.ALL).then(summary => {
+ // In case the method is re-entered, we simply ignore redundant
+ // invocations of the callback, instead of keeping separate state.
+ if (this._summary) {
+ return;
+ }
+ this._summary = summary;
+ return this._summary.addView(this);
+ }).then(null, Cu.reportError);
+ }
+ },
+
+ /**
+ * On Windows, attaches the taskbar indicator to the specified browser window.
+ */
+ _attachIndicator(aWindow) {
+ // Activate the indicator on the specified window.
+ let docShell = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem).treeOwner
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIXULWindow).docShell;
+ this._taskbarProgress = gWinTaskbar.getTaskbarProgress(docShell);
+
+ // If the DownloadSummary object has already been created, we should update
+ // the state of the new indicator, otherwise it will be updated as soon as
+ // the DownloadSummary view is registered.
+ if (this._summary) {
+ this.onSummaryChanged();
+ }
+
+ aWindow.addEventListener("unload", () => {
+ // Locate another browser window, excluding the one being closed.
+ let browserWindow = RecentWindow.getMostRecentBrowserWindow();
+ if (browserWindow) {
+ // Move the progress indicator to the other browser window.
+ this._attachIndicator(browserWindow);
+ } else {
+ // The last browser window has been closed. We remove the reference to
+ // the taskbar progress object so that the indicator will be registered
+ // again on the next browser window that is opened.
+ this._taskbarProgress = null;
+ }
+ }, false);
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// DownloadSummary view
+
+ onSummaryChanged() {
+ // If the last browser window has been closed, we have no indicator any more.
+ if (!this._taskbarProgress) {
+ return;
+ }
+
+ if (this._summary.allHaveStopped || this._summary.progressTotalBytes == 0) {
+ this._taskbarProgress.setProgressState(
+ Ci.nsITaskbarProgress.STATE_NO_PROGRESS, 0, 0);
+ } else {
+ // For a brief moment before completion, some download components may
+ // report more transferred bytes than the total number of bytes. Thus,
+ // ensure that we never break the expectations of the progress indicator.
+ let progressCurrentBytes = Math.min(this._summary.progressTotalBytes,
+ this._summary.progressCurrentBytes);
+ this._taskbarProgress.setProgressState(
+ Ci.nsITaskbarProgress.STATE_NORMAL,
+ progressCurrentBytes,
+ this._summary.progressTotalBytes);
+ }
+ },
+};
diff --git a/components/downloads/DownloadsUI.js b/components/downloads/DownloadsUI.js
new file mode 100644
index 0000000..afdbda8
--- /dev/null
+++ b/components/downloads/DownloadsUI.js
@@ -0,0 +1,151 @@
+/* -*- 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/. */
+
+/**
+ * This component implements the nsIDownloadManagerUI interface and opens the
+ * downloads panel in the most recent browser window when requested.
+ *
+ * If a specific preference is set, this component transparently forwards all
+ * calls to the original implementation in Toolkit, that shows the window UI.
+ */
+
+"use strict";
+
+////////////////////////////////////////////////////////////////////////////////
+//// Globals
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+const Cr = Components.results;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
+ "resource:///modules/DownloadsCommon.jsm");
+XPCOMUtils.defineLazyServiceGetter(this, "gBrowserGlue",
+ "@mozilla.org/browser/browserglue;1",
+ "nsIBrowserGlue");
+XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
+ "resource:///modules/RecentWindow.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsUI
+
+function DownloadsUI()
+{
+ XPCOMUtils.defineLazyGetter(this, "_toolkitUI", function () {
+ // Create Toolkit's nsIDownloadManagerUI implementation.
+ return Components.classesByID["{7dfdf0d1-aff6-4a34-bad1-d0fe74601642}"]
+ .getService(Ci.nsIDownloadManagerUI);
+ });
+}
+
+DownloadsUI.prototype = {
+ classID: Components.ID("{4d99321e-d156-455b-81f7-e7aa2308134f}"),
+
+ _xpcom_factory: XPCOMUtils.generateSingletonFactory(DownloadsUI),
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsISupports
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIDownloadManagerUI]),
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsIDownloadManagerUI
+
+ show: function DUI_show(aWindowContext, aDownload, aReason, aUsePrivateUI)
+ {
+ if (DownloadsCommon.useToolkitUI && !PrivateBrowsingUtils.isWindowPrivate(aWindowContext)) {
+ this._toolkitUI.show(aWindowContext, aDownload, aReason, aUsePrivateUI);
+ return;
+ }
+
+ if (!aReason) {
+ aReason = Ci.nsIDownloadManagerUI.REASON_USER_INTERACTED;
+ }
+
+ if (aReason == Ci.nsIDownloadManagerUI.REASON_NEW_DOWNLOAD) {
+ const kMinimized = Ci.nsIDOMChromeWindow.STATE_MINIMIZED;
+ let browserWin = gBrowserGlue.getMostRecentBrowserWindow();
+
+ if (!browserWin || browserWin.windowState == kMinimized) {
+ this._showDownloadManagerUI(aWindowContext, aUsePrivateUI);
+ }
+ else {
+ // If the indicator is visible, then new download notifications are
+ // already handled by the panel service.
+ browserWin.DownloadsButton.checkIsVisible(function(isVisible) {
+ if (!isVisible) {
+ this._showDownloadManagerUI(aWindowContext, aUsePrivateUI);
+ }
+ }.bind(this));
+ }
+ } else {
+ this._showDownloadManagerUI(aWindowContext, aUsePrivateUI);
+ }
+ },
+
+ get visible()
+ {
+ // If we're still using the toolkit downloads manager, delegate the call
+ // to it. Otherwise, return true for now, until we decide on how we want
+ // to indicate that a new download has started if a browser window is
+ // not available or minimized.
+ return DownloadsCommon.useToolkitUI ? this._toolkitUI.visible : true;
+ },
+
+ getAttention: function DUI_getAttention()
+ {
+ if (DownloadsCommon.useToolkitUI) {
+ this._toolkitUI.getAttention();
+ }
+ },
+
+ /**
+ * Helper function that opens the download manager UI.
+ */
+ _showDownloadManagerUI:
+ function DUI_showDownloadManagerUI(aWindowContext, aUsePrivateUI)
+ {
+ // If we weren't given a window context, try to find a browser window
+ // to use as our parent - and if that doesn't work, error out and give up.
+ let parentWindow = aWindowContext;
+ if (!parentWindow) {
+ parentWindow = RecentWindow.getMostRecentBrowserWindow({ private: !!aUsePrivateUI });
+ if (!parentWindow) {
+ Components.utils.reportError(
+ "Couldn't find a browser window to open the Places Downloads View " +
+ "from.");
+ return;
+ }
+ }
+
+ // If window is private then show it in a tab.
+ if (PrivateBrowsingUtils.isWindowPrivate(parentWindow)) {
+ parentWindow.openUILinkIn("about:downloads", "tab");
+ return;
+ } else {
+ let organizer = Services.wm.getMostRecentWindow("Places:Organizer");
+ if (!organizer) {
+ parentWindow.openDialog("chrome://browser/content/places/places.xul",
+ "", "chrome,toolbar=yes,dialog=no,resizable",
+ "Downloads");
+ } else {
+ organizer.PlacesOrganizer.selectLeftPaneQuery("Downloads");
+ organizer.focus();
+ }
+ }
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// Module
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DownloadsUI]);
diff --git a/components/downloads/DownloadsViewUI.jsm b/components/downloads/DownloadsViewUI.jsm
new file mode 100644
index 0000000..ede593e
--- /dev/null
+++ b/components/downloads/DownloadsViewUI.jsm
@@ -0,0 +1,250 @@
+/* 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 module is imported by code that uses the "download.xml" binding, and
+ * provides prototypes for objects that handle input and display information.
+ */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+ "DownloadsViewUI",
+];
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils",
+ "resource://gre/modules/DownloadUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
+ "resource:///modules/DownloadsCommon.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm");
+
+this.DownloadsViewUI = {};
+
+/**
+ * A download element shell is responsible for handling the commands and the
+ * displayed data for a single element that uses the "download.xml" binding.
+ *
+ * The information to display is obtained through the associated Download object
+ * from the JavaScript API for downloads, and commands are executed using a
+ * combination of Download methods and DownloadsCommon.jsm helper functions.
+ *
+ * Specialized versions of this shell must be defined, and they are required to
+ * implement the "download" property or getter. Currently these objects are the
+ * HistoryDownloadElementShell and the DownloadsViewItem for the panel. The
+ * history view may use a HistoryDownload object in place of a Download object.
+ */
+this.DownloadsViewUI.DownloadElementShell = function () {}
+
+this.DownloadsViewUI.DownloadElementShell.prototype = {
+ /**
+ * The richlistitem for the download, initialized by the derived object.
+ */
+ element: null,
+
+ /**
+ * URI string for the file type icon displayed in the download element.
+ */
+ get image() {
+ if (!this.download.target.path) {
+ // Old history downloads may not have a target path.
+ return "moz-icon://.unknown?size=32";
+ }
+
+ // When a download that was previously in progress finishes successfully, it
+ // means that the target file now exists and we can extract its specific
+ // icon, for example from a Windows executable. To ensure that the icon is
+ // reloaded, however, we must change the URI used by the XUL image element,
+ // for example by adding a query parameter. This only works if we add one of
+ // the parameters explicitly supported by the nsIMozIconURI interface.
+ return "moz-icon://" + this.download.target.path + "?size=32" +
+ (this.download.succeeded ? "&state=normal" : "");
+ },
+
+ /**
+ * The user-facing label for the download. This is normally the leaf name of
+ * the download target file. In case this is a very old history download for
+ * which the target file is unknown, the download source URI is displayed.
+ */
+ get displayName() {
+ if (!this.download.target.path) {
+ return this.download.source.url;
+ }
+ return OS.Path.basename(this.download.target.path);
+ },
+
+ get extendedDisplayName() {
+ let s = DownloadsCommon.strings;
+ let referrer = this.download.source.referrer ||
+ this.download.source.url;
+ let [displayHost, fullHost] = DownloadUtils.getURIHost(referrer);
+ return s.statusSeparator(this.displayName, displayHost);
+ },
+
+ get extendedDisplayNameTip() {
+ let s = DownloadsCommon.strings;
+ let referrer = this.download.source.referrer ||
+ this.download.source.url;
+ let [displayHost, fullHost] = DownloadUtils.getURIHost(referrer);
+ return s.statusSeparator(this.displayName, fullHost);
+ },
+
+ /**
+ * The progress element for the download, or undefined in case the XBL binding
+ * has not been applied yet.
+ */
+ get _progressElement() {
+ if (!this.__progressElement) {
+ // If the element is not available now, we will try again the next time.
+ this.__progressElement =
+ this.element.ownerDocument.getAnonymousElementByAttribute(
+ this.element, "anonid",
+ "progressmeter");
+ }
+ return this.__progressElement;
+ },
+
+ /**
+ * Processes a major state change in the user interface, then proceeds with
+ * the normal progress update. This function is not called for every progress
+ * update in order to improve performance.
+ */
+ _updateState() {
+ this.element.setAttribute("displayName", this.displayName);
+ this.element.setAttribute("extendedDisplayName", this.extendedDisplayName);
+ this.element.setAttribute("extendedDisplayNameTip", this.extendedDisplayNameTip);
+ this.element.setAttribute("image", this.image);
+ this.element.setAttribute("state",
+ DownloadsCommon.stateOfDownload(this.download));
+
+ // Since state changed, reset the time left estimation.
+ this.lastEstimatedSecondsLeft = Infinity;
+
+ this._updateProgress();
+ },
+
+ /**
+ * Updates the elements that change regularly for in-progress downloads,
+ * namely the progress bar and the status line.
+ */
+ _updateProgress() {
+ if (this.download.succeeded) {
+ // We only need to add or remove this attribute for succeeded downloads.
+ if (this.download.target.exists) {
+ this.element.setAttribute("exists", "true");
+ } else {
+ this.element.removeAttribute("exists");
+ }
+ }
+
+ // The progress bar is only displayed for in-progress downloads.
+ if (this.download.hasProgress) {
+ this.element.setAttribute("progressmode", "normal");
+ this.element.setAttribute("progress", this.download.progress);
+ } else {
+ this.element.setAttribute("progressmode", "undetermined");
+ }
+
+ // Dispatch the ValueChange event for accessibility, if possible.
+ if (this._progressElement) {
+ let event = this.element.ownerDocument.createEvent("Events");
+ event.initEvent("ValueChange", true, true);
+ this._progressElement.dispatchEvent(event);
+ }
+
+ let status = this.statusTextAndTip;
+ this.element.setAttribute("status", status.text);
+ this.element.setAttribute("statusTip", status.tip);
+ },
+
+ lastEstimatedSecondsLeft: Infinity,
+
+ /**
+ * Returns the text for the status line and the associated tooltip. These are
+ * returned by a single property because they are computed together. The
+ * result may be overridden by derived objects.
+ */
+ get statusTextAndTip() this.rawStatusTextAndTip,
+
+ /**
+ * Derived objects may call this to get the status text.
+ */
+ get rawStatusTextAndTip() {
+ const nsIDM = Ci.nsIDownloadManager;
+ let s = DownloadsCommon.strings;
+
+ let text = "";
+ let tip = "";
+
+ if (!this.download.stopped) {
+ let totalBytes = this.download.hasProgress ? this.download.totalBytes
+ : -1;
+ // By default, extended status information including the individual
+ // download rate is displayed in the tooltip. The history view overrides
+ // the getter and displays the datails in the main area instead.
+ [text] = DownloadUtils.getDownloadStatusNoRate(
+ this.download.currentBytes,
+ totalBytes,
+ this.download.speed,
+ this.lastEstimatedSecondsLeft);
+ let newEstimatedSecondsLeft;
+ [tip, newEstimatedSecondsLeft] = DownloadUtils.getDownloadStatus(
+ this.download.currentBytes,
+ totalBytes,
+ this.download.speed,
+ this.lastEstimatedSecondsLeft);
+ this.lastEstimatedSecondsLeft = newEstimatedSecondsLeft;
+ } else if (this.download.canceled && this.download.hasPartialData) {
+ let totalBytes = this.download.hasProgress ? this.download.totalBytes
+ : -1;
+ let transfer = DownloadUtils.getTransferTotal(this.download.currentBytes,
+ totalBytes);
+
+ // We use the same XUL label to display both the state and the amount
+ // transferred, for example "Paused - 1.1 MB".
+ text = s.statusSeparatorBeforeNumber(s.statePaused, transfer);
+ } else if (!this.download.succeeded && !this.download.canceled &&
+ !this.download.error) {
+ text = s.stateStarting;
+ } else {
+ let stateLabel;
+
+ if (this.download.succeeded) {
+ // For completed downloads, show the file size (e.g. "1.5 MB").
+ if (this.download.target.size !== undefined) {
+ let [size, unit] =
+ DownloadUtils.convertByteUnits(this.download.target.size);
+ stateLabel = s.sizeWithUnits(size, unit);
+ } else {
+ // History downloads may not have a size defined.
+ stateLabel = s.sizeUnknown;
+ }
+ } else if (this.download.canceled) {
+ stateLabel = s.stateCanceled;
+ } else if (this.download.error.becauseBlockedByParentalControls) {
+ stateLabel = s.stateBlockedParentalControls;
+ } else if (this.download.error.becauseBlockedByReputationCheck) {
+ stateLabel = s.stateDirty;
+ } else {
+ stateLabel = s.stateFailed;
+ }
+
+ let referrer = this.download.source.referrer || this.download.source.url;
+ let [displayHost, fullHost] = DownloadUtils.getURIHost(referrer);
+
+ let date = new Date(this.download.endTime);
+ let [displayDate, fullDate] = DownloadUtils.getReadableDates(date);
+
+ let firstPart = s.statusSeparator(stateLabel, displayHost);
+ text = s.statusSeparator(firstPart, displayDate);
+ tip = s.statusSeparator(fullHost, fullDate);
+ }
+
+ return { text, tip: tip || text };
+ },
+};
diff --git a/components/downloads/content/allDownloadsViewOverlay.css b/components/downloads/content/allDownloadsViewOverlay.css
new file mode 100644
index 0000000..c062ae4
--- /dev/null
+++ b/components/downloads/content/allDownloadsViewOverlay.css
@@ -0,0 +1,56 @@
+/* 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 downloads richlistbox may list thousands of items, and it turns out
+ * XBL binding attachment, and even more so detachment, is a performance hog.
+ * This hack makes sure we don't apply any binding to inactive items (inactive
+ * items are history downloads that haven't been in the visible area).
+ * We can do this because the richlistbox implementation does not interact
+ * much with the richlistitem binding. However, this may turn out to have
+ * some side effects (see bug 828111 for the details).
+ *
+ * We might be able to do away with this workaround once bug 653881 is fixed.
+ */
+richlistitem.download {
+ -moz-binding: none;
+}
+
+richlistitem.download[active] {
+ -moz-binding: url('chrome://browser/content/downloads/download.xml#download-full-ui');
+}
+
+richlistitem.download[active]:-moz-any([state="-1"],/* Starting (initial) */
+ [state="0"], /* Downloading */
+ [state="4"], /* Paused */
+ [state="5"], /* Starting (queued) */
+ [state="7"]) /* Scanning */
+{
+ -moz-binding: url('chrome://browser/content/downloads/download.xml#download-in-progress-full-ui');
+}
+
+.download-state:not( [state="0"] /* Downloading */)
+ .downloadPauseMenuItem,
+.download-state:not( [state="4"] /* Paused */)
+ .downloadResumeMenuItem,
+.download-state:not(:-moz-any([state="2"], /* Failed */
+ [state="4"]) /* Paused */)
+ .downloadCancelMenuItem,
+.download-state[state]:not(:-moz-any([state="1"], /* Finished */
+ [state="2"], /* Failed */
+ [state="3"], /* Canceled */
+ [state="6"], /* Blocked (parental) */
+ [state="8"], /* Blocked (dirty) */
+ [state="9"]) /* Blocked (policy) */)
+ .downloadRemoveFromHistoryMenuItem,
+.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */
+ [state="0"], /* Downloading */
+ [state="1"], /* Finished */
+ [state="4"], /* Paused */
+ [state="5"]) /* Starting (queued) */)
+ .downloadShowMenuItem,
+.download-state[state="7"] /* Scanning */ .downloadCommandsSeparator
+{
+ display: none;
+}
diff --git a/components/downloads/content/allDownloadsViewOverlay.js b/components/downloads/content/allDownloadsViewOverlay.js
new file mode 100644
index 0000000..4830f21
--- /dev/null
+++ b/components/downloads/content/allDownloadsViewOverlay.js
@@ -0,0 +1,1399 @@
+/* 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 { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils",
+ "resource://gre/modules/DownloadUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
+ "resource:///modules/DownloadsCommon.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadsViewUI",
+ "resource:///modules/DownloadsViewUI.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+ "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+ "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
+ "resource:///modules/RecentWindow.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+const nsIDM = Ci.nsIDownloadManager;
+
+const DESTINATION_FILE_URI_ANNO = "downloads/destinationFileURI";
+const DOWNLOAD_META_DATA_ANNO = "downloads/metaData";
+
+const DOWNLOAD_VIEW_SUPPORTED_COMMANDS =
+ ["cmd_delete", "cmd_copy", "cmd_paste", "cmd_selectAll",
+ "downloadsCmd_pauseResume", "downloadsCmd_cancel",
+ "downloadsCmd_open", "downloadsCmd_show", "downloadsCmd_retry",
+ "downloadsCmd_openReferrer", "downloadsCmd_clearDownloads"];
+
+/**
+ * Represents a download from the browser history. It implements part of the
+ * interface of the Download object.
+ *
+ * @param aPlacesNode
+ * The Places node from which the history download should be initialized.
+ */
+function HistoryDownload(aPlacesNode) {
+ // TODO (bug 829201): history downloads should get the referrer from Places.
+ this.source = {
+ url: aPlacesNode.uri,
+ };
+ this.target = {
+ path: undefined,
+ exists: false,
+ size: undefined,
+ };
+
+ // In case this download cannot obtain its end time from the Places metadata,
+ // use the time from the Places node, that is the start time of the download.
+ this.endTime = aPlacesNode.time / 1000;
+}
+
+HistoryDownload.prototype = {
+ /**
+ * Pushes information from Places metadata into this object.
+ */
+ updateFromMetaData(metaData) {
+ try {
+ this.target.path = Cc["@mozilla.org/network/protocol;1?name=file"]
+ .getService(Ci.nsIFileProtocolHandler)
+ .getFileFromURLSpec(metaData.targetFileSpec).path;
+ } catch (ex) {
+ this.target.path = undefined;
+ }
+
+ if ("state" in metaData) {
+ this.succeeded = metaData.state == nsIDM.DOWNLOAD_FINISHED;
+ this.error = metaData.state == nsIDM.DOWNLOAD_FAILED
+ ? { message: "History download failed." }
+ : metaData.state == nsIDM.DOWNLOAD_BLOCKED_PARENTAL
+ ? { becauseBlockedByParentalControls: true }
+ : metaData.state == nsIDM.DOWNLOAD_DIRTY
+ ? { becauseBlockedByReputationCheck: true }
+ : null;
+ this.canceled = metaData.state == nsIDM.DOWNLOAD_CANCELED ||
+ metaData.state == nsIDM.DOWNLOAD_PAUSED;
+ this.endTime = metaData.endTime;
+
+ // Normal history downloads are assumed to exist until the user interface
+ // is refreshed, at which point these values may be updated.
+ this.target.exists = true;
+ this.target.size = metaData.fileSize;
+ } else {
+ // Metadata might be missing from a download that has started but hasn't
+ // stopped already. Normally, this state is overridden with the one from
+ // the corresponding in-progress session download. But if the browser is
+ // terminated abruptly and additionally the file with information about
+ // in-progress downloads is lost, we may end up using this state. We use
+ // the failed state to allow the download to be restarted.
+ //
+ // On the other hand, if the download is missing the target file
+ // annotation as well, it is just a very old one, and we can assume it
+ // succeeded.
+ this.succeeded = !this.target.path;
+ this.error = this.target.path ? { message: "Unstarted download." } : null;
+ this.canceled = false;
+
+ // These properties may be updated if the user interface is refreshed.
+ this.target.exists = false;
+ this.target.size = undefined;
+ }
+ },
+
+ /**
+ * History downloads are never in progress.
+ */
+ stopped: true,
+
+ /**
+ * No percentage indication is shown for history downloads.
+ */
+ hasProgress: false,
+
+ /**
+ * History downloads cannot be restarted using their partial data, even if
+ * they are indicated as paused in their Places metadata. The only way is to
+ * use the information from a persisted session download, that will be shown
+ * instead of the history download. In case this session download is not
+ * available, we show the history download as canceled, not paused.
+ */
+ hasPartialData: false,
+
+ /**
+ * This method mimicks the "start" method of session downloads, and is called
+ * when the user retries a history download.
+ *
+ * At present, we always ask the user for a new target path when retrying a
+ * history download. In the future we may consider reusing the known target
+ * path if the folder still exists and the file name is not already used,
+ * except when the user preferences indicate that the target path should be
+ * requested every time a new download is started.
+ */
+ start() {
+ let browserWin = RecentWindow.getMostRecentBrowserWindow();
+ let initiatingDoc = browserWin ? browserWin.document : document;
+
+ // Do not suggest a file name if we don't know the original target.
+ let leafName = this.target.path ? OS.Path.basename(this.target.path) : null;
+ DownloadURL(this.source.url, leafName, initiatingDoc);
+
+ return Promise.resolve();
+ },
+
+ /**
+ * This method mimicks the "refresh" method of session downloads, except that
+ * it cannot notify that the data changed to the Downloads View.
+ */
+ refresh: Task.async(function* () {
+ try {
+ this.target.size = (yield OS.File.stat(this.target.path)).size;
+ this.target.exists = true;
+ } catch (ex) {
+ // We keep the known file size from the metadata, if any.
+ this.target.exists = false;
+ }
+ }),
+};
+
+/**
+ * A download element shell is responsible for handling the commands and the
+ * displayed data for a single download view element.
+ *
+ * The shell may contain a session download, a history download, or both. When
+ * both a history and a session download are present, the session download gets
+ * priority and its information is displayed.
+ *
+ * On construction, a new richlistitem is created, and can be accessed through
+ * the |element| getter. The shell doesn't insert the item in a richlistbox, the
+ * caller must do it and remove the element when it's no longer needed.
+ *
+ * The caller is also responsible for forwarding status notifications for
+ * session downloads, calling the onStateChanged and onChanged methods.
+ *
+ * @param [optional] aSessionDownload
+ * The session download, required if aHistoryDownload is not set.
+ * @param [optional] aHistoryDownload
+ * The history download, required if aSessionDownload is not set.
+ */
+function HistoryDownloadElementShell(aSessionDownload, aHistoryDownload) {
+ this.element = document.createElement("richlistitem");
+ this.element._shell = this;
+
+ this.element.classList.add("download");
+ this.element.classList.add("download-state");
+
+ if (aSessionDownload) {
+ this.sessionDownload = aSessionDownload;
+ }
+ if (aHistoryDownload) {
+ this.historyDownload = aHistoryDownload;
+ }
+}
+
+HistoryDownloadElementShell.prototype = {
+ __proto__: DownloadsViewUI.DownloadElementShell.prototype,
+
+ /**
+ * Manages the "active" state of the shell. By default all the shells without
+ * a session download are inactive, thus their UI is not updated. They must
+ * be activated when entering the visible area. Session downloads are always
+ * active.
+ */
+ ensureActive: function DES_ensureActive() {
+ if (!this._active) {
+ this._active = true;
+ this.element.setAttribute("active", true);
+ this._updateUI();
+ }
+ },
+ get active() !!this._active,
+
+ /**
+ * Overrides the base getter to return the Download or HistoryDownload object
+ * for displaying information and executing commands in the user interface.
+ */
+ get download() this._sessionDownload || this._historyDownload,
+
+ _sessionDownload: null,
+ get sessionDownload() this._sessionDownload,
+ set sessionDownload(aValue) {
+ if (this._sessionDownload != aValue) {
+ if (!aValue && !this._historyDownload) {
+ throw new Error("Should always have either a Download or a HistoryDownload");
+ }
+
+ this._sessionDownload = aValue;
+
+ this.ensureActive();
+ this._updateUI();
+ }
+ return aValue;
+ },
+
+ _historyDownload: null,
+ get historyDownload() this._historyDownload,
+ set historyDownload(aValue) {
+ if (this._historyDownload != aValue) {
+ if (!aValue && !this._sessionDownload) {
+ throw new Error("Should always have either a Download or a HistoryDownload");
+ }
+
+ this._historyDownload = aValue;
+
+ // We don't need to update the UI if we had a session data item, because
+ // the places information isn't used in this case.
+ if (!this._sessionDownload) {
+ this._updateUI();
+ }
+ }
+ return aValue;
+ },
+
+ _updateUI() {
+ // There is nothing to do if the item has always been invisible.
+ if (!this.active) {
+ return;
+ }
+
+ // Since the state changed, we may need to check the target file again.
+ this._targetFileChecked = false;
+
+ this._updateState();
+ },
+
+ get statusTextAndTip() {
+ let status = this.rawStatusTextAndTip;
+
+ // The base object would show extended progress information in the tooltip,
+ // but we move this to the main view and never display a tooltip.
+ if (!this.download.stopped) {
+ status.text = status.tip;
+ }
+ status.tip = "";
+
+ return status;
+ },
+
+ onStateChanged() {
+ this.element.setAttribute("image", this.image);
+ this.element.setAttribute("state",
+ DownloadsCommon.stateOfDownload(this.download));
+
+ if (this.element.selected) {
+ goUpdateDownloadCommands();
+ } else {
+ goUpdateCommand("downloadsCmd_clearDownloads");
+ }
+ },
+
+ onChanged() {
+ this._updateProgress();
+ },
+
+ /* nsIController */
+ isCommandEnabled: function DES_isCommandEnabled(aCommand) {
+ // The only valid command for inactive elements is cmd_delete.
+ if (!this.active && aCommand != "cmd_delete")
+ return false;
+ switch (aCommand) {
+ case "downloadsCmd_open":
+ // This property is false if the download did not succeed.
+ return this.download.target.exists;
+ case "downloadsCmd_show":
+ // TODO: Bug 827010 - Handle part-file asynchronously.
+ if (this._sessionDownload && this.download.target.partFilePath) {
+ let partFile = new FileUtils.File(this.download.target.partFilePath);
+ if (partFile.exists()) {
+ return true;
+ }
+ }
+
+ // This property is false if the download did not succeed.
+ return this.download.target.exists;
+ case "downloadsCmd_pauseResume":
+ return this.download.hasPartialData && !this.download.error;
+ case "downloadsCmd_retry":
+ return this.download.canceled || this.download.error;
+ case "downloadsCmd_openReferrer":
+ return !!this.download.source.referrer;
+ case "cmd_delete":
+ // We don't want in-progress downloads to be removed accidentally.
+ return this.download.stopped;
+ case "downloadsCmd_cancel":
+ return !!this._sessionDownload;
+ }
+ return false;
+ },
+
+ /* nsIController */
+ doCommand: function DES_doCommand(aCommand) {
+ switch (aCommand) {
+ case "downloadsCmd_open": {
+ let file = new FileUtils.File(this.download.target.path);
+ DownloadsCommon.openDownloadedFile(file, null, window);
+ break;
+ }
+ case "downloadsCmd_show": {
+ let file = new FileUtils.File(this.download.target.path);
+ DownloadsCommon.showDownloadedFile(file);
+ break;
+ }
+ case "downloadsCmd_openReferrer": {
+ openURL(this.download.source.referrer);
+ break;
+ }
+ case "downloadsCmd_cancel": {
+ this.download.cancel().catch(() => {});
+ this.download.removePartialData().catch(Cu.reportError);
+ break;
+ }
+ case "cmd_delete": {
+ if (this._sessionDownload) {
+ DownloadsCommon.removeAndFinalizeDownload(this.download);
+ }
+ if (this._historyDownload) {
+ let uri = NetUtil.newURI(this.download.source.url);
+ PlacesUtils.bhistory.removePage(uri);
+ }
+ break;
+ }
+ case "downloadsCmd_retry": {
+ // Errors when retrying are already reported as download failures.
+ this.download.start().catch(() => {});
+ break;
+ }
+ case "downloadsCmd_pauseResume": {
+ // This command is only enabled for session downloads.
+ if (this.download.stopped) {
+ this.download.start();
+ } else {
+ this.download.cancel();
+ }
+ break;
+ }
+ }
+ },
+
+ // Returns whether or not the download handled by this shell should
+ // show up in the search results for the given term. Both the display
+ // name for the download and the url are searched.
+ matchesSearchTerm: function DES_matchesSearchTerm(aTerm) {
+ if (!aTerm)
+ return true;
+ aTerm = aTerm.toLowerCase();
+ return this.displayName.toLowerCase().contains(aTerm) ||
+ this.download.source.url.toLowerCase().contains(aTerm);
+ },
+
+ // Handles return keypress on the element (the keypress listener is
+ // set in the DownloadsPlacesView object).
+ doDefaultCommand: function DES_doDefaultCommand() {
+ function getDefaultCommandForState(aState) {
+ switch (aState) {
+ case nsIDM.DOWNLOAD_FINISHED:
+ return "downloadsCmd_open";
+ case nsIDM.DOWNLOAD_PAUSED:
+ return "downloadsCmd_pauseResume";
+ case nsIDM.DOWNLOAD_NOTSTARTED:
+ case nsIDM.DOWNLOAD_QUEUED:
+ return "downloadsCmd_cancel";
+ case nsIDM.DOWNLOAD_FAILED:
+ case nsIDM.DOWNLOAD_CANCELED:
+ return "downloadsCmd_retry";
+ case nsIDM.DOWNLOAD_SCANNING:
+ return "downloadsCmd_show";
+ case nsIDM.DOWNLOAD_BLOCKED_PARENTAL:
+ case nsIDM.DOWNLOAD_DIRTY:
+ case nsIDM.DOWNLOAD_BLOCKED_POLICY:
+ return "downloadsCmd_openReferrer";
+ }
+ return "";
+ }
+ let state = DownloadsCommon.stateOfDownload(this.download);
+ let command = getDefaultCommandForState(state);
+ if (command && this.isCommandEnabled(command))
+ this.doCommand(command);
+ },
+
+ /**
+ * This method is called by the outer download view, after the controller
+ * commands have already been updated. In case we did not check for the
+ * existence of the target file already, we can do it now and then update
+ * the commands as needed.
+ */
+ onSelect: function DES_onSelect() {
+ if (!this.active)
+ return;
+
+ // If this is a history download for which no target file information is
+ // available, we cannot retrieve information about the target file.
+ if (!this.download.target.path) {
+ return;
+ }
+
+ // Start checking for existence. This may be done twice if onSelect is
+ // called again before the information is collected.
+ if (!this._targetFileChecked) {
+ this._checkTargetFileOnSelect().catch(Cu.reportError);
+ }
+ },
+
+ _checkTargetFileOnSelect: Task.async(function* () {
+ try {
+ yield this.download.refresh();
+ } finally {
+ // Do not try to check for existence again if this failed once.
+ this._targetFileChecked = true;
+ }
+
+ // Update the commands only if the element is still selected.
+ if (this.element.selected) {
+ goUpdateDownloadCommands();
+ }
+
+ // Ensure the interface has been updated based on the new values. We need to
+ // do this because history downloads can't trigger update notifications.
+ this._updateProgress();
+ }),
+};
+
+/**
+ * A Downloads Places View is a places view designed to show a places query
+ * for history downloads alongside the session downloads.
+ *
+ * As we don't use the places controller, some methods implemented by other
+ * places views are not implemented by this view.
+ *
+ * A richlistitem in this view can represent either a past download or a session
+ * download, or both. Session downloads are shown first in the view, and as long
+ * as they exist they "collapses" their history "counterpart" (So we don't show two
+ * items for every download).
+ */
+function DownloadsPlacesView(aRichListBox, aActive = true) {
+ this._richlistbox = aRichListBox;
+ this._richlistbox._placesView = this;
+ window.controllers.insertControllerAt(0, this);
+
+ // Map download URLs to download element shells regardless of their type
+ this._downloadElementsShellsForURI = new Map();
+
+ // Map download data items to their element shells.
+ this._viewItemsForDownloads = new WeakMap();
+
+ // Points to the last session download element. We keep track of this
+ // in order to keep all session downloads above past downloads.
+ this._lastSessionDownloadElement = null;
+
+ this._searchTerm = "";
+
+ this._active = aActive;
+
+ // Register as a downloads view. The places data will be initialized by
+ // the places setter.
+ this._initiallySelectedElement = null;
+ this._downloadsData = DownloadsCommon.getData(window.opener || window);
+ this._downloadsData.addView(this);
+
+ // Get the Download button out of the attention state since we're about to
+ // view all downloads.
+ DownloadsCommon.getIndicatorData(window).attention = false;
+
+ // Make sure to unregister the view if the window is closed.
+ window.addEventListener("unload", function() {
+ window.controllers.removeController(this);
+ this._downloadsData.removeView(this);
+ this.result = null;
+ }.bind(this), true);
+ // Resizing the window may change items visibility.
+ window.addEventListener("resize", function() {
+ this._ensureVisibleElementsAreActive();
+ }.bind(this), true);
+}
+
+DownloadsPlacesView.prototype = {
+ get associatedElement() this._richlistbox,
+
+ get active() this._active,
+ set active(val) {
+ this._active = val;
+ if (this._active)
+ this._ensureVisibleElementsAreActive();
+ return this._active;
+ },
+
+ /**
+ * This cache exists in order to optimize the load of the Downloads View, when
+ * Places annotations for history downloads must be read. In fact, annotations
+ * are stored in a single table, and reading all of them at once is much more
+ * efficient than an individual query.
+ *
+ * When this property is first requested, it reads the annotations for all the
+ * history downloads and stores them indefinitely.
+ *
+ * The historical annotations are not expected to change for the duration of
+ * the session, except in the case where a session download is running for the
+ * same URI as a history download. To ensure we don't use stale data, URIs
+ * corresponding to session downloads are permanently removed from the cache.
+ * This is a very small mumber compared to history downloads.
+ *
+ * This property returns a Map from each download source URI found in Places
+ * annotations to an object with the format:
+ *
+ * { targetFileSpec, state, endTime, fileSize, ... }
+ *
+ * The targetFileSpec property is the value of "downloads/destinationFileURI",
+ * while the other properties are taken from "downloads/metaData". Any of the
+ * properties may be missing from the object.
+ */
+ get _cachedPlacesMetaData() {
+ if (!this.__cachedPlacesMetaData) {
+ this.__cachedPlacesMetaData = new Map();
+
+ // Read the metadata annotations first, but ignore invalid JSON.
+ for (let result of PlacesUtils.annotations.getAnnotationsWithName(
+ DOWNLOAD_META_DATA_ANNO)) {
+ try {
+ this.__cachedPlacesMetaData.set(result.uri.spec,
+ JSON.parse(result.annotationValue));
+ } catch (ex) {}
+ }
+
+ // Add the target file annotations to the metadata.
+ for (let result of PlacesUtils.annotations.getAnnotationsWithName(
+ DESTINATION_FILE_URI_ANNO)) {
+ let metaData = this.__cachedPlacesMetaData.get(result.uri.spec);
+ if (!metaData) {
+ metaData = {};
+ this.__cachedPlacesMetaData.set(result.uri.spec, metaData);
+ }
+ metaData.targetFileSpec = result.annotationValue;
+ }
+ }
+
+ return this.__cachedPlacesMetaData;
+ },
+ __cachedPlacesMetaData: null,
+
+ /**
+ * Reads current metadata from Places annotations for the specified URI, and
+ * returns an object with the format:
+ *
+ * { targetFileSpec, state, endTime, fileSize, ... }
+ *
+ * The targetFileSpec property is the value of "downloads/destinationFileURI",
+ * while the other properties are taken from "downloads/metaData". Any of the
+ * properties may be missing from the object.
+ */
+ _getPlacesMetaDataFor(spec) {
+ let metaData = {};
+
+ try {
+ let uri = NetUtil.newURI(spec);
+ try {
+ metaData = JSON.parse(PlacesUtils.annotations.getPageAnnotation(
+ uri, DOWNLOAD_META_DATA_ANNO));
+ } catch (ex) {}
+ metaData.targetFileSpec = PlacesUtils.annotations.getPageAnnotation(
+ uri, DESTINATION_FILE_URI_ANNO);
+ } catch (ex) {}
+
+ return metaData;
+ },
+
+ /**
+ * Given a data item for a session download, or a places node for a past
+ * download, updates the view as necessary.
+ * 1. If the given data is a places node, we check whether there are any
+ * elements for the same download url. If there are, then we just reset
+ * their places node. Otherwise we add a new download element.
+ * 2. If the given data is a data item, we first check if there's a history
+ * download in the list that is not associated with a data item. If we
+ * found one, we use it for the data item as well and reposition it
+ * alongside the other session downloads. If we don't, then we go ahead
+ * and create a new element for the download.
+ *
+ * @param [optional] sessionDownload
+ * A Download object, or null for history downloads.
+ * @param [optional] aPlacesNode
+ * The Places node for a history download, or null for session downloads.
+ * @param [optional] aNewest
+ * @see onDownloadAdded. Ignored for history downloads.
+ * @param [optional] aDocumentFragment
+ * To speed up the appending of multiple elements to the end of the
+ * list which are coming in a single batch (i.e. invalidateContainer),
+ * a document fragment may be passed to which the new elements would
+ * be appended. It's the caller's job to ensure the fragment is merged
+ * to the richlistbox at the end.
+ */
+ _addDownloadData(sessionDownload, aPlacesNode, aNewest = false,
+ aDocumentFragment = null) {
+ let downloadURI = aPlacesNode ? aPlacesNode.uri
+ : sessionDownload.source.url;
+ let shellsForURI = this._downloadElementsShellsForURI.get(downloadURI);
+ if (!shellsForURI) {
+ shellsForURI = new Set();
+ this._downloadElementsShellsForURI.set(downloadURI, shellsForURI);
+ }
+
+ // When a session download is attached to a shell, we ensure not to keep
+ // stale metadata around for the corresponding history download. This
+ // prevents stale state from being used if the view is rebuilt.
+ //
+ // Note that we will eagerly load the data in the cache at this point, even
+ // if we have seen no history download. The case where no history download
+ // will appear at all is rare enough in normal usage, so we can apply this
+ // simpler solution rather than keeping a list of cache items to ignore.
+ if (sessionDownload) {
+ this._cachedPlacesMetaData.delete(sessionDownload.source.url);
+ }
+
+ let newOrUpdatedShell = null;
+
+ // Trivial: if there are no shells for this download URI, we always
+ // need to create one.
+ let shouldCreateShell = shellsForURI.size == 0;
+
+ // However, if we do have shells for this download uri, there are
+ // few options:
+ // 1) There's only one shell and it's for a history download (it has
+ // no data item). In this case, we update this shell and move it
+ // if necessary
+ // 2) There are multiple shells, indicating multiple downloads for
+ // the same download uri are running. In this case we create
+ // another shell for the download (so we have one shell for each data
+ // item).
+ //
+ // Note: If a cancelled session download is already in the list, and the
+ // download is retried, onDownloadAdded is called again for the same
+ // data item. Thus, we also check that we make sure we don't have a view item
+ // already.
+ if (!shouldCreateShell &&
+ sessionDownload && !this._viewItemsForDownloads.has(sessionDownload)) {
+ // If there's a past-download-only shell for this download-uri with no
+ // associated data item, use it for the new data item. Otherwise, go ahead
+ // and create another shell.
+ shouldCreateShell = true;
+ for (let shell of shellsForURI) {
+ if (!shell.sessionDownload) {
+ shouldCreateShell = false;
+ shell.sessionDownload = sessionDownload;
+ newOrUpdatedShell = shell;
+ this._viewItemsForDownloads.set(sessionDownload, shell);
+ break;
+ }
+ }
+ }
+
+ if (shouldCreateShell) {
+ // If we are adding a new history download here, it means there is no
+ // associated session download, thus we must read the Places metadata,
+ // because it will not be obscured by the session download.
+ let historyDownload = null;
+ if (aPlacesNode) {
+ let metaData = this._cachedPlacesMetaData.get(aPlacesNode.uri) ||
+ this._getPlacesMetaDataFor(aPlacesNode.uri);
+ historyDownload = new HistoryDownload(aPlacesNode);
+ historyDownload.updateFromMetaData(metaData);
+ }
+ let shell = new HistoryDownloadElementShell(sessionDownload,
+ historyDownload);
+ shell.element._placesNode = aPlacesNode;
+ newOrUpdatedShell = shell;
+ shellsForURI.add(shell);
+ if (sessionDownload) {
+ this._viewItemsForDownloads.set(sessionDownload, shell);
+ }
+ }
+ else if (aPlacesNode) {
+ // We are updating information for a history download for which we have
+ // at least one download element shell already. There are two cases:
+ // 1) There are one or more download element shells for this source URI,
+ // each with an associated session download. We update the Places node
+ // because we may need it later, but we don't need to read the Places
+ // metadata until the last session download is removed.
+ // 2) Occasionally, we may receive a duplicate notification for a history
+ // download with no associated session download. We have exactly one
+ // download element shell in this case, but the metdata cannot have
+ // changed, just the reference to the Places node object is different.
+ // So, we update all the node references and keep the metadata intact.
+ for (let shell of shellsForURI) {
+ if (!shell.historyDownload) {
+ // Create the element to host the metadata when needed.
+ shell.historyDownload = new HistoryDownload(aPlacesNode);
+ }
+ shell.element._placesNode = aPlacesNode;
+ }
+ }
+
+ if (newOrUpdatedShell) {
+ if (aNewest) {
+ this._richlistbox.insertBefore(newOrUpdatedShell.element,
+ this._richlistbox.firstChild);
+ if (!this._lastSessionDownloadElement) {
+ this._lastSessionDownloadElement = newOrUpdatedShell.element;
+ }
+ // Some operations like retrying an history download move an element to
+ // the top of the richlistbox, along with other session downloads.
+ // More generally, if a new download is added, should be made visible.
+ this._richlistbox.ensureElementIsVisible(newOrUpdatedShell.element);
+ } else if (sessionDownload) {
+ let before = this._lastSessionDownloadElement ?
+ this._lastSessionDownloadElement.nextSibling : this._richlistbox.firstChild;
+ this._richlistbox.insertBefore(newOrUpdatedShell.element, before);
+ this._lastSessionDownloadElement = newOrUpdatedShell.element;
+ }
+ else {
+ let appendTo = aDocumentFragment || this._richlistbox;
+ appendTo.appendChild(newOrUpdatedShell.element);
+ }
+
+ if (this.searchTerm) {
+ newOrUpdatedShell.element.hidden =
+ !newOrUpdatedShell.element._shell.matchesSearchTerm(this.searchTerm);
+ }
+ }
+
+ // If aDocumentFragment is defined this is a batch change, so it's up to
+ // the caller to append the fragment and activate the visible shells.
+ if (!aDocumentFragment) {
+ this._ensureVisibleElementsAreActive();
+ goUpdateCommand("downloadsCmd_clearDownloads");
+ }
+ },
+
+ _removeElement: function DPV__removeElement(aElement) {
+ // If the element was selected exclusively, select its next
+ // sibling first, if not, try for previous sibling, if any.
+ if ((aElement.nextSibling || aElement.previousSibling) &&
+ this._richlistbox.selectedItems &&
+ this._richlistbox.selectedItems.length == 1 &&
+ this._richlistbox.selectedItems[0] == aElement) {
+ this._richlistbox.selectItem(aElement.nextSibling ||
+ aElement.previousSibling);
+ }
+
+ if (this._lastSessionDownloadElement == aElement)
+ this._lastSessionDownloadElement = aElement.previousSibling;
+
+ this._richlistbox.removeItemFromSelection(aElement);
+ this._richlistbox.removeChild(aElement);
+ this._ensureVisibleElementsAreActive();
+ goUpdateCommand("downloadsCmd_clearDownloads");
+ },
+
+ _removeHistoryDownloadFromView:
+ function DPV__removeHistoryDownloadFromView(aPlacesNode) {
+ let downloadURI = aPlacesNode.uri;
+ let shellsForURI = this._downloadElementsShellsForURI.get(downloadURI);
+ if (shellsForURI) {
+ for (let shell of shellsForURI) {
+ if (shell.sessionDownload) {
+ shell.historyDownload = null;
+ }
+ else {
+ this._removeElement(shell.element);
+ shellsForURI.delete(shell);
+ if (shellsForURI.size == 0)
+ this._downloadElementsShellsForURI.delete(downloadURI);
+ }
+ }
+ }
+ },
+
+ _removeSessionDownloadFromView(download) {
+ let shells = this._downloadElementsShellsForURI
+ .get(download.source.url);
+ if (shells.size == 0)
+ throw new Error("Should have had at leaat one shell for this uri");
+
+ let shell = this._viewItemsForDownloads.get(download);
+ if (!shells.has(shell))
+ throw new Error("Missing download element shell in shells list for url");
+
+ // If there's more than one item for this download uri, we can let the
+ // view item for this this particular data item go away.
+ // If there's only one item for this download uri, we should only
+ // keep it if it is associated with a history download.
+ if (shells.size > 1 || !shell.historyDownload) {
+ this._removeElement(shell.element);
+ shells.delete(shell);
+ if (shells.size == 0)
+ this._downloadElementsShellsForURI.delete(download.source.url);
+ }
+ else {
+ // We have one download element shell containing both a session download
+ // and a history download, and we are now removing the session download.
+ // Previously, we did not use the Places metadata because it was obscured
+ // by the session download. Since this is no longer the case, we have to
+ // read the latest metadata before removing the session download.
+ let url = shell.historyDownload.source.url;
+ let metaData = this._getPlacesMetaDataFor(url);
+ shell.historyDownload.updateFromMetaData(metaData);
+ shell.sessionDownload = null;
+ // Move it below the session-download items;
+ if (this._lastSessionDownloadElement == shell.element) {
+ this._lastSessionDownloadElement = shell.element.previousSibling;
+ }
+ else {
+ let before = this._lastSessionDownloadElement ?
+ this._lastSessionDownloadElement.nextSibling : this._richlistbox.firstChild;
+ this._richlistbox.insertBefore(shell.element, before);
+ }
+ }
+ },
+
+ _ensureVisibleElementsAreActive:
+ function DPV__ensureVisibleElementsAreActive() {
+ if (!this.active || this._ensureVisibleTimer || !this._richlistbox.firstChild)
+ return;
+
+ this._ensureVisibleTimer = setTimeout(function() {
+ delete this._ensureVisibleTimer;
+ if (!this._richlistbox.firstChild)
+ return;
+
+ let rlbRect = this._richlistbox.getBoundingClientRect();
+ let winUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ let nodes = winUtils.nodesFromRect(rlbRect.left, rlbRect.top,
+ 0, rlbRect.width, rlbRect.height, 0,
+ true, false);
+ // nodesFromRect returns nodes in z-index order, and for the same z-index
+ // sorts them in inverted DOM order, thus starting from the one that would
+ // be on top.
+ let firstVisibleNode, lastVisibleNode;
+ for (let node of nodes) {
+ if (node.localName === "richlistitem" && node._shell) {
+ node._shell.ensureActive();
+ // The first visible node is the last match.
+ firstVisibleNode = node;
+ // While the last visible node is the first match.
+ if (!lastVisibleNode)
+ lastVisibleNode = node;
+ }
+ }
+
+ // Also activate the first invisible nodes in both boundaries (that is,
+ // above and below the visible area) to ensure proper keyboard navigation
+ // in both directions.
+ let nodeBelowVisibleArea = lastVisibleNode && lastVisibleNode.nextSibling;
+ if (nodeBelowVisibleArea && nodeBelowVisibleArea._shell)
+ nodeBelowVisibleArea._shell.ensureActive();
+
+ let nodeABoveVisibleArea =
+ firstVisibleNode && firstVisibleNode.previousSibling;
+ if (nodeABoveVisibleArea && nodeABoveVisibleArea._shell)
+ nodeABoveVisibleArea._shell.ensureActive();
+ }.bind(this), 10);
+ },
+
+ _place: "",
+ get place() this._place,
+ set place(val) {
+ // Don't reload everything if we don't have to.
+ if (this._place == val) {
+ // XXXmano: places.js relies on this behavior (see Bug 822203).
+ this.searchTerm = "";
+ return val;
+ }
+
+ this._place = val;
+
+ let history = PlacesUtils.history;
+ let queries = { }, options = { };
+ history.queryStringToQueries(val, queries, { }, options);
+ if (!queries.value.length)
+ queries.value = [history.getNewQuery()];
+
+ let result = history.executeQueries(queries.value, queries.value.length,
+ options.value);
+ result.addObserver(this, false);
+ return val;
+ },
+
+ _result: null,
+ get result() this._result,
+ set result(val) {
+ if (this._result == val)
+ return val;
+
+ if (this._result) {
+ this._result.removeObserver(this);
+ this._resultNode.containerOpen = false;
+ }
+
+ if (val) {
+ this._result = val;
+ this._resultNode = val.root;
+ this._resultNode.containerOpen = true;
+ this._ensureInitialSelection();
+ }
+ else {
+ delete this._resultNode;
+ delete this._result;
+ }
+
+ return val;
+ },
+
+ get selectedNodes() {
+ return [for (element of this._richlistbox.selectedItems)
+ if (element._placesNode)
+ element._placesNode];
+ },
+
+ get selectedNode() {
+ let selectedNodes = this.selectedNodes;
+ return selectedNodes.length == 1 ? selectedNodes[0] : null;
+ },
+
+ get hasSelection() this.selectedNodes.length > 0,
+
+ containerStateChanged:
+ function DPV_containerStateChanged(aNode, aOldState, aNewState) {
+ this.invalidateContainer(aNode)
+ },
+
+ invalidateContainer:
+ function DPV_invalidateContainer(aContainer) {
+ if (aContainer != this._resultNode)
+ throw new Error("Unexpected container node");
+ if (!aContainer.containerOpen)
+ throw new Error("Root container for the downloads query cannot be closed");
+
+ let suppressOnSelect = this._richlistbox.suppressOnSelect;
+ this._richlistbox.suppressOnSelect = true;
+ try {
+ // Remove the invalidated history downloads from the list and unset the
+ // places node for data downloads.
+ // Loop backwards since _removeHistoryDownloadFromView may removeChild().
+ for (let i = this._richlistbox.childNodes.length - 1; i >= 0; --i) {
+ let element = this._richlistbox.childNodes[i];
+ if (element._placesNode) {
+ this._removeHistoryDownloadFromView(element._placesNode);
+ }
+ }
+ }
+ finally {
+ this._richlistbox.suppressOnSelect = suppressOnSelect;
+ }
+
+ if (aContainer.childCount > 0) {
+ let elementsToAppendFragment = document.createDocumentFragment();
+ for (let i = 0; i < aContainer.childCount; i++) {
+ try {
+ this._addDownloadData(null, aContainer.getChild(i), false,
+ elementsToAppendFragment);
+ }
+ catch(ex) {
+ Cu.reportError(ex);
+ }
+ }
+
+ // _addDownloadData may not add new elements if there were already
+ // data items in place.
+ if (elementsToAppendFragment.firstChild) {
+ this._appendDownloadsFragment(elementsToAppendFragment);
+ this._ensureVisibleElementsAreActive();
+ }
+ }
+
+ goUpdateDownloadCommands();
+ },
+
+ _appendDownloadsFragment: function DPV__appendDownloadsFragment(aDOMFragment) {
+ // Workaround multiple reflows hang by removing the richlistbox
+ // and adding it back when we're done.
+
+ // Hack for bug 836283: reset xbl fields to their old values after the
+ // binding is reattached to avoid breaking the selection state
+ let xblFields = new Map();
+ for (let [key, value] in Iterator(this._richlistbox)) {
+ xblFields.set(key, value);
+ }
+
+ let parentNode = this._richlistbox.parentNode;
+ let nextSibling = this._richlistbox.nextSibling;
+ parentNode.removeChild(this._richlistbox);
+ this._richlistbox.appendChild(aDOMFragment);
+ parentNode.insertBefore(this._richlistbox, nextSibling);
+
+ for (let [key, value] of xblFields) {
+ this._richlistbox[key] = value;
+ }
+ },
+
+ nodeInserted: function DPV_nodeInserted(aParent, aPlacesNode) {
+ this._addDownloadData(null, aPlacesNode);
+ },
+
+ nodeRemoved: function DPV_nodeRemoved(aParent, aPlacesNode, aOldIndex) {
+ this._removeHistoryDownloadFromView(aPlacesNode);
+ },
+
+ nodeAnnotationChanged() {},
+ nodeIconChanged() {},
+ nodeTitleChanged() {},
+ nodeKeywordChanged: function() {},
+ nodeDateAddedChanged: function() {},
+ nodeLastModifiedChanged: function() {},
+ nodeHistoryDetailsChanged: function() {},
+ nodeTagsChanged: function() {},
+ sortingChanged: function() {},
+ nodeMoved: function() {},
+ nodeURIChanged: function() {},
+ batching: function() {},
+
+ get controller() this._richlistbox.controller,
+
+ get searchTerm() this._searchTerm,
+ set searchTerm(aValue) {
+ if (this._searchTerm != aValue) {
+ for (let element of this._richlistbox.childNodes) {
+ element.hidden = !element._shell.matchesSearchTerm(aValue);
+ }
+ this._ensureVisibleElementsAreActive();
+ }
+ return this._searchTerm = aValue;
+ },
+
+ /**
+ * When the view loads, we want to select the first item.
+ * However, because session downloads, for which the data is loaded
+ * asynchronously, always come first in the list, and because the list
+ * may (or may not) already contain history downloads at that point, it
+ * turns out that by the time we can select the first item, the user may
+ * have already started using the view.
+ * To make things even more complicated, in other cases, the places data
+ * may be loaded after the session downloads data. Thus we cannot rely on
+ * the order in which the data comes in.
+ * We work around this by attempting to select the first element twice,
+ * once after the places data is loaded and once when the session downloads
+ * data is done loading. However, if the selection has changed in-between,
+ * we assume the user has already started using the view and give up.
+ */
+ _ensureInitialSelection: function DPV__ensureInitialSelection() {
+ // Either they're both null, or the selection has not changed in between.
+ if (this._richlistbox.selectedItem == this._initiallySelectedElement) {
+ let firstDownloadElement = this._richlistbox.firstChild;
+ if (firstDownloadElement != this._initiallySelectedElement) {
+ // We may be called before _ensureVisibleElementsAreActive,
+ // or before the download binding is attached. Therefore, ensure the
+ // first item is activated, and pass the item to the richlistbox
+ // setters only at a point we know for sure the binding is attached.
+ firstDownloadElement._shell.ensureActive();
+ Services.tm.mainThread.dispatch(function() {
+ this._richlistbox.selectedItem = firstDownloadElement;
+ this._richlistbox.currentItem = firstDownloadElement;
+ this._initiallySelectedElement = firstDownloadElement;
+ }.bind(this), Ci.nsIThread.DISPATCH_NORMAL);
+ }
+ }
+ },
+
+ onDataLoadStarting: function() { },
+ onDataLoadCompleted: function DPV_onDataLoadCompleted() {
+ this._ensureInitialSelection();
+ },
+
+ onDownloadAdded(download, newest) {
+ this._addDownloadData(download, null, newest);
+ },
+
+ onDownloadStateChanged(download) {
+ this._viewItemsForDownloads.get(download).onStateChanged();
+ },
+
+ onDownloadChanged(download) {
+ this._viewItemsForDownloads.get(download).onChanged();
+ },
+
+ onDownloadRemoved(download) {
+ this._removeSessionDownloadFromView(download);
+ },
+
+ supportsCommand: function DPV_supportsCommand(aCommand) {
+ if (DOWNLOAD_VIEW_SUPPORTED_COMMANDS.indexOf(aCommand) != -1) {
+ // The clear-downloads command may be performed by the toolbar-button,
+ // which can be focused on OS X. Thus enable this command even if the
+ // richlistbox is not focused.
+ // For other commands, be prudent and disable them unless the richlistview
+ // is focused. It's important to make the decision here rather than in
+ // isCommandEnabled. Otherwise our controller may "steal" commands from
+ // other controls in the window (see goUpdateCommand &
+ // getControllerForCommand).
+ if (document.activeElement == this._richlistbox ||
+ aCommand == "downloadsCmd_clearDownloads") {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ isCommandEnabled: function DPV_isCommandEnabled(aCommand) {
+ switch (aCommand) {
+ case "cmd_copy":
+ return this._richlistbox.selectedItems.length > 0;
+ case "cmd_selectAll":
+ return true;
+ case "cmd_paste":
+ return this._canDownloadClipboardURL();
+ case "downloadsCmd_clearDownloads":
+ return this._canClearDownloads();
+ default:
+ return Array.every(this._richlistbox.selectedItems, function(element) {
+ return element._shell.isCommandEnabled(aCommand);
+ });
+ }
+ },
+
+ _canClearDownloads: function DPV__canClearDownloads() {
+ // Downloads can be cleared if there's at least one removable download in
+ // the list (either a history download or a completed session download).
+ // Because history downloads are always removable and are listed after the
+ // session downloads, check from bottom to top.
+ for (let elt = this._richlistbox.lastChild; elt; elt = elt.previousSibling) {
+ // Stopped, paused, and failed downloads with partial data are removed.
+ let download = elt._shell.download;
+ if (download.stopped && !(download.canceled && download.hasPartialData)) {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ _copySelectedDownloadsToClipboard:
+ function DPV__copySelectedDownloadsToClipboard() {
+ let urls = [for (element of this._richlistbox.selectedItems)
+ element._shell.download.source.url];
+
+ Cc["@mozilla.org/widget/clipboardhelper;1"]
+ .getService(Ci.nsIClipboardHelper)
+ .copyString(urls.join("\n"), document);
+ },
+
+ _getURLFromClipboardData: function DPV__getURLFromClipboardData() {
+ let trans = Cc["@mozilla.org/widget/transferable;1"].
+ createInstance(Ci.nsITransferable);
+ trans.init(null);
+
+ let flavors = ["text/x-moz-url", "text/unicode"];
+ flavors.forEach(trans.addDataFlavor);
+
+ Services.clipboard.getData(trans, Services.clipboard.kGlobalClipboard);
+
+ // Getting the data or creating the nsIURI might fail.
+ try {
+ let data = {};
+ trans.getAnyTransferData({}, data, {});
+ let [url, name] = data.value.QueryInterface(Ci.nsISupportsString)
+ .data.split("\n");
+ if (url)
+ return [NetUtil.newURI(url, null, null).spec, name];
+ }
+ catch(ex) { }
+
+ return ["", ""];
+ },
+
+ _canDownloadClipboardURL: function DPV__canDownloadClipboardURL() {
+ let [url, name] = this._getURLFromClipboardData();
+ return url != "";
+ },
+
+ _downloadURLFromClipboard: function DPV__downloadURLFromClipboard() {
+ let [url, name] = this._getURLFromClipboardData();
+ let browserWin = RecentWindow.getMostRecentBrowserWindow();
+ let initiatingDoc = browserWin ? browserWin.document : document;
+ DownloadURL(url, name, initiatingDoc);
+ },
+
+ doCommand: function DPV_doCommand(aCommand) {
+ // Commands may be invoked with keyboard shortcuts even if disabled.
+ if (!this.isCommandEnabled(aCommand)) {
+ return;
+ }
+ switch (aCommand) {
+ case "cmd_copy":
+ this._copySelectedDownloadsToClipboard();
+ break;
+ case "cmd_selectAll":
+ this._richlistbox.selectAll();
+ break;
+ case "cmd_paste":
+ this._downloadURLFromClipboard();
+ break;
+ case "downloadsCmd_clearDownloads":
+ this._downloadsData.removeFinished();
+ if (this.result) {
+ Cc["@mozilla.org/browser/download-history;1"]
+ .getService(Ci.nsIDownloadHistory)
+ .removeAllDownloads();
+ }
+ // There may be no selection or focus change as a result
+ // of these change, and we want the command updated immediately.
+ goUpdateCommand("downloadsCmd_clearDownloads");
+ break;
+ default: {
+ // Cloning the nodelist into an array to get a frozen list of selected items.
+ // Otherwise, the selectedItems nodelist is live and doCommand may alter the
+ // selection while we are trying to do one particular action, like removing
+ // items from history.
+ let selectedElements = [...this._richlistbox.selectedItems];
+ for (let element of selectedElements) {
+ element._shell.doCommand(aCommand);
+ }
+ }
+ }
+ },
+
+ onEvent: function() { },
+
+ onContextMenu: function DPV_onContextMenu(aEvent)
+ {
+ let element = this._richlistbox.selectedItem;
+ if (!element || !element._shell)
+ return false;
+
+ // Set the state attribute so that only the appropriate items are displayed.
+ let contextMenu = document.getElementById("downloadsContextMenu");
+ let download = element._shell.download;
+ contextMenu.setAttribute("state",
+ DownloadsCommon.stateOfDownload(download));
+
+ if (!download.stopped) {
+ // The hasPartialData property of a download may change at any time after
+ // it has started, so ensure we update the related command now.
+ goUpdateCommand("downloadsCmd_pauseResume");
+ }
+ return true;
+ },
+
+ onKeyPress: function DPV_onKeyPress(aEvent) {
+ let selectedElements = this._richlistbox.selectedItems;
+ if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN) {
+ // In the content tree, opening bookmarks by pressing return is only
+ // supported when a single item is selected. To be consistent, do the
+ // same here.
+ if (selectedElements.length == 1) {
+ let element = selectedElements[0];
+ if (element._shell)
+ element._shell.doDefaultCommand();
+ }
+ }
+ else if (aEvent.charCode == " ".charCodeAt(0)) {
+ // Pause/Resume every selected download
+ for (let element of selectedElements) {
+ if (element._shell.isCommandEnabled("downloadsCmd_pauseResume"))
+ element._shell.doCommand("downloadsCmd_pauseResume");
+ }
+ }
+ },
+
+ onDoubleClick: function DPV_onDoubleClick(aEvent) {
+ if (aEvent.button != 0)
+ return;
+
+ let selectedElements = this._richlistbox.selectedItems;
+ if (selectedElements.length != 1)
+ return;
+
+ let element = selectedElements[0];
+ if (element._shell)
+ element._shell.doDefaultCommand();
+ },
+
+ onScroll: function DPV_onScroll() {
+ this._ensureVisibleElementsAreActive();
+ },
+
+ onSelect: function DPV_onSelect() {
+ goUpdateDownloadCommands();
+
+ let selectedElements = this._richlistbox.selectedItems;
+ for (let elt of selectedElements) {
+ if (elt._shell)
+ elt._shell.onSelect();
+ }
+ },
+
+ onDragStart: function DPV_onDragStart(aEvent) {
+ // TODO Bug 831358: Support d&d for multiple selection.
+ // For now, we just drag the first element.
+ let selectedItem = this._richlistbox.selectedItem;
+ if (!selectedItem)
+ return;
+
+ let targetPath = selectedItem._shell.download.target.path;
+ if (!targetPath) {
+ return;
+ }
+
+ // We must check for existence synchronously because this is a DOM event.
+ let file = new FileUtils.File(targetPath);
+ if (!file.exists())
+ return;
+
+ let dt = aEvent.dataTransfer;
+ dt.mozSetDataAt("application/x-moz-file", file, 0);
+ let url = Services.io.newFileURI(file).spec;
+ dt.setData("text/uri-list", url);
+ dt.setData("text/plain", url);
+ dt.effectAllowed = "copyMove";
+ dt.addElement(selectedItem);
+ },
+
+ onDragOver: function DPV_onDragOver(aEvent) {
+ let types = aEvent.dataTransfer.types;
+ if (types.contains("text/uri-list") ||
+ types.contains("text/x-moz-url") ||
+ types.contains("text/plain")) {
+ aEvent.preventDefault();
+ }
+ },
+
+ onDrop: function DPV_onDrop(aEvent) {
+ let dt = aEvent.dataTransfer;
+ // If dragged item is from our source, do not try to
+ // redownload already downloaded file.
+ if (dt.mozGetDataAt("application/x-moz-file", 0))
+ return;
+
+ let links = Services.droppedLinkHandler.dropLinks(aEvent);
+ if (!links.length)
+ return;
+ let browserWin = RecentWindow.getMostRecentBrowserWindow();
+ let initiatingDoc = browserWin ? browserWin.document : document;
+ for (let link of links) {
+ if (link.url.startsWith("about:"))
+ continue;
+ DownloadURL(link.url, link.name, initiatingDoc);
+ }
+ }
+};
+
+for (let methodName of ["load", "applyFilter", "selectNode", "selectItems"]) {
+ DownloadsPlacesView.prototype[methodName] = function() {
+ throw new Error("|" + methodName + "| is not implemented by the downloads view.");
+ }
+}
+
+function goUpdateDownloadCommands() {
+ for (let command of DOWNLOAD_VIEW_SUPPORTED_COMMANDS) {
+ goUpdateCommand(command);
+ }
+}
diff --git a/components/downloads/content/allDownloadsViewOverlay.xul b/components/downloads/content/allDownloadsViewOverlay.xul
new file mode 100644
index 0000000..4e9bfd1
--- /dev/null
+++ b/components/downloads/content/allDownloadsViewOverlay.xul
@@ -0,0 +1,119 @@
+<?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/downloads/allDownloadsViewOverlay.css"?>
+<?xml-stylesheet href="chrome://browser/skin/downloads/allDownloadsViewOverlay.css"?>
+
+<!DOCTYPE overlay [
+<!ENTITY % downloadsDTD SYSTEM "chrome://browser/locale/downloads/downloads.dtd">
+%downloadsDTD;
+]>
+
+<!-- This overlay provides a downloads view that lists both session downloads,
+ using the DownloadsView API, and history downloads, using places queries.
+ The view also implements a command controller and a context menu for
+ managing the downloads list. In order to use this view:
+ 1. Apply this overlay to your window.
+ 2. Insert in all the overlay entry-points, namely:
+ <richlistbox id="downloadsRichListBox"/>
+ <commandset id="downloadCommands"/>
+ <menupopup id="downloadsContextMenu"/>
+ 3. Make sure your window has the editMenuOverlay overlay applied,
+ because the view implements cmd_copy and cmd_delete.
+ 4. Make sure your window has the globalOverlay.js script loaded.
+ 5. To initialize the view
+ let view = new DownloadsPlacesView(document.getElementById("downloadsRichListBox"));
+ // This is what the Places Library uses. It could be tweaked a bit as long as the
+ // transition-type is set correctly
+ view.place = "place:transition=7&sort=4";
+-->
+<overlay id="downloadsViewOverlay"
+ 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/downloads/allDownloadsViewOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://global/content/contentAreaUtils.js"/>
+
+ <richlistbox flex="1"
+ seltype="multiple"
+ id="downloadsRichListBox" context="downloadsContextMenu"
+ onscroll="return this._placesView.onScroll();"
+ onkeypress="return this._placesView.onKeyPress(event);"
+ ondblclick="return this._placesView.onDoubleClick(event);"
+ oncontextmenu="return this._placesView.onContextMenu(event);"
+ ondragstart="this._placesView.onDragStart(event);"
+ ondragover="this._placesView.onDragOver(event);"
+ ondrop="this._placesView.onDrop(event);"
+ onfocus="goUpdateDownloadCommands();"
+ onselect="this._placesView.onSelect();"
+ onblur="goUpdateDownloadCommands();"/>
+
+ <commandset id="downloadCommands"
+ commandupdater="true"
+ events="focus,select,contextmenu"
+ oncommandupdate="goUpdateDownloadCommands();">
+ <command id="downloadsCmd_pauseResume"
+ oncommand="goDoCommand('downloadsCmd_pauseResume')"/>
+ <command id="downloadsCmd_cancel"
+ oncommand="goDoCommand('downloadsCmd_cancel')"/>
+ <command id="downloadsCmd_open"
+ oncommand="goDoCommand('downloadsCmd_open')"/>
+ <command id="downloadsCmd_show"
+ oncommand="goDoCommand('downloadsCmd_show')"/>
+ <command id="downloadsCmd_retry"
+ oncommand="goDoCommand('downloadsCmd_retry')"/>
+ <command id="downloadsCmd_openReferrer"
+ oncommand="goDoCommand('downloadsCmd_openReferrer')"/>
+ <command id="downloadsCmd_clearDownloads"
+ oncommand="goDoCommand('downloadsCmd_clearDownloads')"/>
+ </commandset>
+
+ <menupopup id="downloadsContextMenu" class="download-state">
+ <menuitem command="downloadsCmd_pauseResume"
+ class="downloadPauseMenuItem"
+ label="&cmd.pause.label;"
+ accesskey="&cmd.pause.accesskey;"/>
+ <menuitem command="downloadsCmd_pauseResume"
+ class="downloadResumeMenuItem"
+ label="&cmd.resume.label;"
+ accesskey="&cmd.resume.accesskey;"/>
+ <menuitem command="downloadsCmd_cancel"
+ class="downloadCancelMenuItem"
+ label="&cmd.cancel.label;"
+ accesskey="&cmd.cancel.accesskey;"/>
+ <menuitem command="cmd_delete"
+ class="downloadRemoveFromHistoryMenuItem"
+ label="&cmd.removeFromHistory.label;"
+ accesskey="&cmd.removeFromHistory.accesskey;"/>
+ <menuitem command="downloadsCmd_show"
+ class="downloadShowMenuItem"
+#ifdef XP_MACOSX
+ label="&cmd.showMac.label;"
+ accesskey="&cmd.showMac.accesskey;"
+#else
+ label="&cmd.show.label;"
+ accesskey="&cmd.show.accesskey;"
+#endif
+ />
+
+ <menuseparator class="downloadCommandsSeparator"/>
+
+ <menuitem command="downloadsCmd_openReferrer"
+ label="&cmd.goToDownloadPage.label;"
+ accesskey="&cmd.goToDownloadPage.accesskey;"/>
+ <menuitem command="cmd_copy"
+ label="&cmd.copyDownloadLink.label;"
+ accesskey="&cmd.copyDownloadLink.accesskey;"/>
+
+ <menuseparator/>
+
+ <menuitem command="downloadsCmd_clearDownloads"
+ label="&cmd.clearDownloads.label;"
+ accesskey="&cmd.clearDownloads.accesskey;"/>
+ </menupopup>
+</overlay>
diff --git a/components/downloads/content/contentAreaDownloadsView.css b/components/downloads/content/contentAreaDownloadsView.css
new file mode 100644
index 0000000..abaae1f
--- /dev/null
+++ b/components/downloads/content/contentAreaDownloadsView.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/. */
+
+#downloadsListEmptyDescription {
+ display: none;
+}
+
+#downloadsRichListBox:empty + #downloadsListEmptyDescription {
+ display: -moz-box;
+}
diff --git a/components/downloads/content/contentAreaDownloadsView.js b/components/downloads/content/contentAreaDownloadsView.js
new file mode 100644
index 0000000..fbb18ab
--- /dev/null
+++ b/components/downloads/content/contentAreaDownloadsView.js
@@ -0,0 +1,15 @@
+/* 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");
+
+var ContentAreaDownloadsView = {
+ init: function CADV_init() {
+ let view = new DownloadsPlacesView(document.getElementById("downloadsRichListBox"));
+ // Do not display the Places downloads in private windows
+ if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
+ view.place = "place:transition=7&sort=4";
+ }
+ }
+};
diff --git a/components/downloads/content/contentAreaDownloadsView.xul b/components/downloads/content/contentAreaDownloadsView.xul
new file mode 100644
index 0000000..a91de1e
--- /dev/null
+++ b/components/downloads/content/contentAreaDownloadsView.xul
@@ -0,0 +1,45 @@
+<?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/"?>
+<?xml-stylesheet href="chrome://browser/content/downloads/contentAreaDownloadsView.css"?>
+<?xml-stylesheet href="chrome://browser/skin/downloads/contentAreaDownloadsView.css"?>
+
+<?xul-overlay href="chrome://browser/content/downloads/allDownloadsViewOverlay.xul"?>
+
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+
+<!DOCTYPE window [
+<!ENTITY % downloadsDTD SYSTEM "chrome://browser/locale/downloads/downloads.dtd">
+%downloadsDTD;
+]>
+
+<window id="contentAreaDownloadsView"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&downloads.title;"
+ onload="ContentAreaDownloadsView.init();">
+
+ <script type="application/javascript"
+ src="chrome://global/content/globalOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/downloads/contentAreaDownloadsView.js"/>
+
+ <commandset id="editMenuCommands"/>
+
+ <keyset id="editMenuKeys">
+#ifdef XP_MACOSX
+ <key id="key_delete2" keycode="VK_BACK" command="cmd_delete"/>
+#endif
+ </keyset>
+
+ <stack flex="1">
+ <richlistbox id="downloadsRichListBox"/>
+ <description id="downloadsListEmptyDescription"
+ value="&downloadsListEmpty.label;"/>
+ </stack>
+ <commandset id="downloadCommands"/>
+ <menupopup id="downloadsContextMenu"/>
+</window>
diff --git a/components/downloads/content/download.css b/components/downloads/content/download.css
new file mode 100644
index 0000000..7412fa7
--- /dev/null
+++ b/components/downloads/content/download.css
@@ -0,0 +1,45 @@
+/* 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.download button {
+ /* These buttons should never get focus, as that would "disable"
+ the downloads view controller (it's only used when the richlistbox
+ is focused). */
+ -moz-user-focus: none;
+}
+
+/*** Visibility of controls inside download items ***/
+
+.download-state:-moz-any( [state="6"], /* Blocked (parental) */
+ [state="8"], /* Blocked (dirty) */
+ [state="9"]) /* Blocked (policy) */
+ > .downloadTypeIcon:not(.blockedIcon),
+
+.download-state:not(:-moz-any([state="6"], /* Blocked (parental) */
+ [state="8"], /* Blocked (dirty) */
+ [state="9"]) /* Blocked (policy) */)
+ > .downloadTypeIcon.blockedIcon,
+
+.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */
+ [state="5"], /* Starting (queued) */
+ [state="0"], /* Downloading */
+ [state="4"], /* Paused */
+ [state="7"]) /* Scanning */)
+ > vbox > .downloadProgress,
+
+.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */
+ [state="5"], /* Starting (queued) */
+ [state="0"], /* Downloading */
+ [state="4"]) /* Paused */)
+ > .downloadCancel,
+
+.download-state[state]:not(:-moz-any([state="2"], /* Failed */
+ [state="3"]) /* Canceled */)
+ > .downloadRetry,
+
+.download-state:not( [state="1"] /* Finished */)
+ > .downloadShow
+{
+ display: none;
+}
diff --git a/components/downloads/content/download.xml b/components/downloads/content/download.xml
new file mode 100644
index 0000000..542901b
--- /dev/null
+++ b/components/downloads/content/download.xml
@@ -0,0 +1,188 @@
+<?xml version="1.0"?>
+<!-- -*- Mode: HTML; 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/. -->
+
+<!DOCTYPE bindings SYSTEM "chrome://browser/locale/downloads/downloads.dtd">
+
+<bindings id="downloadBindings"
+ 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="download"
+ extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+ <content orient="horizontal"
+ align="center"
+ onclick="DownloadsView.onDownloadClick(event);">
+ <xul:image class="downloadTypeIcon"
+ validate="always"
+ xbl:inherits="src=image"/>
+ <xul:image class="downloadTypeIcon blockedIcon"/>
+ <xul:vbox pack="center"
+ flex="1"
+ class="downloadContainer"
+ style="width: &downloadDetails.width;">
+ <!-- We're letting localizers put a min-width in here primarily
+ because of the downloads summary at the bottom of the list of
+ download items. An element in the summary has the same min-width
+ on a description, and we don't want the panel to change size if the
+ summary isn't being displayed, so we ensure that items share the
+ same minimum width.
+ -->
+ <xul:description class="downloadDisplayName"
+ crop="center"
+ style="min-width: &downloadsSummary.minWidth2;"
+ xbl:inherits="value=displayName,tooltiptext=displayName"/>
+ <xul:progressmeter anonid="progressmeter"
+ class="downloadProgress"
+ min="0"
+ max="100"
+ xbl:inherits="mode=progressmode,value=progress"/>
+ <xul:description class="downloadDetails"
+ crop="end"
+ xbl:inherits="value=status,tooltiptext=statusTip"/>
+ </xul:vbox>
+ <xul:stack>
+ <xul:button class="downloadButton downloadCancel"
+ tooltiptext="&cmd.cancel.label;"
+ oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_cancel');"/>
+ <xul:button class="downloadButton downloadRetry"
+ tooltiptext="&cmd.retry.label;"
+ oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_retry');"/>
+ <xul:button class="downloadButton downloadShow"
+#ifdef XP_MACOSX
+ tooltiptext="&cmd.showMac.label;"
+#else
+ tooltiptext="&cmd.show.label;"
+#endif
+ oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_show');"/>
+ </xul:stack>
+ </content>
+ </binding>
+
+ <binding id="download-in-progress"
+ extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+ <content orient="horizontal"
+ align="center"
+ onclick="DownloadsView.onDownloadClick(event);">
+ <xul:image class="downloadTypeIcon"
+ validate="always"
+ xbl:inherits="src=image"/>
+ <xul:image class="downloadTypeIcon blockedIcon"/>
+ <xul:vbox pack="center"
+ flex="1"
+ class="downloadContainer"
+ style="width: &downloadDetails.width;">
+ <xul:description class="downloadDisplayName"
+ crop="center"
+ style="min-width: &downloadsSummary.minWidth2;"
+ xbl:inherits="value=displayName,tooltiptext=extendedDisplayNameTip"/>
+ <xul:progressmeter anonid="progressmeter"
+ class="downloadProgress"
+ min="0"
+ max="100"
+ xbl:inherits="mode=progressmode,value=progress"/>
+ <xul:description class="downloadDetails"
+ crop="end"
+ xbl:inherits="value=status,tooltiptext=statusTip"/>
+ </xul:vbox>
+ <xul:stack>
+ <xul:button class="downloadButton downloadCancel"
+ tooltiptext="&cmd.cancel.label;"
+ oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_cancel');"/>
+ <xul:button class="downloadButton downloadRetry"
+ tooltiptext="&cmd.retry.label;"
+ oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_retry');"/>
+ <xul:button class="downloadButton downloadShow"
+ tooltiptext="&cmd.show.label;"
+ oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_show');"/>
+ </xul:stack>
+ </content>
+ </binding>
+
+ <binding id="download-full-ui"
+ extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+ <resources>
+ <stylesheet src="chrome://browser/content/downloads/download.css"/>
+ </resources>
+
+ <content orient="horizontal" align="center">
+ <xul:image class="downloadTypeIcon"
+ validate="always"
+ xbl:inherits="src=image"/>
+ <xul:image class="downloadTypeIcon blockedIcon"/>
+ <xul:vbox pack="center" flex="1">
+ <xul:description class="downloadDisplayName"
+ crop="center"
+ xbl:inherits="value=displayName,tooltiptext=displayName"/>
+ <xul:progressmeter anonid="progressmeter"
+ class="downloadProgress"
+ min="0"
+ max="100"
+ xbl:inherits="mode=progressmode,value=progress"/>
+ <xul:description class="downloadDetails"
+ style="width: &downloadDetails.width;"
+ crop="end"
+ xbl:inherits="value=status,tooltiptext=statusTip"/>
+ </xul:vbox>
+
+ <xul:button class="downloadButton downloadCancel"
+ tooltiptext="&cmd.cancel.label;"
+ oncommand="goDoCommand('downloadsCmd_cancel')"/>
+ <xul:button class="downloadButton downloadRetry"
+ tooltiptext="&cmd.retry.label;"
+ oncommand="goDoCommand('downloadsCmd_retry')"/>
+ <xul:button class="downloadButton downloadShow"
+#ifdef XP_MACOSX
+ tooltiptext="&cmd.showMac.label;"
+#else
+ tooltiptext="&cmd.show.label;"
+#endif
+ oncommand="goDoCommand('downloadsCmd_show')"/>
+
+ </content>
+ </binding>
+
+ <binding id="download-in-progress-full-ui"
+ extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+ <resources>
+ <stylesheet src="chrome://browser/content/downloads/download.css"/>
+ </resources>
+
+ <content orient="horizontal" align="center">
+ <xul:image class="downloadTypeIcon"
+ validate="always"
+ xbl:inherits="src=image"/>
+ <xul:image class="downloadTypeIcon blockedIcon"/>
+ <xul:vbox pack="center" flex="1">
+ <xul:description class="downloadDisplayName"
+ crop="end"
+ xbl:inherits="value=extendedDisplayName,tooltiptext=extendedDisplayNameTip"/>
+ <xul:progressmeter anonid="progressmeter"
+ class="downloadProgress"
+ min="0"
+ max="100"
+ xbl:inherits="mode=progressmode,value=progress"/>
+ <xul:description class="downloadDetails"
+ style="width: &downloadDetails.width;"
+ crop="end"
+ xbl:inherits="value=status,tooltiptext=statusTip"/>
+ </xul:vbox>
+
+ <xul:button class="downloadButton downloadCancel"
+ tooltiptext="&cmd.cancel.label;"
+ oncommand="goDoCommand('downloadsCmd_cancel')"/>
+ <xul:button class="downloadButton downloadRetry"
+ tooltiptext="&cmd.retry.label;"
+ oncommand="goDoCommand('downloadsCmd_retry')"/>
+ <xul:button class="downloadButton downloadShow"
+ tooltiptext="&cmd.show.label;"
+ oncommand="goDoCommand('downloadsCmd_show')"/>
+
+ </content>
+ </binding>
+</bindings>
diff --git a/components/downloads/content/downloads.css b/components/downloads/content/downloads.css
new file mode 100644
index 0000000..825db68
--- /dev/null
+++ b/components/downloads/content/downloads.css
@@ -0,0 +1,132 @@
+/* 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/. */
+
+/*** Download items ***/
+
+richlistitem[type="download"] {
+ -moz-binding: url('chrome://browser/content/downloads/download.xml#download');
+}
+
+richlistitem[type="download"]:-moz-any([state="-1"],/* Starting (initial) */
+ [state="0"], /* Downloading */
+ [state="4"], /* Paused */
+ [state="5"], /* Starting (queued) */
+ [state="7"]) /* Scanning */
+{
+ -moz-binding: url('chrome://browser/content/downloads/download.xml#download-in-progress');
+}
+
+richlistitem[type="download"]:not([selected]) button {
+ /* Only focus buttons in the selected item. */
+ -moz-user-focus: none;
+}
+
+/*** Visibility of controls inside download items ***/
+
+.download-state:-moz-any( [state="6"], /* Blocked (parental) */
+ [state="8"], /* Blocked (dirty) */
+ [state="9"]) /* Blocked (policy) */
+ .downloadTypeIcon:not(.blockedIcon),
+
+.download-state:not(:-moz-any([state="6"], /* Blocked (parental) */
+ [state="8"], /* Blocked (dirty) */
+ [state="9"]) /* Blocked (policy) */)
+ .downloadTypeIcon.blockedIcon,
+
+.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */
+ [state="0"], /* Downloading */
+ [state="4"], /* Paused */
+ [state="5"], /* Starting (queued) */
+ [state="7"]) /* Scanning */)
+ .downloadProgress,
+
+.download-state:not( [state="0"] /* Downloading */)
+ .downloadPauseMenuItem,
+
+.download-state:not( [state="4"] /* Paused */)
+ .downloadResumeMenuItem,
+
+.download-state:not(:-moz-any([state="2"], /* Failed */
+ [state="4"]) /* Paused */)
+ .downloadCancelMenuItem,
+
+.download-state:not(:-moz-any([state="1"], /* Finished */
+ [state="2"], /* Failed */
+ [state="3"], /* Canceled */
+ [state="6"], /* Blocked (parental) */
+ [state="8"], /* Blocked (dirty) */
+ [state="9"]) /* Blocked (policy) */)
+ .downloadRemoveFromHistoryMenuItem,
+
+.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */
+ [state="0"], /* Downloading */
+ [state="1"], /* Finished */
+ [state="4"], /* Paused */
+ [state="5"]) /* Starting (queued) */)
+ .downloadShowMenuItem,
+
+.download-state[state="7"] /* Scanning */ .downloadCommandsSeparator
+
+{
+ display: none;
+}
+
+/*** Visibility of download buttons and indicator controls. ***/
+
+.download-state:not(:-moz-any([state="-1"],/* Starting (initial) */
+ [state="0"], /* Downloading */
+ [state="4"], /* Paused */
+ [state="5"]) /* Starting (queued) */)
+ .downloadCancel,
+
+.download-state:not(:-moz-any([state="2"], /* Failed */
+ [state="3"]) /* Canceled */)
+ .downloadRetry,
+
+.download-state:not( [state="1"] /* Finished */)
+ .downloadShow,
+
+#downloads-indicator:-moz-any([progress],
+ [counter],
+ [paused]) #downloads-indicator-icon,
+
+#downloads-indicator:not(:-moz-any([progress],
+ [counter],
+ [paused]))
+ #downloads-indicator-progress-area
+
+{
+ visibility: hidden;
+}
+
+.download-state[state="1"]:not([exists]) .downloadShow
+{
+ display: none;
+}
+
+#downloadsSummary:not([inprogress]) > vbox > #downloadsSummaryProgress,
+#downloadsSummary:not([inprogress]) > vbox > #downloadsSummaryDetails,
+#downloadsFooter[showingsummary] > #downloadsHistory,
+#downloadsFooter:not([showingsummary]) > #downloadsSummary
+{
+ display: none;
+}
+
+/* Hacks for toolbar full and text modes, until bug 573329 removes them */
+
+toolbar[mode="text"] > #downloads-indicator {
+ display: -moz-box;
+ -moz-box-orient: vertical;
+ -moz-box-pack: center;
+}
+
+toolbar[mode="text"] > #downloads-indicator > .toolbarbutton-text {
+ -moz-box-ordinal-group: 1;
+}
+
+toolbar[mode="text"] > #downloads-indicator > .toolbarbutton-icon {
+ display: -moz-box;
+ -moz-box-ordinal-group: 2;
+ visibility: collapse;
+}
diff --git a/components/downloads/content/downloads.js b/components/downloads/content/downloads.js
new file mode 100644
index 0000000..ee1c690
--- /dev/null
+++ b/components/downloads/content/downloads.js
@@ -0,0 +1,1614 @@
+/* -*- 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/. */
+
+var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
+ "resource:///modules/DownloadsCommon.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadsViewUI",
+ "resource:///modules/DownloadsViewUI.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Services",
+ "resource://gre/modules/Services.jsm");
+
+/**
+ * Handles the Downloads panel user interface for each browser window.
+ *
+ * This file includes the following constructors and global objects:
+ *
+ * DownloadsPanel
+ * Main entry point for the downloads panel interface.
+ *
+ * DownloadsOverlayLoader
+ * Allows loading the downloads panel and the status indicator interfaces on
+ * demand, to improve startup performance.
+ *
+ * DownloadsView
+ * Builds and updates the downloads list widget, responding to changes in the
+ * download state and real-time data. In addition, handles part of the user
+ * interaction events raised by the downloads list widget.
+ *
+ * DownloadsViewItem
+ * Builds and updates a single item in the downloads list widget, responding to
+ * changes in the download state and real-time data.
+ *
+ * DownloadsViewController
+ * Handles part of the user interaction events raised by the downloads list
+ * widget, in particular the "commands" that apply to multiple items, and
+ * dispatches the commands that apply to individual items.
+ *
+ * DownloadsViewItemController
+ * Handles all the user interaction events, in particular the "commands",
+ * related to a single item in the downloads list widgets.
+ */
+
+/**
+ * A few words on focus and focusrings
+ *
+ * We do quite a few hacks in the Downloads Panel for focusrings. In fact, we
+ * basically suppress most if not all XUL-level focusrings, and style/draw
+ * them ourselves (using :focus instead of -moz-focusring). There are a few
+ * reasons for this:
+ *
+ * 1) Richlists on OSX don't have focusrings; instead, they are shown as
+ * selected. This makes for some ambiguity when we have a focused/selected
+ * item in the list, and the mouse is hovering a completed download (which
+ * highlights).
+ * 2) Windows doesn't show focusrings until after the first time that tab is
+ * pressed (and by then you're focusing the second item in the panel).
+ * 3) Richlistbox sets -moz-focusring even when we select it with a mouse.
+ *
+ * In general, the desired behaviour is to focus the first item after pressing
+ * tab/down, and show that focus with a ring. Then, if the mouse moves over
+ * the panel, to hide that focus ring; essentially resetting us to the state
+ * before pressing the key.
+ *
+ * We end up capturing the tab/down key events, and preventing their default
+ * behaviour. We then set a "keyfocus" attribute on the panel, which allows
+ * us to draw a ring around the currently focused element. If the panel is
+ * closed or the mouse moves over the panel, we remove the attribute.
+ */
+
+"use strict";
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsPanel
+
+/**
+ * Main entry point for the downloads panel interface.
+ */
+const DownloadsPanel = {
+ //////////////////////////////////////////////////////////////////////////////
+ //// Initialization and termination
+
+ /**
+ * Internal state of the downloads panel, based on one of the kState
+ * constants. This is not the same state as the XUL panel element.
+ */
+ _state: 0,
+
+ /** The panel is not linked to downloads data yet. */
+ get kStateUninitialized() 0,
+ /** This object is linked to data, but the panel is invisible. */
+ get kStateHidden() 1,
+ /** The panel will be shown as soon as possible. */
+ get kStateWaitingData() 2,
+ /** The panel is almost shown - we're just waiting to get a handle on the
+ anchor. */
+ get kStateWaitingAnchor() 3,
+ /** The panel is open. */
+ get kStateShown() 4,
+
+ /**
+ * Location of the panel overlay.
+ */
+ get kDownloadsOverlay()
+ "chrome://browser/content/downloads/downloadsOverlay.xul",
+
+ /**
+ * Starts loading the download data in background, without opening the panel.
+ * Use showPanel instead to load the data and open the panel at the same time.
+ *
+ * @param aCallback
+ * Called when initialization is complete.
+ */
+ initialize: function DP_initialize(aCallback)
+ {
+ DownloadsCommon.log("Attempting to initialize DownloadsPanel for a window.");
+ if (this._state != this.kStateUninitialized) {
+ DownloadsCommon.log("DownloadsPanel is already initialized.");
+ DownloadsOverlayLoader.ensureOverlayLoaded(this.kDownloadsOverlay,
+ aCallback);
+ return;
+ }
+ this._state = this.kStateHidden;
+
+ window.addEventListener("unload", this.onWindowUnload, false);
+
+ // Ensure that the Download Manager service is running. This resumes
+ // active downloads if required. If there are downloads to be shown in the
+ // panel, starting the service will make us load their data asynchronously.
+ DownloadsCommon.initializeAllDataLinks();
+
+
+ // Now that data loading has eventually started, load the required XUL
+ // elements and initialize our views.
+ DownloadsCommon.log("Ensuring DownloadsPanel overlay loaded.");
+ DownloadsOverlayLoader.ensureOverlayLoaded(this.kDownloadsOverlay,
+ function DP_I_callback() {
+ DownloadsViewController.initialize();
+ DownloadsCommon.log("Attaching DownloadsView...");
+ DownloadsCommon.getData(window).addView(DownloadsView);
+ DownloadsCommon.getSummary(window, DownloadsView.kItemCountLimit)
+ .addView(DownloadsSummary);
+ DownloadsCommon.log("DownloadsView attached - the panel for this window",
+ "should now see download items come in.");
+ DownloadsPanel._attachEventListeners();
+ DownloadsCommon.log("DownloadsPanel initialized.");
+ aCallback();
+ });
+ },
+
+ /**
+ * Closes the downloads panel and frees the internal resources related to the
+ * downloads. The downloads panel can be reopened later, even after this
+ * function has been called.
+ */
+ terminate: function DP_terminate()
+ {
+ DownloadsCommon.log("Attempting to terminate DownloadsPanel for a window.");
+ if (this._state == this.kStateUninitialized) {
+ DownloadsCommon.log("DownloadsPanel was never initialized. Nothing to do.");
+ return;
+ }
+
+ window.removeEventListener("unload", this.onWindowUnload, false);
+
+ // Ensure that the panel is closed before shutting down.
+ this.hidePanel();
+
+ DownloadsViewController.terminate();
+ DownloadsCommon.getData(window).removeView(DownloadsView);
+ DownloadsCommon.getSummary(window, DownloadsView.kItemCountLimit)
+ .removeView(DownloadsSummary);
+ this._unattachEventListeners();
+
+ this._state = this.kStateUninitialized;
+
+ DownloadsSummary.active = false;
+ DownloadsCommon.log("DownloadsPanel terminated.");
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Panel interface
+
+ /**
+ * Main panel element in the browser window, or null if the panel overlay
+ * hasn't been loaded yet.
+ */
+ get panel()
+ {
+ // If the downloads panel overlay hasn't loaded yet, just return null
+ // without resetting this.panel.
+ let downloadsPanel = document.getElementById("downloadsPanel");
+ if (!downloadsPanel)
+ return null;
+
+ delete this.panel;
+ return this.panel = downloadsPanel;
+ },
+
+ /**
+ * Starts opening the downloads panel interface, anchored to the downloads
+ * button of the browser window. The list of downloads to display is
+ * initialized the first time this method is called, and the panel is shown
+ * only when data is ready.
+ */
+ showPanel: function DP_showPanel()
+ {
+ DownloadsCommon.log("Opening the downloads panel.");
+
+ if (this.isPanelShowing) {
+ DownloadsCommon.log("Panel is already showing - focusing instead.");
+ this._focusPanel();
+ return;
+ }
+
+ this.initialize(function DP_SP_callback() {
+ // Delay displaying the panel because this function will sometimes be
+ // called while another window is closing (like the window for selecting
+ // whether to save or open the file), and that would cause the panel to
+ // close immediately.
+ setTimeout(function () DownloadsPanel._openPopupIfDataReady(), 0);
+ }.bind(this));
+
+ DownloadsCommon.log("Waiting for the downloads panel to appear.");
+ this._state = this.kStateWaitingData;
+ },
+
+ /**
+ * Hides the downloads panel, if visible, but keeps the internal state so that
+ * the panel can be reopened quickly if required.
+ */
+ hidePanel: function DP_hidePanel()
+ {
+ DownloadsCommon.log("Closing the downloads panel.");
+
+ if (!this.isPanelShowing) {
+ DownloadsCommon.log("Downloads panel is not showing - nothing to do.");
+ return;
+ }
+
+ this.panel.hidePopup();
+
+ // Ensure that we allow the panel to be reopened. Note that, if the popup
+ // was open, then the onPopupHidden event handler has already updated the
+ // current state, otherwise we must update the state ourselves.
+ this._state = this.kStateHidden;
+ DownloadsCommon.log("Downloads panel is now closed.");
+ },
+
+ /**
+ * Indicates whether the panel is shown or will be shown.
+ */
+ get isPanelShowing()
+ {
+ return this._state == this.kStateWaitingData ||
+ this._state == this.kStateWaitingAnchor ||
+ this._state == this.kStateShown;
+ },
+
+ /**
+ * Returns whether the user has started keyboard navigation.
+ */
+ get keyFocusing()
+ {
+ return this.panel.hasAttribute("keyfocus");
+ },
+
+ /**
+ * Set to true if the user has started keyboard navigation, and we should be
+ * showing focusrings in the panel. Also adds a mousemove event handler to
+ * the panel which disables keyFocusing.
+ */
+ set keyFocusing(aValue)
+ {
+ if (aValue) {
+ this.panel.setAttribute("keyfocus", "true");
+ this.panel.addEventListener("mousemove", this);
+ } else {
+ this.panel.removeAttribute("keyfocus");
+ this.panel.removeEventListener("mousemove", this);
+ }
+ return aValue;
+ },
+
+ /**
+ * Handles the mousemove event for the panel, which disables focusring
+ * visualization.
+ */
+ handleEvent: function DP_handleEvent(aEvent)
+ {
+ if (aEvent.type == "mousemove") {
+ this.keyFocusing = false;
+ }
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Callback functions from DownloadsView
+
+ /**
+ * Called after data loading finished.
+ */
+ onViewLoadCompleted: function DP_onViewLoadCompleted()
+ {
+ this._openPopupIfDataReady();
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// User interface event functions
+
+ onWindowUnload: function DP_onWindowUnload()
+ {
+ // This function is registered as an event listener, we can't use "this".
+ DownloadsPanel.terminate();
+ },
+
+ onPopupShown: function DP_onPopupShown(aEvent)
+ {
+ // Ignore events raised by nested popups.
+ if (aEvent.target != aEvent.currentTarget) {
+ return;
+ }
+
+ DownloadsCommon.log("Downloads panel has shown.");
+ this._state = this.kStateShown;
+
+ // Since at most one popup is open at any given time, we can set globally.
+ DownloadsCommon.getIndicatorData(window).attentionSuppressed = true;
+
+ // Ensure that the first item is selected when the panel is focused.
+ if (DownloadsView.richListBox.itemCount > 0 &&
+ DownloadsView.richListBox.selectedIndex == -1) {
+ DownloadsView.richListBox.selectedIndex = 0;
+ }
+
+ this._focusPanel();
+ },
+
+ onPopupHidden: function DP_onPopupHidden(aEvent)
+ {
+ // Ignore events raised by nested popups.
+ if (aEvent.target != aEvent.currentTarget) {
+ return;
+ }
+
+ DownloadsCommon.log("Downloads panel has hidden.");
+
+ // Removes the keyfocus attribute so that we stop handling keyboard
+ // navigation.
+ this.keyFocusing = false;
+
+ // Since at most one popup is open at any given time, we can set globally.
+ DownloadsCommon.getIndicatorData(window).attentionSuppressed = false;
+
+ // Allow the anchor to be hidden.
+ DownloadsButton.releaseAnchor();
+
+ // Allow the panel to be reopened.
+ this._state = this.kStateHidden;
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Related operations
+
+ /**
+ * Shows or focuses the user interface dedicated to downloads history.
+ */
+ showDownloadsHistory: function DP_showDownloadsHistory()
+ {
+ DownloadsCommon.log("Showing download history.");
+ // Hide the panel before showing another window, otherwise focus will return
+ // to the browser window when the panel closes automatically.
+ this.hidePanel();
+
+ BrowserDownloadsUI();
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Internal functions
+
+ /**
+ * Attach event listeners to a panel element. These listeners should be
+ * removed in _unattachEventListeners. This is called automatically after the
+ * panel has successfully loaded.
+ */
+ _attachEventListeners: function DP__attachEventListeners()
+ {
+ // Handle keydown to support accel-V.
+ this.panel.addEventListener("keydown", this._onKeyDown.bind(this), false);
+ // Handle keypress to be able to preventDefault() events before they reach
+ // the richlistbox, for keyboard navigation.
+ this.panel.addEventListener("keypress", this._onKeyPress.bind(this), false);
+ },
+
+ /**
+ * Unattach event listeners that were added in _attachEventListeners. This
+ * is called automatically on panel termination.
+ */
+ _unattachEventListeners: function DP__unattachEventListeners()
+ {
+ this.panel.removeEventListener("keydown", this._onKeyDown.bind(this),
+ false);
+ this.panel.removeEventListener("keypress", this._onKeyPress.bind(this),
+ false);
+ },
+
+ _onKeyPress: function DP__onKeyPress(aEvent)
+ {
+ // Handle unmodified keys only.
+ if (aEvent.altKey || aEvent.ctrlKey || aEvent.shiftKey || aEvent.metaKey) {
+ return;
+ }
+
+ let richListBox = DownloadsView.richListBox;
+
+ // If the user has pressed the tab, up, or down cursor key, start keyboard
+ // navigation, thus enabling focusrings in the panel. Keyboard navigation
+ // is automatically disabled if the user moves the mouse on the panel, or
+ // if the panel is closed.
+ if ((aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_TAB ||
+ aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_UP ||
+ aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_DOWN) &&
+ !this.keyFocusing) {
+ this.keyFocusing = true;
+ // Ensure there's a selection, we will show the focus ring around it and
+ // prevent the richlistbox from changing the selection.
+ if (DownloadsView.richListBox.selectedIndex == -1)
+ DownloadsView.richListBox.selectedIndex = 0;
+ aEvent.preventDefault();
+ return;
+ }
+
+ if (aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_DOWN) {
+ // If the last element in the list is selected, or the footer is already
+ // focused, focus the footer.
+ if (richListBox.selectedItem === richListBox.lastChild ||
+ document.activeElement.parentNode.id === "downloadsFooter") {
+ DownloadsFooter.focus();
+ aEvent.preventDefault();
+ return;
+ }
+ }
+
+ // Pass keypress events to the richlistbox view when it's focused.
+ if (document.activeElement === richListBox) {
+ DownloadsView.onDownloadKeyPress(aEvent);
+ }
+ },
+
+ /**
+ * Keydown listener that listens for the keys to start key focusing, as well
+ * as the the accel-V "paste" event, which initiates a file download if the
+ * pasted item can be resolved to a URI.
+ */
+ _onKeyDown: function DP__onKeyDown(aEvent)
+ {
+ // If the footer is focused and the downloads list has at least 1 element
+ // in it, focus the last element in the list when going up.
+ if (aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_UP &&
+ document.activeElement.parentNode.id === "downloadsFooter" &&
+ DownloadsView.richListBox.firstChild) {
+ DownloadsView.richListBox.focus();
+ DownloadsView.richListBox.selectedItem = DownloadsView.richListBox.lastChild;
+ aEvent.preventDefault();
+ return;
+ }
+
+ let pasting = aEvent.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_V &&
+#ifdef XP_MACOSX
+ aEvent.metaKey;
+#else
+ aEvent.ctrlKey;
+#endif
+
+ if (!pasting) {
+ return;
+ }
+
+ DownloadsCommon.log("Received a paste event.");
+
+ let trans = Cc["@mozilla.org/widget/transferable;1"]
+ .createInstance(Ci.nsITransferable);
+ trans.init(null);
+ let flavors = ["text/x-moz-url", "text/unicode"];
+ flavors.forEach(trans.addDataFlavor);
+ Services.clipboard.getData(trans, Services.clipboard.kGlobalClipboard);
+ // Getting the data or creating the nsIURI might fail
+ try {
+ let data = {};
+ trans.getAnyTransferData({}, data, {});
+ let [url, name] = data.value
+ .QueryInterface(Ci.nsISupportsString)
+ .data
+ .split("\n");
+ if (!url) {
+ return;
+ }
+
+ let uri = NetUtil.newURI(url);
+ DownloadsCommon.log("Pasted URL seems valid. Starting download.");
+ DownloadURL(uri.spec, name, document);
+ } catch (ex) {}
+ },
+
+ /**
+ * Move focus to the main element in the downloads panel, unless another
+ * element in the panel is already focused.
+ */
+ _focusPanel: function DP_focusPanel()
+ {
+ // We may be invoked while the panel is still waiting to be shown.
+ if (this._state != this.kStateShown) {
+ return;
+ }
+
+ let element = document.commandDispatcher.focusedElement;
+ while (element && element != this.panel) {
+ element = element.parentNode;
+ }
+ if (!element) {
+ if (DownloadsView.richListBox.itemCount > 0) {
+ DownloadsView.richListBox.focus();
+ } else {
+ DownloadsFooter.focus();
+ }
+ }
+ },
+
+ /**
+ * Opens the downloads panel when data is ready to be displayed.
+ */
+ _openPopupIfDataReady: function DP_openPopupIfDataReady()
+ {
+ // We don't want to open the popup if we already displayed it, or if we are
+ // still loading data.
+ if (this._state != this.kStateWaitingData || DownloadsView.loading) {
+ return;
+ }
+
+ this._state = this.kStateWaitingAnchor;
+
+ // Ensure the anchor is visible. If that is not possible, show the panel
+ // anchored to the top area of the window, near the default anchor position.
+ DownloadsButton.getAnchor(function DP_OPIDR_callback(aAnchor) {
+ // If somehow we've switched states already (by getting a panel hiding
+ // event before an overlay is loaded, for example), bail out.
+ if (this._state != this.kStateWaitingAnchor)
+ return;
+
+ // At this point, if the window is minimized, opening the panel could fail
+ // without any notification, and there would be no way to either open or
+ // close the panel any more. To prevent this, check if the window is
+ // minimized and in that case force the panel to the closed state.
+ if (window.windowState == Ci.nsIDOMChromeWindow.STATE_MINIMIZED) {
+ DownloadsButton.releaseAnchor();
+ this._state = this.kStateHidden;
+ return;
+ }
+
+ // When the panel is opened, we check if the target files of visible items
+ // still exist, and update the allowed items interactions accordingly. We
+ // do these checks on a background thread, and don't prevent the panel to
+ // be displayed while these checks are being performed.
+ for (let viewItem of DownloadsView._visibleViewItems.values()) {
+ viewItem.download.refresh().catch(Cu.reportError);
+ }
+
+ if (aAnchor) {
+ DownloadsCommon.log("Opening downloads panel popup.");
+ this.panel.openPopup(aAnchor, "bottomcenter topright", 0, 0, false,
+ null);
+ } else {
+ DownloadsCommon.error("We can't find the anchor! Failure case - opening",
+ "downloads panel on TabsToolbar. We should never",
+ "get here!");
+ Components.utils.reportError(
+ "Downloads button cannot be found");
+ }
+ }.bind(this));
+ }
+};
+
+XPCOMUtils.defineConstant(this, "DownloadsPanel", DownloadsPanel);
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsOverlayLoader
+
+/**
+ * Allows loading the downloads panel and the status indicator interfaces on
+ * demand, to improve startup performance.
+ */
+const DownloadsOverlayLoader = {
+ /**
+ * We cannot load two overlays at the same time, thus we use a queue of
+ * pending load requests.
+ */
+ _loadRequests: [],
+
+ /**
+ * True while we are waiting for an overlay to be loaded.
+ */
+ _overlayLoading: false,
+
+ /**
+ * This object has a key for each overlay URI that is already loaded.
+ */
+ _loadedOverlays: {},
+
+ /**
+ * Loads the specified overlay and invokes the given callback when finished.
+ *
+ * @param aOverlay
+ * String containing the URI of the overlay to load in the current
+ * window. If this overlay has already been loaded using this
+ * function, then the overlay is not loaded again.
+ * @param aCallback
+ * Invoked when loading is completed. If the overlay is already
+ * loaded, the function is called immediately.
+ */
+ ensureOverlayLoaded: function DOL_ensureOverlayLoaded(aOverlay, aCallback)
+ {
+ // The overlay is already loaded, invoke the callback immediately.
+ if (aOverlay in this._loadedOverlays) {
+ aCallback();
+ return;
+ }
+
+ // The callback will be invoked when loading is finished.
+ this._loadRequests.push({ overlay: aOverlay, callback: aCallback });
+ if (this._overlayLoading) {
+ return;
+ }
+
+ function DOL_EOL_loadCallback() {
+ this._overlayLoading = false;
+ this._loadedOverlays[aOverlay] = true;
+
+ // Loading the overlay causes all the persisted XUL attributes to be
+ // reapplied, including "iconsize" on the toolbars. Until bug 640158 is
+ // fixed, we must recalculate the correct "iconsize" attributes manually.
+ retrieveToolbarIconsizesFromTheme();
+
+ this.processPendingRequests();
+ }
+
+ this._overlayLoading = true;
+ DownloadsCommon.log("Loading overlay ", aOverlay);
+ document.loadOverlay(aOverlay, DOL_EOL_loadCallback.bind(this));
+ },
+
+ /**
+ * Re-processes all the currently pending requests, invoking the callbacks
+ * and/or loading more overlays as needed. In most cases, there will be a
+ * single request for one overlay, that will be processed immediately.
+ */
+ processPendingRequests: function DOL_processPendingRequests()
+ {
+ // Re-process all the currently pending requests, yet allow more requests
+ // to be appended at the end of the array if we're not ready for them.
+ let currentLength = this._loadRequests.length;
+ for (let i = 0; i < currentLength; i++) {
+ let request = this._loadRequests.shift();
+
+ // We must call ensureOverlayLoaded again for each request, to check if
+ // the associated callback can be invoked now, or if we must still wait
+ // for the associated overlay to load.
+ this.ensureOverlayLoaded(request.overlay, request.callback);
+ }
+ }
+};
+
+XPCOMUtils.defineConstant(this, "DownloadsOverlayLoader", DownloadsOverlayLoader);
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsView
+
+/**
+ * Builds and updates the downloads list widget, responding to changes in the
+ * download state and real-time data. In addition, handles part of the user
+ * interaction events raised by the downloads list widget.
+ */
+const DownloadsView = {
+ //////////////////////////////////////////////////////////////////////////////
+ //// Functions handling download items in the list
+
+ /**
+ * Maximum number of items shown by the list at any given time.
+ */
+ kItemCountLimit: 3,
+
+ /**
+ * Indicates whether we are still loading downloads data asynchronously.
+ */
+ loading: false,
+
+ /**
+ * Ordered array of all Download objects. We need to keep this array because
+ * only a limited number of items are shown at once, and if an item that is
+ * currently visible is removed from the list, we might need to take another
+ * item from the array and make it appear at the bottom.
+ */
+ _downloads: [],
+
+ /**
+ * Associates the visible Download objects with their corresponding
+ * DownloadsViewItem object. There is a limited number of view items in the
+ * panel at any given time.
+ */
+ _visibleViewItems: new Map(),
+
+ /**
+ * Called when the number of items in the list changes.
+ */
+ _itemCountChanged: function DV_itemCountChanged()
+ {
+ DownloadsCommon.log("The downloads item count has changed - we are tracking",
+ this._downloads.length, "downloads in total.");
+ let count = this._downloads.length;
+ let hiddenCount = count - this.kItemCountLimit;
+
+ if (count > 0) {
+ DownloadsCommon.log("Setting the panel's hasdownloads attribute to true.");
+ DownloadsPanel.panel.setAttribute("hasdownloads", "true");
+ } else {
+ DownloadsCommon.log("Removing the panel's hasdownloads attribute.");
+ DownloadsPanel.panel.removeAttribute("hasdownloads");
+ }
+
+ // If we've got some hidden downloads, we should activate the
+ // DownloadsSummary. The DownloadsSummary will determine whether or not
+ // it's appropriate to actually display the summary.
+ DownloadsSummary.active = hiddenCount > 0;
+ },
+
+ /**
+ * Element corresponding to the list of downloads.
+ */
+ get richListBox()
+ {
+ delete this.richListBox;
+ return this.richListBox = document.getElementById("downloadsListBox");
+ },
+
+ /**
+ * Element corresponding to the button for showing more downloads.
+ */
+ get downloadsHistory()
+ {
+ delete this.downloadsHistory;
+ return this.downloadsHistory = document.getElementById("downloadsHistory");
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Callback functions from DownloadsData
+
+ /**
+ * Called before multiple downloads are about to be loaded.
+ */
+ onDataLoadStarting: function DV_onDataLoadStarting()
+ {
+ DownloadsCommon.log("onDataLoadStarting called for DownloadsView.");
+ this.loading = true;
+ },
+
+ /**
+ * Called after data loading finished.
+ */
+ onDataLoadCompleted: function DV_onDataLoadCompleted()
+ {
+ DownloadsCommon.log("onDataLoadCompleted called for DownloadsView.");
+
+ this.loading = false;
+
+ // We suppressed item count change notifications during the batch load, at
+ // this point we should just call the function once.
+ this._itemCountChanged();
+
+ // Notify the panel that all the initially available downloads have been
+ // loaded. This ensures that the interface is visible, if still required.
+ DownloadsPanel.onViewLoadCompleted();
+ },
+
+ /**
+ * Called when the downloads database becomes unavailable (for example,
+ * entering Private Browsing Mode). References to existing data should be
+ * discarded.
+ */
+ onDataInvalidated: function DV_onDataInvalidated()
+ {
+ DownloadsCommon.log("Downloads data has been invalidated. Cleaning up",
+ "DownloadsView.");
+
+ DownloadsPanel.terminate();
+
+ // Clear the list by replacing with a shallow copy.
+ let emptyView = this.richListBox.cloneNode(false);
+ this.richListBox.parentNode.replaceChild(emptyView, this.richListBox);
+ this.richListBox = emptyView;
+ this._viewItems = {};
+ this._dataItems = [];
+ },
+
+ /**
+ * Called when a new download data item is available, either during the
+ * asynchronous data load or when a new download is started.
+ *
+ * @param aDownload
+ * Download object that was just added.
+ * @param aNewest
+ * When true, indicates that this item is the most recent and should be
+ * added in the topmost position. This happens when a new download is
+ * started. When false, indicates that the item is the least recent
+ * and should be appended. The latter generally happens during the
+ * asynchronous data load.
+ */
+ onDownloadAdded(download, aNewest) {
+ DownloadsCommon.log("A new download data item was added - aNewest =",
+ aNewest);
+
+ if (aNewest) {
+ this._downloads.unshift(download);
+ } else {
+ this._downloads.push(download);
+ }
+
+ let itemsNowOverflow = this._downloads.length > this.kItemCountLimit;
+ if (aNewest || !itemsNowOverflow) {
+ // The newly added item is visible in the panel and we must add the
+ // corresponding element. This is either because it is the first item, or
+ // because it was added at the bottom but the list still doesn't overflow.
+ this._addViewItem(download, aNewest);
+ }
+ if (aNewest && itemsNowOverflow) {
+ // If the list overflows, remove the last item from the panel to make room
+ // for the new one that we just added at the top.
+ this._removeViewItem(this._downloads[this.kItemCountLimit]);
+ }
+
+ // For better performance during batch loads, don't update the count for
+ // every item, because the interface won't be visible until load finishes.
+ if (!this.loading) {
+ this._itemCountChanged();
+ }
+ },
+
+ onDownloadStateChanged(download) {
+ let viewItem = this._visibleViewItems.get(download);
+ if (viewItem) {
+ viewItem.onStateChanged();
+ }
+ },
+
+ onDownloadChanged(download) {
+ let viewItem = this._visibleViewItems.get(download);
+ if (viewItem) {
+ viewItem.onChanged();
+ }
+ },
+
+ /**
+ * Called when a data item is removed. Ensures that the widget associated
+ * with the view item is removed from the user interface.
+ *
+ * @param download
+ * Download object that is being removed.
+ */
+ onDownloadRemoved(download) {
+ DownloadsCommon.log("A download data item was removed.");
+
+ let itemIndex = this._downloads.indexOf(download);
+ this._downloads.splice(itemIndex, 1);
+
+ if (itemIndex < this.kItemCountLimit) {
+ // The item to remove is visible in the panel.
+ this._removeViewItem(download);
+ if (this._downloads.length >= this.kItemCountLimit) {
+ // Reinsert the next item into the panel.
+ this._addViewItem(this._downloads[this.kItemCountLimit - 1], false);
+ }
+ }
+
+ this._itemCountChanged();
+ },
+
+ /**
+ * Associates each richlistitem for a download with its corresponding
+ * DownloadsViewItemController object.
+ */
+ _controllersForElements: new Map(),
+
+ controllerForElement(element) {
+ return this._controllersForElements.get(element);
+ },
+
+ /**
+ * Creates a new view item associated with the specified data item, and adds
+ * it to the top or the bottom of the list.
+ */
+ _addViewItem(download, aNewest)
+ {
+ DownloadsCommon.log("Adding a new DownloadsViewItem to the downloads list.",
+ "aNewest =", aNewest);
+
+ let element = document.createElement("richlistitem");
+ let viewItem = new DownloadsViewItem(download, element);
+ this._visibleViewItems.set(download, viewItem);
+ let viewItemController = new DownloadsViewItemController(download);
+ this._controllersForElements.set(element, viewItemController);
+ if (aNewest) {
+ this.richListBox.insertBefore(element, this.richListBox.firstChild);
+ } else {
+ this.richListBox.appendChild(element);
+ }
+ },
+
+ /**
+ * Removes the view item associated with the specified data item.
+ */
+ _removeViewItem(download) {
+ DownloadsCommon.log("Removing a DownloadsViewItem from the downloads list.");
+ let element = this._visibleViewItems.get(download).element;
+ let previousSelectedIndex = this.richListBox.selectedIndex;
+ this.richListBox.removeChild(element);
+ if (previousSelectedIndex != -1) {
+ this.richListBox.selectedIndex = Math.min(previousSelectedIndex,
+ this.richListBox.itemCount - 1);
+ }
+ this._visibleViewItems.delete(download);
+ this._controllersForElements.delete(element);
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// User interface event functions
+
+ /**
+ * Helper function to do commands on a specific download item.
+ *
+ * @param aEvent
+ * Event object for the event being handled. If the event target is
+ * not a richlistitem that represents a download, this function will
+ * walk up the parent nodes until it finds a DOM node that is.
+ * @param aCommand
+ * The command to be performed.
+ */
+ onDownloadCommand: function DV_onDownloadCommand(aEvent, aCommand)
+ {
+ let target = aEvent.target;
+ while (target.nodeName != "richlistitem") {
+ target = target.parentNode;
+ }
+ DownloadsView.controllerForElement(target).doCommand(aCommand);
+ },
+
+ onDownloadClick: function DV_onDownloadClick(aEvent)
+ {
+ // Handle primary clicks only, and exclude the action button.
+ if (aEvent.button == 0 &&
+ !aEvent.originalTarget.hasAttribute("oncommand")) {
+ goDoCommand("downloadsCmd_open");
+ }
+ },
+
+ /**
+ * Handles keypress events on a download item.
+ */
+ onDownloadKeyPress: function DV_onDownloadKeyPress(aEvent)
+ {
+ // Pressing the key on buttons should not invoke the action because the
+ // event has already been handled by the button itself.
+ if (aEvent.originalTarget.hasAttribute("command") ||
+ aEvent.originalTarget.hasAttribute("oncommand")) {
+ return;
+ }
+
+ if (aEvent.charCode == " ".charCodeAt(0)) {
+ goDoCommand("downloadsCmd_pauseResume");
+ return;
+ }
+
+ if (aEvent.keyCode == KeyEvent.DOM_VK_ENTER ||
+ aEvent.keyCode == KeyEvent.DOM_VK_RETURN) {
+ goDoCommand("downloadsCmd_doDefault");
+ }
+ },
+
+
+ /**
+ * Mouse listeners to handle selection on hover.
+ */
+ onDownloadMouseOver: function DV_onDownloadMouseOver(aEvent)
+ {
+ if (aEvent.originalTarget.parentNode == this.richListBox)
+ this.richListBox.selectedItem = aEvent.originalTarget;
+ },
+ onDownloadMouseOut: function DV_onDownloadMouseOut(aEvent)
+ {
+ if (aEvent.originalTarget.parentNode == this.richListBox) {
+ // If the destination element is outside of the richlistitem, clear the
+ // selection.
+ let element = aEvent.relatedTarget;
+ while (element && element != aEvent.originalTarget) {
+ element = element.parentNode;
+ }
+ if (!element)
+ this.richListBox.selectedIndex = -1;
+ }
+ },
+
+ onDownloadContextMenu: function DV_onDownloadContextMenu(aEvent)
+ {
+ let element = this.richListBox.selectedItem;
+ if (!element) {
+ return;
+ }
+
+ DownloadsViewController.updateCommands();
+
+ // Set the state attribute so that only the appropriate items are displayed.
+ let contextMenu = document.getElementById("downloadsContextMenu");
+ contextMenu.setAttribute("state", element.getAttribute("state"));
+ },
+
+ onDownloadDragStart: function DV_onDownloadDragStart(aEvent)
+ {
+ let element = this.richListBox.selectedItem;
+ if (!element) {
+ return;
+ }
+
+ // We must check for existence synchronously because this is a DOM event.
+ let localFile = new FileUtils.File(DownloadsView.controllerForElement(element)
+ .download.target.path);
+ if (!localFile.exists()) {
+ return;
+ }
+
+ let dataTransfer = aEvent.dataTransfer;
+ dataTransfer.mozSetDataAt("application/x-moz-file", localFile, 0);
+ dataTransfer.effectAllowed = "copyMove";
+ var url = Services.io.newFileURI(localFile).spec;
+ dataTransfer.setData("text/uri-list", url);
+ dataTransfer.setData("text/plain", url);
+ dataTransfer.addElement(element);
+
+ aEvent.stopPropagation();
+ }
+}
+
+XPCOMUtils.defineConstant(this, "DownloadsView", DownloadsView);
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsViewItem
+
+/**
+ * Builds and updates a single item in the downloads list widget, responding to
+ * changes in the download state and real-time data.
+ *
+ * @param download
+ * Download object to be associated with the view item.
+ * @param aElement
+ * XUL element corresponding to the single download item in the view.
+ */
+function DownloadsViewItem(download, aElement) {
+ this.download = download;
+
+ this.element = aElement;
+ this.element._shell = this;
+
+ this.element.setAttribute("type", "download");
+ this.element.classList.add("download-state");
+
+ this._updateState();
+}
+
+DownloadsViewItem.prototype = {
+ __proto__: DownloadsViewUI.DownloadElementShell.prototype,
+
+ /**
+ * The XUL element corresponding to the associated richlistbox item.
+ */
+ _element: null,
+
+ onStateChanged() {
+ this.element.setAttribute("image", this.image);
+ this.element.setAttribute("state",
+ DownloadsCommon.stateOfDownload(this.download));
+ },
+
+ onChanged() {
+ this._updateProgress();
+ },
+};
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsViewController
+
+/**
+ * Handles part of the user interaction events raised by the downloads list
+ * widget, in particular the "commands" that apply to multiple items, and
+ * dispatches the commands that apply to individual items.
+ */
+const DownloadsViewController = {
+ //////////////////////////////////////////////////////////////////////////////
+ //// Initialization and termination
+
+ initialize: function DVC_initialize()
+ {
+ window.controllers.insertControllerAt(0, this);
+ },
+
+ terminate: function DVC_terminate()
+ {
+ window.controllers.removeController(this);
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// nsIController
+
+ supportsCommand: function DVC_supportsCommand(aCommand)
+ {
+ // Firstly, determine if this is a command that we can handle.
+ if (!(aCommand in this.commands) &&
+ !(aCommand in DownloadsViewItemController.prototype.commands)) {
+ return false;
+ }
+ // Secondly, determine if focus is on a control in the downloads list.
+ let element = document.commandDispatcher.focusedElement;
+ while (element && element != DownloadsView.richListBox) {
+ element = element.parentNode;
+ }
+ // We should handle the command only if the downloads list is among the
+ // ancestors of the focused element.
+ return !!element;
+ },
+
+ isCommandEnabled: function DVC_isCommandEnabled(aCommand)
+ {
+ // Handle commands that are not selection-specific.
+ if (aCommand == "downloadsCmd_clearList") {
+ return DownloadsCommon.getData(window).canRemoveFinished;
+ }
+
+ // Other commands are selection-specific.
+ let element = DownloadsView.richListBox.selectedItem;
+ return element && DownloadsView.controllerForElement(element)
+ .isCommandEnabled(aCommand);
+ },
+
+ doCommand: function DVC_doCommand(aCommand)
+ {
+ // If this command is not selection-specific, execute it.
+ if (aCommand in this.commands) {
+ this.commands[aCommand].apply(this);
+ return;
+ }
+
+ // Other commands are selection-specific.
+ let element = DownloadsView.richListBox.selectedItem;
+ if (element) {
+ // The doCommand function also checks if the command is enabled.
+ DownloadsView.controllerForElement(element).doCommand(aCommand);
+ }
+ },
+
+ onEvent: function () { },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Other functions
+
+ updateCommands: function DVC_updateCommands()
+ {
+ Object.keys(this.commands).forEach(goUpdateCommand);
+ Object.keys(DownloadsViewItemController.prototype.commands)
+ .forEach(goUpdateCommand);
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Selection-independent commands
+
+ /**
+ * This object contains one key for each command that operates regardless of
+ * the currently selected item in the list.
+ */
+ commands: {
+ downloadsCmd_clearList: function DVC_downloadsCmd_clearList()
+ {
+ DownloadsCommon.getData(window).removeFinished();
+ }
+ }
+};
+
+XPCOMUtils.defineConstant(this, "DownloadsViewController", DownloadsViewController);
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsViewItemController
+
+/**
+ * Handles all the user interaction events, in particular the "commands",
+ * related to a single item in the downloads list widgets.
+ */
+function DownloadsViewItemController(download) {
+ this.download = download;
+}
+
+DownloadsViewItemController.prototype = {
+ isCommandEnabled: function DVIC_isCommandEnabled(aCommand)
+ {
+ switch (aCommand) {
+ case "downloadsCmd_open": {
+ if (!this.download.succeeded) {
+ return false;
+ }
+
+ let file = new FileUtils.File(this.download.target.path);
+ return file.exists();
+ }
+ case "downloadsCmd_show": {
+ let file = new FileUtils.File(this.download.target.path);
+ if (file.exists()) {
+ return true;
+ }
+
+ if (!this.download.target.partFilePath) {
+ return false;
+ }
+
+ let partFile = new FileUtils.File(this.download.target.partFilePath);
+ return partFile.exists();
+ }
+ case "downloadsCmd_pauseResume":
+ return this.download.hasPartialData && !this.download.error;
+ case "downloadsCmd_retry":
+ return this.download.canceled || this.download.error;
+ case "downloadsCmd_openReferrer":
+ return !!this.download.source.referrer;
+ case "cmd_delete":
+ case "downloadsCmd_cancel":
+ case "downloadsCmd_copyLocation":
+ case "downloadsCmd_doDefault":
+ return true;
+ }
+ return false;
+ },
+
+ doCommand: function DVIC_doCommand(aCommand)
+ {
+ if (this.isCommandEnabled(aCommand)) {
+ this.commands[aCommand].apply(this);
+ }
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Item commands
+
+ /**
+ * This object contains one key for each command that operates on this item.
+ *
+ * In commands, the "this" identifier points to the controller item.
+ */
+ commands: {
+ cmd_delete: function DVIC_cmd_delete()
+ {
+ DownloadsCommon.removeAndFinalizeDownload(this.download);
+ PlacesUtils.bhistory.removePage(
+ NetUtil.newURI(this.download.source.url));
+ },
+
+ downloadsCmd_cancel: function DVIC_downloadsCmd_cancel()
+ {
+ this.download.cancel().catch(() => {});
+ this.download.removePartialData().catch(Cu.reportError);
+ },
+
+ downloadsCmd_open: function DVIC_downloadsCmd_open()
+ {
+ this.download.launch().catch(Cu.reportError);
+
+ // We explicitly close the panel here to give the user the feedback that
+ // their click has been received, and we're handling the action.
+ // Otherwise, we'd have to wait for the file-type handler to execute
+ // before the panel would close. This also helps to prevent the user from
+ // accidentally opening a file several times.
+ DownloadsPanel.hidePanel();
+ },
+
+ downloadsCmd_show: function DVIC_downloadsCmd_show()
+ {
+ let file = new FileUtils.File(this.download.target.path);
+ DownloadsCommon.showDownloadedFile(file);
+
+ // We explicitly close the panel here to give the user the feedback that
+ // their click has been received, and we're handling the action.
+ // Otherwise, we'd have to wait for the operating system file manager
+ // window to open before the panel closed. This also helps to prevent the
+ // user from opening the containing folder several times.
+ DownloadsPanel.hidePanel();
+ },
+
+ downloadsCmd_pauseResume: function DVIC_downloadsCmd_pauseResume()
+ {
+ if (this.download.stopped) {
+ this.download.start();
+ } else {
+ this.download.cancel();
+ }
+ },
+
+ downloadsCmd_retry: function DVIC_downloadsCmd_retry()
+ {
+ this.download.start().catch(() => {});
+ },
+
+ downloadsCmd_openReferrer: function DVIC_downloadsCmd_openReferrer()
+ {
+ openURL(this.download.source.referrer);
+ },
+
+ downloadsCmd_copyLocation: function DVIC_downloadsCmd_copyLocation()
+ {
+ let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"]
+ .getService(Ci.nsIClipboardHelper);
+ clipboard.copyString(this.download.source.url, document);
+ },
+
+ downloadsCmd_doDefault: function DVIC_downloadsCmd_doDefault()
+ {
+ const nsIDM = Ci.nsIDownloadManager;
+
+ // Determine the default command for the current item.
+ let defaultCommand = function () {
+ switch (DownloadsCommon.stateOfDownload(this.download)) {
+ case nsIDM.DOWNLOAD_NOTSTARTED: return "downloadsCmd_cancel";
+ case nsIDM.DOWNLOAD_FINISHED: return "downloadsCmd_open";
+ case nsIDM.DOWNLOAD_FAILED: return "downloadsCmd_retry";
+ case nsIDM.DOWNLOAD_CANCELED: return "downloadsCmd_retry";
+ case nsIDM.DOWNLOAD_PAUSED: return "downloadsCmd_pauseResume";
+ case nsIDM.DOWNLOAD_QUEUED: return "downloadsCmd_cancel";
+ case nsIDM.DOWNLOAD_BLOCKED_PARENTAL: return "downloadsCmd_openReferrer";
+ case nsIDM.DOWNLOAD_SCANNING: return "downloadsCmd_show";
+ case nsIDM.DOWNLOAD_DIRTY: return "downloadsCmd_openReferrer";
+ case nsIDM.DOWNLOAD_BLOCKED_POLICY: return "downloadsCmd_openReferrer";
+ }
+ return "";
+ }.apply(this);
+ if (defaultCommand && this.isCommandEnabled(defaultCommand))
+ this.doCommand(defaultCommand);
+ }
+ }
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsSummary
+
+/**
+ * Manages the summary at the bottom of the downloads panel list if the number
+ * of items in the list exceeds the panels limit.
+ */
+const DownloadsSummary = {
+
+ /**
+ * Sets the active state of the summary. When active, the summary subscribes
+ * to the DownloadsCommon DownloadsSummaryData singleton.
+ *
+ * @param aActive
+ * Set to true to activate the summary.
+ */
+ set active(aActive)
+ {
+ if (aActive == this._active || !this._summaryNode) {
+ return this._active;
+ }
+ if (aActive) {
+ DownloadsCommon.getSummary(window, DownloadsView.kItemCountLimit)
+ .refreshView(this);
+ } else {
+ DownloadsFooter.showingSummary = false;
+ }
+
+ return this._active = aActive;
+ },
+
+ /**
+ * Returns the active state of the downloads summary.
+ */
+ get active() this._active,
+
+ _active: false,
+
+ /**
+ * Sets whether or not we show the progress bar.
+ *
+ * @param aShowingProgress
+ * True if we should show the progress bar.
+ */
+ set showingProgress(aShowingProgress)
+ {
+ if (aShowingProgress) {
+ this._summaryNode.setAttribute("inprogress", "true");
+ } else {
+ this._summaryNode.removeAttribute("inprogress");
+ }
+ // If progress isn't being shown, then we simply do not show the summary.
+ return DownloadsFooter.showingSummary = aShowingProgress;
+ },
+
+ /**
+ * Sets the amount of progress that is visible in the progress bar.
+ *
+ * @param aValue
+ * A value between 0 and 100 to represent the progress of the
+ * summarized downloads.
+ */
+ set percentComplete(aValue)
+ {
+ if (this._progressNode) {
+ this._progressNode.setAttribute("value", aValue);
+ }
+ return aValue;
+ },
+
+ /**
+ * Sets the description for the download summary.
+ *
+ * @param aValue
+ * A string representing the description of the summarized
+ * downloads.
+ */
+ set description(aValue)
+ {
+ if (this._descriptionNode) {
+ this._descriptionNode.setAttribute("value", aValue);
+ this._descriptionNode.setAttribute("tooltiptext", aValue);
+ }
+ return aValue;
+ },
+
+ /**
+ * Sets the details for the download summary, such as the time remaining,
+ * the amount of bytes transferred, etc.
+ *
+ * @param aValue
+ * A string representing the details of the summarized
+ * downloads.
+ */
+ set details(aValue)
+ {
+ if (this._detailsNode) {
+ this._detailsNode.setAttribute("value", aValue);
+ this._detailsNode.setAttribute("tooltiptext", aValue);
+ }
+ return aValue;
+ },
+
+ /**
+ * Focuses the root element of the summary.
+ */
+ focus: function()
+ {
+ if (this._summaryNode) {
+ this._summaryNode.focus();
+ }
+ },
+
+ /**
+ * Respond to keydown events on the Downloads Summary node.
+ *
+ * @param aEvent
+ * The keydown event being handled.
+ */
+ onKeyDown: function DS_onKeyDown(aEvent)
+ {
+ if (aEvent.charCode == " ".charCodeAt(0) ||
+ aEvent.keyCode == KeyEvent.DOM_VK_ENTER ||
+ aEvent.keyCode == KeyEvent.DOM_VK_RETURN) {
+ DownloadsPanel.showDownloadsHistory();
+ }
+ },
+
+ /**
+ * Respond to click events on the Downloads Summary node.
+ *
+ * @param aEvent
+ * The click event being handled.
+ */
+ onClick: function DS_onClick(aEvent)
+ {
+ DownloadsPanel.showDownloadsHistory();
+ },
+
+ /**
+ * Element corresponding to the root of the downloads summary.
+ */
+ get _summaryNode()
+ {
+ let node = document.getElementById("downloadsSummary");
+ if (!node) {
+ return null;
+ }
+ delete this._summaryNode;
+ return this._summaryNode = node;
+ },
+
+ /**
+ * Element corresponding to the progress bar in the downloads summary.
+ */
+ get _progressNode()
+ {
+ let node = document.getElementById("downloadsSummaryProgress");
+ if (!node) {
+ return null;
+ }
+ delete this._progressNode;
+ return this._progressNode = node;
+ },
+
+ /**
+ * Element corresponding to the main description of the downloads
+ * summary.
+ */
+ get _descriptionNode()
+ {
+ let node = document.getElementById("downloadsSummaryDescription");
+ if (!node) {
+ return null;
+ }
+ delete this._descriptionNode;
+ return this._descriptionNode = node;
+ },
+
+ /**
+ * Element corresponding to the secondary description of the downloads
+ * summary.
+ */
+ get _detailsNode()
+ {
+ let node = document.getElementById("downloadsSummaryDetails");
+ if (!node) {
+ return null;
+ }
+ delete this._detailsNode;
+ return this._detailsNode = node;
+ }
+};
+
+XPCOMUtils.defineConstant(this, "DownloadsSummary", DownloadsSummary);
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsFooter
+
+/**
+ * Manages events sent to to the footer vbox, which contains both the
+ * DownloadsSummary as well as the "Show All Downloads" button.
+ */
+const DownloadsFooter = {
+
+ /**
+ * Focuses the appropriate element within the footer. If the summary
+ * is visible, focus it. If not, focus the "Show All Downloads"
+ * button.
+ */
+ focus: function DF_focus()
+ {
+ if (this._showingSummary) {
+ DownloadsSummary.focus();
+ } else {
+ DownloadsView.downloadsHistory.focus();
+ }
+ },
+
+ _showingSummary: false,
+
+ /**
+ * Sets whether or not the Downloads Summary should be displayed in the
+ * footer. If not, the "Show All Downloads" button is shown instead.
+ */
+ set showingSummary(aValue)
+ {
+ if (this._footerNode) {
+ if (aValue) {
+ this._footerNode.setAttribute("showingsummary", "true");
+ } else {
+ this._footerNode.removeAttribute("showingsummary");
+ }
+ this._showingSummary = aValue;
+ }
+ return aValue;
+ },
+
+ /**
+ * Element corresponding to the footer of the downloads panel.
+ */
+ get _footerNode()
+ {
+ let node = document.getElementById("downloadsFooter");
+ if (!node) {
+ return null;
+ }
+ delete this._footerNode;
+ return this._footerNode = node;
+ }
+};
+
+XPCOMUtils.defineConstant(this, "DownloadsFooter", DownloadsFooter);
diff --git a/components/downloads/content/downloadsOverlay.xul b/components/downloads/content/downloadsOverlay.xul
new file mode 100644
index 0000000..ca35ee3
--- /dev/null
+++ b/components/downloads/content/downloadsOverlay.xul
@@ -0,0 +1,142 @@
+<?xml version="1.0"?>
+# -*- Mode: HTML; 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/.
+
+<?xml-stylesheet href="chrome://browser/content/downloads/downloads.css"?>
+<?xml-stylesheet href="chrome://browser/skin/downloads/downloads.css"?>
+
+<!DOCTYPE overlay SYSTEM "chrome://browser/locale/downloads/downloads.dtd">
+
+<overlay xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="downloadsOverlay">
+
+ <commandset>
+ <command id="downloadsCmd_doDefault"
+ oncommand="goDoCommand('downloadsCmd_doDefault')"/>
+ <command id="downloadsCmd_pauseResume"
+ oncommand="goDoCommand('downloadsCmd_pauseResume')"/>
+ <command id="downloadsCmd_cancel"
+ oncommand="goDoCommand('downloadsCmd_cancel')"/>
+ <command id="downloadsCmd_open"
+ oncommand="goDoCommand('downloadsCmd_open')"/>
+ <command id="downloadsCmd_show"
+ oncommand="goDoCommand('downloadsCmd_show')"/>
+ <command id="downloadsCmd_retry"
+ oncommand="goDoCommand('downloadsCmd_retry')"/>
+ <command id="downloadsCmd_openReferrer"
+ oncommand="goDoCommand('downloadsCmd_openReferrer')"/>
+ <command id="downloadsCmd_copyLocation"
+ oncommand="goDoCommand('downloadsCmd_copyLocation')"/>
+ <command id="downloadsCmd_clearList"
+ oncommand="goDoCommand('downloadsCmd_clearList')"/>
+ </commandset>
+
+ <popupset id="mainPopupSet">
+ <!-- The panel has level="top" to ensure that it is never hidden by the
+ taskbar on Windows. See bug 672365. For accessibility to screen
+ readers, we use a label on the panel instead of the anchor because the
+ panel can also be displayed without an anchor. -->
+ <panel id="downloadsPanel"
+ aria-label="&downloads.title;"
+ role="group"
+ type="arrow"
+ orient="vertical"
+ level="top"
+ consumeoutsideclicks="true"
+ onpopupshown="DownloadsPanel.onPopupShown(event);"
+ onpopuphidden="DownloadsPanel.onPopupHidden(event);">
+ <!-- The following popup menu should be a child of the panel element,
+ otherwise flickering may occur when the cursor is moved over the area
+ of a disabled menu item that overlaps the panel. See bug 492960. -->
+ <menupopup id="downloadsContextMenu"
+ class="download-state">
+ <menuitem command="downloadsCmd_pauseResume"
+ class="downloadPauseMenuItem"
+ label="&cmd.pause.label;"
+ accesskey="&cmd.pause.accesskey;"/>
+ <menuitem command="downloadsCmd_pauseResume"
+ class="downloadResumeMenuItem"
+ label="&cmd.resume.label;"
+ accesskey="&cmd.resume.accesskey;"/>
+ <menuitem command="downloadsCmd_cancel"
+ class="downloadCancelMenuItem"
+ label="&cmd.cancel.label;"
+ accesskey="&cmd.cancel.accesskey;"/>
+ <menuitem command="cmd_delete"
+ class="downloadRemoveFromHistoryMenuItem"
+ label="&cmd.removeFromHistory.label;"
+ accesskey="&cmd.removeFromHistory.accesskey;"/>
+ <menuitem command="downloadsCmd_show"
+ class="downloadShowMenuItem"
+#ifdef XP_MACOSX
+ label="&cmd.showMac.label;"
+ accesskey="&cmd.showMac.accesskey;"
+#else
+ label="&cmd.show.label;"
+ accesskey="&cmd.show.accesskey;"
+#endif
+ />
+
+ <menuseparator class="downloadCommandsSeparator"/>
+
+ <menuitem command="downloadsCmd_openReferrer"
+ label="&cmd.goToDownloadPage.label;"
+ accesskey="&cmd.goToDownloadPage.accesskey;"/>
+ <menuitem command="downloadsCmd_copyLocation"
+ label="&cmd.copyDownloadLink.label;"
+ accesskey="&cmd.copyDownloadLink.accesskey;"/>
+
+ <menuseparator/>
+
+ <menuitem command="downloadsCmd_clearList"
+ label="&cmd.clearList.label;"
+ accesskey="&cmd.clearList.accesskey;"/>
+ </menupopup>
+
+ <richlistbox id="downloadsListBox"
+ class="plain"
+ flex="1"
+ context="downloadsContextMenu"
+ onmouseover="DownloadsView.onDownloadMouseOver(event);"
+ onmouseout="DownloadsView.onDownloadMouseOut(event);"
+ oncontextmenu="DownloadsView.onDownloadContextMenu(event);"
+ ondragstart="DownloadsView.onDownloadDragStart(event);"/>
+ <description id="emptyDownloads"
+ mousethrough="always">
+ &downloadsPanelEmpty.label;
+ </description>
+
+ <vbox id="downloadsFooter">
+ <hbox id="downloadsSummary"
+ align="center"
+ orient="horizontal"
+ onkeydown="DownloadsSummary.onKeyDown(event);"
+ onclick="DownloadsSummary.onClick(event);">
+ <image class="downloadTypeIcon" />
+ <vbox>
+ <description id="downloadsSummaryDescription"
+ style="min-width: &downloadsSummary.minWidth2;"/>
+ <progressmeter id="downloadsSummaryProgress"
+ class="downloadProgress"
+ min="0"
+ max="100"
+ mode="normal" />
+ <description id="downloadsSummaryDetails"
+ style="width: &downloadDetails.width;"
+ crop="end"/>
+ </vbox>
+ </hbox>
+
+ <button id="downloadsHistory"
+ class="plain"
+ label="&downloadsHistory.label;"
+ accesskey="&downloadsHistory.accesskey;"
+ oncommand="DownloadsPanel.showDownloadsHistory();"/>
+ </vbox>
+ </panel>
+ </popupset>
+</overlay>
diff --git a/components/downloads/content/indicator.js b/components/downloads/content/indicator.js
new file mode 100644
index 0000000..1a2175a
--- /dev/null
+++ b/components/downloads/content/indicator.js
@@ -0,0 +1,609 @@
+/* -*- 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/. */
+
+/**
+ * Handles the indicator that displays the progress of ongoing downloads, which
+ * is also used as the anchor for the downloads panel.
+ *
+ * This module includes the following constructors and global objects:
+ *
+ * DownloadsButton
+ * Main entry point for the downloads indicator. Depending on how the toolbars
+ * have been customized, this object determines if we should show a fully
+ * functional indicator, a placeholder used during customization and in the
+ * customization palette, or a neutral view as a temporary anchor for the
+ * downloads panel.
+ *
+ * DownloadsIndicatorView
+ * Builds and updates the actual downloads status widget, responding to changes
+ * in the global status data, or provides a neutral view if the indicator is
+ * removed from the toolbars and only used as a temporary anchor. In addition,
+ * handles the user interaction events raised by the widget.
+ */
+
+"use strict";
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsButton
+
+/**
+ * Main entry point for the downloads indicator. Depending on how the toolbars
+ * have been customized, this object determines if we should show a fully
+ * functional indicator, a placeholder used during customization and in the
+ * customization palette, or a neutral view as a temporary anchor for the
+ * downloads panel.
+ */
+const DownloadsButton = {
+ /**
+ * Location of the indicator overlay.
+ */
+ get kIndicatorOverlay()
+ "chrome://browser/content/downloads/indicatorOverlay.xul",
+
+ /**
+ * Returns a reference to the downloads button position placeholder, or null
+ * if not available because it has been removed from the toolbars.
+ */
+ get _placeholder()
+ {
+ return document.getElementById("downloads-button");
+ },
+
+ /**
+ * This function is called asynchronously just after window initialization.
+ *
+ * NOTE: This function should limit the input/output it performs to improve
+ * startup time, and in particular should not cause the Download Manager
+ * service to start.
+ */
+ initializeIndicator: function DB_initializeIndicator()
+ {
+ this._update();
+ },
+
+ /**
+ * Indicates whether toolbar customization is in progress.
+ */
+ _customizing: false,
+
+ /**
+ * This function is called when toolbar customization starts.
+ *
+ * During customization, we never show the actual download progress indication
+ * or the event notifications, but we show a neutral placeholder. The neutral
+ * placeholder is an ordinary button defined in the browser window that can be
+ * moved freely between the toolbars and the customization palette.
+ */
+ customizeStart: function DB_customizeStart()
+ {
+ // Hide the indicator and prevent it to be displayed as a temporary anchor
+ // during customization, even if requested using the getAnchor method.
+ this._customizing = true;
+ this._anchorRequested = false;
+
+ let indicator = DownloadsIndicatorView.indicator;
+ if (indicator) {
+ indicator.collapsed = true;
+ }
+
+ let placeholder = this._placeholder;
+ if (placeholder) {
+ placeholder.collapsed = false;
+ }
+ },
+
+ /**
+ * This function is called when toolbar customization ends.
+ */
+ customizeDone: function DB_customizeDone()
+ {
+ this._customizing = false;
+ this._update();
+ },
+
+ /**
+ * This function is called during initialization or when toolbar customization
+ * ends. It determines if we should enable or disable the object that keeps
+ * the indicator updated, and ensures that the placeholder is hidden unless it
+ * has been moved to the customization palette.
+ *
+ * NOTE: This function is also called on startup, thus it should limit the
+ * input/output it performs, and in particular should not cause the
+ * Download Manager service to start.
+ */
+ _update: function DB_update() {
+ this._updatePositionInternal();
+
+ if (!DownloadsCommon.useToolkitUI) {
+ DownloadsIndicatorView.ensureInitialized();
+ } else {
+ DownloadsIndicatorView.ensureTerminated();
+ }
+ },
+
+ /**
+ * Determines the position where the indicator should appear, and moves its
+ * associated element to the new position. This does not happen if the
+ * indicator is currently being used as the anchor for the panel, to ensure
+ * that the panel doesn't flicker because we move the DOM element to which
+ * it's anchored.
+ */
+ updatePosition: function DB_updatePosition()
+ {
+ if (!this._anchorRequested) {
+ this._updatePositionInternal();
+ }
+ },
+
+ /**
+ * Determines the position where the indicator should appear, and moves its
+ * associated element to the new position.
+ *
+ * @return Anchor element, or null if the indicator is not visible.
+ */
+ _updatePositionInternal: function DB_updatePositionInternal()
+ {
+ let indicator = DownloadsIndicatorView.indicator;
+ if (!indicator) {
+ // Exit now if the indicator overlay isn't loaded yet.
+ return null;
+ }
+
+ let placeholder = this._placeholder;
+ if (!placeholder) {
+ // The placeholder has been removed from the browser window.
+ indicator.collapsed = true;
+ // Move the indicator to a safe position on the toolbar, since otherwise
+ // it may break the merge of adjacent items, like back/forward + urlbar.
+ indicator.parentNode.appendChild(indicator);
+ return null;
+ }
+
+ // Position the indicator where the placeholder is located. We should
+ // update the position even if the placeholder is located on an invisible
+ // toolbar, because the toolbar may be displayed later.
+ placeholder.parentNode.insertBefore(indicator, placeholder);
+ placeholder.collapsed = true;
+ indicator.collapsed = false;
+
+ indicator.open = this._anchorRequested;
+
+ // Determine if the placeholder is located on an invisible toolbar.
+ if (!isElementVisible(placeholder.parentNode)) {
+ return null;
+ }
+
+ return DownloadsIndicatorView.indicatorAnchor;
+ },
+
+ /**
+ * Checks whether the indicator is, or will soon be visible in the browser
+ * window.
+ *
+ * @param aCallback
+ * Called once the indicator overlay has loaded. Gets a boolean
+ * argument representing the indicator visibility.
+ */
+ checkIsVisible: function DB_checkIsVisible(aCallback)
+ {
+ function DB_CEV_callback() {
+ if (!this._placeholder) {
+ aCallback(false);
+ } else {
+ let element = DownloadsIndicatorView.indicator || this._placeholder;
+ aCallback(isElementVisible(element.parentNode));
+ }
+ }
+ DownloadsOverlayLoader.ensureOverlayLoaded(this.kIndicatorOverlay,
+ DB_CEV_callback.bind(this));
+ },
+
+ /**
+ * Indicates whether we should try and show the indicator temporarily as an
+ * anchor for the panel, even if the indicator would be hidden by default.
+ */
+ _anchorRequested: false,
+
+ /**
+ * Ensures that there is an anchor available for the panel.
+ *
+ * @param aCallback
+ * Called when the anchor is available, passing the element where the
+ * panel should be anchored, or null if an anchor is not available (for
+ * example because both the tab bar and the navigation bar are hidden).
+ */
+ getAnchor: function DB_getAnchor(aCallback)
+ {
+ // Do not allow anchoring the panel to the element while customizing.
+ if (this._customizing) {
+ aCallback(null);
+ return;
+ }
+
+ function DB_GA_callback() {
+ this._anchorRequested = true;
+ aCallback(this._updatePositionInternal());
+ }
+
+ DownloadsOverlayLoader.ensureOverlayLoaded(this.kIndicatorOverlay,
+ DB_GA_callback.bind(this));
+ },
+
+ /**
+ * Allows the temporary anchor to be hidden.
+ */
+ releaseAnchor: function DB_releaseAnchor()
+ {
+ this._anchorRequested = false;
+ this._updatePositionInternal();
+ },
+
+ get _tabsToolbar()
+ {
+ delete this._tabsToolbar;
+ return this._tabsToolbar = document.getElementById("TabsToolbar");
+ },
+
+ get _navBar()
+ {
+ delete this._navBar;
+ return this._navBar = document.getElementById("nav-bar");
+ }
+};
+
+Object.defineProperty(this, "DownloadsButton", {
+ value: DownloadsButton,
+ enumerable: true,
+ writable: false
+});
+
+////////////////////////////////////////////////////////////////////////////////
+//// DownloadsIndicatorView
+
+/**
+ * Builds and updates the actual downloads status widget, responding to changes
+ * in the global status data, or provides a neutral view if the indicator is
+ * removed from the toolbars and only used as a temporary anchor. In addition,
+ * handles the user interaction events raised by the widget.
+ */
+const DownloadsIndicatorView = {
+ /**
+ * True when the view is connected with the underlying downloads data.
+ */
+ _initialized: false,
+
+ /**
+ * True when the user interface elements required to display the indicator
+ * have finished loading in the browser window, and can be referenced.
+ */
+ _operational: false,
+
+ /**
+ * Prepares the downloads indicator to be displayed.
+ */
+ ensureInitialized: function DIV_ensureInitialized()
+ {
+ if (this._initialized) {
+ return;
+ }
+ this._initialized = true;
+
+ window.addEventListener("unload", this.onWindowUnload, false);
+ DownloadsCommon.getIndicatorData(window).addView(this);
+ },
+
+ /**
+ * Frees the internal resources related to the indicator.
+ */
+ ensureTerminated: function DIV_ensureTerminated()
+ {
+ if (!this._initialized) {
+ return;
+ }
+ this._initialized = false;
+
+ window.removeEventListener("unload", this.onWindowUnload, false);
+ DownloadsCommon.getIndicatorData(window).removeView(this);
+
+ // Reset the view properties, so that a neutral indicator is displayed if we
+ // are visible only temporarily as an anchor.
+ this.counter = "";
+ this.percentComplete = 0;
+ this.paused = false;
+ this.attention = false;
+ },
+
+ /**
+ * Ensures that the user interface elements required to display the indicator
+ * are loaded, then invokes the given callback.
+ */
+ _ensureOperational: function DIV_ensureOperational(aCallback)
+ {
+ if (this._operational) {
+ aCallback();
+ return;
+ }
+
+ function DIV_EO_callback() {
+ this._operational = true;
+
+ // If the view is initialized, we need to update the elements now that
+ // they are finally available in the document.
+ if (this._initialized) {
+ DownloadsCommon.getIndicatorData(window).refreshView(this);
+ }
+
+ aCallback();
+ }
+
+ DownloadsOverlayLoader.ensureOverlayLoaded(
+ DownloadsButton.kIndicatorOverlay,
+ DIV_EO_callback.bind(this));
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Direct control functions
+
+ /**
+ * Set while we are waiting for a notification to fade out.
+ */
+ _notificationTimeout: null,
+
+ /**
+ * If the status indicator is visible in its assigned position, shows for a
+ * brief time a visual notification of a relevant event, like a new download.
+ *
+ * @param aType
+ * Set to "start" for new downloads, "finish" for completed downloads.
+ */
+ showEventNotification: function DIV_showEventNotification(aType)
+ {
+ if (!this._initialized) {
+ return;
+ }
+
+ if (!DownloadsCommon.animateNotifications) {
+ return;
+ }
+
+ // No need to show visual notification if the panel is visible.
+ if (DownloadsPanel.isPanelShowing) {
+ return;
+ }
+
+ function DIV_SEN_callback() {
+ if (this._notificationTimeout) {
+ clearTimeout(this._notificationTimeout);
+ }
+
+ // Now that the overlay is loaded, place the indicator in its final
+ // position.
+ DownloadsButton.updatePosition();
+
+ let indicator = this.indicator;
+ indicator.setAttribute("notification", aType);
+ this._notificationTimeout = setTimeout(
+ function () indicator.removeAttribute("notification"), 1000);
+ }
+
+ this._ensureOperational(DIV_SEN_callback.bind(this));
+ },
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// Callback functions from DownloadsIndicatorData
+
+ /**
+ * Indicates whether the indicator should be shown because there are some
+ * downloads to be displayed.
+ */
+ set hasDownloads(aValue)
+ {
+ if (this._hasDownloads != aValue) {
+ this._hasDownloads = aValue;
+
+ // If there is at least one download, ensure that the view elements are
+ // loaded before determining the position of the downloads button.
+ if (aValue) {
+ this._ensureOperational(function() DownloadsButton.updatePosition());
+ } else {
+ DownloadsButton.updatePosition();
+ }
+ }
+ return aValue;
+ },
+ get hasDownloads()
+ {
+ return this._hasDownloads;
+ },
+ _hasDownloads: false,
+
+ /**
+ * Status text displayed in the indicator. If this is set to an empty value,
+ * then the small downloads icon is displayed instead of the text.
+ */
+ set counter(aValue)
+ {
+ if (!this._operational) {
+ return this._counter;
+ }
+
+ if (this._counter !== aValue) {
+ this._counter = aValue;
+ if (this._counter)
+ this.indicator.setAttribute("counter", "true");
+ else
+ this.indicator.removeAttribute("counter");
+ // We have to set the attribute instead of using the property because the
+ // XBL binding isn't applied if the element is invisible for any reason.
+ this._indicatorCounter.setAttribute("value", aValue);
+ }
+ return aValue;
+ },
+ _counter: null,
+
+ /**
+ * Progress indication to display, from 0 to 100, or -1 if unknown. The
+ * progress bar is hidden if the current progress is unknown and no status
+ * text is set in the "counter" property.
+ */
+ set percentComplete(aValue)
+ {
+ if (!this._operational) {
+ return this._percentComplete;
+ }
+
+ if (this._percentComplete !== aValue) {
+ this._percentComplete = aValue;
+ if (this._percentComplete >= 0)
+ this.indicator.setAttribute("progress", "true");
+ else
+ this.indicator.removeAttribute("progress");
+ // We have to set the attribute instead of using the property because the
+ // XBL binding isn't applied if the element is invisible for any reason.
+ this._indicatorProgress.setAttribute("value", Math.max(aValue, 0));
+ }
+ return aValue;
+ },
+ _percentComplete: null,
+
+ /**
+ * Indicates whether the progress won't advance because of a paused state.
+ * Setting this property forces a paused progress bar to be displayed, even if
+ * the current progress information is unavailable.
+ */
+ set paused(aValue)
+ {
+ if (!this._operational) {
+ return this._paused;
+ }
+
+ if (this._paused != aValue) {
+ this._paused = aValue;
+ if (this._paused) {
+ this.indicator.setAttribute("paused", "true")
+ } else {
+ this.indicator.removeAttribute("paused");
+ }
+ }
+ return aValue;
+ },
+ _paused: false,
+
+ /**
+ * Set when the indicator should draw user attention to itself.
+ */
+ set attention(aValue)
+ {
+ if (!this._operational) {
+ return this._attention;
+ }
+
+ if (this._attention != aValue) {
+ this._attention = aValue;
+ if (aValue) {
+ this.indicator.setAttribute("attention", "true");
+ } else {
+ this.indicator.removeAttribute("attention");
+ }
+ }
+ return aValue;
+ },
+ _attention: false,
+
+ //////////////////////////////////////////////////////////////////////////////
+ //// User interface event functions
+
+ onWindowUnload: function DIV_onWindowUnload()
+ {
+ // This function is registered as an event listener, we can't use "this".
+ DownloadsIndicatorView.ensureTerminated();
+ },
+
+ onCommand: function DIV_onCommand(aEvent)
+ {
+ if (DownloadsCommon.useToolkitUI) {
+ // The panel won't suppress attention for us, we need to clear now.
+ DownloadsCommon.getIndicatorData(window).attention = false;
+ BrowserDownloadsUI();
+ } else {
+ DownloadsPanel.showPanel();
+ }
+
+ aEvent.stopPropagation();
+ },
+
+ onDragOver: function DIV_onDragOver(aEvent)
+ {
+ browserDragAndDrop.dragOver(aEvent);
+ },
+
+ onDrop: function DIV_onDrop(aEvent)
+ {
+ let dt = aEvent.dataTransfer;
+ // If dragged item is from our source, do not try to
+ // redownload already downloaded file.
+ if (dt.mozGetDataAt("application/x-moz-file", 0))
+ return;
+
+ let links = browserDragAndDrop.dropLinks(aEvent);
+ if (!links.length)
+ return;
+ let sourceDoc = dt.mozSourceNode ? dt.mozSourceNode.ownerDocument : document;
+ let handled = false;
+ for (let link of links) {
+ if (link.url.startsWith("about:"))
+ continue;
+ saveURL(link.url, link.name, null, true, true, null, sourceDoc);
+ handled = true;
+ }
+ if (handled) {
+ aEvent.preventDefault();
+ }
+ },
+
+ /**
+ * Returns a reference to the main indicator element, or null if the element
+ * is not present in the browser window yet.
+ */
+ get indicator()
+ {
+ let indicator = document.getElementById("downloads-indicator");
+ if (!indicator) {
+ return null;
+ }
+
+ // Once the element is loaded, it will never be unloaded.
+ delete this.indicator;
+ return this.indicator = indicator;
+ },
+
+ get indicatorAnchor()
+ {
+ delete this.indicatorAnchor;
+ return this.indicatorAnchor =
+ document.getElementById("downloads-indicator-anchor");
+ },
+
+ get _indicatorCounter()
+ {
+ delete this._indicatorCounter;
+ return this._indicatorCounter =
+ document.getElementById("downloads-indicator-counter");
+ },
+
+ get _indicatorProgress()
+ {
+ delete this._indicatorProgress;
+ return this._indicatorProgress =
+ document.getElementById("downloads-indicator-progress");
+ }
+};
+
+Object.defineProperty(this, "DownloadsIndicatorView", {
+ value: DownloadsIndicatorView,
+ enumerable: true,
+ writable: false
+});
diff --git a/components/downloads/content/indicatorOverlay.xul b/components/downloads/content/indicatorOverlay.xul
new file mode 100644
index 0000000..efb6cab
--- /dev/null
+++ b/components/downloads/content/indicatorOverlay.xul
@@ -0,0 +1,60 @@
+<?xml version="1.0"?>
+<!-- -*- Mode: HTML; 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/. -->
+
+<?xml-stylesheet href="chrome://browser/content/downloads/downloads.css"?>
+<?xml-stylesheet href="chrome://browser/skin/downloads/downloads.css"?>
+
+<!DOCTYPE overlay [
+ <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd" >
+ %browserDTD;
+ <!ENTITY % downloadsDTD SYSTEM "chrome://browser/locale/downloads/downloads.dtd" >
+ %downloadsDTD;
+]>
+
+<overlay xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="indicatorOverlay">
+
+ <popupset>
+ <!-- The downloads indicator is placed in its final toolbar location
+ programmatically, and can be shown temporarily even when its
+ placeholder is removed from the toolbars. Its initial location within
+ the document must not be a toolbar or the toolbar palette, otherwise the
+ toolbar handling code could remove it from the document. -->
+ <toolbarbutton id="downloads-indicator"
+ class="toolbarbutton-1 chromeclass-toolbar-additional"
+ tooltiptext="&downloads.tooltip;"
+ collapsed="true"
+ oncommand="DownloadsIndicatorView.onCommand(event);"
+ ondrop="DownloadsIndicatorView.onDrop(event);"
+ ondragover="DownloadsIndicatorView.onDragOver(event);"
+ ondragenter="DownloadsIndicatorView.onDragOver(event);"
+ ondragleave="DownloadsIndicatorView.onDragLeave(event);"
+ skipintoolbarset="true">
+ <!-- The panel's anchor area is smaller than the outer button, but must
+ always be visible and must not move or resize when the indicator
+ state changes, otherwise the panel could change its position or lose
+ its arrow unexpectedly. -->
+ <stack id="downloads-indicator-anchor"
+ class="toolbarbutton-icon">
+ <vbox id="downloads-indicator-progress-area"
+ pack="center">
+ <description id="downloads-indicator-counter"/>
+ <progressmeter id="downloads-indicator-progress"
+ class="plain"
+ min="0"
+ max="100"/>
+ </vbox>
+ <vbox id="downloads-indicator-icon"/>
+ <vbox id="downloads-indicator-notification"/>
+ </stack>
+ <label class="toolbarbutton-text" crop="right" flex="1"
+ value="&downloads.label;"/>
+ </toolbarbutton>
+ </popupset>
+</overlay>
diff --git a/components/downloads/jar.mn b/components/downloads/jar.mn
new file mode 100644
index 0000000..8c0b519
--- /dev/null
+++ b/components/downloads/jar.mn
@@ -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/.
+
+browser.jar:
+* content/browser/downloads/download.xml (content/download.xml)
+ content/browser/downloads/download.css (content/download.css)
+ content/browser/downloads/downloads.css (content/downloads.css)
+* content/browser/downloads/downloads.js (content/downloads.js)
+* content/browser/downloads/downloadsOverlay.xul (content/downloadsOverlay.xul)
+ content/browser/downloads/indicator.js (content/indicator.js)
+ content/browser/downloads/indicatorOverlay.xul (content/indicatorOverlay.xul)
+* content/browser/downloads/allDownloadsViewOverlay.xul (content/allDownloadsViewOverlay.xul)
+ content/browser/downloads/allDownloadsViewOverlay.js (content/allDownloadsViewOverlay.js)
+ content/browser/downloads/allDownloadsViewOverlay.css (content/allDownloadsViewOverlay.css)
+* content/browser/downloads/contentAreaDownloadsView.xul (content/contentAreaDownloadsView.xul)
+ content/browser/downloads/contentAreaDownloadsView.js (content/contentAreaDownloadsView.js)
+ content/browser/downloads/contentAreaDownloadsView.css (content/contentAreaDownloadsView.css)
diff --git a/components/downloads/moz.build b/components/downloads/moz.build
new file mode 100644
index 0000000..abfaab7
--- /dev/null
+++ b/components/downloads/moz.build
@@ -0,0 +1,23 @@
+# -*- 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/.
+
+JAR_MANIFESTS += ['jar.mn']
+
+EXTRA_COMPONENTS += [
+ 'BrowserDownloads.manifest',
+ 'DownloadsStartup.js',
+ 'DownloadsUI.js',
+]
+
+EXTRA_JS_MODULES += [
+ 'DownloadsLogger.jsm',
+ 'DownloadsTaskbar.jsm',
+ 'DownloadsViewUI.jsm',
+]
+
+EXTRA_PP_JS_MODULES += [
+ 'DownloadsCommon.jsm',
+]
diff --git a/components/feeds/BrowserFeeds.manifest b/components/feeds/BrowserFeeds.manifest
new file mode 100644
index 0000000..a584323
--- /dev/null
+++ b/components/feeds/BrowserFeeds.manifest
@@ -0,0 +1,28 @@
+# WebappRT doesn't need these instructions, and they don't necessarily work
+# with it, but it does use a GRE directory that the GRE shares with Firefox,
+# so in order to prevent the instructions from being processed for WebappRT,
+# we need to restrict them to the applications that depend on them, i.e.:
+#
+# b2g: {3c2e2abc-06d4-11e1-ac3b-374f68613e61}
+# browser: {8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
+# mobile/android: {aa3c5121-dab2-40e2-81ca-7ea25febc110}
+# mobile/xul: {a23983c0-fd0e-11dc-95ff-0800200c9a66}
+#
+# In theory we should do this for all these instructions, but in practice it is
+# sufficient to do it for the app-startup one, and the file is simpler that way.
+
+component {229fa115-9412-4d32-baf3-2fc407f76fb1} FeedConverter.js
+contract @mozilla.org/streamconv;1?from=application/vnd.mozilla.maybe.feed&to=*/* {229fa115-9412-4d32-baf3-2fc407f76fb1}
+contract @mozilla.org/streamconv;1?from=application/vnd.mozilla.maybe.video.feed&to=*/* {229fa115-9412-4d32-baf3-2fc407f76fb1}
+contract @mozilla.org/streamconv;1?from=application/vnd.mozilla.maybe.audio.feed&to=*/* {229fa115-9412-4d32-baf3-2fc407f76fb1}
+component {2376201c-bbc6-472f-9b62-7548040a61c6} FeedConverter.js
+contract @mozilla.org/browser/feeds/result-service;1 {2376201c-bbc6-472f-9b62-7548040a61c6}
+component {4f91ef2e-57ba-472e-ab7a-b4999e42d6c0} FeedConverter.js
+contract @mozilla.org/network/protocol;1?name=feed {4f91ef2e-57ba-472e-ab7a-b4999e42d6c0}
+component {1c31ed79-accd-4b94-b517-06e0c81999d5} FeedConverter.js
+contract @mozilla.org/network/protocol;1?name=pcast {1c31ed79-accd-4b94-b517-06e0c81999d5}
+component {49bb6593-3aff-4eb3-a068-2712c28bd58e} FeedWriter.js
+contract @mozilla.org/browser/feeds/result-writer;1 {49bb6593-3aff-4eb3-a068-2712c28bd58e}
+component {792a7e82-06a0-437c-af63-b2d12e808acc} WebContentConverter.js
+contract @mozilla.org/embeddor.implemented/web-content-handler-registrar;1 {792a7e82-06a0-437c-af63-b2d12e808acc}
+category app-startup WebContentConverter service,@mozilla.org/embeddor.implemented/web-content-handler-registrar;1 application={3c2e2abc-06d4-11e1-ac3b-374f68613e61} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4} application={aa3c5121-dab2-40e2-81ca-7ea25febc110} application={a23983c0-fd0e-11dc-95ff-0800200c9a66}
diff --git a/components/feeds/FeedConverter.js b/components/feeds/FeedConverter.js
new file mode 100644
index 0000000..d0f5737
--- /dev/null
+++ b/components/feeds/FeedConverter.js
@@ -0,0 +1,591 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/debug.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+function LOG(str) {
+ dump("*** " + str + "\n");
+}
+
+const FS_CONTRACTID = "@mozilla.org/browser/feeds/result-service;1";
+const FPH_CONTRACTID = "@mozilla.org/network/protocol;1?name=feed";
+const PCPH_CONTRACTID = "@mozilla.org/network/protocol;1?name=pcast";
+
+const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
+const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed";
+const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed";
+const TYPE_ANY = "*/*";
+
+const PREF_SELECTED_APP = "browser.feeds.handlers.application";
+const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice";
+const PREF_SELECTED_ACTION = "browser.feeds.handler";
+const PREF_SELECTED_READER = "browser.feeds.handler.default";
+
+const PREF_VIDEO_SELECTED_APP = "browser.videoFeeds.handlers.application";
+const PREF_VIDEO_SELECTED_WEB = "browser.videoFeeds.handlers.webservice";
+const PREF_VIDEO_SELECTED_ACTION = "browser.videoFeeds.handler";
+const PREF_VIDEO_SELECTED_READER = "browser.videoFeeds.handler.default";
+
+const PREF_AUDIO_SELECTED_APP = "browser.audioFeeds.handlers.application";
+const PREF_AUDIO_SELECTED_WEB = "browser.audioFeeds.handlers.webservice";
+const PREF_AUDIO_SELECTED_ACTION = "browser.audioFeeds.handler";
+const PREF_AUDIO_SELECTED_READER = "browser.audioFeeds.handler.default";
+
+function getPrefAppForType(t) {
+ switch (t) {
+ case Ci.nsIFeed.TYPE_VIDEO:
+ return PREF_VIDEO_SELECTED_APP;
+
+ case Ci.nsIFeed.TYPE_AUDIO:
+ return PREF_AUDIO_SELECTED_APP;
+
+ default:
+ return PREF_SELECTED_APP;
+ }
+}
+
+function getPrefWebForType(t) {
+ switch (t) {
+ case Ci.nsIFeed.TYPE_VIDEO:
+ return PREF_VIDEO_SELECTED_WEB;
+
+ case Ci.nsIFeed.TYPE_AUDIO:
+ return PREF_AUDIO_SELECTED_WEB;
+
+ default:
+ return PREF_SELECTED_WEB;
+ }
+}
+
+function getPrefActionForType(t) {
+ switch (t) {
+ case Ci.nsIFeed.TYPE_VIDEO:
+ return PREF_VIDEO_SELECTED_ACTION;
+
+ case Ci.nsIFeed.TYPE_AUDIO:
+ return PREF_AUDIO_SELECTED_ACTION;
+
+ default:
+ return PREF_SELECTED_ACTION;
+ }
+}
+
+function getPrefReaderForType(t) {
+ switch (t) {
+ case Ci.nsIFeed.TYPE_VIDEO:
+ return PREF_VIDEO_SELECTED_READER;
+
+ case Ci.nsIFeed.TYPE_AUDIO:
+ return PREF_AUDIO_SELECTED_READER;
+
+ default:
+ return PREF_SELECTED_READER;
+ }
+}
+
+function safeGetCharPref(pref, defaultValue) {
+ var prefs =
+ Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+ try {
+ return prefs.getCharPref(pref);
+ }
+ catch (e) {
+ }
+ return defaultValue;
+}
+
+function FeedConverter() {
+}
+FeedConverter.prototype = {
+ classID: Components.ID("{229fa115-9412-4d32-baf3-2fc407f76fb1}"),
+
+ /**
+ * This is the downloaded text data for the feed.
+ */
+ _data: null,
+
+ /**
+ * This is the object listening to the conversion, which is ultimately the
+ * docshell for the load.
+ */
+ _listener: null,
+
+ /**
+ * Records if the feed was sniffed
+ */
+ _sniffed: false,
+
+ /**
+ * See nsIStreamConverter.idl
+ */
+ convert: function FC_convert(sourceStream, sourceType, destinationType,
+ context) {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ /**
+ * See nsIStreamConverter.idl
+ */
+ asyncConvertData: function FC_asyncConvertData(sourceType, destinationType,
+ listener, context) {
+ this._listener = listener;
+ },
+
+ /**
+ * Whether or not the preview page is being forced.
+ */
+ _forcePreviewPage: false,
+
+ /**
+ * Release our references to various things once we're done using them.
+ */
+ _releaseHandles: function FC__releaseHandles() {
+ this._listener = null;
+ this._request = null;
+ this._processor = null;
+ },
+
+ /**
+ * See nsIFeedResultListener.idl
+ */
+ handleResult: function FC_handleResult(result) {
+ // Feeds come in various content types, which our feed sniffer coerces to
+ // the maybe.feed type. However, feeds are used as a transport for
+ // different data types, e.g. news/blogs (traditional feed), video/audio
+ // (podcasts) and photos (photocasts, photostreams). Each of these is
+ // different in that there's a different class of application suitable for
+ // handling feeds of that type, but without a content-type differentiation
+ // it is difficult for us to disambiguate.
+ //
+ // The other problem is that if the user specifies an auto-action handler
+ // for one feed application, the fact that the content type is shared means
+ // that all other applications will auto-load with that handler too,
+ // regardless of the content-type.
+ //
+ // This means that content-type alone is not enough to determine whether
+ // or not a feed should be auto-handled. This means that for feeds we need
+ // to always use this stream converter, even when an auto-action is
+ // specified, not the basic one provided by WebContentConverter. This
+ // converter needs to consume all of the data and parse it, and based on
+ // that determination make a judgment about type.
+ //
+ // Since there are no content types for this content, and I'm not going to
+ // invent any, the upshot is that while a user can set an auto-handler for
+ // generic feed content, the system will prevent them from setting an auto-
+ // handler for other stream types. In those cases, the user will always see
+ // the preview page and have to select a handler. We can guess and show
+ // a client handler, but will not be able to show web handlers for those
+ // types.
+ //
+ // If this is just a feed, not some kind of specialized application, then
+ // auto-handlers can be set and we should obey them.
+ try {
+ var feedService =
+ Cc["@mozilla.org/browser/feeds/result-service;1"].
+ getService(Ci.nsIFeedResultService);
+ if (!this._forcePreviewPage && result.doc) {
+ var feed = result.doc.QueryInterface(Ci.nsIFeed);
+ var handler = safeGetCharPref(getPrefActionForType(feed.type), "ask");
+
+ if (handler != "ask") {
+ if (handler == "reader")
+ handler = safeGetCharPref(getPrefReaderForType(feed.type), "bookmarks");
+ switch (handler) {
+ case "web":
+ var wccr =
+ Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
+ getService(Ci.nsIWebContentConverterService);
+ if ((feed.type == Ci.nsIFeed.TYPE_FEED &&
+ wccr.getAutoHandler(TYPE_MAYBE_FEED)) ||
+ (feed.type == Ci.nsIFeed.TYPE_VIDEO &&
+ wccr.getAutoHandler(TYPE_MAYBE_VIDEO_FEED)) ||
+ (feed.type == Ci.nsIFeed.TYPE_AUDIO &&
+ wccr.getAutoHandler(TYPE_MAYBE_AUDIO_FEED))) {
+ wccr.loadPreferredHandler(this._request);
+ return;
+ }
+ break;
+
+ default:
+ LOG("unexpected handler: " + handler);
+ // fall through -- let feed service handle error
+ case "bookmarks":
+ case "client":
+ try {
+ var title = feed.title ? feed.title.plainText() : "";
+ var desc = feed.subtitle ? feed.subtitle.plainText() : "";
+ feedService.addToClientReader(result.uri.spec, title, desc, feed.type);
+ return;
+ } catch(ex) { /* fallback to preview mode */ }
+ }
+ }
+ }
+
+ var ios =
+ Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ var chromeChannel;
+
+ // handling a redirect, hence forwarding the loadInfo from the old channel
+ // to the newchannel.
+ var oldChannel = this._request.QueryInterface(Ci.nsIChannel);
+ var loadInfo = oldChannel.loadInfo;
+
+ // If there was no automatic handler, or this was a podcast,
+ // photostream or some other kind of application, show the preview page
+ // if the parser returned a document.
+ if (result.doc) {
+
+ // Store the result in the result service so that the display
+ // page can access it.
+ feedService.addFeedResult(result);
+
+ // Now load the actual XUL document.
+ var aboutFeedsURI = ios.newURI("about:feeds", null, null);
+ chromeChannel = ios.newChannelFromURIWithLoadInfo(aboutFeedsURI, loadInfo);
+ chromeChannel.originalURI = result.uri;
+ chromeChannel.owner =
+ Services.scriptSecurityManager.getNoAppCodebasePrincipal(aboutFeedsURI);
+ } else {
+ chromeChannel = ios.newChannelFromURIWithLoadInfo(result.uri, loadInfo);
+ }
+
+ chromeChannel.loadGroup = this._request.loadGroup;
+ chromeChannel.asyncOpen2(this._listener);
+ }
+ finally {
+ this._releaseHandles();
+ }
+ },
+
+ /**
+ * See nsIStreamListener.idl
+ */
+ onDataAvailable: function FC_onDataAvailable(request, context, inputStream,
+ sourceOffset, count) {
+ if (this._processor)
+ this._processor.onDataAvailable(request, context, inputStream,
+ sourceOffset, count);
+ },
+
+ /**
+ * See nsIRequestObserver.idl
+ */
+ onStartRequest: function FC_onStartRequest(request, context) {
+ var channel = request.QueryInterface(Ci.nsIChannel);
+
+ // Check for a header that tells us there was no sniffing
+ // The value doesn't matter.
+ try {
+ var httpChannel = channel.QueryInterface(Ci.nsIHttpChannel);
+ // Make sure to check requestSucceeded before the potentially-throwing
+ // getResponseHeader.
+ if (!httpChannel.requestSucceeded) {
+ // Just give up, but don't forget to cancel the channel first!
+ request.cancel(Cr.NS_BINDING_ABORTED);
+ return;
+ }
+ var noSniff = httpChannel.getResponseHeader("X-Moz-Is-Feed");
+ }
+ catch (ex) {
+ this._sniffed = true;
+ }
+
+ this._request = request;
+
+ // Save and reset the forced state bit early, in case there's some kind of
+ // error.
+ var feedService =
+ Cc["@mozilla.org/browser/feeds/result-service;1"].
+ getService(Ci.nsIFeedResultService);
+ this._forcePreviewPage = feedService.forcePreviewPage;
+ feedService.forcePreviewPage = false;
+
+ // Parse feed data as it comes in
+ this._processor =
+ Cc["@mozilla.org/feed-processor;1"].
+ createInstance(Ci.nsIFeedProcessor);
+ this._processor.listener = this;
+ this._processor.parseAsync(null, channel.URI);
+
+ this._processor.onStartRequest(request, context);
+ },
+
+ /**
+ * See nsIRequestObserver.idl
+ */
+ onStopRequest: function FC_onStopRequest(request, context, status) {
+ if (this._processor)
+ this._processor.onStopRequest(request, context, status);
+ },
+
+ /**
+ * See nsISupports.idl
+ */
+ QueryInterface: function FC_QueryInterface(iid) {
+ if (iid.equals(Ci.nsIFeedResultListener) ||
+ iid.equals(Ci.nsIStreamConverter) ||
+ iid.equals(Ci.nsIStreamListener) ||
+ iid.equals(Ci.nsIRequestObserver)||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+};
+
+/**
+ * Keeps parsed FeedResults around for use elsewhere in the UI after the stream
+ * converter completes.
+ */
+function FeedResultService() {
+}
+
+FeedResultService.prototype = {
+ classID: Components.ID("{2376201c-bbc6-472f-9b62-7548040a61c6}"),
+
+ /**
+ * A URI spec -> [nsIFeedResult] hash. We have to keep a list as the
+ * value in case the same URI is requested concurrently.
+ */
+ _results: { },
+
+ /**
+ * See nsIFeedResultService.idl
+ */
+ forcePreviewPage: false,
+
+ /**
+ * See nsIFeedResultService.idl
+ */
+ addToClientReader: function FRS_addToClientReader(spec, title, subtitle, feedType) {
+ var prefs =
+ Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+
+ var handler = safeGetCharPref(getPrefActionForType(feedType), "bookmarks");
+ if (handler == "ask" || handler == "reader")
+ handler = safeGetCharPref(getPrefReaderForType(feedType), "bookmarks");
+
+ switch (handler) {
+ case "client":
+ var clientApp = prefs.getComplexValue(getPrefAppForType(feedType), Ci.nsILocalFile);
+
+ // For the benefit of applications that might know how to deal with more
+ // URLs than just feeds, send feed: URLs in the following format:
+ //
+ // http urls: replace scheme with feed, e.g.
+ // http://foo.com/index.rdf -> feed://foo.com/index.rdf
+ // other urls: prepend feed: scheme, e.g.
+ // https://foo.com/index.rdf -> feed:https://foo.com/index.rdf
+ var ios =
+ Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ var feedURI = ios.newURI(spec, null, null);
+ if (feedURI.schemeIs("http")) {
+ feedURI.scheme = "feed";
+ spec = feedURI.spec;
+ }
+ else
+ spec = "feed:" + spec;
+
+ // Retrieving the shell service might fail on some systems, most
+ // notably systems where GNOME is not installed.
+ try {
+ var ss =
+ Cc["@mozilla.org/browser/shell-service;1"].
+ getService(Ci.nsIShellService);
+ ss.openApplicationWithURI(clientApp, spec);
+ } catch(e) {
+ // If we couldn't use the shell service, fallback to using a
+ // nsIProcess instance
+ var p =
+ Cc["@mozilla.org/process/util;1"].
+ createInstance(Ci.nsIProcess);
+ p.init(clientApp);
+ p.run(false, [spec], 1);
+ }
+ break;
+
+ default:
+ // "web" should have been handled elsewhere
+ LOG("unexpected handler: " + handler);
+ // fall through
+ case "bookmarks":
+ var wm =
+ Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator);
+ var topWindow = wm.getMostRecentWindow("navigator:browser");
+ topWindow.PlacesCommandHook.addLiveBookmark(spec, title, subtitle);
+ break;
+ }
+ },
+
+ /**
+ * See nsIFeedResultService.idl
+ */
+ addFeedResult: function FRS_addFeedResult(feedResult) {
+ NS_ASSERT(feedResult.uri != null, "null URI!");
+ NS_ASSERT(feedResult.uri != null, "null feedResult!");
+ var spec = feedResult.uri.spec;
+ if(!this._results[spec])
+ this._results[spec] = [];
+ this._results[spec].push(feedResult);
+ },
+
+ /**
+ * See nsIFeedResultService.idl
+ */
+ getFeedResult: function RFS_getFeedResult(uri) {
+ NS_ASSERT(uri != null, "null URI!");
+ var resultList = this._results[uri.spec];
+ for (var i in resultList) {
+ if (resultList[i].uri == uri)
+ return resultList[i];
+ }
+ return null;
+ },
+
+ /**
+ * See nsIFeedResultService.idl
+ */
+ removeFeedResult: function FRS_removeFeedResult(uri) {
+ NS_ASSERT(uri != null, "null URI!");
+ var resultList = this._results[uri.spec];
+ if (!resultList)
+ return;
+ var deletions = 0;
+ for (var i = 0; i < resultList.length; ++i) {
+ if (resultList[i].uri == uri) {
+ delete resultList[i];
+ ++deletions;
+ }
+ }
+
+ // send the holes to the end
+ resultList.sort();
+ // and trim the list
+ resultList.splice(resultList.length - deletions, deletions);
+ if (resultList.length == 0)
+ delete this._results[uri.spec];
+ },
+
+ createInstance: function FRS_createInstance(outer, iid) {
+ if (outer != null)
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ return this.QueryInterface(iid);
+ },
+
+ QueryInterface: function FRS_QueryInterface(iid) {
+ if (iid.equals(Ci.nsIFeedResultService) ||
+ iid.equals(Ci.nsIFactory) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+};
+
+/**
+ * A protocol handler that attempts to deal with the variant forms of feed:
+ * URIs that are actually either http or https.
+ */
+function GenericProtocolHandler() {
+}
+GenericProtocolHandler.prototype = {
+ _init: function GPH_init(scheme) {
+ var ios =
+ Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ this._http = ios.getProtocolHandler("http");
+ this._scheme = scheme;
+ },
+
+ get scheme() {
+ return this._scheme;
+ },
+
+ get protocolFlags() {
+ return this._http.protocolFlags;
+ },
+
+ get defaultPort() {
+ return this._http.defaultPort;
+ },
+
+ allowPort: function GPH_allowPort(port, scheme) {
+ return this._http.allowPort(port, scheme);
+ },
+
+ newURI: function GPH_newURI(spec, originalCharset, baseURI) {
+ // Feed URIs can be either nested URIs of the form feed:realURI (in which
+ // case we create a nested URI for the realURI) or feed://example.com, in
+ // which case we create a nested URI for the real protocol which is http.
+
+ var scheme = this._scheme + ":";
+ if (spec.substr(0, scheme.length) != scheme)
+ throw Cr.NS_ERROR_MALFORMED_URI;
+
+ var prefix = spec.substr(scheme.length, 2) == "//" ? "http:" : "";
+ var inner = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService).newURI(spec.replace(scheme, prefix),
+ originalCharset, baseURI);
+ var netutil = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil);
+ const URI_INHERITS_SECURITY_CONTEXT = Ci.nsIProtocolHandler
+ .URI_INHERITS_SECURITY_CONTEXT;
+ if (netutil.URIChainHasFlags(inner, URI_INHERITS_SECURITY_CONTEXT))
+ throw Cr.NS_ERROR_MALFORMED_URI;
+
+ var uri = netutil.newSimpleNestedURI(inner);
+ uri.spec = inner.spec.replace(prefix, scheme);
+ return uri;
+ },
+
+ newChannel2: function GPH_newChannel(aUri, aLoadInfo) {
+ var inner = aUri.QueryInterface(Ci.nsINestedURI).innerURI;
+ var channel = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService).
+ newChannelFromURIWithLoadInfo(inner, aLoadInfo);
+
+ if (channel instanceof Components.interfaces.nsIHttpChannel)
+ // Set this so we know this is supposed to be a feed
+ channel.setRequestHeader("X-Moz-Is-Feed", "1", false);
+ channel.originalURI = aUri;
+ return channel;
+ },
+
+
+ QueryInterface: function GPH_QueryInterface(iid) {
+ if (iid.equals(Ci.nsIProtocolHandler) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+function FeedProtocolHandler() {
+ this._init('feed');
+}
+FeedProtocolHandler.prototype = new GenericProtocolHandler();
+FeedProtocolHandler.prototype.classID = Components.ID("{4f91ef2e-57ba-472e-ab7a-b4999e42d6c0}");
+
+function PodCastProtocolHandler() {
+ this._init('pcast');
+}
+PodCastProtocolHandler.prototype = new GenericProtocolHandler();
+PodCastProtocolHandler.prototype.classID = Components.ID("{1c31ed79-accd-4b94-b517-06e0c81999d5}");
+
+var components = [FeedConverter,
+ FeedResultService,
+ FeedProtocolHandler,
+ PodCastProtocolHandler];
+
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/components/feeds/FeedWriter.js b/components/feeds/FeedWriter.js
new file mode 100644
index 0000000..facde58
--- /dev/null
+++ b/components/feeds/FeedWriter.js
@@ -0,0 +1,1397 @@
+# -*- indent-tabs-mode: nil; js-indent-level: 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;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+const FEEDWRITER_CID = Components.ID("{49bb6593-3aff-4eb3-a068-2712c28bd58e}");
+const FEEDWRITER_CONTRACTID = "@mozilla.org/browser/feeds/result-writer;1";
+
+function LOG(str) {
+ var prefB = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+
+ var shouldLog = prefB.getBoolPref("feeds.log", false);
+
+ if (shouldLog)
+ dump("*** Feeds: " + str + "\n");
+}
+
+/**
+ * Wrapper function for nsIIOService::newURI.
+ * @param aURLSpec
+ * The URL string from which to create an nsIURI.
+ * @returns an nsIURI object, or null if the creation of the URI failed.
+ */
+function makeURI(aURLSpec, aCharset) {
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ try {
+ return ios.newURI(aURLSpec, aCharset, null);
+ } catch (ex) { }
+
+ return null;
+}
+
+const XML_NS = "http://www.w3.org/XML/1998/namespace";
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
+const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed";
+const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed";
+const URI_BUNDLE = "chrome://browser/locale/feeds/subscribe.properties";
+
+const PREF_SELECTED_APP = "browser.feeds.handlers.application";
+const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice";
+const PREF_SELECTED_ACTION = "browser.feeds.handler";
+const PREF_SELECTED_READER = "browser.feeds.handler.default";
+
+const PREF_VIDEO_SELECTED_APP = "browser.videoFeeds.handlers.application";
+const PREF_VIDEO_SELECTED_WEB = "browser.videoFeeds.handlers.webservice";
+const PREF_VIDEO_SELECTED_ACTION = "browser.videoFeeds.handler";
+const PREF_VIDEO_SELECTED_READER = "browser.videoFeeds.handler.default";
+
+const PREF_AUDIO_SELECTED_APP = "browser.audioFeeds.handlers.application";
+const PREF_AUDIO_SELECTED_WEB = "browser.audioFeeds.handlers.webservice";
+const PREF_AUDIO_SELECTED_ACTION = "browser.audioFeeds.handler";
+const PREF_AUDIO_SELECTED_READER = "browser.audioFeeds.handler.default";
+
+const PREF_SHOW_FIRST_RUN_UI = "browser.feeds.showFirstRunUI";
+
+const TITLE_ID = "feedTitleText";
+const SUBTITLE_ID = "feedSubtitleText";
+
+function getPrefAppForType(t) {
+ switch (t) {
+ case Ci.nsIFeed.TYPE_VIDEO:
+ return PREF_VIDEO_SELECTED_APP;
+
+ case Ci.nsIFeed.TYPE_AUDIO:
+ return PREF_AUDIO_SELECTED_APP;
+
+ default:
+ return PREF_SELECTED_APP;
+ }
+}
+
+function getPrefWebForType(t) {
+ switch (t) {
+ case Ci.nsIFeed.TYPE_VIDEO:
+ return PREF_VIDEO_SELECTED_WEB;
+
+ case Ci.nsIFeed.TYPE_AUDIO:
+ return PREF_AUDIO_SELECTED_WEB;
+
+ default:
+ return PREF_SELECTED_WEB;
+ }
+}
+
+function getPrefActionForType(t) {
+ switch (t) {
+ case Ci.nsIFeed.TYPE_VIDEO:
+ return PREF_VIDEO_SELECTED_ACTION;
+
+ case Ci.nsIFeed.TYPE_AUDIO:
+ return PREF_AUDIO_SELECTED_ACTION;
+
+ default:
+ return PREF_SELECTED_ACTION;
+ }
+}
+
+function getPrefReaderForType(t) {
+ switch (t) {
+ case Ci.nsIFeed.TYPE_VIDEO:
+ return PREF_VIDEO_SELECTED_READER;
+
+ case Ci.nsIFeed.TYPE_AUDIO:
+ return PREF_AUDIO_SELECTED_READER;
+
+ default:
+ return PREF_SELECTED_READER;
+ }
+}
+
+/**
+ * Converts a number of bytes to the appropriate unit that results in a
+ * number that needs fewer than 4 digits
+ *
+ * @return a pair: [new value with 3 sig. figs., its unit]
+ */
+function convertByteUnits(aBytes) {
+ var units = ["bytes", "kilobyte", "megabyte", "gigabyte"];
+ let unitIndex = 0;
+
+ // convert to next unit if it needs 4 digits (after rounding), but only if
+ // we know the name of the next unit
+ while ((aBytes >= 999.5) && (unitIndex < units.length - 1)) {
+ aBytes /= 1024;
+ unitIndex++;
+ }
+
+ // Get rid of insignificant bits by truncating to 1 or 0 decimal points
+ // 0 -> 0; 1.2 -> 1.2; 12.3 -> 12.3; 123.4 -> 123; 234.5 -> 235
+ aBytes = aBytes.toFixed((aBytes > 0) && (aBytes < 100) ? 1 : 0);
+
+ return [aBytes, units[unitIndex]];
+}
+
+function FeedWriter() {}
+FeedWriter.prototype = {
+ _mimeSvc : Cc["@mozilla.org/mime;1"].
+ getService(Ci.nsIMIMEService),
+
+ _getPropertyAsBag: function FW__getPropertyAsBag(container, property) {
+ return container.fields.getProperty(property).
+ QueryInterface(Ci.nsIPropertyBag2);
+ },
+
+ _getPropertyAsString: function FW__getPropertyAsString(container, property) {
+ try {
+ return container.fields.getPropertyAsAString(property);
+ }
+ catch (e) {
+ }
+ return "";
+ },
+
+ _setContentText: function FW__setContentText(id, text) {
+ this._contentSandbox.element = this._document.getElementById(id);
+ this._contentSandbox.textNode = text.createDocumentFragment(this._contentSandbox.element);
+ var codeStr =
+ "while (element.hasChildNodes()) " +
+ " element.removeChild(element.firstChild);" +
+ "element.appendChild(textNode);";
+ if (text.base) {
+ this._contentSandbox.spec = text.base.spec;
+ codeStr += "element.setAttributeNS('" + XML_NS + "', 'base', spec);";
+ }
+ Cu.evalInSandbox(codeStr, this._contentSandbox);
+ this._contentSandbox.element = null;
+ this._contentSandbox.textNode = null;
+ },
+
+ /**
+ * Safely sets the href attribute on an anchor tag, providing the URI
+ * specified can be loaded according to rules.
+ * @param element
+ * The element to set a URI attribute on
+ * @param attribute
+ * The attribute of the element to set the URI to, e.g. href or src
+ * @param uri
+ * The URI spec to set as the href
+ */
+ _safeSetURIAttribute:
+ function FW__safeSetURIAttribute(element, attribute, uri) {
+ var secman = Cc["@mozilla.org/scriptsecuritymanager;1"].
+ getService(Ci.nsIScriptSecurityManager);
+ const flags = Ci.nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL;
+ try {
+ secman.checkLoadURIStrWithPrincipal(this._feedPrincipal, uri, flags);
+ // checkLoadURIStrWithPrincipal will throw if the link URI should not be
+ // loaded, either because our feedURI isn't allowed to load it or per
+ // the rules specified in |flags|, so we'll never "linkify" the link...
+ }
+ catch (e) {
+ // Not allowed to load this link because secman.checkLoadURIStr threw
+ return;
+ }
+
+ this._contentSandbox.element = element;
+ this._contentSandbox.uri = uri;
+ var codeStr = "element.setAttribute('" + attribute + "', uri);";
+ Cu.evalInSandbox(codeStr, this._contentSandbox);
+ },
+
+ /**
+ * Use this sandbox to run any dom manipulation code on nodes which
+ * are already inserted into the content document.
+ */
+ __contentSandbox: null,
+ get _contentSandbox() {
+ // This whole sandbox setup is totally archaic. It was introduced in bug
+ // 360529, presumably before the existence of a solid security membrane,
+ // since all of the manipulation of content here should be made safe by
+ // Xrays.
+ // Now that anonymous content is no longer content-accessible, manipulating
+ // the xml stylesheet content can't be done from content anymore.
+ //
+ // The right solution would be to rip out all of this sandbox junk and
+ // manipulate the DOM directly, but that would require a lot of rewriting.
+ // So, for now, we just give the sandbox an nsExpandedPrincipal with [].
+ // This has the effect of giving it Xrays, and making it same-origin with
+ // the XBL scope, thereby letting it manipulate anonymous content.
+ if (!this.__contentSandbox)
+ this.__contentSandbox = new Cu.Sandbox([this._window],
+ {sandboxName: 'FeedWriter'});
+
+ return this.__contentSandbox;
+ },
+
+ /**
+ * Calls doCommand for a given XUL element within the context of the
+ * content document.
+ *
+ * @param aElement
+ * the XUL element to call doCommand() on.
+ */
+ _safeDoCommand: function FW___safeDoCommand(aElement) {
+ this._contentSandbox.element = aElement;
+ Cu.evalInSandbox("element.doCommand();", this._contentSandbox);
+ this._contentSandbox.element = null;
+ },
+
+ __faviconService: null,
+ get _faviconService() {
+ if (!this.__faviconService)
+ this.__faviconService = Cc["@mozilla.org/browser/favicon-service;1"].
+ getService(Ci.nsIFaviconService);
+
+ return this.__faviconService;
+ },
+
+ __bundle: null,
+ get _bundle() {
+ if (!this.__bundle) {
+ this.__bundle = Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService).
+ createBundle(URI_BUNDLE);
+ }
+ return this.__bundle;
+ },
+
+ _getFormattedString: function FW__getFormattedString(key, params) {
+ return this._bundle.formatStringFromName(key, params, params.length);
+ },
+
+ _getString: function FW__getString(key) {
+ return this._bundle.GetStringFromName(key);
+ },
+
+ /* Magic helper methods to be used instead of xbl properties */
+ _getSelectedItemFromMenulist: function FW__getSelectedItemFromList(aList) {
+ var node = aList.firstChild.firstChild;
+ while (node) {
+ if (node.localName == "menuitem" && node.getAttribute("selected") == "true")
+ return node;
+
+ node = node.nextSibling;
+ }
+
+ return null;
+ },
+
+ _setCheckboxCheckedState: function FW__setCheckboxCheckedState(aCheckbox, aValue) {
+ // see checkbox.xml, xbl bindings are not applied within the sandbox!
+ this._contentSandbox.checkbox = aCheckbox;
+ var codeStr;
+ var change = (aValue != (aCheckbox.getAttribute('checked') == 'true'));
+ if (aValue)
+ codeStr = "checkbox.setAttribute('checked', 'true'); ";
+ else
+ codeStr = "checkbox.removeAttribute('checked'); ";
+
+ if (change) {
+ this._contentSandbox.document = this._document;
+ codeStr += "var event = document.createEvent('Events'); " +
+ "event.initEvent('CheckboxStateChange', true, true);" +
+ "checkbox.dispatchEvent(event);"
+ }
+
+ Cu.evalInSandbox(codeStr, this._contentSandbox);
+ },
+
+ /**
+ * Returns a date suitable for displaying in the feed preview.
+ * If the date cannot be parsed, the return value is "false".
+ * @param dateString
+ * A date as extracted from a feed entry. (entry.updated)
+ */
+ _parseDate: function FW__parseDate(dateString) {
+ // Convert the date into the user's local time zone
+ dateObj = new Date(dateString);
+
+ // Make sure the date we're given is valid.
+ if (!dateObj.getTime())
+ return false;
+
+ var dateService = Cc["@mozilla.org/intl/scriptabledateformat;1"].
+ getService(Ci.nsIScriptableDateFormat);
+ return dateService.FormatDateTime("", dateService.dateFormatLong, dateService.timeFormatNoSeconds,
+ dateObj.getFullYear(), dateObj.getMonth()+1, dateObj.getDate(),
+ dateObj.getHours(), dateObj.getMinutes(), dateObj.getSeconds());
+ },
+
+ /**
+ * Returns the feed type.
+ */
+ __feedType: null,
+ _getFeedType: function FW__getFeedType() {
+ if (this.__feedType != null)
+ return this.__feedType;
+
+ try {
+ // grab the feed because it's got the feed.type in it.
+ var container = this._getContainer();
+ var feed = container.QueryInterface(Ci.nsIFeed);
+ this.__feedType = feed.type;
+ return feed.type;
+ } catch (ex) { }
+
+ return Ci.nsIFeed.TYPE_FEED;
+ },
+
+ /**
+ * Maps a feed type to a maybe-feed mimetype.
+ */
+ _getMimeTypeForFeedType: function FW__getMimeTypeForFeedType() {
+ switch (this._getFeedType()) {
+ case Ci.nsIFeed.TYPE_VIDEO:
+ return TYPE_MAYBE_VIDEO_FEED;
+
+ case Ci.nsIFeed.TYPE_AUDIO:
+ return TYPE_MAYBE_AUDIO_FEED;
+
+ default:
+ return TYPE_MAYBE_FEED;
+ }
+ },
+
+ /**
+ * Writes the feed title into the preview document.
+ * @param container
+ * The feed container
+ */
+ _setTitleText: function FW__setTitleText(container) {
+ if (container.title) {
+ var title = container.title.plainText();
+ this._setContentText(TITLE_ID, container.title);
+ this._contentSandbox.document = this._document;
+ this._contentSandbox.title = title;
+ var codeStr = "document.title = title;"
+ Cu.evalInSandbox(codeStr, this._contentSandbox);
+ }
+
+ var feed = container.QueryInterface(Ci.nsIFeed);
+ if (feed && feed.subtitle)
+ this._setContentText(SUBTITLE_ID, container.subtitle);
+ },
+
+ /**
+ * Writes the title image into the preview document if one is present.
+ * @param container
+ * The feed container
+ */
+ _setTitleImage: function FW__setTitleImage(container) {
+ try {
+ var parts = container.image;
+
+ // Set up the title image (supplied by the feed)
+ var feedTitleImage = this._document.getElementById("feedTitleImage");
+ this._safeSetURIAttribute(feedTitleImage, "src",
+ parts.getPropertyAsAString("url"));
+
+ // Set up the title image link
+ var feedTitleLink = this._document.getElementById("feedTitleLink");
+
+ var titleText = this._getFormattedString("linkTitleTextFormat",
+ [parts.getPropertyAsAString("title")]);
+ this._contentSandbox.feedTitleLink = feedTitleLink;
+ this._contentSandbox.titleText = titleText;
+ this._contentSandbox.feedTitleText = this._document.getElementById("feedTitleText");
+ this._contentSandbox.titleImageWidth = parseInt(parts.getPropertyAsAString("width")) + 15;
+
+ // Fix the margin on the main title, so that the image doesn't run over
+ // the underline
+ var codeStr = "feedTitleLink.setAttribute('title', titleText); " +
+ "feedTitleText.style.marginRight = titleImageWidth + 'px';";
+ Cu.evalInSandbox(codeStr, this._contentSandbox);
+ this._contentSandbox.feedTitleLink = null;
+ this._contentSandbox.titleText = null;
+ this._contentSandbox.feedTitleText = null;
+ this._contentSandbox.titleImageWidth = null;
+
+ this._safeSetURIAttribute(feedTitleLink, "href",
+ parts.getPropertyAsAString("link"));
+ }
+ catch (e) {
+ LOG("Failed to set Title Image (this is benign): " + e);
+ }
+ },
+
+ /**
+ * Writes all entries contained in the feed.
+ * @param container
+ * The container of entries in the feed
+ */
+ _writeFeedContent: function FW__writeFeedContent(container) {
+ // Build the actual feed content
+ var feed = container.QueryInterface(Ci.nsIFeed);
+ if (feed.items.length == 0)
+ return;
+
+ this._contentSandbox.feedContent =
+ this._document.getElementById("feedContent");
+
+ for (var i = 0; i < feed.items.length; ++i) {
+ var entry = feed.items.queryElementAt(i, Ci.nsIFeedEntry);
+ entry.QueryInterface(Ci.nsIFeedContainer);
+
+ var entryContainer = this._document.createElementNS(HTML_NS, "div");
+ entryContainer.className = "entry";
+
+ // If the entry has a title, make it a link
+ if (entry.title) {
+ var a = this._document.createElementNS(HTML_NS, "a");
+ var span = this._document.createElementNS(HTML_NS, "span");
+ a.appendChild(span);
+ if (entry.title.base)
+ span.setAttributeNS(XML_NS, "base", entry.title.base.spec);
+ span.appendChild(entry.title.createDocumentFragment(a));
+
+ // Entries are not required to have links, so entry.link can be null.
+ if (entry.link)
+ this._safeSetURIAttribute(a, "href", entry.link.spec);
+
+ var title = this._document.createElementNS(HTML_NS, "h3");
+ title.appendChild(a);
+
+ var lastUpdated = this._parseDate(entry.updated);
+ if (lastUpdated) {
+ var dateDiv = this._document.createElementNS(HTML_NS, "div");
+ dateDiv.className = "lastUpdated";
+ dateDiv.textContent = lastUpdated;
+ title.appendChild(dateDiv);
+ }
+
+ entryContainer.appendChild(title);
+ }
+
+ var body = this._document.createElementNS(HTML_NS, "div");
+ var summary = entry.summary || entry.content;
+ var docFragment = null;
+ if (summary) {
+ if (summary.base)
+ body.setAttributeNS(XML_NS, "base", summary.base.spec);
+ else
+ LOG("no base?");
+ docFragment = summary.createDocumentFragment(body);
+ if (docFragment)
+ body.appendChild(docFragment);
+
+ // If the entry doesn't have a title, append a # permalink
+ // See http://scripting.com/rss.xml for an example
+ if (!entry.title && entry.link) {
+ var a = this._document.createElementNS(HTML_NS, "a");
+ a.appendChild(this._document.createTextNode("#"));
+ this._safeSetURIAttribute(a, "href", entry.link.spec);
+ body.appendChild(this._document.createTextNode(" "));
+ body.appendChild(a);
+ }
+
+ }
+ body.className = "feedEntryContent";
+ entryContainer.appendChild(body);
+
+ if (entry.enclosures && entry.enclosures.length > 0) {
+ var enclosuresDiv = this._buildEnclosureDiv(entry);
+ entryContainer.appendChild(enclosuresDiv);
+ }
+
+ this._contentSandbox.entryContainer = entryContainer;
+ this._contentSandbox.clearDiv =
+ this._document.createElementNS(HTML_NS, "div");
+ this._contentSandbox.clearDiv.style.clear = "both";
+
+ var codeStr = "feedContent.appendChild(entryContainer); " +
+ "feedContent.appendChild(clearDiv);"
+ Cu.evalInSandbox(codeStr, this._contentSandbox);
+ }
+
+ this._contentSandbox.feedContent = null;
+ this._contentSandbox.entryContainer = null;
+ this._contentSandbox.clearDiv = null;
+ },
+
+ /**
+ * Takes a url to a media item and returns the best name it can come up with.
+ * Frequently this is the filename portion (e.g. passing in
+ * http://example.com/foo.mpeg would return "foo.mpeg"), but in more complex
+ * cases, this will return the entire url (e.g. passing in
+ * http://example.com/somedirectory/ would return
+ * http://example.com/somedirectory/).
+ * @param aURL
+ * The URL string from which to create a display name
+ * @returns a string
+ */
+ _getURLDisplayName: function FW__getURLDisplayName(aURL) {
+ var url = makeURI(aURL);
+ url.QueryInterface(Ci.nsIURL);
+ if (url == null || url.fileName.length == 0)
+ return decodeURIComponent(aURL);
+
+ return decodeURIComponent(url.fileName);
+ },
+
+ /**
+ * Takes a FeedEntry with enclosures, generates the HTML code to represent
+ * them, and returns that.
+ * @param entry
+ * FeedEntry with enclosures
+ * @returns element
+ */
+ _buildEnclosureDiv: function FW__buildEnclosureDiv(entry) {
+ var enclosuresDiv = this._document.createElementNS(HTML_NS, "div");
+ enclosuresDiv.className = "enclosures";
+
+ enclosuresDiv.appendChild(this._document.createTextNode(this._getString("mediaLabel")));
+
+ var roundme = function(n) {
+ return (Math.round(n * 100) / 100).toLocaleString();
+ }
+
+ for (var i_enc = 0; i_enc < entry.enclosures.length; ++i_enc) {
+ var enc = entry.enclosures.queryElementAt(i_enc, Ci.nsIWritablePropertyBag2);
+
+ if (!(enc.hasKey("url")))
+ continue;
+
+ var enclosureDiv = this._document.createElementNS(HTML_NS, "div");
+ enclosureDiv.setAttribute("class", "enclosure");
+
+ var mozicon = "moz-icon://.txt?size=16";
+ var type_text = null;
+ var size_text = null;
+
+ if (enc.hasKey("type")) {
+ type_text = enc.get("type");
+ try {
+ var handlerInfoWrapper = this._mimeSvc.getFromTypeAndExtension(enc.get("type"), null);
+
+ if (handlerInfoWrapper)
+ type_text = handlerInfoWrapper.description;
+
+ if (type_text && type_text.length > 0)
+ mozicon = "moz-icon://goat?size=16&contentType=" + enc.get("type");
+
+ } catch (ex) { }
+
+ }
+
+ if (enc.hasKey("length") && /^[0-9]+$/.test(enc.get("length"))) {
+ var enc_size = convertByteUnits(parseInt(enc.get("length")));
+
+ var size_text = this._getFormattedString("enclosureSizeText",
+ [enc_size[0], this._getString(enc_size[1])]);
+ }
+
+ var iconimg = this._document.createElementNS(HTML_NS, "img");
+ iconimg.setAttribute("src", mozicon);
+ iconimg.setAttribute("class", "type-icon");
+ enclosureDiv.appendChild(iconimg);
+
+ enclosureDiv.appendChild(this._document.createTextNode( " " ));
+
+ var enc_href = this._document.createElementNS(HTML_NS, "a");
+ enc_href.appendChild(this._document.createTextNode(this._getURLDisplayName(enc.get("url"))));
+ this._safeSetURIAttribute(enc_href, "href", enc.get("url"));
+ enclosureDiv.appendChild(enc_href);
+
+ if (type_text && size_text)
+ enclosureDiv.appendChild(this._document.createTextNode( " (" + type_text + ", " + size_text + ")"));
+
+ else if (type_text)
+ enclosureDiv.appendChild(this._document.createTextNode( " (" + type_text + ")"))
+
+ else if (size_text)
+ enclosureDiv.appendChild(this._document.createTextNode( " (" + size_text + ")"))
+
+ enclosuresDiv.appendChild(enclosureDiv);
+ }
+
+ return enclosuresDiv;
+ },
+
+ /**
+ * Gets a valid nsIFeedContainer object from the parsed nsIFeedResult.
+ * Displays error information if there was one.
+ * @param result
+ * The parsed feed result
+ * @returns A valid nsIFeedContainer object containing the contents of
+ * the feed.
+ */
+ _getContainer: function FW__getContainer(result) {
+ var feedService =
+ Cc["@mozilla.org/browser/feeds/result-service;1"].
+ getService(Ci.nsIFeedResultService);
+
+ result = null;
+ try {
+ result =
+ feedService.getFeedResult(this._getOriginalURI(this._window));
+ }
+ catch (e) {
+ // Ignore.
+ }
+
+ if (!result) {
+ LOG("Subscribe Preview: feed not available?!");
+ return null;
+ }
+
+ if (result.bozo) {
+ LOG("Subscribe Preview: feed result is bozo?!");
+ }
+
+ try {
+ var container = result.doc;
+ }
+ catch (e) {
+ LOG("Subscribe Preview: no result.doc? Why didn't the original reload?");
+ return null;
+ }
+ return container;
+ },
+
+ /**
+ * Get the human-readable display name of a file. This could be the
+ * application name.
+ * @param file
+ * A nsIFile to look up the name of
+ * @returns The display name of the application represented by the file.
+ */
+ _getFileDisplayName: function FW__getFileDisplayName(file) {
+#ifdef XP_WIN
+ if (file instanceof Ci.nsILocalFileWin) {
+ try {
+ return file.getVersionInfoField("FileDescription");
+ } catch (e) {}
+ }
+#endif
+#ifdef XP_MACOSX
+ if (file instanceof Ci.nsILocalFileMac) {
+ try {
+ return file.bundleDisplayName;
+ } catch (e) {}
+ }
+#endif
+ return file.leafName;
+ },
+
+ /**
+ * Helper method to set the selected application and system default
+ * reader menuitems details from a file object
+ * @param aMenuItem
+ * The menuitem on which the attributes should be set
+ * @param aFile
+ * The menuitem's associated file
+ */
+ _initMenuItemWithFile: function(aMenuItem, aFile) {
+ this._contentSandbox.menuitem = aMenuItem;
+ this._contentSandbox.label = this._getFileDisplayName(aFile);
+ // For security reasons, access to moz-icon:file://... URIs is
+ // no longer allowed (indirect file system access from content).
+ // We use a dummy application instead to get a generic icon.
+ this._contentSandbox.image = "moz-icon://dummy.exe?size=16";
+ var codeStr = "menuitem.setAttribute('label', label); " +
+ "menuitem.setAttribute('image', image);"
+ Cu.evalInSandbox(codeStr, this._contentSandbox);
+ },
+
+ /**
+ * Helper method to get an element in the XBL binding where the handler
+ * selection UI lives
+ */
+ _getUIElement: function FW__getUIElement(id) {
+ return this._document.getAnonymousElementByAttribute(
+ this._document.getElementById("feedSubscribeLine"), "anonid", id);
+ },
+
+ /**
+ * Displays a prompt from which the user may choose a (client) feed reader.
+ * @param aCallback the callback method, passes in true if a feed reader was
+ * selected, false otherwise.
+ */
+ _chooseClientApp: function FW__chooseClientApp(aCallback) {
+ try {
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ let fpCallback = function fpCallback_done(aResult) {
+ if (aResult == Ci.nsIFilePicker.returnOK) {
+ this._selectedApp = fp.file;
+ if (this._selectedApp) {
+ // XXXben - we need to compare this with the running instance
+ // executable just don't know how to do that via script
+ // XXXmano TBD: can probably add this to nsIShellService
+#ifdef XP_WIN
+#expand if (fp.file.leafName != "__MOZ_APP_NAME__.exe") {
+#else
+#ifdef XP_MACOSX
+#expand if (fp.file.leafName != "__MOZ_MACBUNDLE_NAME__") {
+#else
+#expand if (fp.file.leafName != "__MOZ_APP_NAME__-bin") {
+#endif
+#endif
+ this._initMenuItemWithFile(this._contentSandbox.selectedAppMenuItem,
+ this._selectedApp);
+
+ // Show and select the selected application menuitem
+ let codeStr = "selectedAppMenuItem.hidden = false;" +
+ "selectedAppMenuItem.doCommand();"
+ Cu.evalInSandbox(codeStr, this._contentSandbox);
+ if (aCallback) {
+ aCallback(true);
+ return;
+ }
+ }
+ }
+ }
+ if (aCallback) {
+ aCallback(false);
+ }
+ }.bind(this);
+
+ fp.init(this._window, this._getString("chooseApplicationDialogTitle"),
+ Ci.nsIFilePicker.modeOpen);
+ fp.appendFilters(Ci.nsIFilePicker.filterApps);
+ fp.open(fpCallback);
+ } catch(ex) {
+ }
+ },
+
+ _setAlwaysUseCheckedState: function FW__setAlwaysUseCheckedState(feedType) {
+ var checkbox = this._getUIElement("alwaysUse");
+ if (checkbox) {
+ var alwaysUse = false;
+ try {
+ var prefs = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+ if (prefs.getCharPref(getPrefActionForType(feedType)) != "ask")
+ alwaysUse = true;
+ }
+ catch(ex) { }
+ this._setCheckboxCheckedState(checkbox, alwaysUse);
+ }
+ },
+
+ _setSubscribeUsingLabel: function FW__setSubscribeUsingLabel() {
+ var stringLabel = "subscribeFeedUsing";
+ switch (this._getFeedType()) {
+ case Ci.nsIFeed.TYPE_VIDEO:
+ stringLabel = "subscribeVideoPodcastUsing";
+ break;
+
+ case Ci.nsIFeed.TYPE_AUDIO:
+ stringLabel = "subscribeAudioPodcastUsing";
+ break;
+ }
+
+ this._contentSandbox.subscribeUsing =
+ this._getUIElement("subscribeUsingDescription");
+ this._contentSandbox.label = this._getString(stringLabel);
+ var codeStr = "subscribeUsing.setAttribute('value', label);"
+ Cu.evalInSandbox(codeStr, this._contentSandbox);
+ },
+
+ _setAlwaysUseLabel: function FW__setAlwaysUseLabel() {
+ var checkbox = this._getUIElement("alwaysUse");
+ if (checkbox) {
+ if (this._handlersMenuList) {
+ var handlerName = this._getSelectedItemFromMenulist(this._handlersMenuList)
+ .getAttribute("label");
+ var stringLabel = "alwaysUseForFeeds";
+ switch (this._getFeedType()) {
+ case Ci.nsIFeed.TYPE_VIDEO:
+ stringLabel = "alwaysUseForVideoPodcasts";
+ break;
+
+ case Ci.nsIFeed.TYPE_AUDIO:
+ stringLabel = "alwaysUseForAudioPodcasts";
+ break;
+ }
+
+ this._contentSandbox.checkbox = checkbox;
+ this._contentSandbox.label = this._getFormattedString(stringLabel, [handlerName]);
+
+ var codeStr = "checkbox.setAttribute('label', label);";
+ Cu.evalInSandbox(codeStr, this._contentSandbox);
+ }
+ }
+ },
+
+ // nsIDomEventListener
+ handleEvent: function(event) {
+ if (event.target.ownerDocument != this._document) {
+ LOG("FeedWriter.handleEvent: Someone passed the feed writer as a listener to the events of another document!");
+ return;
+ }
+
+ if (event.type == "command") {
+ switch (event.target.getAttribute("anonid")) {
+ case "subscribeButton":
+ this.subscribe();
+ break;
+ case "chooseApplicationMenuItem":
+ /* Bug 351263: Make sure to not steal focus if the "Choose
+ * Application" item is being selected with the keyboard. We do this
+ * by ignoring command events while the dropdown is closed (user
+ * arrowing through the combobox), but handling them while the
+ * combobox dropdown is open (user pressed enter when an item was
+ * selected). If we don't show the filepicker here, it will be shown
+ * when clicking "Subscribe Now".
+ */
+ var popupbox = this._handlersMenuList.firstChild.boxObject;
+ if (popupbox.popupState == "hiding") {
+ this._chooseClientApp(function(aResult) {
+ if (!aResult) {
+ // Select the (per-prefs) selected handler if no application
+ // was selected
+ this._setSelectedHandler(this._getFeedType());
+ }
+ }.bind(this));
+ }
+ break;
+ default:
+ this._setAlwaysUseLabel();
+ }
+ }
+ },
+
+ _setSelectedHandler: function FW__setSelectedHandler(feedType) {
+ var prefs =
+ Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+
+ var handler = prefs.getCharPref(getPrefReaderForType(feedType), "bookmarks");
+
+ switch (handler) {
+ case "web": {
+ if (this._handlersMenuList) {
+ var url;
+ try {
+ url = prefs.getComplexValue(getPrefWebForType(feedType), Ci.nsISupportsString).data;
+ } catch (ex) {
+ LOG("FeedWriter._setSelectedHandler: invalid or no handler in prefs");
+ return;
+ }
+ var handlers =
+ this._handlersMenuList.getElementsByAttribute("webhandlerurl", url);
+ if (handlers.length == 0) {
+ LOG("FeedWriter._setSelectedHandler: selected web handler isn't in the menulist")
+ return;
+ }
+
+ this._safeDoCommand(handlers[0]);
+ }
+ break;
+ }
+ case "client": {
+ try {
+ this._selectedApp =
+ prefs.getComplexValue(getPrefAppForType(feedType), Ci.nsILocalFile);
+ }
+ catch(ex) {
+ this._selectedApp = null;
+ }
+
+ if (this._selectedApp) {
+ this._initMenuItemWithFile(this._contentSandbox.selectedAppMenuItem,
+ this._selectedApp);
+ var codeStr = "selectedAppMenuItem.hidden = false; " +
+ "selectedAppMenuItem.doCommand(); ";
+
+ // Only show the default reader menuitem if the default reader
+ // isn't the selected application
+ if (this._defaultSystemReader) {
+ var shouldHide =
+ this._defaultSystemReader.path == this._selectedApp.path;
+ codeStr += "defaultHandlerMenuItem.hidden = " + shouldHide + ";"
+ }
+ Cu.evalInSandbox(codeStr, this._contentSandbox);
+ break;
+ }
+ }
+ case "bookmarks":
+ default: {
+ var liveBookmarksMenuItem = this._getUIElement("liveBookmarksMenuItem");
+ if (liveBookmarksMenuItem)
+ this._safeDoCommand(liveBookmarksMenuItem);
+ }
+ }
+ },
+
+ _initSubscriptionUI: function FW__initSubscriptionUI() {
+ var handlersMenuPopup = this._getUIElement("handlersMenuPopup");
+ if (!handlersMenuPopup)
+ return;
+
+ var feedType = this._getFeedType();
+ var codeStr;
+
+ // change the background
+ var header = this._document.getElementById("feedHeader");
+ this._contentSandbox.header = header;
+ switch (feedType) {
+ case Ci.nsIFeed.TYPE_VIDEO:
+ codeStr = "header.className = 'videoPodcastBackground'; ";
+ break;
+
+ case Ci.nsIFeed.TYPE_AUDIO:
+ codeStr = "header.className = 'audioPodcastBackground'; ";
+ break;
+
+ default:
+ codeStr = "header.className = 'feedBackground'; ";
+ }
+
+ var liveBookmarksMenuItem = this._getUIElement("liveBookmarksMenuItem");
+
+ // Last-selected application
+ var menuItem = liveBookmarksMenuItem.cloneNode(false);
+ menuItem.removeAttribute("selected");
+ menuItem.setAttribute("anonid", "selectedAppMenuItem");
+ menuItem.className = "menuitem-iconic selectedAppMenuItem";
+ menuItem.setAttribute("handlerType", "client");
+ try {
+ var prefs = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+ this._selectedApp = prefs.getComplexValue(getPrefAppForType(feedType),
+ Ci.nsILocalFile);
+
+ if (this._selectedApp.exists())
+ this._initMenuItemWithFile(menuItem, this._selectedApp);
+ else {
+ // Hide the menuitem if the last selected application doesn't exist
+ menuItem.setAttribute("hidden", true);
+ }
+ }
+ catch(ex) {
+ // Hide the menuitem until an application is selected
+ menuItem.setAttribute("hidden", true);
+ }
+ this._contentSandbox.handlersMenuPopup = handlersMenuPopup;
+ this._contentSandbox.selectedAppMenuItem = menuItem;
+
+ codeStr += "handlersMenuPopup.appendChild(selectedAppMenuItem); ";
+
+ // List the default feed reader
+ try {
+ this._defaultSystemReader = Cc["@mozilla.org/browser/shell-service;1"].
+ getService(Ci.nsIShellService).
+ defaultFeedReader;
+ menuItem = liveBookmarksMenuItem.cloneNode(false);
+ menuItem.removeAttribute("selected");
+ menuItem.setAttribute("anonid", "defaultHandlerMenuItem");
+ menuItem.className = "menuitem-iconic defaultHandlerMenuItem";
+ menuItem.setAttribute("handlerType", "client");
+
+ this._initMenuItemWithFile(menuItem, this._defaultSystemReader);
+
+ // Hide the default reader item if it points to the same application
+ // as the last-selected application
+ if (this._selectedApp &&
+ this._selectedApp.path == this._defaultSystemReader.path)
+ menuItem.hidden = true;
+ }
+ catch(ex) { menuItem = null; /* no default reader */ }
+
+ if (menuItem) {
+ this._contentSandbox.defaultHandlerMenuItem = menuItem;
+ codeStr += "handlersMenuPopup.appendChild(defaultHandlerMenuItem); ";
+ }
+
+ // "Choose Application..." menuitem
+ menuItem = liveBookmarksMenuItem.cloneNode(false);
+ menuItem.removeAttribute("selected");
+ menuItem.setAttribute("anonid", "chooseApplicationMenuItem");
+ menuItem.className = "menuitem-iconic chooseApplicationMenuItem";
+ menuItem.setAttribute("label", this._getString("chooseApplicationMenuItem"));
+
+ this._contentSandbox.chooseAppMenuItem = menuItem;
+ codeStr += "handlersMenuPopup.appendChild(chooseAppMenuItem); ";
+
+ // separator
+ this._contentSandbox.chooseAppSep =
+ menuItem = liveBookmarksMenuItem.nextSibling.cloneNode(false);
+ codeStr += "handlersMenuPopup.appendChild(chooseAppSep); ";
+
+ Cu.evalInSandbox(codeStr, this._contentSandbox);
+
+ // List of web handlers
+ var wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
+ getService(Ci.nsIWebContentConverterService);
+ var handlers = wccr.getContentHandlers(this._getMimeTypeForFeedType(feedType));
+ if (handlers.length != 0) {
+ for (var i = 0; i < handlers.length; ++i) {
+ if (!handlers[i].uri) {
+ LOG("Handler with name " + handlers[i].name + " has no URI!? Skipping...");
+ continue;
+ }
+ menuItem = liveBookmarksMenuItem.cloneNode(false);
+ menuItem.removeAttribute("selected");
+ menuItem.className = "menuitem-iconic";
+ menuItem.setAttribute("label", handlers[i].name);
+ menuItem.setAttribute("handlerType", "web");
+ menuItem.setAttribute("webhandlerurl", handlers[i].uri);
+ this._contentSandbox.menuItem = menuItem;
+ codeStr = "handlersMenuPopup.appendChild(menuItem);";
+ Cu.evalInSandbox(codeStr, this._contentSandbox);
+
+ this._setFaviconForWebReader(handlers[i].uri, menuItem);
+ }
+ this._contentSandbox.menuItem = null;
+ }
+
+ this._setSelectedHandler(feedType);
+
+ // "Subscribe using..."
+ this._setSubscribeUsingLabel();
+
+ // "Always use..." checkbox initial state
+ this._setAlwaysUseCheckedState(feedType);
+ this._setAlwaysUseLabel();
+
+ // We update the "Always use.." checkbox label whenever the selected item
+ // in the list is changed
+ handlersMenuPopup.addEventListener("command", this, false);
+
+ // Set up the "Subscribe Now" button
+ this._getUIElement("subscribeButton")
+ .addEventListener("command", this, false);
+
+ // first-run ui
+ var showFirstRunUI = prefs.getBoolPref(PREF_SHOW_FIRST_RUN_UI, true);
+ if (showFirstRunUI) {
+ var textfeedinfo1, textfeedinfo2;
+ switch (feedType) {
+ case Ci.nsIFeed.TYPE_VIDEO:
+ textfeedinfo1 = "feedSubscriptionVideoPodcast1";
+ textfeedinfo2 = "feedSubscriptionVideoPodcast2";
+ break;
+ case Ci.nsIFeed.TYPE_AUDIO:
+ textfeedinfo1 = "feedSubscriptionAudioPodcast1";
+ textfeedinfo2 = "feedSubscriptionAudioPodcast2";
+ break;
+ default:
+ textfeedinfo1 = "feedSubscriptionFeed1";
+ textfeedinfo2 = "feedSubscriptionFeed2";
+ }
+
+ this._contentSandbox.feedinfo1 =
+ this._document.getElementById("feedSubscriptionInfo1");
+ this._contentSandbox.feedinfo1Str = this._getString(textfeedinfo1);
+ this._contentSandbox.feedinfo2 =
+ this._document.getElementById("feedSubscriptionInfo2");
+ this._contentSandbox.feedinfo2Str = this._getString(textfeedinfo2);
+ this._contentSandbox.header = header;
+ codeStr = "feedinfo1.textContent = feedinfo1Str; " +
+ "feedinfo2.textContent = feedinfo2Str; " +
+ "header.setAttribute('firstrun', 'true');"
+ Cu.evalInSandbox(codeStr, this._contentSandbox);
+ prefs.setBoolPref(PREF_SHOW_FIRST_RUN_UI, false);
+ }
+ },
+
+ /**
+ * Returns the original URI object of the feed and ensures that this
+ * component is only ever invoked from the preview document.
+ * @param aWindow
+ * The window of the document invoking the BrowserFeedWriter
+ */
+ _getOriginalURI: function FW__getOriginalURI(aWindow) {
+ var chan = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIWebNavigation).
+ QueryInterface(Ci.nsIDocShell).currentDocumentChannel;
+
+ var nullPrincipal = Cc["@mozilla.org/nullprincipal;1"].
+ createInstance(Ci.nsIPrincipal);
+
+ // this channel is not going to be openend, use a nullPrincipal
+ // and the most restrctive securityFlag.
+ let resolvedURI = NetUtil.newChannel({
+ uri: "about:feeds",
+ loadingPrincipal: nullPrincipal,
+ securityFlags: Ci.nsILoadInfo.SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER
+ }).URI;
+
+ if (resolvedURI.equals(chan.URI))
+ return chan.originalURI;
+
+ return null;
+ },
+
+ _window: null,
+ _document: null,
+ _feedURI: null,
+ _feedPrincipal: null,
+ _handlersMenuList: null,
+
+ // BrowserFeedWriter WebIDL methods
+ init: function FW_init(aWindow) {
+ var window = aWindow;
+ this._feedURI = this._getOriginalURI(window);
+ if (!this._feedURI)
+ return;
+
+ this._window = window;
+ this._document = window.document;
+ this._document.getElementById("feedSubscribeLine").offsetTop;
+ this._handlersMenuList = this._getUIElement("handlersMenuList");
+
+ var secman = Cc["@mozilla.org/scriptsecuritymanager;1"].
+ getService(Ci.nsIScriptSecurityManager);
+ this._feedPrincipal = secman.createCodebasePrincipal(this._feedURI, {});
+
+ LOG("Subscribe Preview: feed uri = " + this._window.location.href);
+
+ // Set up the subscription UI
+ this._initSubscriptionUI();
+ var prefs = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+ prefs.addObserver(PREF_SELECTED_ACTION, this, false);
+ prefs.addObserver(PREF_SELECTED_READER, this, false);
+ prefs.addObserver(PREF_SELECTED_WEB, this, false);
+ prefs.addObserver(PREF_SELECTED_APP, this, false);
+ prefs.addObserver(PREF_VIDEO_SELECTED_ACTION, this, false);
+ prefs.addObserver(PREF_VIDEO_SELECTED_READER, this, false);
+ prefs.addObserver(PREF_VIDEO_SELECTED_WEB, this, false);
+ prefs.addObserver(PREF_VIDEO_SELECTED_APP, this, false);
+
+ prefs.addObserver(PREF_AUDIO_SELECTED_ACTION, this, false);
+ prefs.addObserver(PREF_AUDIO_SELECTED_READER, this, false);
+ prefs.addObserver(PREF_AUDIO_SELECTED_WEB, this, false);
+ prefs.addObserver(PREF_AUDIO_SELECTED_APP, this, false);
+ },
+
+ writeContent: function FW_writeContent() {
+ if (!this._window)
+ return;
+
+ try {
+ // Set up the feed content
+ var container = this._getContainer();
+ if (!container)
+ return;
+
+ this._setTitleText(container);
+ this._setTitleImage(container);
+ this._writeFeedContent(container);
+ }
+ finally {
+ this._removeFeedFromCache();
+ }
+ },
+
+ close: function FW_close() {
+ this._getUIElement("handlersMenuPopup")
+ .removeEventListener("command", this, false);
+ this._getUIElement("subscribeButton")
+ .removeEventListener("command", this, false);
+ this._document = null;
+ this._window = null;
+ var prefs = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+ prefs.removeObserver(PREF_SELECTED_ACTION, this);
+ prefs.removeObserver(PREF_SELECTED_READER, this);
+ prefs.removeObserver(PREF_SELECTED_WEB, this);
+ prefs.removeObserver(PREF_SELECTED_APP, this);
+ prefs.removeObserver(PREF_VIDEO_SELECTED_ACTION, this);
+ prefs.removeObserver(PREF_VIDEO_SELECTED_READER, this);
+ prefs.removeObserver(PREF_VIDEO_SELECTED_WEB, this);
+ prefs.removeObserver(PREF_VIDEO_SELECTED_APP, this);
+
+ prefs.removeObserver(PREF_AUDIO_SELECTED_ACTION, this);
+ prefs.removeObserver(PREF_AUDIO_SELECTED_READER, this);
+ prefs.removeObserver(PREF_AUDIO_SELECTED_WEB, this);
+ prefs.removeObserver(PREF_AUDIO_SELECTED_APP, this);
+
+ this._removeFeedFromCache();
+ this.__faviconService = null;
+ this.__bundle = null;
+ this._feedURI = null;
+ this.__contentSandbox = null;
+ },
+
+ _removeFeedFromCache: function FW__removeFeedFromCache() {
+ if (this._feedURI) {
+ var feedService = Cc["@mozilla.org/browser/feeds/result-service;1"].
+ getService(Ci.nsIFeedResultService);
+ feedService.removeFeedResult(this._feedURI);
+ this._feedURI = null;
+ }
+ },
+
+ subscribe: function FW_subscribe() {
+ var feedType = this._getFeedType();
+
+ // Subscribe to the feed using the selected handler and save prefs
+ var prefs = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+ var defaultHandler = "reader";
+ var useAsDefault = this._getUIElement("alwaysUse").getAttribute("checked");
+
+ var selectedItem = this._getSelectedItemFromMenulist(this._handlersMenuList);
+ let subscribeCallback = function() {
+ if (selectedItem.hasAttribute("webhandlerurl")) {
+ var webURI = selectedItem.getAttribute("webhandlerurl");
+ prefs.setCharPref(getPrefReaderForType(feedType), "web");
+
+ var supportsString = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ supportsString.data = webURI;
+ prefs.setComplexValue(getPrefWebForType(feedType), Ci.nsISupportsString,
+ supportsString);
+
+ var wccr = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
+ getService(Ci.nsIWebContentConverterService);
+ var handler = wccr.getWebContentHandlerByURI(this._getMimeTypeForFeedType(feedType), webURI);
+ if (handler) {
+ if (useAsDefault) {
+ wccr.setAutoHandler(this._getMimeTypeForFeedType(feedType), handler);
+ }
+
+ this._window.location.href = handler.getHandlerURI(this._window.location.href);
+ }
+ } else {
+ switch (selectedItem.getAttribute("anonid")) {
+ case "selectedAppMenuItem":
+ prefs.setComplexValue(getPrefAppForType(feedType), Ci.nsILocalFile,
+ this._selectedApp);
+ prefs.setCharPref(getPrefReaderForType(feedType), "client");
+ break;
+ case "defaultHandlerMenuItem":
+ prefs.setComplexValue(getPrefAppForType(feedType), Ci.nsILocalFile,
+ this._defaultSystemReader);
+ prefs.setCharPref(getPrefReaderForType(feedType), "client");
+ break;
+ case "liveBookmarksMenuItem":
+ defaultHandler = "bookmarks";
+ prefs.setCharPref(getPrefReaderForType(feedType), "bookmarks");
+ break;
+ }
+ var feedService = Cc["@mozilla.org/browser/feeds/result-service;1"].
+ getService(Ci.nsIFeedResultService);
+
+ // Pull the title and subtitle out of the document
+ var feedTitle = this._document.getElementById(TITLE_ID).textContent;
+ var feedSubtitle = this._document.getElementById(SUBTITLE_ID).textContent;
+ feedService.addToClientReader(this._window.location.href, feedTitle, feedSubtitle, feedType);
+ }
+
+ // If "Always use..." is checked, we should set PREF_*SELECTED_ACTION
+ // to either "reader" (If a web reader or if an application is selected),
+ // or to "bookmarks" (if the live bookmarks option is selected).
+ // Otherwise, we should set it to "ask"
+ if (useAsDefault) {
+ prefs.setCharPref(getPrefActionForType(feedType), defaultHandler);
+ } else {
+ prefs.setCharPref(getPrefActionForType(feedType), "ask");
+ }
+ }.bind(this);
+
+ // Show the file picker before subscribing if the
+ // choose application menuitem was chosen using the keyboard
+ if (selectedItem.getAttribute("anonid") == "chooseApplicationMenuItem") {
+ this._chooseClientApp(function(aResult) {
+ if (aResult) {
+ selectedItem =
+ this._getSelectedItemFromMenulist(this._handlersMenuList);
+ subscribeCallback();
+ }
+ }.bind(this));
+ } else {
+ subscribeCallback();
+ }
+ },
+
+ // nsIObserver
+ observe: function FW_observe(subject, topic, data) {
+ if (!this._window) {
+ // this._window is null unless this.init was called with a trusted
+ // window object.
+ return;
+ }
+
+ var feedType = this._getFeedType();
+
+ if (topic == "nsPref:changed") {
+ switch (data) {
+ case PREF_SELECTED_READER:
+ case PREF_SELECTED_WEB:
+ case PREF_SELECTED_APP:
+ case PREF_VIDEO_SELECTED_READER:
+ case PREF_VIDEO_SELECTED_WEB:
+ case PREF_VIDEO_SELECTED_APP:
+ case PREF_AUDIO_SELECTED_READER:
+ case PREF_AUDIO_SELECTED_WEB:
+ case PREF_AUDIO_SELECTED_APP:
+ this._setSelectedHandler(feedType);
+ break;
+ case PREF_SELECTED_ACTION:
+ case PREF_VIDEO_SELECTED_ACTION:
+ case PREF_AUDIO_SELECTED_ACTION:
+ this._setAlwaysUseCheckedState(feedType);
+ }
+ }
+ },
+
+ /**
+ * Sets the icon for the given web-reader item in the readers menu.
+ * The icon is fetched and stored through the favicon service.
+ *
+ * @param aReaderUrl
+ * the reader url.
+ * @param aMenuItem
+ * the reader item in the readers menulist.
+ *
+ * @note For privacy reasons we cannot set the image attribute directly
+ * to the icon url. See Bug 358878 for details.
+ */
+ _setFaviconForWebReader:
+ function FW__setFaviconForWebReader(aReaderUrl, aMenuItem) {
+ var readerURI = makeURI(aReaderUrl);
+ if (!/^https?$/.test(readerURI.scheme)) {
+ // Don't try to get a favicon for non http(s) URIs.
+ return;
+ }
+ var faviconURI = makeURI(readerURI.prePath + "/favicon.ico");
+ var self = this;
+ var usePrivateBrowsing = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .QueryInterface(Ci.nsILoadContext)
+ .usePrivateBrowsing;
+ var nullPrincipal = Cc["@mozilla.org/nullprincipal;1"]
+ .createInstance(Ci.nsIPrincipal);
+ this._faviconService.setAndFetchFaviconForPage(readerURI, faviconURI, false,
+ usePrivateBrowsing ? this._faviconService.FAVICON_LOAD_PRIVATE
+ : this._faviconService.FAVICON_LOAD_NON_PRIVATE,
+ function (aURI, aDataLen, aData, aMimeType) {
+ if (aDataLen > 0) {
+ var dataURL = "data:" + aMimeType + ";base64," +
+ btoa(String.fromCharCode.apply(null, aData));
+ self._contentSandbox.menuItem = aMenuItem;
+ self._contentSandbox.dataURL = dataURL;
+ var codeStr = "menuItem.setAttribute('image', dataURL);";
+ Cu.evalInSandbox(codeStr, self._contentSandbox);
+ self._contentSandbox.menuItem = null;
+ self._contentSandbox.dataURL = null;
+ }
+ }, nullPrincipal);
+ },
+
+ classID: FEEDWRITER_CID,
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMEventListener, Ci.nsIObserver,
+ Ci.nsINavHistoryObserver,
+ Ci.nsIDOMGlobalPropertyInitializer])
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([FeedWriter]);
diff --git a/components/feeds/WebContentConverter.js b/components/feeds/WebContentConverter.js
new file mode 100644
index 0000000..42e2ede
--- /dev/null
+++ b/components/feeds/WebContentConverter.js
@@ -0,0 +1,927 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+function LOG(str) {
+ dump("*** " + str + "\n");
+}
+
+const WCCR_CONTRACTID = "@mozilla.org/embeddor.implemented/web-content-handler-registrar;1";
+const WCCR_CLASSID = Components.ID("{792a7e82-06a0-437c-af63-b2d12e808acc}");
+
+const WCC_CLASSID = Components.ID("{db7ebf28-cc40-415f-8a51-1b111851df1e}");
+const WCC_CLASSNAME = "Web Service Handler";
+
+const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
+const TYPE_ANY = "*/*";
+const TYPE_BLACKLIST = [
+ "application/x-www-form-urlencoded",
+ "application/xhtml+xml",
+ "application/xml",
+ "application/mathml+xml",
+ "application/xslt+xml",
+ "application/x-xpinstall",
+ "image/gif",
+ "image/jpg",
+ "image/jpeg",
+ "image/png",
+ "image/x-png",
+ "image/webp",
+#ifdef MOZ_JXR
+ "image/jxr",
+ "image/vnd.ms-photo",
+#endif
+ "image/svg+xml",
+ "image/bmp",
+ "image/x-ms-bmp",
+ "image/icon",
+ "image/x-icon",
+ "image/vnd.microsoft.icon",
+ "multipart/x-mixed-replace",
+ "multipart/form-data",
+ "text/cache-manifest",
+ "text/css",
+ "text/xsl",
+ "text/html",
+ "text/ping",
+ "text/plain",
+ "text/xml",
+ "text/javascript", // To prevent malicious intent blocking scripting.
+ "text/ecmascript"];
+
+const PREF_CONTENTHANDLERS_AUTO = "browser.contentHandlers.auto.";
+const PREF_CONTENTHANDLERS_BRANCH = "browser.contentHandlers.types.";
+const PREF_SELECTED_WEB = "browser.feeds.handlers.webservice";
+const PREF_SELECTED_ACTION = "browser.feeds.handler";
+const PREF_SELECTED_READER = "browser.feeds.handler.default";
+const PREF_HANDLER_EXTERNAL_PREFIX = "network.protocol-handler.external";
+const PREF_ALLOW_DIFFERENT_HOST = "gecko.handlerService.allowRegisterFromDifferentHost";
+
+const STRING_BUNDLE_URI = "chrome://browser/locale/feeds/subscribe.properties";
+
+const NS_ERROR_MODULE_DOM = 2152923136;
+const NS_ERROR_DOM_SYNTAX_ERR = NS_ERROR_MODULE_DOM + 12;
+
+function WebContentConverter() {
+}
+WebContentConverter.prototype = {
+ convert: function WCC_convert() { },
+ asyncConvertData: function WCC_asyncConvertData() { },
+ onDataAvailable: function WCC_onDataAvailable() { },
+ onStopRequest: function WCC_onStopRequest() { },
+
+ onStartRequest: function WCC_onStartRequest(request, context) {
+ var wccr =
+ Cc[WCCR_CONTRACTID].
+ getService(Ci.nsIWebContentConverterService);
+ wccr.loadPreferredHandler(request);
+ },
+
+ QueryInterface: function WCC_QueryInterface(iid) {
+ if (iid.equals(Ci.nsIStreamConverter) ||
+ iid.equals(Ci.nsIStreamListener) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+var WebContentConverterFactory = {
+ createInstance: function WCCF_createInstance(outer, iid) {
+ if (outer != null)
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ return new WebContentConverter().QueryInterface(iid);
+ },
+
+ QueryInterface: function WCC_QueryInterface(iid) {
+ if (iid.equals(Ci.nsIFactory) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+function ServiceInfo(contentType, uri, name) {
+ this._contentType = contentType;
+ this._uri = uri;
+ this._name = name;
+}
+ServiceInfo.prototype = {
+ /**
+ * See nsIHandlerApp
+ */
+ get name() {
+ return this._name;
+ },
+
+ /**
+ * See nsIHandlerApp
+ */
+ equals: function SI_equals(aHandlerApp) {
+ if (!aHandlerApp)
+ throw Cr.NS_ERROR_NULL_POINTER;
+
+ if (aHandlerApp instanceof Ci.nsIWebContentHandlerInfo &&
+ aHandlerApp.contentType == this.contentType &&
+ aHandlerApp.uri == this.uri)
+ return true;
+
+ return false;
+ },
+
+ /**
+ * See nsIWebContentHandlerInfo
+ */
+ get contentType() {
+ return this._contentType;
+ },
+
+ /**
+ * See nsIWebContentHandlerInfo
+ */
+ get uri() {
+ return this._uri;
+ },
+
+ /**
+ * See nsIWebContentHandlerInfo
+ */
+ getHandlerURI: function SI_getHandlerURI(uri) {
+ return this._uri.replace(/%s/gi, encodeURIComponent(uri));
+ },
+
+ QueryInterface: function SI_QueryInterface(iid) {
+ if (iid.equals(Ci.nsIWebContentHandlerInfo) ||
+ iid.equals(Ci.nsISupports))
+ return this;
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ }
+};
+
+function WebContentConverterRegistrar() {
+ this._contentTypes = { };
+ this._autoHandleContentTypes = { };
+}
+
+WebContentConverterRegistrar.prototype = {
+ get stringBundle() {
+ var sb = Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService).
+ createBundle(STRING_BUNDLE_URI);
+ delete WebContentConverterRegistrar.prototype.stringBundle;
+ return WebContentConverterRegistrar.prototype.stringBundle = sb;
+ },
+
+ _getFormattedString: function WCCR__getFormattedString(key, params) {
+ return this.stringBundle.formatStringFromName(key, params, params.length);
+ },
+
+ _getString: function WCCR_getString(key) {
+ return this.stringBundle.GetStringFromName(key);
+ },
+
+ /**
+ * See nsIWebContentConverterService
+ */
+ getAutoHandler:
+ function WCCR_getAutoHandler(contentType) {
+ contentType = this._resolveContentType(contentType);
+ if (contentType in this._autoHandleContentTypes)
+ return this._autoHandleContentTypes[contentType];
+ return null;
+ },
+
+ /**
+ * See nsIWebContentConverterService
+ */
+ setAutoHandler:
+ function WCCR_setAutoHandler(contentType, handler) {
+ if (handler && !this._typeIsRegistered(contentType, handler.uri))
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+
+ contentType = this._resolveContentType(contentType);
+ this._setAutoHandler(contentType, handler);
+
+ var ps =
+ Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService);
+ var autoBranch = ps.getBranch(PREF_CONTENTHANDLERS_AUTO);
+ if (handler)
+ autoBranch.setCharPref(contentType, handler.uri);
+ else if (autoBranch.prefHasUserValue(contentType))
+ autoBranch.clearUserPref(contentType);
+
+ ps.savePrefFile(null);
+ },
+
+ /**
+ * Update the internal data structure (not persistent)
+ */
+ _setAutoHandler:
+ function WCCR__setAutoHandler(contentType, handler) {
+ if (handler)
+ this._autoHandleContentTypes[contentType] = handler;
+ else if (contentType in this._autoHandleContentTypes)
+ delete this._autoHandleContentTypes[contentType];
+ },
+
+ /**
+ * See nsIWebContentConverterService
+ */
+ getWebContentHandlerByURI:
+ function WCCR_getWebContentHandlerByURI(contentType, uri) {
+ var handlers = this.getContentHandlers(contentType, { });
+ for (var i = 0; i < handlers.length; ++i) {
+ if (handlers[i].uri == uri)
+ return handlers[i];
+ }
+ return null;
+ },
+
+ /**
+ * See nsIWebContentConverterService
+ */
+ loadPreferredHandler:
+ function WCCR_loadPreferredHandler(request) {
+ var channel = request.QueryInterface(Ci.nsIChannel);
+ var contentType = this._resolveContentType(channel.contentType);
+ var handler = this.getAutoHandler(contentType);
+ if (handler) {
+ request.cancel(Cr.NS_ERROR_FAILURE);
+
+ var webNavigation =
+ channel.notificationCallbacks.getInterface(Ci.nsIWebNavigation);
+ webNavigation.loadURI(handler.getHandlerURI(channel.URI.spec),
+ Ci.nsIWebNavigation.LOAD_FLAGS_NONE,
+ null, null, null);
+ }
+ },
+
+ /**
+ * See nsIWebContentConverterService
+ */
+ removeProtocolHandler:
+ function WCCR_removeProtocolHandler(aProtocol, aURITemplate) {
+ var eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+ getService(Ci.nsIExternalProtocolService);
+ var handlerInfo = eps.getProtocolHandlerInfo(aProtocol);
+ var handlers = handlerInfo.possibleApplicationHandlers;
+ for (let i = 0; i < handlers.length; i++) {
+ try { // We only want to test web handlers
+ let handler = handlers.queryElementAt(i, Ci.nsIWebHandlerApp);
+ if (handler.uriTemplate == aURITemplate) {
+ handlers.removeElementAt(i);
+ var hs = Cc["@mozilla.org/uriloader/handler-service;1"].
+ getService(Ci.nsIHandlerService);
+ hs.store(handlerInfo);
+ return;
+ }
+ } catch (e) { /* it wasn't a web handler */ }
+ }
+ },
+
+ /**
+ * See nsIWebContentConverterService
+ */
+ removeContentHandler:
+ function WCCR_removeContentHandler(contentType, uri) {
+ function notURI(serviceInfo) {
+ return serviceInfo.uri != uri;
+ }
+
+ if (contentType in this._contentTypes) {
+ this._contentTypes[contentType] =
+ this._contentTypes[contentType].filter(notURI);
+ }
+ },
+
+ /**
+ *
+ */
+ _mappings: {
+ "application/rss+xml": TYPE_MAYBE_FEED,
+ "application/atom+xml": TYPE_MAYBE_FEED,
+ },
+
+ /**
+ * These are types for which there is a separate content converter aside
+ * from our built in generic one. We should not automatically register
+ * a factory for creating a converter for these types.
+ */
+ _blockedTypes: {
+ "application/vnd.mozilla.maybe.feed": true,
+ },
+
+ /**
+ * Determines the "internal" content type based on the _mappings.
+ * @param contentType
+ * @returns The resolved contentType value.
+ */
+ _resolveContentType:
+ function WCCR__resolveContentType(contentType) {
+ if (contentType in this._mappings)
+ return this._mappings[contentType];
+ return contentType;
+ },
+
+ _makeURI: function(aURL, aOriginCharset, aBaseURI) {
+ var ioService = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ return ioService.newURI(aURL, aOriginCharset, aBaseURI);
+ },
+
+ _checkAndGetURI:
+ function WCCR_checkAndGetURI(aURIString, aContentWindow)
+ {
+ try {
+ let baseURI = aContentWindow.document.baseURIObject;
+ var uri = this._makeURI(aURIString, null, baseURI);
+ } catch (ex) {
+ // not supposed to throw according to spec
+ return;
+ }
+
+ // For security reasons we reject non-http(s) urls (see bug 354316),
+ // we may need to revise this once we support more content types
+ // XXX this should be a "security exception" according to spec, but that
+ // isn't defined yet.
+ if (uri.scheme != "http" && uri.scheme != "https")
+ throw("Permission denied to add " + uri.spec + " as a content or protocol handler");
+
+ // We also reject handlers registered from a different host (see bug 402287)
+ // The pref allows us to test the feature
+ var pb = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+ if (!pb.getBoolPref(PREF_ALLOW_DIFFERENT_HOST) &&
+ (!["http:", "https:"].includes(aContentWindow.location.protocol) ||
+ aContentWindow.location.hostname != uri.host)) {
+ throw("Permission denied to add " + uri.spec + " as a content or protocol handler");
+ }
+
+ // If the uri doesn't contain '%s', it won't be a good handler
+ if (uri.spec.indexOf("%s") < 0)
+ throw NS_ERROR_DOM_SYNTAX_ERR;
+
+ return uri;
+ },
+
+ /**
+ * Determines if a web handler is already registered.
+ *
+ * @param aProtocol
+ * The scheme of the web handler we are checking for.
+ * @param aURITemplate
+ * The URI template that the handler uses to handle the protocol.
+ * @return true if it is already registered, false otherwise.
+ */
+ _protocolHandlerRegistered:
+ function WCCR_protocolHandlerRegistered(aProtocol, aURITemplate) {
+ var eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+ getService(Ci.nsIExternalProtocolService);
+ var handlerInfo = eps.getProtocolHandlerInfo(aProtocol);
+ var handlers = handlerInfo.possibleApplicationHandlers;
+ for (let i = 0; i < handlers.length; i++) {
+ try { // We only want to test web handlers
+ let handler = handlers.queryElementAt(i, Ci.nsIWebHandlerApp);
+ if (handler.uriTemplate == aURITemplate)
+ return true;
+ } catch (e) { /* it wasn't a web handler */ }
+ }
+ return false;
+ },
+
+ /**
+ * See nsIWebContentHandlerRegistrar
+ */
+ registerProtocolHandler:
+ function WCCR_registerProtocolHandler(aProtocol, aURIString, aTitle, aContentWindow) {
+ LOG("registerProtocolHandler(" + aProtocol + "," + aURIString + "," + aTitle + ")");
+
+ var uri = this._checkAndGetURI(aURIString, aContentWindow);
+
+ // If the protocol handler is already registered, just return early.
+ if (this._protocolHandlerRegistered(aProtocol, uri.spec)) {
+ return;
+ }
+
+ var browserWindow = this._getBrowserWindowForContentWindow(aContentWindow);
+ if (PrivateBrowsingUtils.isWindowPrivate(browserWindow)) {
+ // Inside the private browsing mode, we don't want to alert the user to save
+ // a protocol handler. We log it to the error console so that web developers
+ // would have some way to tell what's going wrong.
+ Cc["@mozilla.org/consoleservice;1"].
+ getService(Ci.nsIConsoleService).
+ logStringMessage("Web page denied access to register a protocol handler inside private browsing mode");
+ return;
+ }
+
+ // First, check to make sure this isn't already handled internally (we don't
+ // want to let them take over, say "chrome").
+ var ios = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ var handler = ios.getProtocolHandler(aProtocol);
+ if (!(handler instanceof Ci.nsIExternalProtocolHandler)) {
+ // This is handled internally, so we don't want them to register
+ // XXX this should be a "security exception" according to spec, but that
+ // isn't defined yet.
+ throw("Permission denied to add " + aURIString + "as a protocol handler");
+ }
+
+ // check if it is in the black list
+ var pb = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+ var allowed = pb.getBoolPref(PREF_HANDLER_EXTERNAL_PREFIX + "." + aProtocol,
+ pb.getBoolPref(PREF_HANDLER_EXTERNAL_PREFIX + "-default"));
+ if (!allowed) {
+ // XXX this should be a "security exception" according to spec
+ throw("Not allowed to register a protocol handler for " + aProtocol);
+ }
+
+ // Now Ask the user and provide the proper callback
+ var message = this._getFormattedString("addProtocolHandler",
+ [aTitle, uri.host, aProtocol]);
+
+ var notificationIcon = uri.prePath + "/favicon.ico";
+ var notificationValue = "Protocol Registration: " + aProtocol;
+ var addButton = {
+ label: this._getString("addProtocolHandlerAddButton"),
+ accessKey: this._getString("addHandlerAddButtonAccesskey"),
+ protocolInfo: { protocol: aProtocol, uri: uri.spec, name: aTitle },
+
+ callback:
+ function WCCR_addProtocolHandlerButtonCallback(aNotification, aButtonInfo) {
+ var protocol = aButtonInfo.protocolInfo.protocol;
+ var uri = aButtonInfo.protocolInfo.uri;
+ var name = aButtonInfo.protocolInfo.name;
+
+ var handler = Cc["@mozilla.org/uriloader/web-handler-app;1"].
+ createInstance(Ci.nsIWebHandlerApp);
+ handler.name = name;
+ handler.uriTemplate = uri;
+
+ var eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"].
+ getService(Ci.nsIExternalProtocolService);
+ var handlerInfo = eps.getProtocolHandlerInfo(protocol);
+ handlerInfo.possibleApplicationHandlers.appendElement(handler, false);
+
+ // Since the user has agreed to add a new handler, chances are good
+ // that the next time they see a handler of this type, they're going
+ // to want to use it. Reset the handlerInfo to ask before the next
+ // use.
+ handlerInfo.alwaysAskBeforeHandling = true;
+
+ var hs = Cc["@mozilla.org/uriloader/handler-service;1"].
+ getService(Ci.nsIHandlerService);
+ hs.store(handlerInfo);
+ }
+ };
+ var browserElement = this._getBrowserForContentWindow(browserWindow, aContentWindow);
+ var notificationBox = browserWindow.gBrowser.getNotificationBox(browserElement);
+ notificationBox.appendNotification(message,
+ notificationValue,
+ notificationIcon,
+ notificationBox.PRIORITY_INFO_LOW,
+ [addButton]);
+ },
+
+ /**
+ * See nsIWebContentHandlerRegistrar
+ * If a DOM window is provided, then the request came from content, so we
+ * prompt the user to confirm the registration.
+ */
+ registerContentHandler:
+ function WCCR_registerContentHandler(aContentType, aURIString, aTitle, aContentWindow) {
+ LOG("registerContentHandler(" + aContentType + "," + aURIString + "," + aTitle + ")");
+
+ // Check against the type blacklist.
+ // XXX this should be a "security exception" according to spec, but that
+ // isn't defined yet.
+ var contentType = this._resolveContentType(aContentType);
+ for (let blacklistType of TYPE_BLACKLIST) {
+ if (contentType == blacklistType) {
+ console.error("Unable to register content handler for prohibited MIME type %s.", contentType);
+ return;
+ }
+ }
+
+ if (aContentWindow) {
+ var uri = this._checkAndGetURI(aURIString, aContentWindow);
+
+ var browserWindow = this._getBrowserWindowForContentWindow(aContentWindow);
+ var browserElement = this._getBrowserForContentWindow(browserWindow, aContentWindow);
+ var notificationBox = browserWindow.gBrowser.getNotificationBox(browserElement);
+ this._appendFeedReaderNotification(uri, aTitle, notificationBox);
+ }
+ else
+ this._registerContentHandler(contentType, aURIString, aTitle);
+ },
+
+ /**
+ * Returns the browser chrome window in which the content window is in
+ */
+ _getBrowserWindowForContentWindow:
+ function WCCR__getBrowserWindowForContentWindow(aContentWindow) {
+ return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow)
+ .wrappedJSObject;
+ },
+
+ /**
+ * Returns the <xul:browser> element associated with the given content
+ * window.
+ *
+ * @param aBrowserWindow
+ * The browser window in which the content window is in.
+ * @param aContentWindow
+ * The content window. It's possible to pass a child content window
+ * (i.e. the content window of a frame/iframe).
+ */
+ _getBrowserForContentWindow:
+ function WCCR__getBrowserForContentWindow(aBrowserWindow, aContentWindow) {
+ // This depends on pseudo APIs of browser.js and tabbrowser.xml
+ aContentWindow = aContentWindow.top;
+ var browsers = aBrowserWindow.gBrowser.browsers;
+ for (var i = 0; i < browsers.length; ++i) {
+ if (browsers[i].contentWindow == aContentWindow)
+ return browsers[i];
+ }
+ },
+
+ /**
+ * Appends a notifcation for the given feed reader details.
+ *
+ * The notification could be either a pseudo-dialog which lets
+ * the user to add the feed reader:
+ * [ [icon] Add %feed-reader-name% (%feed-reader-host%) as a Feed Reader? (Add) [x] ]
+ *
+ * or a simple message for the case where the feed reader is already registered:
+ * [ [icon] %feed-reader-name% is already registered as a Feed Reader [x] ]
+ *
+ * A new notification isn't appended if the given notificationbox has a
+ * notification for the same feed reader.
+ *
+ * @param aURI
+ * The url of the feed reader as a nsIURI object
+ * @param aName
+ * The feed reader name as it was passed to registerContentHandler
+ * @param aNotificationBox
+ * The notification box to which a notification might be appended
+ * @return true if a notification has been appended, false otherwise.
+ */
+ _appendFeedReaderNotification:
+ function WCCR__appendFeedReaderNotification(aURI, aName, aNotificationBox) {
+ var uriSpec = aURI.spec;
+ var notificationValue = "feed reader notification: " + uriSpec;
+ var notificationIcon = aURI.prePath + "/favicon.ico";
+
+ // Don't append a new notification if the notificationbox
+ // has a notification for the given feed reader already
+ if (aNotificationBox.getNotificationWithValue(notificationValue))
+ return false;
+
+ var buttons, message;
+ if (this.getWebContentHandlerByURI(TYPE_MAYBE_FEED, uriSpec))
+ message = this._getFormattedString("handlerRegistered", [aName]);
+ else {
+ message = this._getFormattedString("addHandler", [aName, aURI.host]);
+ var self = this;
+ var addButton = {
+ _outer: self,
+ label: self._getString("addHandlerAddButton"),
+ accessKey: self._getString("addHandlerAddButtonAccesskey"),
+ feedReaderInfo: { uri: uriSpec, name: aName },
+
+ /* static */
+ callback:
+ function WCCR__addFeedReaderButtonCallback(aNotification, aButtonInfo) {
+ var uri = aButtonInfo.feedReaderInfo.uri;
+ var name = aButtonInfo.feedReaderInfo.name;
+ var outer = aButtonInfo._outer;
+
+ // The reader could have been added from another window mean while
+ if (!outer.getWebContentHandlerByURI(TYPE_MAYBE_FEED, uri))
+ outer._registerContentHandler(TYPE_MAYBE_FEED, uri, name);
+
+ // avoid reference cycles
+ aButtonInfo._outer = null;
+
+ return false;
+ }
+ };
+ buttons = [addButton];
+ }
+
+ aNotificationBox.appendNotification(message,
+ notificationValue,
+ notificationIcon,
+ aNotificationBox.PRIORITY_INFO_LOW,
+ buttons);
+ return true;
+ },
+
+ /**
+ * Save Web Content Handler metadata to persistent preferences.
+ * @param contentType
+ * The content Type being handled
+ * @param uri
+ * The uri of the web service
+ * @param title
+ * The human readable name of the web service
+ *
+ * This data is stored under:
+ *
+ * browser.contentHandlers.type0 = content/type
+ * browser.contentHandlers.uri0 = http://www.foo.com/q=%s
+ * browser.contentHandlers.title0 = Foo 2.0alphr
+ */
+ _saveContentHandlerToPrefs:
+ function WCCR__saveContentHandlerToPrefs(contentType, uri, title) {
+ var ps =
+ Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService);
+ var i = 0;
+ var typeBranch = null;
+ while (true) {
+ typeBranch =
+ ps.getBranch(PREF_CONTENTHANDLERS_BRANCH + i + ".");
+ try {
+ typeBranch.getCharPref("type");
+ ++i;
+ }
+ catch (e) {
+ // No more handlers
+ break;
+ }
+ }
+ if (typeBranch) {
+ typeBranch.setCharPref("type", contentType);
+ var pls =
+ Cc["@mozilla.org/pref-localizedstring;1"].
+ createInstance(Ci.nsIPrefLocalizedString);
+ pls.data = uri;
+ typeBranch.setComplexValue("uri", Ci.nsIPrefLocalizedString, pls);
+ pls.data = title;
+ typeBranch.setComplexValue("title", Ci.nsIPrefLocalizedString, pls);
+
+ ps.savePrefFile(null);
+ }
+ },
+
+ /**
+ * Determines if there is a type with a particular uri registered for the
+ * specified content type already.
+ * @param contentType
+ * The content type that the uri handles
+ * @param uri
+ * The uri of the
+ */
+ _typeIsRegistered: function WCCR__typeIsRegistered(contentType, uri) {
+ if (!(contentType in this._contentTypes))
+ return false;
+
+ var services = this._contentTypes[contentType];
+ for (var i = 0; i < services.length; ++i) {
+ // This uri has already been registered
+ if (services[i].uri == uri)
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Gets a stream converter contract id for the specified content type.
+ * @param contentType
+ * The source content type for the conversion.
+ * @returns A contract id to construct a converter to convert between the
+ * contentType and *\/*.
+ */
+ _getConverterContractID: function WCCR__getConverterContractID(contentType) {
+ const template = "@mozilla.org/streamconv;1?from=%s&to=*/*";
+ return template.replace(/%s/, contentType);
+ },
+
+ /**
+ * Register a web service handler for a content type.
+ *
+ * @param contentType
+ * the content type being handled
+ * @param uri
+ * the URI of the web service
+ * @param title
+ * the human readable name of the web service
+ */
+ _registerContentHandler:
+ function WCCR__registerContentHandler(contentType, uri, title) {
+ this._updateContentTypeHandlerMap(contentType, uri, title);
+ this._saveContentHandlerToPrefs(contentType, uri, title);
+
+ if (contentType == TYPE_MAYBE_FEED) {
+ // Make the new handler the last-selected reader in the preview page
+ // and make sure the preview page is shown the next time a feed is visited
+ var pb = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService).getBranch(null);
+ pb.setCharPref(PREF_SELECTED_READER, "web");
+
+ var supportsString =
+ Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ supportsString.data = uri;
+ pb.setComplexValue(PREF_SELECTED_WEB, Ci.nsISupportsString,
+ supportsString);
+ pb.setCharPref(PREF_SELECTED_ACTION, "ask");
+ this._setAutoHandler(TYPE_MAYBE_FEED, null);
+ }
+ },
+
+ /**
+ * Update the content type -> handler map. This mapping is not persisted, use
+ * registerContentHandler or _saveContentHandlerToPrefs for that purpose.
+ * @param contentType
+ * The content Type being handled
+ * @param uri
+ * The uri of the web service
+ * @param title
+ * The human readable name of the web service
+ */
+ _updateContentTypeHandlerMap:
+ function WCCR__updateContentTypeHandlerMap(contentType, uri, title) {
+ if (!(contentType in this._contentTypes))
+ this._contentTypes[contentType] = [];
+
+ // Avoid adding duplicates
+ if (this._typeIsRegistered(contentType, uri))
+ return;
+
+ this._contentTypes[contentType].push(new ServiceInfo(contentType, uri, title));
+
+ if (!(contentType in this._blockedTypes)) {
+ var converterContractID = this._getConverterContractID(contentType);
+ var cr = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+ cr.registerFactory(WCC_CLASSID, WCC_CLASSNAME, converterContractID,
+ WebContentConverterFactory);
+ }
+ },
+
+ /**
+ * See nsIWebContentConverterService
+ */
+ getContentHandlers:
+ function WCCR_getContentHandlers(contentType, countRef) {
+ countRef.value = 0;
+ if (!(contentType in this._contentTypes))
+ return [];
+
+ var handlers = this._contentTypes[contentType];
+ countRef.value = handlers.length;
+ return handlers;
+ },
+
+ /**
+ * See nsIWebContentConverterService
+ */
+ resetHandlersForType:
+ function WCCR_resetHandlersForType(contentType) {
+ // currently unused within the tree, so only useful for extensions; previous
+ // impl. was buggy (and even infinite-looped!), so I argue that this is a
+ // definite improvement
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ /**
+ * Registers a handler from the settings on a preferences branch.
+ *
+ * @param branch
+ * an nsIPrefBranch containing "type", "uri", and "title" preferences
+ * corresponding to the content handler to be registered
+ */
+ _registerContentHandlerWithBranch: function(branch) {
+ /**
+ * Since we support up to six predefined readers, we need to handle gaps
+ * better, since the first branch with user-added values will be .6
+ *
+ * How we deal with that is to check to see if there's no prefs in the
+ * branch and stop cycling once that's true. This doesn't fix the case
+ * where a user manually removes a reader, but that's not supported yet!
+ */
+ var vals = branch.getChildList("");
+ if (vals.length == 0)
+ return;
+
+ try {
+ var type = branch.getCharPref("type");
+ var uri = branch.getComplexValue("uri", Ci.nsIPrefLocalizedString).data;
+ var title = branch.getComplexValue("title",
+ Ci.nsIPrefLocalizedString).data;
+ this._updateContentTypeHandlerMap(type, uri, title);
+ }
+ catch(ex) {
+ // do nothing, the next branch might have values
+ }
+ },
+
+ /**
+ * Load the auto handler, content handler and protocol tables from
+ * preferences.
+ */
+ _init: function WCCR__init() {
+ var ps =
+ Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefService);
+
+ var kids = ps.getBranch(PREF_CONTENTHANDLERS_BRANCH)
+ .getChildList("");
+
+ // first get the numbers of the providers by getting all ###.uri prefs
+ var nums = [];
+ for (var i = 0; i < kids.length; i++) {
+ var match = /^(\d+)\.uri$/.exec(kids[i]);
+ if (!match)
+ continue;
+ else
+ nums.push(match[1]);
+ }
+
+ // sort them, to get them back in order
+ nums.sort(function(a, b) {return a - b;});
+
+ // now register them
+ for (var i = 0; i < nums.length; i++) {
+ var branch = ps.getBranch(PREF_CONTENTHANDLERS_BRANCH + nums[i] + ".");
+ this._registerContentHandlerWithBranch(branch);
+ }
+
+ // We need to do this _after_ registering all of the available handlers,
+ // so that getWebContentHandlerByURI can return successfully.
+ try {
+ var autoBranch = ps.getBranch(PREF_CONTENTHANDLERS_AUTO);
+ var childPrefs = autoBranch.getChildList("");
+ for (var i = 0; i < childPrefs.length; ++i) {
+ var type = childPrefs[i];
+ var uri = autoBranch.getCharPref(type);
+ if (uri) {
+ var handler = this.getWebContentHandlerByURI(type, uri);
+ this._setAutoHandler(type, handler);
+ }
+ }
+ }
+ catch (e) {
+ // No auto branch yet, that's fine
+ //LOG("WCCR.init: There is no auto branch, benign");
+ }
+ },
+
+ /**
+ * See nsIObserver
+ */
+ observe: function WCCR_observe(subject, topic, data) {
+ var os =
+ Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ switch (topic) {
+ case "app-startup":
+ os.addObserver(this, "browser-ui-startup-complete", false);
+ break;
+ case "browser-ui-startup-complete":
+ os.removeObserver(this, "browser-ui-startup-complete");
+ this._init();
+ break;
+ }
+ },
+
+ /**
+ * See nsIFactory
+ */
+ createInstance: function WCCR_createInstance(outer, iid) {
+ if (outer != null)
+ throw Cr.NS_ERROR_NO_AGGREGATION;
+ return this.QueryInterface(iid);
+ },
+
+ classID: WCCR_CLASSID,
+
+ /**
+ * See nsISupports
+ */
+ QueryInterface: XPCOMUtils.generateQI(
+ [Ci.nsIWebContentConverterService,
+ Ci.nsIWebContentHandlerRegistrar,
+ Ci.nsIObserver,
+ Ci.nsIFactory]),
+
+ _xpcom_categories: [{
+ category: "app-startup",
+ service: true
+ }]
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WebContentConverterRegistrar]);
diff --git a/components/feeds/content/subscribe.css b/components/feeds/content/subscribe.css
new file mode 100644
index 0000000..bf2524d
--- /dev/null
+++ b/components/feeds/content/subscribe.css
@@ -0,0 +1,7 @@
+/* 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/. */
+
+#feedSubscribeLine {
+ -moz-binding: url(subscribe.xml#feedreaderUI);
+}
diff --git a/components/feeds/content/subscribe.js b/components/feeds/content/subscribe.js
new file mode 100644
index 0000000..ab2eac4
--- /dev/null
+++ b/components/feeds/content/subscribe.js
@@ -0,0 +1,23 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 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 SubscribeHandler = {
+ /**
+ * The nsIFeedWriter object that produces the UI
+ */
+ _feedWriter: null,
+
+ init: function SH_init() {
+ this._feedWriter = new BrowserFeedWriter();
+ },
+
+ writeContent: function SH_writeContent() {
+ this._feedWriter.writeContent();
+ },
+
+ uninit: function SH_uninit() {
+ this._feedWriter.close();
+ }
+};
diff --git a/components/feeds/content/subscribe.xhtml b/components/feeds/content/subscribe.xhtml
new file mode 100644
index 0000000..8ad069f
--- /dev/null
+++ b/components/feeds/content/subscribe.xhtml
@@ -0,0 +1,65 @@
+<?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 % feedDTD
+ SYSTEM "chrome://browser/locale/feeds/subscribe.dtd">
+ %feedDTD;
+]>
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+
+<html id="feedHandler"
+ xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>&feedPage.title;</title>
+ <link rel="stylesheet"
+ href="chrome://browser/skin/feeds/subscribe.css"
+ type="text/css"
+ media="all"/>
+ <link rel="stylesheet"
+ href="chrome://browser/content/feeds/subscribe.css"
+ type="text/css"
+ media="all"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/feeds/subscribe.js"/>
+ </head>
+ <body onload="SubscribeHandler.writeContent();" onunload="SubscribeHandler.uninit();">
+ <div id="feedHeaderContainer">
+ <div id="feedHeader" dir="&locale.dir;">
+ <div id="feedIntroText">
+ <p id="feedSubscriptionInfo1" />
+ <p id="feedSubscriptionInfo2" />
+ </div>
+ <div id="feedSubscribeLine"></div>
+ </div>
+ </div>
+
+ <script type="application/javascript">
+ SubscribeHandler.init();
+ </script>
+
+ <div id="feedBody">
+ <div id="feedTitle">
+ <a id="feedTitleLink">
+ <img id="feedTitleImage"/>
+ </a>
+ <div id="feedTitleContainer">
+ <h1 id="feedTitleText"/>
+ <h2 id="feedSubtitleText"/>
+ </div>
+ </div>
+ <div id="feedContent"/>
+ </div>
+ </body>
+</html>
diff --git a/components/feeds/content/subscribe.xml b/components/feeds/content/subscribe.xml
new file mode 100644
index 0000000..949bcfd
--- /dev/null
+++ b/components/feeds/content/subscribe.xml
@@ -0,0 +1,40 @@
+<?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 bindings [
+ <!ENTITY % feedDTD
+ SYSTEM "chrome://browser/locale/feeds/subscribe.dtd">
+ %feedDTD;
+]>
+<bindings id="feedBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <binding id="feedreaderUI" bindToUntrustedContent="true">
+ <content>
+ <xul:vbox>
+ <xul:hbox align="center">
+ <xul:description anonid="subscribeUsingDescription" class="subscribeUsingDescription"/>
+ <xul:menulist anonid="handlersMenuList" class="handlersMenuList" aria-labelledby="subscribeUsingDescription">
+ <xul:menupopup anonid="handlersMenuPopup" class="handlersMenuPopup">
+ <xul:menuitem anonid="liveBookmarksMenuItem" label="&feedLiveBookmarks;" class="menuitem-iconic liveBookmarksMenuItem" image="chrome://browser/skin/page-livemarks.png" selected="true"/>
+ <xul:menuseparator/>
+ </xul:menupopup>
+ </xul:menulist>
+ </xul:hbox>
+ <xul:hbox>
+ <xul:checkbox anonid="alwaysUse" class="alwaysUse" checked="false"/>
+ </xul:hbox>
+ <xul:hbox align="center">
+ <xul:spacer flex="1"/>
+ <xul:button label="&feedSubscribeNow;" anonid="subscribeButton" class="subscribeButton"/>
+ </xul:hbox>
+ </xul:vbox>
+ </content>
+ <resources>
+ <stylesheet src="chrome://browser/skin/feeds/subscribe-ui.css"/>
+ </resources>
+ </binding>
+</bindings>
+
diff --git a/components/feeds/jar.mn b/components/feeds/jar.mn
new file mode 100644
index 0000000..f8896f8
--- /dev/null
+++ b/components/feeds/jar.mn
@@ -0,0 +1,9 @@
+# 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/feeds/subscribe.xhtml (content/subscribe.xhtml)
+ content/browser/feeds/subscribe.js (content/subscribe.js)
+ content/browser/feeds/subscribe.xml (content/subscribe.xml)
+ content/browser/feeds/subscribe.css (content/subscribe.css)
diff --git a/components/feeds/moz.build b/components/feeds/moz.build
new file mode 100644
index 0000000..736920a
--- /dev/null
+++ b/components/feeds/moz.build
@@ -0,0 +1,33 @@
+# -*- 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/.
+
+JAR_MANIFESTS += ['jar.mn']
+
+XPIDL_SOURCES += [
+ 'nsIFeedResultService.idl',
+ 'nsIWebContentConverterRegistrar.idl',
+]
+
+XPIDL_MODULE = 'browser-feeds'
+
+SOURCES += ['nsFeedSniffer.cpp']
+
+EXTRA_COMPONENTS += [
+ 'BrowserFeeds.manifest',
+ 'FeedConverter.js',
+]
+
+EXTRA_PP_COMPONENTS += [
+ 'FeedWriter.js',
+ 'WebContentConverter.js',
+]
+
+FINAL_LIBRARY = 'browsercomps'
+
+for var in ('MOZ_APP_NAME', 'MOZ_MACBUNDLE_NAME'):
+ DEFINES[var] = CONFIG[var]
+
+LOCAL_INCLUDES += ['../build']
diff --git a/components/feeds/nsFeedSniffer.cpp b/components/feeds/nsFeedSniffer.cpp
new file mode 100644
index 0000000..f314d3d
--- /dev/null
+++ b/components/feeds/nsFeedSniffer.cpp
@@ -0,0 +1,363 @@
+/* -*- 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/. */
+
+#include "nsFeedSniffer.h"
+
+
+#include "nsNetCID.h"
+#include "nsXPCOM.h"
+#include "nsCOMPtr.h"
+#include "nsStringStream.h"
+
+#include "nsBrowserCompsCID.h"
+
+#include "nsICategoryManager.h"
+#include "nsIServiceManager.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+
+#include "nsIStreamConverterService.h"
+#include "nsIStreamConverter.h"
+
+#include "nsIStreamListener.h"
+
+#include "nsIHttpChannel.h"
+#include "nsIMIMEHeaderParam.h"
+
+#include "nsMimeTypes.h"
+#include "nsIURI.h"
+#include <algorithm>
+
+#define TYPE_ATOM "application/atom+xml"
+#define TYPE_RSS "application/rss+xml"
+#define TYPE_MAYBE_FEED "application/vnd.mozilla.maybe.feed"
+
+#define NS_RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+#define NS_RSS "http://purl.org/rss/1.0/"
+
+#define MAX_BYTES 512u
+
+NS_IMPL_ISUPPORTS(nsFeedSniffer,
+ nsIContentSniffer,
+ nsIStreamListener,
+ nsIRequestObserver)
+
+nsresult
+nsFeedSniffer::ConvertEncodedData(nsIRequest* request,
+ const uint8_t* data,
+ uint32_t length)
+{
+ nsresult rv = NS_OK;
+
+ mDecodedData = "";
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request));
+ if (!httpChannel)
+ return NS_ERROR_NO_INTERFACE;
+
+ nsAutoCString contentEncoding;
+ httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Encoding"),
+ contentEncoding);
+ if (!contentEncoding.IsEmpty()) {
+ nsCOMPtr<nsIStreamConverterService> converterService(do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID));
+ if (converterService) {
+ ToLowerCase(contentEncoding);
+
+ nsCOMPtr<nsIStreamListener> converter;
+ rv = converterService->AsyncConvertData(contentEncoding.get(),
+ "uncompressed", this, nullptr,
+ getter_AddRefs(converter));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ converter->OnStartRequest(request, nullptr);
+
+ nsCOMPtr<nsIStringInputStream> rawStream =
+ do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID);
+ if (!rawStream)
+ return NS_ERROR_FAILURE;
+
+ rv = rawStream->SetData((const char*)data, length);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = converter->OnDataAvailable(request, nullptr, rawStream, 0, length);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ converter->OnStopRequest(request, nullptr, NS_OK);
+ }
+ }
+ return rv;
+}
+
+template<int N>
+static bool
+StringBeginsWithLowercaseLiteral(nsAString& aString,
+ const char (&aSubstring)[N])
+{
+ return StringHead(aString, N).LowerCaseEqualsLiteral(aSubstring);
+}
+
+bool
+HasAttachmentDisposition(nsIHttpChannel* httpChannel)
+{
+ if (!httpChannel)
+ return false;
+
+ uint32_t disp;
+ nsresult rv = httpChannel->GetContentDisposition(&disp);
+
+ if (NS_SUCCEEDED(rv) && disp == nsIChannel::DISPOSITION_ATTACHMENT)
+ return true;
+
+ return false;
+}
+
+/**
+ * @return the first occurrence of a character within a string buffer,
+ * or nullptr if not found
+ */
+static const char*
+FindChar(char c, const char *begin, const char *end)
+{
+ for (; begin < end; ++begin) {
+ if (*begin == c)
+ return begin;
+ }
+ return nullptr;
+}
+
+/**
+ *
+ * Determine if a substring is the "documentElement" in the document.
+ *
+ * All of our sniffed substrings: <rss, <feed, <rdf:RDF must be the "document"
+ * element within the XML DOM, i.e. the root container element. Otherwise,
+ * it's possible that someone embedded one of these tags inside a document of
+ * another type, e.g. a HTML document, and we don't want to show the preview
+ * page if the document isn't actually a feed.
+ *
+ * @param start
+ * The beginning of the data being sniffed
+ * @param end
+ * The end of the data being sniffed, right before the substring that
+ * was found.
+ * @returns true if the found substring is the documentElement, false
+ * otherwise.
+ */
+static bool
+IsDocumentElement(const char *start, const char* end)
+{
+ // For every tag in the buffer, check to see if it's a PI, Doctype or
+ // comment, our desired substring or something invalid.
+ while ( (start = FindChar('<', start, end)) ) {
+ ++start;
+ if (start >= end)
+ return false;
+
+ // Check to see if the character following the '<' is either '?' or '!'
+ // (processing instruction or doctype or comment)... these are valid nodes
+ // to have in the prologue.
+ if (*start != '?' && *start != '!')
+ return false;
+
+ // Now advance the iterator until the '>' (We do this because we don't want
+ // to sniff indicator substrings that are embedded within other nodes, e.g.
+ // comments: <!-- <rdf:RDF .. > -->
+ start = FindChar('>', start, end);
+ if (!start)
+ return false;
+
+ ++start;
+ }
+ return true;
+}
+
+/**
+ * Determines whether or not a string exists as the root element in an XML data
+ * string buffer.
+ * @param dataString
+ * The data being sniffed
+ * @param substring
+ * The substring being tested for existence and root-ness.
+ * @returns true if the substring exists and is the documentElement, false
+ * otherwise.
+ */
+static bool
+ContainsTopLevelSubstring(nsACString& dataString, const char *substring)
+{
+ int32_t offset = dataString.Find(substring);
+ if (offset == -1)
+ return false;
+
+ const char *begin = dataString.BeginReading();
+
+ // Only do the validation when we find the substring.
+ return IsDocumentElement(begin, begin + offset);
+}
+
+NS_IMETHODIMP
+nsFeedSniffer::GetMIMETypeFromContent(nsIRequest* request,
+ const uint8_t* data,
+ uint32_t length,
+ nsACString& sniffedType)
+{
+ nsCOMPtr<nsIHttpChannel> channel(do_QueryInterface(request));
+ if (!channel)
+ return NS_ERROR_NO_INTERFACE;
+
+ // Check that this is a GET request, since you can't subscribe to a POST...
+ nsAutoCString method;
+ channel->GetRequestMethod(method);
+ if (!method.EqualsLiteral("GET")) {
+ sniffedType.Truncate();
+ return NS_OK;
+ }
+
+ // We need to find out if this is a load of a view-source document. In this
+ // case we do not want to override the content type, since the source display
+ // does not need to be converted from feed format to XUL. More importantly,
+ // we don't want to change the content type from something
+ // nsContentDLF::CreateInstance knows about (e.g. application/xml, text/html
+ // etc) to something that only the application fe knows about (maybe.feed)
+ // thus deactivating syntax highlighting.
+ nsCOMPtr<nsIURI> originalURI;
+ channel->GetOriginalURI(getter_AddRefs(originalURI));
+
+ nsAutoCString scheme;
+ originalURI->GetScheme(scheme);
+ if (scheme.EqualsLiteral("view-source")) {
+ sniffedType.Truncate();
+ return NS_OK;
+ }
+
+ // Check the Content-Type to see if it is set correctly. If it is set to
+ // something specific that we think is a reliable indication of a feed, don't
+ // bother sniffing since we assume the site maintainer knows what they're
+ // doing.
+ nsAutoCString contentType;
+ channel->GetContentType(contentType);
+ bool noSniff = contentType.EqualsLiteral(TYPE_RSS) ||
+ contentType.EqualsLiteral(TYPE_ATOM);
+
+ // Check to see if this was a feed request from the location bar or from
+ // the feed: protocol. This is also a reliable indication.
+ // The value of the header doesn't matter.
+ if (!noSniff) {
+ nsAutoCString sniffHeader;
+ nsresult foundHeader =
+ channel->GetRequestHeader(NS_LITERAL_CSTRING("X-Moz-Is-Feed"),
+ sniffHeader);
+ noSniff = NS_SUCCEEDED(foundHeader);
+ }
+
+ if (noSniff) {
+ // check for an attachment after we have a likely feed.
+ if(HasAttachmentDisposition(channel)) {
+ sniffedType.Truncate();
+ return NS_OK;
+ }
+
+ // set the feed header as a response header, since we have good metadata
+ // telling us that the feed is supposed to be RSS or Atom
+ channel->SetResponseHeader(NS_LITERAL_CSTRING("X-Moz-Is-Feed"),
+ NS_LITERAL_CSTRING("1"), false);
+ sniffedType.AssignLiteral(TYPE_MAYBE_FEED);
+ return NS_OK;
+ }
+
+ // Don't sniff arbitrary types. Limit sniffing to situations that
+ // we think can reasonably arise.
+ if (!contentType.EqualsLiteral(TEXT_HTML) &&
+ !contentType.EqualsLiteral(APPLICATION_OCTET_STREAM) &&
+ // Same criterion as XMLHttpRequest. Should we be checking for "+xml"
+ // and check for text/xml and application/xml by hand instead?
+ contentType.Find("xml") == -1) {
+ sniffedType.Truncate();
+ return NS_OK;
+ }
+
+ // Now we need to potentially decompress data served with
+ // Content-Encoding: gzip
+ nsresult rv = ConvertEncodedData(request, data, length);
+ if (NS_FAILED(rv))
+ return rv;
+
+ // We cap the number of bytes to scan at MAX_BYTES to prevent picking up
+ // false positives by accidentally reading document content, e.g. a "how to
+ // make a feed" page.
+ const char* testData;
+ if (mDecodedData.IsEmpty()) {
+ testData = (const char*)data;
+ length = std::min(length, MAX_BYTES);
+ } else {
+ testData = mDecodedData.get();
+ length = std::min(mDecodedData.Length(), MAX_BYTES);
+ }
+
+ // The strategy here is based on that described in:
+ // http://blogs.msdn.com/rssteam/articles/PublishersGuide.aspx
+ // for interoperarbility purposes.
+
+ // Thus begins the actual sniffing.
+ nsDependentCSubstring dataString((const char*)testData, length);
+
+ bool isFeed = false;
+
+ // RSS 0.91/0.92/2.0
+ isFeed = ContainsTopLevelSubstring(dataString, "<rss");
+
+ // Atom 1.0
+ if (!isFeed)
+ isFeed = ContainsTopLevelSubstring(dataString, "<feed");
+
+ // RSS 1.0
+ if (!isFeed) {
+ isFeed = ContainsTopLevelSubstring(dataString, "<rdf:RDF") &&
+ dataString.Find(NS_RDF) != -1 &&
+ dataString.Find(NS_RSS) != -1;
+ }
+
+ // If we sniffed a feed, coerce our internal type
+ if (isFeed && !HasAttachmentDisposition(channel))
+ sniffedType.AssignLiteral(TYPE_MAYBE_FEED);
+ else
+ sniffedType.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFeedSniffer::OnStartRequest(nsIRequest* request, nsISupports* context)
+{
+ return NS_OK;
+}
+
+nsresult
+nsFeedSniffer::AppendSegmentToString(nsIInputStream* inputStream,
+ void* closure,
+ const char* rawSegment,
+ uint32_t toOffset,
+ uint32_t count,
+ uint32_t* writeCount)
+{
+ nsCString* decodedData = static_cast<nsCString*>(closure);
+ decodedData->Append(rawSegment, count);
+ *writeCount = count;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFeedSniffer::OnDataAvailable(nsIRequest* request, nsISupports* context,
+ nsIInputStream* stream, uint64_t offset,
+ uint32_t count)
+{
+ uint32_t read;
+ return stream->ReadSegments(AppendSegmentToString, &mDecodedData, count,
+ &read);
+}
+
+NS_IMETHODIMP
+nsFeedSniffer::OnStopRequest(nsIRequest* request, nsISupports* context,
+ nsresult status)
+{
+ return NS_OK;
+}
diff --git a/components/feeds/nsFeedSniffer.h b/components/feeds/nsFeedSniffer.h
new file mode 100644
index 0000000..a0eb986
--- /dev/null
+++ b/components/feeds/nsFeedSniffer.h
@@ -0,0 +1,37 @@
+/* -*- 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/. */
+
+
+#include "nsIContentSniffer.h"
+#include "nsIStreamListener.h"
+#include "nsStringAPI.h"
+#include "mozilla/Attributes.h"
+
+class nsFeedSniffer final : public nsIContentSniffer,
+ nsIStreamListener
+{
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICONTENTSNIFFER
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+
+ static nsresult AppendSegmentToString(nsIInputStream* inputStream,
+ void* closure,
+ const char* rawSegment,
+ uint32_t toOffset,
+ uint32_t count,
+ uint32_t* writeCount);
+
+protected:
+ ~nsFeedSniffer() {}
+
+ nsresult ConvertEncodedData(nsIRequest* request, const uint8_t* data,
+ uint32_t length);
+
+private:
+ nsCString mDecodedData;
+};
+
diff --git a/components/feeds/nsIFeedResultService.idl b/components/feeds/nsIFeedResultService.idl
new file mode 100644
index 0000000..cb0f332
--- /dev/null
+++ b/components/feeds/nsIFeedResultService.idl
@@ -0,0 +1,66 @@
+/* -*- 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/. */
+
+#include "nsISupports.idl"
+interface nsIURI;
+interface nsIRequest;
+interface nsIFeedResult;
+
+/**
+ * nsIFeedResultService provides a globally-accessible object for retrieving
+ * the results of feed processing.
+ */
+[scriptable, uuid(950a829e-c20e-4dc3-b447-f8b753ae54da)]
+interface nsIFeedResultService : nsISupports
+{
+ /**
+ * When set to true, forces the preview page to be displayed, regardless
+ * of the user's preferences.
+ */
+ attribute boolean forcePreviewPage;
+
+ /**
+ * Adds a URI to the user's specified external feed handler, or live
+ * bookmarks.
+ * @param uri
+ * The uri of the feed to add.
+ * @param title
+ * The title of the feed to add.
+ * @param subtitle
+ * The subtitle of the feed to add.
+ * @param feedType
+ * The nsIFeed type of the feed. See nsIFeed.idl
+ */
+ void addToClientReader(in AUTF8String uri,
+ in AString title,
+ in AString subtitle,
+ in unsigned long feedType);
+
+ /**
+ * Registers a Feed Result object with a globally accessible service
+ * so that it can be accessed by a singleton method outside the usual
+ * flow of control in document loading.
+ *
+ * @param feedResult
+ * An object implementing nsIFeedResult representing the feed.
+ */
+ void addFeedResult(in nsIFeedResult feedResult);
+
+ /**
+ * Gets a Feed Handler object registered using addFeedResult.
+ *
+ * @param uri
+ * The URI of the feed a handler is being requested for
+ */
+ nsIFeedResult getFeedResult(in nsIURI uri);
+
+ /**
+ * Unregisters a Feed Handler object registered using addFeedResult.
+ * @param uri
+ * The feed URI the handler was registered under. This must be
+ * the same *instance* the feed was registered under.
+ */
+ void removeFeedResult(in nsIURI uri);
+};
diff --git a/components/feeds/nsIWebContentConverterRegistrar.idl b/components/feeds/nsIWebContentConverterRegistrar.idl
new file mode 100644
index 0000000..08ce2f4
--- /dev/null
+++ b/components/feeds/nsIWebContentConverterRegistrar.idl
@@ -0,0 +1,117 @@
+/* -*- 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/. */
+
+#include "nsIMIMEInfo.idl"
+#include "nsIWebContentHandlerRegistrar.idl"
+
+interface nsIRequest;
+
+[scriptable, uuid(eb361098-5158-4b21-8f98-50b445f1f0b2)]
+interface nsIWebContentHandlerInfo : nsIHandlerApp
+{
+ /**
+ * The content type handled by the handler
+ */
+ readonly attribute AString contentType;
+
+ /**
+ * The uri of the handler, with an embedded %s where the URI of the loaded
+ * document will be encoded.
+ */
+ readonly attribute AString uri;
+
+ /**
+ * Gets the service URL Spec, with the loading document URI encoded in it.
+ * @param uri
+ * The URI of the document being loaded
+ * @returns The URI of the service with the loading document URI encoded in
+ * it.
+ */
+ AString getHandlerURI(in AString uri);
+};
+
+[scriptable, uuid(de7cc06e-e778-45cb-b7db-7a114e1e75b1)]
+interface nsIWebContentConverterService : nsIWebContentHandlerRegistrar
+{
+ /**
+ * Specifies the handler to be used to automatically handle all links of a
+ * certain content type from now on.
+ * @param contentType
+ * The content type to automatically load with the specified handler
+ * @param handler
+ * A web service handler. If this is null, no automatic action is
+ * performed and the user must choose.
+ * @throws NS_ERROR_NOT_AVAILABLE if the service refered to by |handler| is
+ * not already registered.
+ */
+ void setAutoHandler(in AString contentType, in nsIWebContentHandlerInfo handler);
+
+ /**
+ * Gets the auto handler specified for a particular content type
+ * @param contentType
+ * The content type to look up an auto handler for.
+ * @returns The web service handler that will automatically handle all
+ * documents of the specified type. null if there is no automatic
+ * handler. (Handlers may be registered, just none of them specified
+ * as "automatic").
+ */
+ nsIWebContentHandlerInfo getAutoHandler(in AString contentType);
+
+ /**
+ * Gets a web handler for the specified service URI
+ * @param contentType
+ * The content type of the service being located
+ * @param uri
+ * The service URI of the handler to locate.
+ * @returns A web service handler that uses the specified uri.
+ */
+ nsIWebContentHandlerInfo getWebContentHandlerByURI(in AString contentType,
+ in AString uri);
+
+ /**
+ * Loads the preferred handler when content of a registered type is about
+ * to be loaded.
+ * @param request
+ * The nsIRequest for the load of the content
+ */
+ void loadPreferredHandler(in nsIRequest request);
+
+ /**
+ * Removes a registered protocol handler
+ * @param protocol
+ * The protocol scheme to remove a service handler for
+ * @param uri
+ * The uri of the service handler to remove
+ */
+ void removeProtocolHandler(in AString protocol, in AString uri);
+
+ /**
+ * Removes a registered content handler
+ * @param contentType
+ * The content type to remove a service handler for
+ * @param uri
+ * The uri of the service handler to remove
+ */
+ void removeContentHandler(in AString contentType, in AString uri);
+
+ /**
+ * Gets the list of content handlers for a particular type.
+ * @param contentType
+ * The content type to get handlers for
+ * @returns An array of nsIWebContentHandlerInfo objects
+ */
+ void getContentHandlers(in AString contentType,
+ [optional] out unsigned long count,
+ [retval,array,size_is(count)] out nsIWebContentHandlerInfo handlers);
+
+ /**
+ * Resets the list of available content handlers to the default set from
+ * the distribution.
+ * @param contentType
+ * The content type to reset handlers for
+ */
+ void resetHandlersForType(in AString contentType);
+};
+
diff --git a/components/fuel/fuelApplication.js b/components/fuel/fuelApplication.js
new file mode 100644
index 0000000..bc3a091
--- /dev/null
+++ b/components/fuel/fuelApplication.js
@@ -0,0 +1,822 @@
+/* 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://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
+ "resource://gre/modules/Deprecated.jsm");
+
+const APPLICATION_CID = Components.ID("fe74cf80-aa2d-11db-abbd-0800200c9a66");
+const APPLICATION_CONTRACTID = "@mozilla.org/fuel/application;1";
+
+//=================================================
+// Singleton that holds services and utilities
+var Utilities = {
+ get bookmarks() {
+ let bookmarks = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
+ getService(Ci.nsINavBookmarksService);
+ this.__defineGetter__("bookmarks", function() bookmarks);
+ return this.bookmarks;
+ },
+
+ get bookmarksObserver() {
+ let bookmarksObserver = new BookmarksObserver();
+ this.__defineGetter__("bookmarksObserver", function() bookmarksObserver);
+ return this.bookmarksObserver;
+ },
+
+ get annotations() {
+ let annotations = Cc["@mozilla.org/browser/annotation-service;1"].
+ getService(Ci.nsIAnnotationService);
+ this.__defineGetter__("annotations", function() annotations);
+ return this.annotations;
+ },
+
+ get history() {
+ let history = Cc["@mozilla.org/browser/nav-history-service;1"].
+ getService(Ci.nsINavHistoryService);
+ this.__defineGetter__("history", function() history);
+ return this.history;
+ },
+
+ get windowMediator() {
+ let windowMediator = Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator);
+ this.__defineGetter__("windowMediator", function() windowMediator);
+ return this.windowMediator;
+ },
+
+ makeURI: function fuelutil_makeURI(aSpec) {
+ if (!aSpec)
+ return null;
+ var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+ return ios.newURI(aSpec, null, null);
+ },
+
+ free: function fuelutil_free() {
+ delete this.bookmarks;
+ delete this.bookmarksObserver;
+ delete this.annotations;
+ delete this.history;
+ delete this.windowMediator;
+ }
+};
+
+
+//=================================================
+// Window implementation
+
+var fuelWindowMap = new WeakMap();
+function getWindow(aWindow) {
+ let fuelWindow = fuelWindowMap.get(aWindow);
+ if (!fuelWindow) {
+ fuelWindow = new Window(aWindow);
+ fuelWindowMap.set(aWindow, fuelWindow);
+ }
+ return fuelWindow;
+}
+
+// Don't call new Window() directly; use getWindow instead.
+function Window(aWindow) {
+ this._window = aWindow;
+ this._events = new Events();
+
+ this._watch("TabOpen");
+ this._watch("TabMove");
+ this._watch("TabClose");
+ this._watch("TabSelect");
+}
+
+Window.prototype = {
+ get events() {
+ return this._events;
+ },
+
+ get _tabbrowser() {
+ return this._window.getBrowser();
+ },
+
+ /*
+ * Helper used to setup event handlers on the XBL element. Note that the events
+ * are actually dispatched to tabs, so we capture them.
+ */
+ _watch: function win_watch(aType) {
+ this._tabbrowser.tabContainer.addEventListener(aType, this,
+ /* useCapture = */ true);
+ },
+
+ handleEvent: function win_handleEvent(aEvent) {
+ this._events.dispatch(aEvent.type, getBrowserTab(this, aEvent.originalTarget.linkedBrowser));
+ },
+
+ get tabs() {
+ var tabs = [];
+ var browsers = this._tabbrowser.browsers;
+ for (var i=0; i<browsers.length; i++)
+ tabs.push(getBrowserTab(this, browsers[i]));
+ return tabs;
+ },
+
+ get activeTab() {
+ return getBrowserTab(this, this._tabbrowser.selectedBrowser);
+ },
+
+ open: function win_open(aURI) {
+ return getBrowserTab(this, this._tabbrowser.addTab(aURI.spec).linkedBrowser);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.fuelIWindow])
+};
+
+//=================================================
+// BrowserTab implementation
+
+var fuelBrowserTabMap = new WeakMap();
+function getBrowserTab(aFUELWindow, aBrowser) {
+ let fuelBrowserTab = fuelBrowserTabMap.get(aBrowser);
+ if (!fuelBrowserTab) {
+ fuelBrowserTab = new BrowserTab(aFUELWindow, aBrowser);
+ fuelBrowserTabMap.set(aBrowser, fuelBrowserTab);
+ }
+ else {
+ // This tab may have moved to another window, so make sure its cached
+ // window is up-to-date.
+ fuelBrowserTab._window = aFUELWindow;
+ }
+
+ return fuelBrowserTab;
+}
+
+// Don't call new BrowserTab() directly; call getBrowserTab instead.
+function BrowserTab(aFUELWindow, aBrowser) {
+ this._window = aFUELWindow;
+ this._browser = aBrowser;
+ this._events = new Events();
+
+ this._watch("load");
+}
+
+BrowserTab.prototype = {
+ get _tabbrowser() {
+ return this._window._tabbrowser;
+ },
+
+ get uri() {
+ return this._browser.currentURI;
+ },
+
+ get index() {
+ var tabs = this._tabbrowser.tabs;
+ for (var i=0; i<tabs.length; i++) {
+ if (tabs[i].linkedBrowser == this._browser)
+ return i;
+ }
+ return -1;
+ },
+
+ get events() {
+ return this._events;
+ },
+
+ get window() {
+ return this._window;
+ },
+
+ get document() {
+ return this._browser.contentDocument;
+ },
+
+ /*
+ * Helper used to setup event handlers on the XBL element
+ */
+ _watch: function bt_watch(aType) {
+ this._browser.addEventListener(aType, this,
+ /* useCapture = */ true);
+ },
+
+ handleEvent: function bt_handleEvent(aEvent) {
+ if (aEvent.type == "load") {
+ if (!(aEvent.originalTarget instanceof Ci.nsIDOMDocument))
+ return;
+
+ if (aEvent.originalTarget.defaultView instanceof Ci.nsIDOMWindow &&
+ aEvent.originalTarget.defaultView.frameElement)
+ return;
+ }
+ this._events.dispatch(aEvent.type, this);
+ },
+ /*
+ * Helper used to determine the index offset of the browsertab
+ */
+ _getTab: function bt_gettab() {
+ var tabs = this._tabbrowser.tabs;
+ return tabs[this.index] || null;
+ },
+
+ load: function bt_load(aURI) {
+ this._browser.loadURI(aURI.spec, null, null);
+ },
+
+ focus: function bt_focus() {
+ this._tabbrowser.selectedTab = this._getTab();
+ this._tabbrowser.focus();
+ },
+
+ close: function bt_close() {
+ this._tabbrowser.removeTab(this._getTab());
+ },
+
+ moveBefore: function bt_movebefore(aBefore) {
+ this._tabbrowser.moveTabTo(this._getTab(), aBefore.index);
+ },
+
+ moveToEnd: function bt_moveend() {
+ this._tabbrowser.moveTabTo(this._getTab(), this._tabbrowser.browsers.length);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.fuelIBrowserTab])
+};
+
+
+//=================================================
+// Annotations implementation
+function Annotations(aId) {
+ this._id = aId;
+}
+
+Annotations.prototype = {
+ get names() {
+ return Utilities.annotations.getItemAnnotationNames(this._id);
+ },
+
+ has: function ann_has(aName) {
+ return Utilities.annotations.itemHasAnnotation(this._id, aName);
+ },
+
+ get: function ann_get(aName) {
+ if (this.has(aName))
+ return Utilities.annotations.getItemAnnotation(this._id, aName);
+ return null;
+ },
+
+ set: function ann_set(aName, aValue, aExpiration) {
+ Utilities.annotations.setItemAnnotation(this._id, aName, aValue, 0, aExpiration);
+ },
+
+ remove: function ann_remove(aName) {
+ if (aName)
+ Utilities.annotations.removeItemAnnotation(this._id, aName);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.fuelIAnnotations])
+};
+
+
+//=================================================
+// BookmarksObserver implementation (internal class)
+//
+// BookmarksObserver is a global singleton which watches the browser's
+// bookmarks and sends you events when things change.
+//
+// You can register three different kinds of event listeners on
+// BookmarksObserver, using addListener, addFolderListener, and
+// addRootlistener.
+//
+// - addListener(aId, aEvent, aListener) lets you listen to a specific
+// bookmark. You can listen to the "change", "move", and "remove" events.
+//
+// - addFolderListener(aId, aEvent, aListener) lets you listen to a specific
+// bookmark folder. You can listen to "addchild" and "removechild".
+//
+// - addRootListener(aEvent, aListener) lets you listen to the root bookmark
+// node. This lets you hear "add", "remove", and "change" events on all
+// bookmarks.
+//
+
+function BookmarksObserver() {
+ this._eventsDict = {};
+ this._folderEventsDict = {};
+ this._rootEvents = new Events();
+ Utilities.bookmarks.addObserver(this, /* ownsWeak = */ true);
+}
+
+BookmarksObserver.prototype = {
+ onBeginUpdateBatch: function () {},
+ onEndUpdateBatch: function () {},
+ onItemVisited: function () {},
+
+ onItemAdded: function bo_onItemAdded(aId, aFolder, aIndex, aItemType, aURI) {
+ this._rootEvents.dispatch("add", aId);
+ this._dispatchToEvents("addchild", aId, this._folderEventsDict[aFolder]);
+ },
+
+ onItemRemoved: function bo_onItemRemoved(aId, aFolder, aIndex) {
+ this._rootEvents.dispatch("remove", aId);
+ this._dispatchToEvents("remove", aId, this._eventsDict[aId]);
+ this._dispatchToEvents("removechild", aId, this._folderEventsDict[aFolder]);
+ },
+
+ onItemChanged: function bo_onItemChanged(aId, aProperty, aIsAnnotationProperty, aValue) {
+ this._rootEvents.dispatch("change", aProperty);
+ this._dispatchToEvents("change", aProperty, this._eventsDict[aId]);
+ },
+
+ onItemMoved: function bo_onItemMoved(aId, aOldParent, aOldIndex, aNewParent, aNewIndex) {
+ this._dispatchToEvents("move", aId, this._eventsDict[aId]);
+ },
+
+ _dispatchToEvents: function bo_dispatchToEvents(aEvent, aData, aEvents) {
+ if (aEvents) {
+ aEvents.dispatch(aEvent, aData);
+ }
+ },
+
+ _addListenerToDict: function bo_addListenerToDict(aId, aEvent, aListener, aDict) {
+ var events = aDict[aId];
+ if (!events) {
+ events = new Events();
+ aDict[aId] = events;
+ }
+ events.addListener(aEvent, aListener);
+ },
+
+ _removeListenerFromDict: function bo_removeListenerFromDict(aId, aEvent, aListener, aDict) {
+ var events = aDict[aId];
+ if (!events) {
+ return;
+ }
+ events.removeListener(aEvent, aListener);
+ if (events._listeners.length == 0) {
+ delete aDict[aId];
+ }
+ },
+
+ addListener: function bo_addListener(aId, aEvent, aListener) {
+ this._addListenerToDict(aId, aEvent, aListener, this._eventsDict);
+ },
+
+ removeListener: function bo_removeListener(aId, aEvent, aListener) {
+ this._removeListenerFromDict(aId, aEvent, aListener, this._eventsDict);
+ },
+
+ addFolderListener: function addFolderListener(aId, aEvent, aListener) {
+ this._addListenerToDict(aId, aEvent, aListener, this._folderEventsDict);
+ },
+
+ removeFolderListener: function removeFolderListener(aId, aEvent, aListener) {
+ this._removeListenerFromDict(aId, aEvent, aListener, this._folderEventsDict);
+ },
+
+ addRootListener: function addRootListener(aEvent, aListener) {
+ this._rootEvents.addListener(aEvent, aListener);
+ },
+
+ removeRootListener: function removeRootListener(aEvent, aListener) {
+ this._rootEvents.removeListener(aEvent, aListener);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsINavBookmarksObserver,
+ Ci.nsISupportsWeakReference])
+};
+
+//=================================================
+// Bookmark implementation
+//
+// Bookmark event listeners are stored in BookmarksObserver, not in the
+// Bookmark objects themselves. Thus, you don't have to hold on to a Bookmark
+// object in order for your event listener to stay valid, and Bookmark objects
+// not kept alive by the extension can be GC'ed.
+//
+// A consequence of this is that if you have two different Bookmark objects x
+// and y for the same bookmark (i.e., x != y but x.id == y.id), and you do
+//
+// x.addListener("foo", fun);
+// y.removeListener("foo", fun);
+//
+// the second line will in fact remove the listener added in the first line.
+//
+
+function Bookmark(aId, aParent, aType) {
+ this._id = aId;
+ this._parent = aParent;
+ this._type = aType || "bookmark";
+ this._annotations = new Annotations(this._id);
+
+ // Our _events object forwards to bookmarksObserver.
+ var self = this;
+ this._events = {
+ addListener: function bookmarkevents_al(aEvent, aListener) {
+ Utilities.bookmarksObserver.addListener(self._id, aEvent, aListener);
+ },
+ removeListener: function bookmarkevents_rl(aEvent, aListener) {
+ Utilities.bookmarksObserver.removeListener(self._id, aEvent, aListener);
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.extIEvents])
+ };
+
+ // For our onItemMoved listener, which updates this._parent.
+ Utilities.bookmarks.addObserver(this, /* ownsWeak = */ true);
+}
+
+Bookmark.prototype = {
+ get id() {
+ return this._id;
+ },
+
+ get title() {
+ return Utilities.bookmarks.getItemTitle(this._id);
+ },
+
+ set title(aTitle) {
+ Utilities.bookmarks.setItemTitle(this._id, aTitle);
+ },
+
+ get uri() {
+ return Utilities.bookmarks.getBookmarkURI(this._id);
+ },
+
+ set uri(aURI) {
+ return Utilities.bookmarks.changeBookmarkURI(this._id, aURI);
+ },
+
+ get description() {
+ return this._annotations.get("bookmarkProperties/description");
+ },
+
+ set description(aDesc) {
+ this._annotations.set("bookmarkProperties/description", aDesc, Ci.nsIAnnotationService.EXPIRE_NEVER);
+ },
+
+ get keyword() {
+ return Utilities.bookmarks.getKeywordForBookmark(this._id);
+ },
+
+ set keyword(aKeyword) {
+ Utilities.bookmarks.setKeywordForBookmark(this._id, aKeyword);
+ },
+
+ get type() {
+ return this._type;
+ },
+
+ get parent() {
+ return this._parent;
+ },
+
+ set parent(aFolder) {
+ Utilities.bookmarks.moveItem(this._id, aFolder.id, Utilities.bookmarks.DEFAULT_INDEX);
+ // this._parent is updated in onItemMoved
+ },
+
+ get annotations() {
+ return this._annotations;
+ },
+
+ get events() {
+ return this._events;
+ },
+
+ remove : function bm_remove() {
+ Utilities.bookmarks.removeItem(this._id);
+ },
+
+ onBeginUpdateBatch: function () {},
+ onEndUpdateBatch: function () {},
+ onItemAdded: function () {},
+ onItemVisited: function () {},
+ onItemRemoved: function () {},
+ onItemChanged: function () {},
+
+ onItemMoved: function(aId, aOldParent, aOldIndex, aNewParent, aNewIndex) {
+ if (aId == this._id) {
+ this._parent = new BookmarkFolder(aNewParent, Utilities.bookmarks.getFolderIdForItem(aNewParent));
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.fuelIBookmark,
+ Ci.nsINavBookmarksObserver,
+ Ci.nsISupportsWeakReference])
+};
+
+
+//=================================================
+// BookmarkFolder implementation
+//
+// As with Bookmark, events on BookmarkFolder are handled by the
+// BookmarksObserver singleton.
+//
+
+function BookmarkFolder(aId, aParent) {
+ this._id = aId;
+ this._parent = aParent;
+ this._annotations = new Annotations(this._id);
+
+ // Our event listeners are handled by the BookmarksObserver singleton. This
+ // is a bit complicated because there are three different kinds of events we
+ // might want to listen to here:
+ //
+ // - If this._parent is null, we're the root bookmark folder, and all our
+ // listeners should be root listeners.
+ //
+ // - Otherwise, events ending with "child" (addchild, removechild) are
+ // handled by a folder listener.
+ //
+ // - Other events are handled by a vanilla bookmark listener.
+
+ var self = this;
+ this._events = {
+ addListener: function bmfevents_al(aEvent, aListener) {
+ if (self._parent) {
+ if (/child$/.test(aEvent)) {
+ Utilities.bookmarksObserver.addFolderListener(self._id, aEvent, aListener);
+ }
+ else {
+ Utilities.bookmarksObserver.addListener(self._id, aEvent, aListener);
+ }
+ }
+ else {
+ Utilities.bookmarksObserver.addRootListener(aEvent, aListener);
+ }
+ },
+ removeListener: function bmfevents_rl(aEvent, aListener) {
+ if (self._parent) {
+ if (/child$/.test(aEvent)) {
+ Utilities.bookmarksObserver.removeFolderListener(self._id, aEvent, aListener);
+ }
+ else {
+ Utilities.bookmarksObserver.removeListener(self._id, aEvent, aListener);
+ }
+ }
+ else {
+ Utilities.bookmarksObserver.removeRootListener(aEvent, aListener);
+ }
+ },
+ QueryInterface: XPCOMUtils.generateQI([Ci.extIEvents])
+ };
+
+ // For our onItemMoved listener, which updates this._parent.
+ Utilities.bookmarks.addObserver(this, /* ownsWeak = */ true);
+}
+
+BookmarkFolder.prototype = {
+ get id() {
+ return this._id;
+ },
+
+ get title() {
+ return Utilities.bookmarks.getItemTitle(this._id);
+ },
+
+ set title(aTitle) {
+ Utilities.bookmarks.setItemTitle(this._id, aTitle);
+ },
+
+ get description() {
+ return this._annotations.get("bookmarkProperties/description");
+ },
+
+ set description(aDesc) {
+ this._annotations.set("bookmarkProperties/description", aDesc, Ci.nsIAnnotationService.EXPIRE_NEVER);
+ },
+
+ get type() {
+ return "folder";
+ },
+
+ get parent() {
+ return this._parent;
+ },
+
+ set parent(aFolder) {
+ Utilities.bookmarks.moveItem(this._id, aFolder.id, Utilities.bookmarks.DEFAULT_INDEX);
+ // this._parent is updated in onItemMoved
+ },
+
+ get annotations() {
+ return this._annotations;
+ },
+
+ get events() {
+ return this._events;
+ },
+
+ get children() {
+ var items = [];
+
+ var options = Utilities.history.getNewQueryOptions();
+ var query = Utilities.history.getNewQuery();
+ query.setFolders([this._id], 1);
+ var result = Utilities.history.executeQuery(query, options);
+ var rootNode = result.root;
+ rootNode.containerOpen = true;
+ var cc = rootNode.childCount;
+ for (var i=0; i<cc; ++i) {
+ var node = rootNode.getChild(i);
+ if (node.type == node.RESULT_TYPE_FOLDER) {
+ var folder = new BookmarkFolder(node.itemId, this._id);
+ items.push(folder);
+ }
+ else if (node.type == node.RESULT_TYPE_SEPARATOR) {
+ var separator = new Bookmark(node.itemId, this._id, "separator");
+ items.push(separator);
+ }
+ else {
+ var bookmark = new Bookmark(node.itemId, this._id, "bookmark");
+ items.push(bookmark);
+ }
+ }
+ rootNode.containerOpen = false;
+
+ return items;
+ },
+
+ addBookmark: function bmf_addbm(aTitle, aUri) {
+ var newBookmarkID = Utilities.bookmarks.insertBookmark(this._id, aUri, Utilities.bookmarks.DEFAULT_INDEX, aTitle);
+ var newBookmark = new Bookmark(newBookmarkID, this, "bookmark");
+ return newBookmark;
+ },
+
+ addSeparator: function bmf_addsep() {
+ var newBookmarkID = Utilities.bookmarks.insertSeparator(this._id, Utilities.bookmarks.DEFAULT_INDEX);
+ var newBookmark = new Bookmark(newBookmarkID, this, "separator");
+ return newBookmark;
+ },
+
+ addFolder: function bmf_addfolder(aTitle) {
+ var newFolderID = Utilities.bookmarks.createFolder(this._id, aTitle, Utilities.bookmarks.DEFAULT_INDEX);
+ var newFolder = new BookmarkFolder(newFolderID, this);
+ return newFolder;
+ },
+
+ remove: function bmf_remove() {
+ Utilities.bookmarks.removeItem(this._id);
+ },
+
+ // observer
+ onBeginUpdateBatch: function () {},
+ onEndUpdateBatch : function () {},
+ onItemAdded : function () {},
+ onItemRemoved : function () {},
+ onItemChanged : function () {},
+
+ onItemMoved: function bf_onItemMoved(aId, aOldParent, aOldIndex, aNewParent, aNewIndex) {
+ if (this._id == aId) {
+ this._parent = new BookmarkFolder(aNewParent, Utilities.bookmarks.getFolderIdForItem(aNewParent));
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.fuelIBookmarkFolder,
+ Ci.nsINavBookmarksObserver,
+ Ci.nsISupportsWeakReference])
+};
+
+//=================================================
+// BookmarkRoots implementation
+function BookmarkRoots() {
+}
+
+BookmarkRoots.prototype = {
+ get menu() {
+ if (!this._menu)
+ this._menu = new BookmarkFolder(Utilities.bookmarks.bookmarksMenuFolder, null);
+
+ return this._menu;
+ },
+
+ get toolbar() {
+ if (!this._toolbar)
+ this._toolbar = new BookmarkFolder(Utilities.bookmarks.toolbarFolder, null);
+
+ return this._toolbar;
+ },
+
+ get tags() {
+ if (!this._tags)
+ this._tags = new BookmarkFolder(Utilities.bookmarks.tagsFolder, null);
+
+ return this._tags;
+ },
+
+ get unfiled() {
+ if (!this._unfiled)
+ this._unfiled = new BookmarkFolder(Utilities.bookmarks.unfiledBookmarksFolder, null);
+
+ return this._unfiled;
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.fuelIBookmarkRoots])
+};
+
+
+//=================================================
+// Factory - Treat Application as a singleton
+// XXX This is required, because we're registered for the 'JavaScript global
+// privileged property' category, whose handler always calls createInstance.
+// See bug 386535.
+var gSingleton = null;
+var ApplicationFactory = {
+ createInstance: function af_ci(aOuter, aIID) {
+ if (aOuter != null)
+ throw Components.results.NS_ERROR_NO_AGGREGATION;
+
+ if (gSingleton == null) {
+ gSingleton = new Application();
+ }
+
+ return gSingleton.QueryInterface(aIID);
+ }
+};
+
+
+#include ../../../../toolkit/components/exthelper/extApplication.js
+
+//=================================================
+// Application constructor
+function Application() {
+ Deprecated.warning("FUEL is deprecated, you should use the standard Toolkit API instead.",
+ "https://github.com/MoonchildProductions/UXP/issues/1083");
+ this.initToolkitHelpers();
+}
+
+//=================================================
+// Application implementation
+function ApplicationPrototype() {
+ // for nsIClassInfo + XPCOMUtils
+ this.classID = APPLICATION_CID;
+
+ // redefine the default factory for XPCOMUtils
+ this._xpcom_factory = ApplicationFactory;
+
+ // for nsISupports
+ this.QueryInterface = XPCOMUtils.generateQI([
+ Ci.fuelIApplication,
+ Ci.extIApplication,
+ Ci.nsIObserver,
+ Ci.nsISupportsWeakReference
+ ]);
+
+ // for nsIClassInfo
+ this.classInfo = XPCOMUtils.generateCI({
+ classID: APPLICATION_CID,
+ contractID: APPLICATION_CONTRACTID,
+ interfaces: [
+ Ci.fuelIApplication,
+ Ci.extIApplication,
+ Ci.nsIObserver
+ ],
+ flags: Ci.nsIClassInfo.SINGLETON
+ });
+
+ // for nsIObserver
+ this.observe = function (aSubject, aTopic, aData) {
+ // Call the extApplication version of this function first
+ var superPrototype = Object.getPrototypeOf(Object.getPrototypeOf(this));
+ superPrototype.observe.call(this, aSubject, aTopic, aData);
+ if (aTopic == "xpcom-shutdown") {
+ this._obs.removeObserver(this, "xpcom-shutdown");
+ Utilities.free();
+ }
+ };
+
+ Object.defineProperty(this, "bookmarks", {
+ get: function bookmarks () {
+ let bookmarks = new BookmarkRoots();
+ Object.defineProperty(this, "bookmarks", { value: bookmarks });
+ return this.bookmarks;
+ },
+ enumerable: true,
+ configurable: true
+ });
+
+ Object.defineProperty(this, "windows", {
+ get: function windows() {
+ var win = [];
+ var browserEnum = Utilities.windowMediator.getEnumerator("navigator:browser");
+
+ while (browserEnum.hasMoreElements())
+ win.push(getWindow(browserEnum.getNext()));
+
+ return win;
+ },
+ enumerable: true,
+ configurable: true
+ });
+
+ Object.defineProperty(this, "activeWindow", {
+ get: () => getWindow(Utilities.windowMediator.getMostRecentWindow("navigator:browser")),
+ enumerable: true,
+ configurable: true
+ });
+
+};
+
+// set the proto, defined in extApplication.js
+ApplicationPrototype.prototype = extApplication.prototype;
+
+Application.prototype = new ApplicationPrototype();
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Application]);
+
diff --git a/components/fuel/fuelApplication.manifest b/components/fuel/fuelApplication.manifest
new file mode 100644
index 0000000..67e6d0f
--- /dev/null
+++ b/components/fuel/fuelApplication.manifest
@@ -0,0 +1,3 @@
+component {fe74cf80-aa2d-11db-abbd-0800200c9a66} fuelApplication.js
+contract @mozilla.org/fuel/application;1 {fe74cf80-aa2d-11db-abbd-0800200c9a66}
+category JavaScript-global-privileged-property Application @mozilla.org/fuel/application;1
diff --git a/components/fuel/fuelIApplication.idl b/components/fuel/fuelIApplication.idl
new file mode 100644
index 0000000..69b51b0
--- /dev/null
+++ b/components/fuel/fuelIApplication.idl
@@ -0,0 +1,347 @@
+/* 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/. */
+
+#include "nsISupports.idl"
+#include "extIApplication.idl"
+
+interface nsIVariant;
+interface nsIURI;
+interface nsIDOMHTMLDocument;
+
+interface fuelIBookmarkFolder;
+interface fuelIBrowserTab;
+
+/**
+ * Interface representing a collection of annotations associated
+ * with a bookmark or bookmark folder.
+ */
+[scriptable, uuid(335c9292-91a1-4ca0-ad0b-07d5f63ed6cd)]
+interface fuelIAnnotations : nsISupports
+{
+ /**
+ * Array of the annotation names associated with the owning item
+ */
+ readonly attribute nsIVariant names;
+
+ /**
+ * Determines if an annotation exists with the given name.
+ * @param aName
+ * The name of the annotation
+ * @returns true if an annotation exists with the given name,
+ * false otherwise.
+ */
+ boolean has(in AString aName);
+
+ /**
+ * Gets the value of an annotation with the given name.
+ * @param aName
+ * The name of the annotation
+ * @returns A variant containing the value of the annotation. Supports
+ * string, boolean and number.
+ */
+ nsIVariant get(in AString aName);
+
+ /**
+ * Sets the value of an annotation with the given name.
+ * @param aName
+ * The name of the annotation
+ * @param aValue
+ * The new value of the annotation. Supports string, boolean
+ * and number
+ * @param aExpiration
+ * The expiration policy for the annotation.
+ * See nsIAnnotationService.
+ */
+ void set(in AString aName, in nsIVariant aValue, in int32_t aExpiration);
+
+ /**
+ * Removes the named annotation from the owner item.
+ * @param aName
+ * The name of annotation.
+ */
+ void remove(in AString aName);
+};
+
+
+/**
+ * Interface representing a bookmark item.
+ */
+[scriptable, uuid(808585b6-7568-4b26-8c62-545221bf2b8c)]
+interface fuelIBookmark : nsISupports
+{
+ /**
+ * The id of the bookmark.
+ */
+ readonly attribute long long id;
+
+ /**
+ * The title of the bookmark.
+ */
+ attribute AString title;
+
+ /**
+ * The uri of the bookmark.
+ */
+ attribute nsIURI uri;
+
+ /**
+ * The description of the bookmark.
+ */
+ attribute AString description;
+
+ /**
+ * The keyword associated with the bookmark.
+ */
+ attribute AString keyword;
+
+ /**
+ * The type of the bookmark.
+ * values: "bookmark", "separator"
+ */
+ readonly attribute AString type;
+
+ /**
+ * The parent folder of the bookmark.
+ */
+ attribute fuelIBookmarkFolder parent;
+
+ /**
+ * The annotations object for the bookmark.
+ */
+ readonly attribute fuelIAnnotations annotations;
+
+ /**
+ * The events object for the bookmark.
+ * supports: "remove", "change", "visit", "move"
+ */
+ readonly attribute extIEvents events;
+
+ /**
+ * Removes the item from the parent folder. Used to
+ * delete a bookmark or separator
+ */
+ void remove();
+};
+
+
+/**
+ * Interface representing a bookmark folder. Folders
+ * can hold bookmarks, separators and other folders.
+ */
+[scriptable, uuid(9f42fe20-52de-4a55-8632-a459c7716aa0)]
+interface fuelIBookmarkFolder : nsISupports
+{
+ /**
+ * The id of the folder.
+ */
+ readonly attribute long long id;
+
+ /**
+ * The title of the folder.
+ */
+ attribute AString title;
+
+ /**
+ * The description of the folder.
+ */
+ attribute AString description;
+
+ /**
+ * The type of the folder.
+ * values: "folder"
+ */
+ readonly attribute AString type;
+
+ /**
+ * The parent folder of the folder.
+ */
+ attribute fuelIBookmarkFolder parent;
+
+ /**
+ * The annotations object for the folder.
+ */
+ readonly attribute fuelIAnnotations annotations;
+
+ /**
+ * The events object for the folder.
+ * supports: "add", "addchild", "remove", "removechild", "change", "move"
+ */
+ readonly attribute extIEvents events;
+
+ /**
+ * Array of all bookmarks, separators and folders contained
+ * in this folder.
+ */
+ readonly attribute nsIVariant children;
+
+ /**
+ * Adds a new child bookmark to this folder.
+ * @param aTitle
+ * The title of bookmark.
+ * @param aURI
+ * The uri of bookmark.
+ */
+ fuelIBookmark addBookmark(in AString aTitle, in nsIURI aURI);
+
+ /**
+ * Adds a new child separator to this folder.
+ */
+ fuelIBookmark addSeparator();
+
+ /**
+ * Adds a new child folder to this folder.
+ * @param aTitle
+ * The title of folder.
+ */
+ fuelIBookmarkFolder addFolder(in AString aTitle);
+
+ /**
+ * Removes the folder from the parent folder.
+ */
+ void remove();
+};
+
+/**
+ * Interface representing a container for bookmark roots. Roots
+ * are the top level parents for the various types of bookmarks in the system.
+ */
+[scriptable, uuid(c9a80870-eb3c-11dc-95ff-0800200c9a66)]
+interface fuelIBookmarkRoots : nsISupports
+{
+ /**
+ * The folder for the 'bookmarks menu' root.
+ */
+ readonly attribute fuelIBookmarkFolder menu;
+
+ /**
+ * The folder for the 'personal toolbar' root.
+ */
+ readonly attribute fuelIBookmarkFolder toolbar;
+
+ /**
+ * The folder for the 'tags' root.
+ */
+ readonly attribute fuelIBookmarkFolder tags;
+
+ /**
+ * The folder for the 'unfiled bookmarks' root.
+ */
+ readonly attribute fuelIBookmarkFolder unfiled;
+};
+
+/**
+ * Interface representing a browser window.
+ */
+[scriptable, uuid(207edb28-eb5e-424e-a862-b0e97C8de866)]
+interface fuelIWindow : nsISupports
+{
+ /**
+ * A collection of browser tabs within the browser window.
+ */
+ readonly attribute nsIVariant tabs;
+
+ /**
+ * The currently-active tab within the browser window.
+ */
+ readonly attribute fuelIBrowserTab activeTab;
+
+ /**
+ * Open a new browser tab, pointing to the specified URI.
+ * @param aURI
+ * The uri to open the browser tab to
+ */
+ fuelIBrowserTab open(in nsIURI aURI);
+
+ /**
+ * The events object for the browser window.
+ * supports: "TabOpen", "TabClose", "TabMove", "TabSelect"
+ */
+ readonly attribute extIEvents events;
+};
+
+/**
+ * Interface representing a browser tab.
+ */
+[scriptable, uuid(3073ceff-777c-41ce-9ace-ab37268147c1)]
+interface fuelIBrowserTab : nsISupports
+{
+ /**
+ * The current uri of this tab.
+ */
+ readonly attribute nsIURI uri;
+
+ /**
+ * The current index of this tab in the browser window.
+ */
+ readonly attribute int32_t index;
+
+ /**
+ * The browser window that is holding the tab.
+ */
+ readonly attribute fuelIWindow window;
+
+ /**
+ * The content document of the browser tab.
+ */
+ readonly attribute nsIDOMHTMLDocument document;
+
+ /**
+ * The events object for the browser tab.
+ * supports: "load"
+ */
+ readonly attribute extIEvents events;
+
+ /**
+ * Load a new URI into this browser tab.
+ * @param aURI
+ * The uri to load into the browser tab
+ */
+ void load(in nsIURI aURI);
+
+ /**
+ * Give focus to this browser tab, and bring it to the front.
+ */
+ void focus();
+
+ /**
+ * Close the browser tab. This may not actually close the tab
+ * as script may abort the close operation.
+ */
+ void close();
+
+ /**
+ * Moves this browser tab before another browser tab within the window.
+ * @param aBefore
+ * The tab before which the target tab will be moved
+ */
+ void moveBefore(in fuelIBrowserTab aBefore);
+
+ /**
+ * Move this browser tab to the last tab within the window.
+ */
+ void moveToEnd();
+};
+
+/**
+ * Interface for managing and accessing the applications systems
+ */
+[scriptable, uuid(fe74cf80-aa2d-11db-abbd-0800200c9a66)]
+interface fuelIApplication : extIApplication
+{
+ /**
+ * The root bookmarks object for the application.
+ * Contains all the bookmark roots in the system.
+ */
+ readonly attribute fuelIBookmarkRoots bookmarks;
+
+ /**
+ * An array of browser windows within the application.
+ */
+ readonly attribute nsIVariant windows;
+
+ /**
+ * The currently active browser window.
+ */
+ readonly attribute fuelIWindow activeWindow;
+};
diff --git a/components/fuel/moz.build b/components/fuel/moz.build
new file mode 100644
index 0000000..5c468f2
--- /dev/null
+++ b/components/fuel/moz.build
@@ -0,0 +1,14 @@
+# -*- 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/.
+
+XPIDL_SOURCES += ['fuelIApplication.idl']
+
+XPIDL_MODULE = 'fuel'
+
+EXTRA_COMPONENTS += ['fuelApplication.manifest']
+
+EXTRA_PP_COMPONENTS += ['fuelApplication.js']
+
diff --git a/components/moz.build b/components/moz.build
new file mode 100644
index 0000000..48d4552
--- /dev/null
+++ b/components/moz.build
@@ -0,0 +1,47 @@
+# -*- 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 += [
+ 'abouthome',
+ 'certerror',
+ 'dirprovider',
+ 'downloads',
+ 'feeds',
+ 'fuel',
+ 'newtab',
+ 'pageinfo',
+ 'places',
+ 'permissions',
+ 'preferences',
+ 'privatebrowsing',
+ 'search',
+ 'sessionstore',
+ 'shell',
+ 'statusbar',
+]
+
+if CONFIG['MOZ_SERVICES_SYNC']:
+ DIRS += ['sync']
+
+DIRS += ['build']
+
+XPIDL_SOURCES += [
+ 'nsIBrowserGlue.idl',
+ 'nsIBrowserHandler.idl',
+]
+
+XPIDL_MODULE = 'browsercompsbase'
+
+EXTRA_PP_COMPONENTS += [
+ 'BrowserComponents.manifest',
+ 'nsAboutRedirector.js',
+ 'nsBrowserContentHandler.js',
+ 'nsBrowserGlue.js',
+]
+
+EXTRA_JS_MODULES += [
+ 'distribution.js',
+] \ No newline at end of file
diff --git a/components/newtab/cells.js b/components/newtab/cells.js
new file mode 100644
index 0000000..47d4ef5
--- /dev/null
+++ b/components/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() { return 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/components/newtab/drag.js b/components/newtab/drag.js
new file mode 100644
index 0000000..e3928eb
--- /dev/null
+++ b/components/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.
+ */
+var gDrag = {
+ /**
+ * The site offset to the drag start point.
+ */
+ _offsetX: null,
+ _offsetY: null,
+
+ /**
+ * The site that is dragged.
+ */
+ _draggedSite: null,
+ get draggedSite() { return this._draggedSite; },
+
+ /**
+ * The cell width/height at the point the drag started.
+ */
+ _cellWidth: null,
+ _cellHeight: null,
+ get cellWidth() { return this._cellWidth; },
+ get cellHeight() { return 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-vertical-margin");
+ 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(() => scrollbox.removeChild(dragElement), 0);
+ }
+};
diff --git a/components/newtab/dragDataHelper.js b/components/newtab/dragDataHelper.js
new file mode 100644
index 0000000..675ff26
--- /dev/null
+++ b/components/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
+
+var gDragDataHelper = {
+ get mimeType() {
+ return "text/x-moz-url";
+ },
+
+ getLinkFromDragEvent: function DragDataHelper_getLinkFromDragEvent(aEvent) {
+ let dt = aEvent.dataTransfer;
+ if (!dt || !dt.types.includes(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/components/newtab/drop.js b/components/newtab/drop.js
new file mode 100644
index 0000000..7486524
--- /dev/null
+++ b/components/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.
+ */
+var 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(aSite => aSite.pin(sites.indexOf(aSite)));
+ },
+
+ /**
+ * 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/components/newtab/dropPreview.js b/components/newtab/dropPreview.js
new file mode 100644
index 0000000..fd7587a
--- /dev/null
+++ b/components/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.
+ */
+var 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/components/newtab/dropTargetShim.js b/components/newtab/dropTargetShim.js
new file mode 100644
index 0000000..57a97fa
--- /dev/null
+++ b/components/newtab/dropTargetShim.js
@@ -0,0 +1,232 @@
+#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.
+ */
+var 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 () {
+ gGrid.node.addEventListener("dragstart", this, true);
+ },
+
+ /**
+ * Add all event listeners needed during a drag operation.
+ */
+ _addEventListeners: function () {
+ gGrid.node.addEventListener("dragend", this);
+
+ let docElement = document.documentElement;
+ docElement.addEventListener("dragover", this);
+ docElement.addEventListener("dragenter", this);
+ docElement.addEventListener("drop", this);
+ },
+
+ /**
+ * Remove all event listeners that were needed during a drag operation.
+ */
+ _removeEventListeners: function () {
+ gGrid.node.removeEventListener("dragend", this);
+
+ let docElement = document.documentElement;
+ docElement.removeEventListener("dragover", this);
+ docElement.removeEventListener("dragenter", this);
+ docElement.removeEventListener("drop", this);
+ },
+
+ /**
+ * Handles all shim events.
+ */
+ handleEvent: function (aEvent) {
+ switch (aEvent.type) {
+ case "dragstart":
+ this._dragstart(aEvent);
+ break;
+ case "dragenter":
+ aEvent.preventDefault();
+ break;
+ case "dragover":
+ this._dragover(aEvent);
+ break;
+ case "drop":
+ this._drop(aEvent);
+ break;
+ case "dragend":
+ this._dragend(aEvent);
+ break;
+ }
+ },
+
+ /**
+ * Handles the 'dragstart' event.
+ * @param aEvent The 'dragstart' event.
+ */
+ _dragstart: function (aEvent) {
+ if (aEvent.target.classList.contains("newtab-link")) {
+ gGrid.lock();
+ this._addEventListeners();
+ }
+ },
+
+ /**
+ * Handles the 'dragover' event.
+ * @param aEvent The 'dragover' event.
+ */
+ _dragover: function (aEvent) {
+ // XXX bug 505521 - Use the dragover event to retrieve the
+ // current mouse coordinates while dragging.
+ let sourceNode = aEvent.dataTransfer.mozSourceNode.parentNode;
+ gDrag.drag(sourceNode._newtabSite, aEvent);
+
+ // Find the current drop target, if there's one.
+ this._updateDropTarget(aEvent);
+
+ // If we have a valid drop target,
+ // let the drag-and-drop service know.
+ if (this._lastDropTarget) {
+ aEvent.preventDefault();
+ }
+ },
+
+ /**
+ * Handles the 'drop' event.
+ * @param aEvent The 'drop' event.
+ */
+ _drop: function (aEvent) {
+ // We're accepting all drops.
+ aEvent.preventDefault();
+
+ // remember that drop event was seen, this explicitly
+ // assumes that drop event preceeds dragend event
+ this._dropSeen = true;
+
+ // Make sure to determine the current drop target
+ // in case the dragover event hasn't been fired.
+ this._updateDropTarget(aEvent);
+
+ // A site was successfully dropped.
+ this._dispatchEvent(aEvent, "drop", this._lastDropTarget);
+ },
+
+ /**
+ * Handles the 'dragend' event.
+ * @param aEvent The 'dragend' event.
+ */
+ _dragend: function (aEvent) {
+ if (this._lastDropTarget) {
+ if (aEvent.dataTransfer.mozUserCancelled || !this._dropSeen) {
+ // The drag operation was cancelled or no drop event was generated
+ this._dispatchEvent(aEvent, "dragexit", this._lastDropTarget);
+ this._dispatchEvent(aEvent, "dragleave", this._lastDropTarget);
+ }
+
+ // Clean up.
+ this._lastDropTarget = null;
+ this._cellPositions = null;
+ }
+
+ this._dropSeen = false;
+ gGrid.unlock();
+ this._removeEventListeners();
+ },
+
+ /**
+ * Tries to find the current drop target and will fire
+ * appropriate dragenter, dragexit, and dragleave events.
+ * @param aEvent The current drag event.
+ */
+ _updateDropTarget: function (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;
+ }
+ },
+
+ /**
+ * 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 () {
+ // 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 (aEvent, aType, aTarget) {
+ let node = aTarget.node;
+ let event = document.createEvent("DragEvent");
+
+ // The event should not bubble to prevent recursion.
+ event.initDragEvent(aType, false, true, window, 0, 0, 0, 0, 0, false, false,
+ false, false, 0, node, aEvent.dataTransfer);
+
+ node.dispatchEvent(event);
+ }
+};
diff --git a/components/newtab/grid.js b/components/newtab/grid.js
new file mode 100644
index 0000000..e63ea54
--- /dev/null
+++ b/components/newtab/grid.js
@@ -0,0 +1,175 @@
+#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.
+ */
+var gGrid = {
+ /**
+ * The DOM node of the grid.
+ */
+ _node: null,
+ _gridDefaultContent: null,
+ get node() { return this._node; },
+
+ /**
+ * The cached DOM fragment for sites.
+ */
+ _siteFragment: null,
+
+ /**
+ * All cells contained in the grid.
+ */
+ _cells: [],
+ get cells() { return this._cells; },
+
+ /**
+ * All sites contained in the grid's cells. Sites may be empty.
+ */
+ get sites() {
+ // return [for (cell of this.cells) cell.site];
+ let aSites = [];
+ for (let cell of this.cells) {
+ aSites.push(cell.site);
+ }
+ return aSites;
+ },
+
+ // Tells whether the grid has already been initialized.
+ get ready() { return !!this._ready; },
+
+ // Returns whether the page has finished loading yet.
+ get isDocumentLoaded() { return document.readyState == "complete"; },
+
+ /**
+ * Initializes the grid.
+ * @param aSelector The query selector of the grid.
+ */
+ init: function Grid_init() {
+ this._node = document.getElementById("newtab-grid");
+ this._gridDefaultContent = this._node.lastChild;
+ this._createSiteFragment();
+
+ gLinks.populateCache(() => {
+ this._refreshGrid();
+ this._ready = true;
+ });
+ },
+
+ /**
+ * 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);
+ },
+
+ /**
+ * Handles all grid events.
+ */
+ handleEvent: function Grid_handleEvent(aEvent) {
+ // Any specific events should go here.
+ },
+
+ /**
+ * 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");
+ },
+
+ /**
+ * Renders the grid.
+ */
+ refresh() {
+ this._refreshGrid();
+ },
+
+ /**
+ * Renders the grid, including cells and sites.
+ */
+ _refreshGrid() {
+ let row = document.createElementNS(HTML_NAMESPACE, "div");
+ row.classList.add("newtab-row");
+ let cell = document.createElementNS(HTML_NAMESPACE, "div");
+ 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));
+ }
+
+ // Create cell array.
+ let cellElements = this.node.querySelectorAll(".newtab-cell");
+ let cells = Array.from(cellElements, (cell) => new Cell(this, cell));
+
+ // Fetch links.
+ let links = gLinks.getLinks();
+
+ // Create sites.
+ let numLinks = Math.min(links.length, cells.length);
+ for (let i = 0; i < numLinks; i++) {
+ if (links[i]) {
+ this.createSite(links[i], cells[i]);
+ }
+ }
+
+ this._cells = cells;
+ },
+
+ /**
+ * 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 placeholder"/>' +
+ ' <span class="newtab-thumbnail 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);
+ },
+
+ /**
+ * Test a tile at a given position for being pinned or history
+ * @param position Position in sites array
+ */
+ _isHistoricalTile: function Grid_isHistoricalTile(aPos) {
+ let site = this.sites[aPos];
+ return site && (site.isPinned() || site.link && site.link.type == "history");
+ }
+
+};
diff --git a/components/newtab/jar.mn b/components/newtab/jar.mn
new file mode 100644
index 0000000..2d62914
--- /dev/null
+++ b/components/newtab/jar.mn
@@ -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/.
+
+browser.jar:
+ content/browser/newtab/newTab.xhtml
+* content/browser/newtab/newTab.js
+ content/browser/newtab/newTab.css \ No newline at end of file
diff --git a/components/newtab/moz.build b/components/newtab/moz.build
new file mode 100644
index 0000000..2d64d50
--- /dev/null
+++ b/components/newtab/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/.
+
+JAR_MANIFESTS += ['jar.mn']
+
diff --git a/components/newtab/newTab.css b/components/newtab/newTab.css
new file mode 100644
index 0000000..3c7cfa1
--- /dev/null
+++ b/components/newtab/newTab.css
@@ -0,0 +1,349 @@
+/* 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 {
+ width: 100%;
+ height: 100%;
+}
+
+body {
+ font: message-box;
+ width: 100%;
+ height: 100%;
+ padding: 0;
+ margin: 0;
+ background-color: #F9F9F9;
+ display: -moz-box;
+ position: relative;
+ -moz-box-flex: 1;
+ -moz-user-focus: normal;
+ -moz-box-orient: vertical;
+}
+
+input {
+ font: message-box;
+ font-size: 16px;
+}
+
+input[type=button] {
+ cursor: pointer;
+}
+
+/* UNDO */
+#newtab-undo-container {
+ transition: opacity 100ms ease-out;
+ -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-undo-container {
+ display: -moz-box;
+ left: 6px;
+ position: absolute;
+ top: 6px;
+ z-index: 1;
+}
+
+#newtab-margin-undo-container:dir(rtl) {
+ left: auto;
+ right: 6px;
+}
+
+#newtab-undo-close-button:dir(rtl) {
+ float:left;
+}
+
+#newtab-horizontal-margin {
+ display: -moz-box;
+ -moz-box-flex: 5;
+}
+
+#newtab-margin-top {
+ min-height: 10px;
+ max-height: 30px;
+ display: -moz-box;
+ -moz-box-flex: 1;
+ -moz-box-align: center;
+ -moz-box-pack: center;
+}
+
+#newtab-margin-bottom {
+ min-height: 40px;
+ max-height: 80px;
+ -moz-box-flex: 1;
+}
+
+.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: 175ms 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;
+}
+
+/*
+ * Thumbnail image sizes are determined in the preferences:
+ * toolkit.pageThumbs.minWidth
+ * toolkit.pageThumbs.minHeight
+ */
+/* CELLS */
+.newtab-cell {
+ display: -moz-box;
+ -moz-box-flex: 1;
+}
+
+/* SITES */
+.newtab-site {
+ position: relative;
+ -moz-box-flex: 1;
+ transition: 150ms 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;
+}
+
+/* TITLES */
+.newtab-title {
+ overflow: hidden;
+ position: absolute;
+ right: 0;
+ text-align: center;
+}
+
+.newtab-title {
+ bottom: 0;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ vertical-align: middle;
+}
+
+.newtab-title {
+ left: 0;
+ padding: 0 4px;
+}
+
+/* CONTROLS */
+.newtab-control {
+ position: absolute;
+ opacity: 0;
+ transition: opacity 100ms ease-out;
+}
+
+.newtab-control:-moz-focusring,
+.newtab-cell:not([ignorehover]) > .newtab-site:hover > .newtab-control {
+ opacity: 1;
+}
+
+.newtab-control[dragged] {
+ opacity: 0 !important;
+}
+
+@media (-moz-touch-enabled) {
+ .newtab-control {
+ opacity: 1;
+ }
+}
+
+/* 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;
+}
+
+/* SEARCH */
+#searchContainer {
+ display: -moz-box;
+ position: relative;
+ -moz-box-pack: center;
+ margin: 10px 0 15px;
+}
+
+#searchContainer[page-disabled] {
+ opacity: 0;
+ pointer-events: none;
+}
+
+#searchForm {
+ display: -moz-box;
+ position: relative;
+ height: 36px;
+ -moz-box-flex: 1;
+ max-width: 600px; /* 2 * (290 cell width + 10 cell margin) */
+}
+
+#searchEngineLogo {
+ border: 1px transparent;
+ padding: 2px 4px;
+ margin: 0;
+ width: 32px;
+ height: 32px;
+ position: absolute;
+}
+
+#searchText {
+ -moz-box-flex: 1;
+ padding-top: 6px;
+ padding-bottom: 6px;
+ padding-inline-start: 42px;
+ padding-inline-end: 8px;
+ background: hsla(0,0%,100%,.9) padding-box;
+ border: 1px solid;
+ border-spacing: 0;
+ border-radius: 2px 0 0 2px;
+ 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);
+ color: inherit;
+ unicode-bidi: plaintext;
+}
+
+#searchText:dir(rtl) {
+ border-radius: 0 2px 2px 0;
+}
+
+#searchText[aria-expanded="true"] {
+ border-radius: 2px 0 0 0;
+}
+
+#searchText[aria-expanded="true"]:dir(rtl) {
+ border-radius: 0 2px 0 0;
+}
+
+#searchText[keepfocus],
+#searchText:focus {
+ border-color: hsla(216,100%,60%,.6) hsla(216,76%,52%,.6) hsla(214,100%,40%,.6);
+}
+
+#searchSubmit {
+ margin-inline-start: -1px;
+ padding: 0;
+ border: 1px solid;
+ background-color: #e0e0e0;
+ color: black;
+ border-color: hsla(220,54%,20%,.15) hsla(220,54%,20%,.17) hsla(220,54%,20%,.2);
+ border-radius: 0 2px 2px 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;
+ width: 50px;
+}
+
+#searchSubmit:dir(rtl) {
+ border-radius: 2px 0 0 2px;
+}
+
+#searchSubmit:hover {
+ background-color: hsl(220,54%,20%);
+ color: white;
+}
+
+#searchText:focus + #searchSubmit,
+#searchText + #searchSubmit:hover {
+ border-color: #5985fc #4573e7 #3264d5;
+}
+
+#searchText:focus + #searchSubmit,
+#searchText[keepfocus] + #searchSubmit {
+ 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(220,54%,20%,.03);
+}
+
+#searchText + #searchSubmit:hover {
+ 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(220,54%,20%,.03),
+ 0 0 4px hsla(216,100%,20%,.2);
+}
+
+#searchText + #searchSubmit:hover:active {
+ box-shadow: 0 1px 1px hsla(221,79%,6%,.1) inset,
+ 0 0 1px hsla(221,79%,6%,.2) inset;
+ transition-duration: 0ms;
+}
+
+.contentSearchSuggestionTable {
+ font: message-box;
+ font-size: 16px;
+}
diff --git a/components/newtab/newTab.js b/components/newtab/newTab.js
new file mode 100644
index 0000000..0022f21
--- /dev/null
+++ b/components/newtab/newTab.js
@@ -0,0 +1,69 @@
+/* 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";
+
+var Cu = Components.utils;
+var 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/BackgroundPageThumbs.jsm");
+Cu.import("resource://gre/modules/NewTabUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Rect",
+ "resource://gre/modules/Geometry.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+var {
+ 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, args) {
+ let stringName = "newtab." + name;
+ if (!args) {
+ return gStringBundle.GetStringFromName(stringName);
+ }
+ return gStringBundle.formatStringFromName(stringName, args, args.length);
+}
+
+function inPrivateBrowsingMode() {
+ return PrivateBrowsingUtils.isContentWindowPrivate(window);
+}
+
+const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml";
+const XUL_NAMESPACE = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+const TILES_EXPLAIN_LINK = "https://support.mozilla.org/kb/how-do-tiles-work-firefox";
+const TILES_INTRO_LINK = "https://www.mozilla.org/firefox/tiles/";
+const TILES_PRIVACY_LINK = "https://www.mozilla.org/privacy/";
+
+#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
+#include search.js
+
+// Everything is loaded. Initialize the New Tab Page.
+gPage.init();
diff --git a/components/newtab/newTab.xhtml b/components/newtab/newTab.xhtml
new file mode 100644
index 0000000..de000e7
--- /dev/null
+++ b/components/newtab/newTab.xhtml
@@ -0,0 +1,61 @@
+<?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 % newTabDTD SYSTEM "chrome://browser/locale/newTab.dtd">
+ %newTabDTD;
+ <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
+ %browserDTD;
+ <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
+ %globalDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>&newtab.pageTitle;</title>
+
+ <link rel="stylesheet" type="text/css" media="all" href="chrome://global/skin/" />
+ <link rel="stylesheet" type="text/css" media="all" href="chrome://browser/content/newtab/newTab.css" />
+ <link rel="stylesheet" type="text/css" media="all" href="chrome://browser/skin/newtab/newTab.css" />
+</head>
+
+<body dir="&locale.dir;">
+ <div id="newtab-vertical-margin">
+ <div id="newtab-margin-top"/>
+
+ <div id="newtab-margin-undo-container">
+ <div id="newtab-undo-container" undo-disabled="true">
+ <label id="newtab-undo-label">&newtab.undo.removedLabel;</label>
+ <button id="newtab-undo-button" tabindex="-1"
+ class="newtab-undo-button">&newtab.undo.undoButton;</button>
+ <button id="newtab-undo-restore-button" tabindex="-1"
+ class="newtab-undo-button">&newtab.undo.restoreButton;</button>
+ <button id="newtab-undo-close-button" tabindex="-1" title="&newtab.undo.closeTooltip;"/>
+ </div>
+ </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"/>
+ <input id="searchSubmit" type="submit" value="&newtab.searchEngineButton.label;"/>
+ </form>
+ </div>
+
+ <div id="newtab-horizontal-margin">
+ <div class="newtab-side-margin"/>
+ <div id="newtab-grid">
+ <!-- site grid -->
+ </div>
+ <div class="newtab-side-margin"/>
+ </div>
+
+ <div id="newtab-margin-bottom"/>
+ <input id="newtab-toggle" type="button"/>
+ </div>
+</body>
+<script type="text/javascript;version=1.8" src="chrome://browser/content/newtab/newTab.js"/>
+</html>
diff --git a/components/newtab/page.js b/components/newtab/page.js
new file mode 100644
index 0000000..34387fd
--- /dev/null
+++ b/components/newtab/page.js
@@ -0,0 +1,244 @@
+#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
+
+// The amount of time we wait while coalescing updates for hidden pages.
+const SCHEDULE_UPDATE_TIMEOUT_MS = 1000;
+
+/**
+ * This singleton represents the whole 'New Tab Page' and takes care of
+ * initializing all its components.
+ */
+var 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", e => this.toggleEnabled(e));
+
+ // XXX bug 991111 - Not all click events are correctly triggered when
+ // listening from xhtml nodes -- in particular middle clicks on sites, so
+ // listen from the xul window and filter then delegate
+ 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(aSubject, aTopic, aData) {
+ if (aTopic == "nsPref:changed") {
+ 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();
+ }
+ } else if (aTopic == "page-thumbnail:create" && gGrid.ready) {
+ for (let site of gGrid.sites) {
+ if (site && site.url === aData) {
+ site.refreshThumbnail();
+ }
+ }
+ }
+ },
+
+ /**
+ * Updates the page's grid right away for visible pages. If the page is
+ * currently hidden, i.e. in a background tab or in the preloader, then we
+ * batch multiple update requests and refresh the grid once after a short
+ * delay. Accepts a single parameter the specifies the reason for requesting
+ * a page update. The page may decide to delay or prevent a requested updated
+ * based on the given reason.
+ */
+ update(reason = "") {
+ // Update immediately if we're visible.
+ if (!document.hidden) {
+ // Ignore updates where reason=links-changed as those signal that the
+ // provider's set of links changed. We don't want to update visible pages
+ // in that case, it is ok to wait until the user opens the next tab.
+ if (reason != "links-changed" && gGrid.ready) {
+ gGrid.refresh();
+ }
+
+ return;
+ }
+
+ // Bail out if we scheduled before.
+ if (this._scheduleUpdateTimeout) {
+ return;
+ }
+
+ this._scheduleUpdateTimeout = setTimeout(() => {
+ // Refresh if the grid is ready.
+ if (gGrid.ready) {
+ gGrid.refresh();
+ }
+
+ this._scheduleUpdateTimeout = null;
+ }, SCHEDULE_UPDATE_TIMEOUT_MS);
+ },
+
+ /**
+ * 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;
+
+ // Set submit button label for when CSS background are disabled (e.g.
+ // high contrast mode).
+ document.getElementById("searchSubmit").value =
+ document.body.getAttribute("dir") == "ltr" ? "\u25B6" : "\u25C0";
+
+ if (document.hidden) {
+ addEventListener("visibilitychange", this);
+ } else {
+ setTimeout(() => this.onPageFirstVisible());
+ }
+
+ // 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
+ },
+
+ /**
+ * 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-grid, #searchContainer";
+ 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");
+ }
+ },
+
+ /**
+ * Handles unload event
+ */
+ _handleUnloadEvent: function Page_handleUnloadEvent() {
+ gAllPages.unregister(this);
+ },
+
+ /**
+ * Handles all page events.
+ */
+ handleEvent: function Page_handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "load":
+ this.onPageVisibleAndLoaded();
+ break;
+ case "unload":
+ this._handleUnloadEvent();
+ break;
+ case "click":
+ let {button, target} = aEvent;
+ // Go up ancestors until we find a Site or not
+ while (target) {
+ if (target.hasOwnProperty("_newtabSite")) {
+ target._newtabSite.onClick(aEvent);
+ break;
+ }
+ target = target.parentNode;
+ }
+ 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;
+ case "visibilitychange":
+ // Cancel any delayed updates for hidden pages now that we're visible.
+ if (this._scheduleUpdateTimeout) {
+ clearTimeout(this._scheduleUpdateTimeout);
+ this._scheduleUpdateTimeout = null;
+
+ // An update was pending so force an update now.
+ this.update();
+ }
+
+ setTimeout(() => this.onPageFirstVisible());
+ removeEventListener("visibilitychange", this);
+ break;
+ }
+ },
+
+ onPageFirstVisible: function () {
+ // Record another page impression.
+ Services.telemetry.getHistogramById("NEWTAB_PAGE_SHOWN").add(true);
+
+ for (let site of gGrid.sites) {
+ if (site) {
+ // The site may need to modify and/or re-render itself if
+ // something changed after newtab was created by preloader.
+ // For example, the suggested tile endTime may have passed.
+ site.onFirstVisible();
+ }
+ }
+
+ // save timestamp to compute page life-span delta
+ this._firstVisibleTime = Date.now();
+
+ if (document.readyState == "complete") {
+ this.onPageVisibleAndLoaded();
+ } else {
+ addEventListener("load", this);
+ }
+ },
+
+ onPageVisibleAndLoaded() {
+ },
+
+ toggleEnabled: function(aEvent) {
+ gAllPages.enabled = !gAllPages.enabled;
+ aEvent.stopPropagation();
+ }
+};
diff --git a/components/newtab/search.js b/components/newtab/search.js
new file mode 100644
index 0000000..8bc959e
--- /dev/null
+++ b/components/newtab/search.js
@@ -0,0 +1,134 @@
+#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
+
+const SEARCH_ENGINES = {
+ "DuckDuckGo": {
+ image: "data:image/png;base64," +
+ "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAACT1BMVEXvISn/////9/fvUlr3ra3/" +
+ "zs7/7+/va2v/5+f/xsbvMTn/tbX/3t7/vb3vOUL3WmPvQkr/zgDvKTHvSlL3hIT3paX/1tbnISn3" +
+ "c3v3e3v3a3P3jIz3nJz/tb33c3PvKSn3lJT39/cAc73vSkr3e4Tv7+/3Yxj3pa3/tQj3jJT3nKX3" +
+ "Y2P/xs73hIzvQkL/vQjvQiHn5+f3hBD/ztbvMTH/vcb/3ucIc733lJz/pQilzufe7/fvMSHOzs73" +
+ "//cQrUpKvVprxmP3Y2vvShiUzmvWlJRzzmMYtUrvOTnn7/davVrWra3v9//nY2PvISGUxudztd7e" +
+ "3t7/76XvKSHea2v/xgDnOUK93vfW5/f/1t73Uhj/52ut3q2l3rXO784pjMZrrdb/rQjera3/5+/e" +
+ "paWMxufO79aEazkYrUr/nAj3jBD3axj3lBD///fehIRKpd7/1hCEYzk5vVL3//8ptVLW77UxtVLn" +
+ "SlLW1tZCvVp7vef/1gj/3invSkL//+fWtbXvpaX/3kr/97XvnJznWmMxjM5zvefOxsbWnKXWjIzG" +
+ "3u/ea3Pn997O5/fnQkqExuf3Whit1u/nUlrnxs7v5+d7zmuU1pT3exDOSjFjrVL/987/pUoQe8b/" +
+ "75T/3jFKxnO158bWKSl7zoRSxmtajEK1e0pzxlqcUjH/1iHOMSnOvb33cxDWnJx7td6EzmP/74xz" +
+ "azlrcznec3Pe771jxlpzczne78YpvVqEvWPn99YxvWOtSjHee3vG787OOTE5lEK1QjHv9+drzmve" +
+ "tbXO772q+r8wAAAFbUlEQVR4Xo2X84PzTBDHN3Zqu2fbemzbNl7atm3btvGHvTNJ2myuyd3NL2mT" +
+ "zmdnvjM76RImyGQlH5dCHBeSmscNmQkyfwBrZMLEY2aRF5cMSDYPEx+LZpUlAYRQbVEpnuc1je/M" +
+ "SbVwYoVFAbpE0IaLmiwqiVymmE3H84YuGs2mheCEhQH5qPUrje2ONxHKVIkXR2x2MxsMkDnLvftk" +
+ "2fSTQNCzSAgngwCCipkXxHiU+BsnCDFE8f6AQgnwaTGhkmDLymW8jPsBeIsth8iCpha618El1wgo" +
+ "4FOhWyWLWY+O8pbnAwTI29S1ElncJBmF4L0AGeJSdR4dUpt5w+DL0nAgoUuGGKKCBxDCOxrykaDb" +
+ "+yFQjhUylLlXpAB5jGnIqV6uvvWUcAAhLmDBXIAMrkXRdHQ+cerUiWefq1hRrAgg8LikUgdkQUAx" +
+ "6+2Ze0WLEO/1BQzrHCFNrAPAeDSD4q/Ln6R3p68MSYzDAUiwIEutJM0bHXE/gpEhJMxaAB3T6aT8" +
+ "mfkm+QBiMlwKFqAHvrHu9tvTOLrEdX4hFAkJWQB42qbVyam75ruv3zvF+wBCKJ0MAAV6SAy5+raA" +
+ "y+lb9tYBUw9sffKRJh+CDl2SAEAPquaC76swU1c+zlxbA9if/EIY78AcCBODDKjnVzDM0+sb57zq" +
+ "N14gdpbg4nraBaxm3NWpIDKNgJIIDTxEAKMyVM9/VrFcpijK52PbNhmk0RQORCA8dhGhIkDA+qPV" +
+ "Y/U8No2NHZsUfQCdzYTECSiRSRJKgxYAnK6+tnVrPYL7q2P7GNNnT0L3SQSS61AowK4BAExWq9XJ" +
+ "OmDT5D4GtUab7p92W1aD6AFBOjUKcONNKMG2o9vmScmhd+v5SCTS91StDLBwmHR5q0iiM4yv3X5g" +
+ "sD1i24tUHc0GQOrOihdw+ZV7drx+8I1IzfpaCQ1oSIGsbqEBdxy8KkLb8dYt7m7AFBpEJI8OUIAd" +
+ "Hve+wX509IqYgzLqxKMi5X+r6737wgHfMrZBKGwpQMWP0PN8/8qLn15cSRosEQeI3coxGrzRVfE2" +
+ "BEyTAMNpmbA3k2erPOyq+CUCPGvv3OmGykYBQhiYFbynDLu2uyW826qb7bSlv/VCe2R3vQqhIYQQ" +
+ "nLmSGKUAT1AqXn7V6p72iUsTThsNuhKUAeKMNFaiW2nG08H90IF1m6DywVdsHgA4bPgRGgAqUgBr" +
+ "DwxOtPcdv9RK6yklnaGKOXBMmN7RVCtJJMiUdG2s78dv9HbY7KrI9AQBOHwjaxaA6cKhRLXCHkpF" +
+ "PrAJYBz1su7LtSBQIjzozgI5AJDWsQ7gTJxETTHuEh5yW8kR5+1fvQBT5PDdWgPokE6GSuK3Aaby" +
+ "2KwNyGFIZ8/NfexVMAGXEfe8MA5QTVdrgGe2M9evev6FMwiAYr308nVzcx/SgHwSlswyLgDLHU0K" +
+ "tX5UZwCwZsM1b7516J1333v/g2UAuJoCNMsmZkEDZBXujCoOIfVJxQKsvXnDshvWfrEcAV9RAoqY" +
+ "rfdvHjY06R3tVmtjzQYsQ8ByC/C1O0dEzqkAGqELbiZ1W/RvBr51Ad9ZgO8dQCkh4/q5xvMC6hot" +
+ "sBl7rP1QT+HHQz9RGoSHhkyMgqEBdNPFWSWMY+1nBPxy+MjvZ2aZxB9n/zz3FwKiOTZfotb3AhhF" +
+ "xSUUNmGSjX+vWvPPYacVWJOkUilUT05ymEVb0JFHj9l/AVn+35b/jsx6YzNz8mja+iAEH7rYDntY" +
+ "Gaz3dizW080KWaeICx77kiG7lTKG6EEoPb0Wu0lZ9OA5whFH8GxHQjOMQls5HSs5t/glHX2FYtT/" +
+ "mGAs/fCtFU0vQJUSQYfvIBvVyukuLhbjuood/H6WCbD/AQSFvIO3JDxgAAAAAElFTkSuQmCC"
+ }
+};
+
+// This global tracks if the page has been set up before, to prevent double inits
+var gInitialized = false;
+var gObserver = new MutationObserver(function (mutations) {
+ for (let mutation of mutations) {
+ if (mutation.attributeName == "searchEngineURL") {
+ setupSearchEngine();
+ if (!gInitialized) {
+ gInitialized = true;
+ }
+ return;
+ }
+ }
+});
+
+window.addEventListener("pageshow", function () {
+ window.gObserver.observe(document.documentElement, { attributes: true });
+});
+
+window.addEventListener("pagehide", function() {
+ window.gObserver.disconnect();
+});
+
+function onSearchSubmit(aEvent) {
+ let searchTerms = document.getElementById("searchText").value;
+ let searchURL = document.documentElement.getAttribute("searchEngineURL");
+
+ if (searchURL && searchTerms.length > 0) {
+ 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() {
+ let searchText = document.getElementById("searchText");
+ 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;
+ }
+}
diff --git a/components/newtab/sites.js b/components/newtab/sites.js
new file mode 100644
index 0000000..cb56752
--- /dev/null
+++ b/components/newtab/sites.js
@@ -0,0 +1,353 @@
+#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
+
+const THUMBNAIL_PLACEHOLDER_ENABLED =
+ Services.prefs.getBoolPref("browser.newtabpage.thumbnailPlaceholder");
+
+/**
+ * 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() { return this._node; },
+
+ /**
+ * The site's link.
+ */
+ get link() { return this._link; },
+
+ /**
+ * The url of the site's link.
+ */
+ get url() { return this.link.url; },
+
+ /**
+ * The title of the site's link.
+ */
+ get title() { return this.link.title || this.link.url; },
+
+ /**
+ * 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).
+ * @return true if link changed type after pin
+ */
+ pin: function Site_pin(aIndex) {
+ if (typeof aIndex == "undefined")
+ aIndex = this.cell.index;
+
+ this._updateAttributes(true);
+ let changed = gPinnedLinks.pin(this._link, aIndex);
+ if (changed) {
+ // render site again
+ this._render();
+ }
+ return changed;
+ },
+
+ /**
+ * 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) {
+ this.node.setAttribute("pinned", true);
+ control.setAttribute("title", newTabString("unpin"));
+ } else {
+ this.node.removeAttribute("pinned");
+ control.setAttribute("title", newTabString("pin"));
+ }
+ },
+
+ _newTabString: function(str, substrArr) {
+ let regExp = /%[0-9]\$S/g;
+ let matches;
+ while ((matches = regExp.exec(str))) {
+ let match = matches[0];
+ let index = match.charAt(1); // Get the digit in the regExp.
+ str = str.replace(match, substrArr[index - 1]);
+ }
+ return str;
+ },
+
+ /**
+ * Checks for and modifies link at campaign end time
+ */
+ _checkLinkEndTime: function Site_checkLinkEndTime() {
+ if (this.link.endTime && this.link.endTime < Date.now()) {
+ let oldUrl = this.url;
+ // chop off the path part from url
+ this.link.url = Services.io.newURI(this.url, null, null).resolve("/");
+ // clear supplied images - this triggers thumbnail download for new url
+ delete this.link.imageURI;
+ // remove endTime to avoid further time checks
+ delete this.link.endTime;
+ gPinnedLinks.replace(oldUrl, this.link);
+ }
+ },
+
+ /**
+ * Renders the site's data (fills the HTML fragment).
+ */
+ _render: function Site_render() {
+ // first check for end time, as it may modify the link
+ this._checkLinkEndTime();
+ // setup display variables
+ let url = this.url;
+ let title = this.link.type == "history" ? this.link.baseDomain :
+ this.title;
+ let tooltip = (this.title == url ? this.title : this.title + "\n" + url);
+
+ let link = this._querySelector(".newtab-link");
+ link.setAttribute("title", tooltip);
+ link.setAttribute("href", url);
+ this.node.setAttribute("type", this.link.type);
+
+ let titleNode = this._querySelector(".newtab-title");
+ titleNode.textContent = title;
+ if (this.link.titleBgColor) {
+ titleNode.style.backgroundColor = this.link.titleBgColor;
+ }
+
+ if (this.isPinned())
+ this._updateAttributes(true);
+ // Capture the page if the thumbnail is missing, which will cause page.js
+ // to be notified and call our refreshThumbnail() method.
+ this.captureIfMissing();
+ // but still display whatever thumbnail might be available now.
+ this.refreshThumbnail();
+ },
+
+ /**
+ * Called when the site's tab becomes visible for the first time.
+ * Since the newtab may be preloaded long before it's displayed,
+ * check for changed conditions and re-render if needed
+ */
+ onFirstVisible: function Site_onFirstVisible() {
+ if (this.link.endTime && this.link.endTime < Date.now()) {
+ // site needs to change landing url and background image
+ this._render();
+ }
+ else {
+ this.captureIfMissing();
+ }
+ },
+
+ /**
+ * Captures the site's thumbnail in the background, but only if there's no
+ * existing thumbnail and the page allows background captures.
+ */
+ captureIfMissing: function Site_captureIfMissing() {
+ if (!document.hidden && !this.link.imageURI) {
+ BackgroundPageThumbs.captureIfMissing(this.url);
+ }
+ },
+
+ /**
+ * Refreshes the thumbnail for the site.
+ */
+ refreshThumbnail: function Site_refreshThumbnail() {
+ let link = this.link;
+
+ let thumbnail = this._querySelector(".newtab-thumbnail.thumbnail");
+ if (link.bgColor) {
+ thumbnail.style.backgroundColor = link.bgColor;
+ }
+ let uri = link.imageURI || PageThumbs.getThumbnailURL(this.url);
+ thumbnail.style.backgroundImage = 'url("' + uri + '")';
+
+ if (THUMBNAIL_PLACEHOLDER_ENABLED &&
+ link.type == "history" &&
+ link.baseDomain) {
+ let placeholder = this._querySelector(".newtab-thumbnail.placeholder");
+ let charCodeSum = 0;
+ for (let c of link.baseDomain) {
+ charCodeSum += c.charCodeAt(0);
+ }
+ const COLORS = 16;
+ let hue = Math.round((charCodeSum % COLORS) / COLORS * 360);
+ placeholder.style.backgroundColor = "hsl(" + hue + ",80%,40%)";
+ placeholder.textContent = link.baseDomain.substr(0,1).toUpperCase();
+ }
+ },
+
+ _ignoreHoverEvents: function(element) {
+ element.addEventListener("mouseover", () => {
+ this.cell.node.setAttribute("ignorehover", "true");
+ });
+ element.addEventListener("mouseout", () => {
+ this.cell.node.removeAttribute("ignorehover");
+ });
+ },
+
+ /**
+ * 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);
+ },
+
+ /**
+ * 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);
+ try {
+ // This can throw for certain internal URLs, when they wind up in
+ // about:newtab. Be sure not to propagate the error.
+ sc.speculativeConnect(uri, null);
+ } catch (e) {}
+ },
+
+ /**
+ * Record interaction with site using telemetry.
+ */
+ _recordSiteClicked: function Site_recordSiteClicked(aIndex) {
+ if (Services.prefs.prefHasUserValue("browser.newtabpage.rows") ||
+ Services.prefs.prefHasUserValue("browser.newtabpage.columns") ||
+ aIndex > 8) {
+ // We only want to get indices for the default configuration, everything
+ // else goes in the same bucket.
+ aIndex = 9;
+ }
+ Services.telemetry.getHistogramById("NEWTAB_PAGE_SITE_CLICKED")
+ .add(aIndex);
+ },
+
+ _toggleLegalText: function(buttonClass, explanationTextClass) {
+ let button = this._querySelector(buttonClass);
+ if (button.hasAttribute("active")) {
+ let explain = this._querySelector(explanationTextClass);
+ explain.parentNode.removeChild(explain);
+
+ button.removeAttribute("active");
+ }
+ },
+
+ /**
+ * Handles site click events.
+ */
+ onClick: function Site_onClick(aEvent) {
+ let action;
+ let pinned = this.isPinned();
+ let tileIndex = this.cell.index;
+ let {button, target} = aEvent;
+
+ // Handle tile/thumbnail link click
+ if (target.classList.contains("newtab-link") ||
+ target.parentElement.classList.contains("newtab-link")) {
+ // Record for primary and middle clicks
+ if (button == 0 || button == 1) {
+ this._recordSiteClicked(tileIndex);
+ action = "click";
+ }
+ }
+ // Only handle primary clicks for the remaining targets
+ else if (button == 0) {
+ aEvent.preventDefault();
+ if (target.classList.contains("newtab-control-block")) {
+ this.block();
+ action = "block";
+ }
+ else if (pinned && target.classList.contains("newtab-control-pin")) {
+ this.unpin();
+ action = "unpin";
+ }
+ else if (!pinned && target.classList.contains("newtab-control-pin")) {
+ if (this.pin()) {
+ // link has changed - update rest of the pages
+ gAllPages.update(gPage);
+ }
+ action = "pin";
+ }
+ }
+ },
+
+ /**
+ * Handles all site events.
+ */
+ handleEvent: function Site_handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "mouseover":
+ this._node.removeEventListener("mouseover", this, false);
+ this._speculativeConnect();
+ break;
+ case "dragstart":
+ gDrag.start(this, aEvent);
+ break;
+ case "dragend":
+ gDrag.end(this, aEvent);
+ break;
+ }
+ }
+};
diff --git a/components/newtab/transformations.js b/components/newtab/transformations.js
new file mode 100644
index 0000000..f7db0ad
--- /dev/null
+++ b/components/newtab/transformations.js
@@ -0,0 +1,270 @@
+#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.
+ */
+var 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, ["left", "top"], 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;
+
+ batch.push(new Promise(resolve => {
+ if (!cells[aIndex]) {
+ // The site disappeared from the grid, hide it.
+ this.hideSite(aSite, resolve);
+ } else if (this._getNodeOpacity(aSite.node) != 1) {
+ // The site disappeared before but is now back, show it.
+ this.showSite(aSite, resolve);
+ } else {
+ // The site's position has changed, move it around.
+ this._moveSite(aSite, aIndex, {unfreeze: unfreeze, callback: resolve});
+ }
+ }));
+ }, this);
+
+ if (callback) {
+ Promise.all(batch).then(callback);
+ }
+ },
+
+ /**
+ * Listens for the 'transitionend' event on a given node and calls the given
+ * callback.
+ * @param aNode The node that is transitioned.
+ * @param aProperties The properties we'll wait to be transitioned.
+ * @param aCallback The callback to call when finished.
+ */
+ _whenTransitionEnded:
+ function Transformation_whenTransitionEnded(aNode, aProperties, aCallback) {
+
+ let props = new Set(aProperties);
+ aNode.addEventListener("transitionend", function onEnd(e) {
+ if (props.has(e.propertyName)) {
+ aNode.removeEventListener("transitionend", onEnd);
+ aCallback();
+ }
+ });
+ },
+
+ /**
+ * 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, ["opacity"], 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/components/newtab/undo.js b/components/newtab/undo.js
new file mode 100644
index 0000000..b856914
--- /dev/null
+++ b/components/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.
+ */
+var 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/components/newtab/updater.js b/components/newtab/updater.js
new file mode 100644
index 0000000..2bab74d
--- /dev/null
+++ b/components/newtab/updater.js
@@ -0,0 +1,177 @@
+#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.
+ */
+var 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);
+
+ // Remove sites that are no longer in the grid.
+ this._removeLegacySites(sites, () => {
+ // Freeze all site positions so that we can move their DOM nodes around
+ // without any visual impact.
+ this._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.
+ this._moveSiteNodes(sites);
+
+ // Now it's time to animate the sites actually moving to their new
+ // positions.
+ this._rearrangeSites(sites, () => {
+ // Try to fill empty cells and finish.
+ this._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;
+
+ batch.push(new Promise(resolve => {
+ // 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);
+ resolve();
+ });
+ }));
+ });
+
+ Promise.all(batch).then(aCallback);
+ },
+
+ /**
+ * 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;
+
+ // Find empty cells and fill them.
+ Promise.all(sites.map((aSite, aIndex) => {
+ if (aSite || !aLinks[aIndex])
+ return null;
+
+ return new Promise(resolve => {
+ // 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, resolve);
+ });
+ })).then(aCallback).catch(console.exception);
+ }
+};
diff --git a/components/nsAboutRedirector.js b/components/nsAboutRedirector.js
new file mode 100644
index 0000000..4d99a78
--- /dev/null
+++ b/components/nsAboutRedirector.js
@@ -0,0 +1,114 @@
+/* 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 Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+// See: netwerk/protocol/about/nsIAboutModule.idl
+const URI_SAFE_FOR_UNTRUSTED_CONTENT = Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT;
+const ALLOW_SCRIPT = Ci.nsIAboutModule.ALLOW_SCRIPT;
+const HIDE_FROM_ABOUTABOUT = Ci.nsIAboutModule.HIDE_FROM_ABOUTABOUT;
+const MAKE_LINKABLE = Ci.nsIAboutModule.MAKE_LINKABLE;
+
+function AboutRedirector() {}
+AboutRedirector.prototype = {
+ classDescription: "Browser about: Redirector",
+ classID: Components.ID("{8cc51368-6aa0-43e8-b762-bde9b9fd828c}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]),
+
+ // Each entry in the map has the key as the part after the "about:" and the
+ // value as a record with url and flags entries. Note that each addition here
+ // should be coupled with a corresponding addition in BrowserComponents.manifest.
+ _redirMap: {
+ "certerror": {
+ url: "chrome://browser/content/certerror/aboutCertError.xhtml",
+ flags: (URI_SAFE_FOR_UNTRUSTED_CONTENT | ALLOW_SCRIPT | HIDE_FROM_ABOUTABOUT)
+ },
+ "downloads": {
+ url: "chrome://browser/content/downloads/contentAreaDownloadsView.xul",
+ flags: ALLOW_SCRIPT
+ },
+ "feeds": {
+ url: "chrome://browser/content/feeds/subscribe.xhtml",
+ flags: (URI_SAFE_FOR_UNTRUSTED_CONTENT | ALLOW_SCRIPT | HIDE_FROM_ABOUTABOUT)
+ },
+ "home": {
+ url: "chrome://browser/content/abouthome/aboutHome.xhtml",
+ flags: (URI_SAFE_FOR_UNTRUSTED_CONTENT | MAKE_LINKABLE | ALLOW_SCRIPT)
+ },
+ "newtab": {
+ url: "chrome://browser/content/newtab/newTab.xhtml",
+ flags: ALLOW_SCRIPT
+ },
+ "palemoon": {
+ url: "chrome://browser/content/palemoon.xhtml",
+ flags: (URI_SAFE_FOR_UNTRUSTED_CONTENT | HIDE_FROM_ABOUTABOUT)
+ },
+ "permissions": {
+ url: "chrome://browser/content/permissions/aboutPermissions.xul",
+ flags: ALLOW_SCRIPT
+ },
+ "privatebrowsing": {
+ url: "chrome://browser/content/aboutPrivateBrowsing.xhtml",
+ flags: ALLOW_SCRIPT
+ },
+ "rights": {
+ url: "chrome://global/content/aboutRights.xhtml",
+ flags: (URI_SAFE_FOR_UNTRUSTED_CONTENT | MAKE_LINKABLE | ALLOW_SCRIPT)
+ },
+ "sessionrestore": {
+ url: "chrome://browser/content/aboutSessionRestore.xhtml",
+ flags: ALLOW_SCRIPT
+ },
+#ifdef MOZ_SERVICES_SYNC
+ "sync-progress": {
+ url: "chrome://browser/content/sync/progress.xhtml",
+ flags: ALLOW_SCRIPT
+ },
+ "sync-tabs": {
+ url: "chrome://browser/content/sync/aboutSyncTabs.xul",
+ flags: ALLOW_SCRIPT
+ },
+#endif
+ },
+
+ /**
+ * Gets the module name from the given URI.
+ */
+ _getModuleName: function AboutRedirector__getModuleName(aURI) {
+ // Strip out the first ? or #, and anything following it
+ let name = (/[^?#]+/.exec(aURI.path))[0];
+ return name.toLowerCase();
+ },
+
+ getURIFlags: function(aURI) {
+ let name = this._getModuleName(aURI);
+ if (!(name in this._redirMap))
+ throw Cr.NS_ERROR_ILLEGAL_VALUE;
+ return this._redirMap[name].flags;
+ },
+
+ newChannel: function(aURI, aLoadInfo) {
+ let name = this._getModuleName(aURI);
+ if (!(name in this._redirMap))
+ throw Cr.NS_ERROR_ILLEGAL_VALUE;
+
+ let newURI = Services.io.newURI(this._redirMap[name].url, null, null);
+ let channel = Services.io.newChannelFromURIWithLoadInfo(newURI, aLoadInfo);
+ channel.originalURI = aURI;
+
+ if (this._redirMap[name].flags & Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT) {
+ let principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(aURI);
+ channel.owner = principal;
+ }
+
+ return channel;
+ }
+};
+
+var NSGetFactory = XPCOMUtils.generateNSGetFactory([AboutRedirector]);
diff --git a/components/nsBrowserContentHandler.js b/components/nsBrowserContentHandler.js
new file mode 100644
index 0000000..e7f1414
--- /dev/null
+++ b/components/nsBrowserContentHandler.js
@@ -0,0 +1,803 @@
+# 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");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
+ "resource:///modules/RecentWindow.jsm");
+
+const nsISupports = Components.interfaces.nsISupports;
+
+const nsIBrowserDOMWindow = Components.interfaces.nsIBrowserDOMWindow;
+const nsIBrowserHandler = Components.interfaces.nsIBrowserHandler;
+const nsIBrowserHistory = Components.interfaces.nsIBrowserHistory;
+const nsIChannel = Components.interfaces.nsIChannel;
+const nsICommandLine = Components.interfaces.nsICommandLine;
+const nsICommandLineHandler = Components.interfaces.nsICommandLineHandler;
+const nsIContentHandler = Components.interfaces.nsIContentHandler;
+const nsIDocShellTreeItem = Components.interfaces.nsIDocShellTreeItem;
+const nsIDOMChromeWindow = Components.interfaces.nsIDOMChromeWindow;
+const nsIDOMWindow = Components.interfaces.nsIDOMWindow;
+const nsIFileURL = Components.interfaces.nsIFileURL;
+const nsIInterfaceRequestor = Components.interfaces.nsIInterfaceRequestor;
+const nsINetUtil = Components.interfaces.nsINetUtil;
+const nsIPrefBranch = Components.interfaces.nsIPrefBranch;
+const nsIPrefLocalizedString = Components.interfaces.nsIPrefLocalizedString;
+const nsISupportsString = Components.interfaces.nsISupportsString;
+const nsIURIFixup = Components.interfaces.nsIURIFixup;
+const nsIWebNavigation = Components.interfaces.nsIWebNavigation;
+const nsIWindowMediator = Components.interfaces.nsIWindowMediator;
+const nsIWindowWatcher = Components.interfaces.nsIWindowWatcher;
+const nsIWebNavigationInfo = Components.interfaces.nsIWebNavigationInfo;
+const nsIBrowserSearchService = Components.interfaces.nsIBrowserSearchService;
+const nsICommandLineValidator = Components.interfaces.nsICommandLineValidator;
+
+const NS_BINDING_ABORTED = Components.results.NS_BINDING_ABORTED;
+const NS_ERROR_WONT_HANDLE_CONTENT = 0x805d0001;
+const NS_ERROR_ABORT = Components.results.NS_ERROR_ABORT;
+
+const URI_INHERITS_SECURITY_CONTEXT = Components.interfaces.nsIHttpProtocolHandler
+ .URI_INHERITS_SECURITY_CONTEXT;
+
+function shouldLoadURI(aURI) {
+ if (aURI && !aURI.schemeIs("chrome"))
+ return true;
+
+ dump("*** Preventing external load of chrome: URI into browser window\n");
+ dump(" Use -chrome <uri> instead\n");
+ return false;
+}
+
+function resolveURIInternal(aCmdLine, aArgument) {
+ var uri = aCmdLine.resolveURI(aArgument);
+ var urifixup = Components.classes["@mozilla.org/docshell/urifixup;1"]
+ .getService(nsIURIFixup);
+
+ if (!(uri instanceof nsIFileURL)) {
+ return urifixup.createFixupURI(aArgument,
+ urifixup.FIXUP_FLAG_FIX_SCHEME_TYPOS);
+ }
+
+ try {
+ if (uri.file.exists())
+ return uri;
+ }
+ catch (e) {
+ Components.utils.reportError(e);
+ }
+
+ // We have interpreted the argument as a relative file URI, but the file
+ // doesn't exist. Try URI fixup heuristics: see bug 290782.
+
+ try {
+ uri = urifixup.createFixupURI(aArgument, 0);
+ }
+ catch (e) {
+ Components.utils.reportError(e);
+ }
+
+ return uri;
+}
+
+var gFirstWindow = false;
+
+const OVERRIDE_NONE = 0;
+const OVERRIDE_NEW_PROFILE = 1;
+const OVERRIDE_NEW_MSTONE = 2;
+const OVERRIDE_NEW_BUILD_ID = 3;
+/**
+ * Determines whether a home page override is needed.
+ * Returns:
+ * OVERRIDE_NEW_PROFILE if this is the first run with a new profile.
+ * OVERRIDE_NEW_MSTONE if this is the first run with a build with a different
+ * Goanna milestone (i.e. right after an upgrade).
+ * OVERRIDE_NEW_BUILD_ID if this is the first run with a new build ID of the
+ * same Goanna milestone (i.e. after a nightly upgrade).
+ * OVERRIDE_NONE otherwise.
+ */
+function needHomepageOverride(prefb) {
+ var savedmstone = prefb.getCharPref("browser.startup.homepage_override.mstone", "");
+
+ if (savedmstone == "ignore")
+ return OVERRIDE_NONE;
+
+ var mstone = Services.appinfo.platformVersion;
+
+ var savedBuildID = prefb.getCharPref("browser.startup.homepage_override.buildID", "");
+
+ var buildID = Services.appinfo.platformBuildID;
+
+ if (mstone != savedmstone) {
+ // Bug 462254. Previous releases had a default pref to suppress the EULA
+ // agreement if the platform's installer had already shown one. Now with
+ // about:rights we've removed the EULA stuff and default pref, but we need
+ // a way to make existing profiles retain the default that we removed.
+ if (savedmstone)
+ prefb.setBoolPref("browser.rights.3.shown", true);
+
+ prefb.setCharPref("browser.startup.homepage_override.mstone", mstone);
+ prefb.setCharPref("browser.startup.homepage_override.buildID", buildID);
+ return (savedmstone ? OVERRIDE_NEW_MSTONE : OVERRIDE_NEW_PROFILE);
+ }
+
+ if (buildID != savedBuildID) {
+ prefb.setCharPref("browser.startup.homepage_override.buildID", buildID);
+ return OVERRIDE_NEW_BUILD_ID;
+ }
+
+ return OVERRIDE_NONE;
+}
+
+/**
+ * Gets the override page for the first run after the application has been
+ * updated.
+ * @param defaultOverridePage
+ * The default override page.
+ * @return The override page.
+ */
+function getPostUpdateOverridePage(defaultOverridePage) {
+ var um = Components.classes["@mozilla.org/updates/update-manager;1"]
+ .getService(Components.interfaces.nsIUpdateManager);
+ try {
+ // If the updates.xml file is deleted then getUpdateAt will throw.
+ var update = um.getUpdateAt(0)
+ .QueryInterface(Components.interfaces.nsIPropertyBag);
+ } catch (e) {
+ // This should never happen.
+ Components.utils.reportError("Unable to find update: " + e);
+ return defaultOverridePage;
+ }
+
+ let actions = update.getProperty("actions");
+ // When the update doesn't specify actions fallback to the original behavior
+ // of displaying the default override page.
+ if (!actions)
+ return defaultOverridePage;
+
+ // The existence of silent or the non-existence of showURL in the actions both
+ // mean that an override page should not be displayed.
+ if (actions.indexOf("silent") != -1 || actions.indexOf("showURL") == -1)
+ return "";
+
+ return update.getProperty("openURL") || defaultOverridePage;
+}
+
+// Flag used to indicate that the arguments to openWindow can be passed directly.
+const NO_EXTERNAL_URIS = 1;
+
+function openWindow(parent, url, target, features, args, noExternalArgs) {
+ var wwatch = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
+ .getService(nsIWindowWatcher);
+
+ if (noExternalArgs == NO_EXTERNAL_URIS) {
+ // Just pass in the defaultArgs directly
+ var argstring;
+ if (args) {
+ argstring = Components.classes["@mozilla.org/supports-string;1"]
+ .createInstance(nsISupportsString);
+ argstring.data = args;
+ }
+
+ return wwatch.openWindow(parent, url, target, features, argstring);
+ }
+
+ // Pass an array to avoid the browser "|"-splitting behavior.
+ var argArray = Components.classes["@mozilla.org/supports-array;1"]
+ .createInstance(Components.interfaces.nsISupportsArray);
+
+ // add args to the arguments array
+ var stringArgs = null;
+ if (args instanceof Array) // array
+ stringArgs = args;
+ else if (args) // string
+ stringArgs = [args];
+
+ if (stringArgs) {
+ // put the URIs into argArray
+ var uriArray = Components.classes["@mozilla.org/supports-array;1"]
+ .createInstance(Components.interfaces.nsISupportsArray);
+ stringArgs.forEach(function (uri) {
+ var sstring = Components.classes["@mozilla.org/supports-string;1"]
+ .createInstance(nsISupportsString);
+ sstring.data = uri;
+ uriArray.AppendElement(sstring);
+ });
+ argArray.AppendElement(uriArray);
+ } else {
+ argArray.AppendElement(null);
+ }
+
+ // Pass these as null to ensure that we always trigger the "single URL"
+ // behavior in browser.js's gBrowserInit.onLoad (which handles the window
+ // arguments)
+ argArray.AppendElement(null); // charset
+ argArray.AppendElement(null); // referer
+ argArray.AppendElement(null); // postData
+ argArray.AppendElement(null); // allowThirdPartyFixup
+
+ return wwatch.openWindow(parent, url, target, features, argArray);
+}
+
+function openPreferences() {
+ var features = "chrome,titlebar,toolbar,centerscreen,dialog=no";
+ var url = "chrome://browser/content/preferences/preferences.xul";
+
+ var win = getMostRecentWindow("Browser:Preferences");
+ if (win) {
+ win.focus();
+ } else {
+ openWindow(null, url, "_blank", features);
+ }
+}
+
+function getMostRecentWindow(aType) {
+ var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
+ .getService(nsIWindowMediator);
+ return wm.getMostRecentWindow(aType);
+}
+
+function doSearch(searchTerm, cmdLine) {
+ var ss = Components.classes["@mozilla.org/browser/search-service;1"]
+ .getService(nsIBrowserSearchService);
+
+ var submission = ss.defaultEngine.getSubmission(searchTerm);
+
+ // fill our nsISupportsArray with uri-as-wstring, null, null, postData
+ var sa = Components.classes["@mozilla.org/supports-array;1"]
+ .createInstance(Components.interfaces.nsISupportsArray);
+
+ var wuri = Components.classes["@mozilla.org/supports-string;1"]
+ .createInstance(Components.interfaces.nsISupportsString);
+ wuri.data = submission.uri.spec;
+
+ sa.AppendElement(wuri);
+ sa.AppendElement(null);
+ sa.AppendElement(null);
+ sa.AppendElement(submission.postData);
+
+ // XXXbsmedberg: use handURIToExistingBrowser to obey tabbed-browsing
+ // preferences, but need nsIBrowserDOMWindow extensions
+
+ var wwatch = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
+ .getService(nsIWindowWatcher);
+
+ return wwatch.openWindow(null, gBrowserContentHandler.chromeURL,
+ "_blank",
+ "chrome,dialog=no,all" +
+ gBrowserContentHandler.getFeatures(cmdLine),
+ sa);
+}
+
+function nsBrowserContentHandler() {
+}
+nsBrowserContentHandler.prototype = {
+ classID: Components.ID("{5d0ce354-df01-421a-83fb-7ead0990c24e}"),
+
+ _xpcom_factory: {
+ createInstance: function bch_factory_ci(outer, iid) {
+ if (outer)
+ throw Components.results.NS_ERROR_NO_AGGREGATION;
+ return gBrowserContentHandler.QueryInterface(iid);
+ }
+ },
+
+ /* helper functions */
+
+ mChromeURL : null,
+
+ get chromeURL() {
+ if (this.mChromeURL) {
+ return this.mChromeURL;
+ }
+
+ var prefb = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(nsIPrefBranch);
+ this.mChromeURL = prefb.getCharPref("browser.chromeURL");
+
+ return this.mChromeURL;
+ },
+
+ /* nsISupports */
+ QueryInterface : XPCOMUtils.generateQI([nsICommandLineHandler,
+ nsIBrowserHandler,
+ nsIContentHandler,
+ nsICommandLineValidator]),
+
+ /* nsICommandLineHandler */
+ handle : function bch_handle(cmdLine) {
+ if (cmdLine.handleFlag("browser", false)) {
+ // Passing defaultArgs, so use NO_EXTERNAL_URIS
+ openWindow(null, this.chromeURL, "_blank",
+ "chrome,dialog=no,all" + this.getFeatures(cmdLine),
+ this.defaultArgs, NO_EXTERNAL_URIS);
+ cmdLine.preventDefault = true;
+ }
+
+ try {
+ var remoteCommand = cmdLine.handleFlagWithParam("remote", true);
+ }
+ catch (e) {
+ throw NS_ERROR_ABORT;
+ }
+
+ if (remoteCommand != null) {
+ try {
+ var a = /^\s*(\w+)\(([^\)]*)\)\s*$/.exec(remoteCommand);
+ var remoteVerb;
+ if (a) {
+ remoteVerb = a[1].toLowerCase();
+ var remoteParams = [];
+ var sepIndex = a[2].lastIndexOf(",");
+ if (sepIndex == -1)
+ remoteParams[0] = a[2];
+ else {
+ remoteParams[0] = a[2].substring(0, sepIndex);
+ remoteParams[1] = a[2].substring(sepIndex + 1);
+ }
+ }
+
+ switch (remoteVerb) {
+ case "openurl":
+ case "openfile":
+ // openURL(<url>)
+ // openURL(<url>,new-window)
+ // openURL(<url>,new-tab)
+
+ // First param is the URL, second param (if present) is the "target"
+ // (tab, window)
+ var url = remoteParams[0];
+ var target = nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW;
+ if (remoteParams[1]) {
+ var targetParam = remoteParams[1].toLowerCase()
+ .replace(/^\s*|\s*$/g, "");
+ if (targetParam == "new-tab")
+ target = nsIBrowserDOMWindow.OPEN_NEWTAB;
+ else if (targetParam == "new-window")
+ target = nsIBrowserDOMWindow.OPEN_NEWWINDOW;
+ else {
+ // The "target" param isn't one of our supported values, so
+ // assume it's part of a URL that contains commas.
+ url += "," + remoteParams[1];
+ }
+ }
+
+ var uri = resolveURIInternal(cmdLine, url);
+ handURIToExistingBrowser(uri, target, cmdLine);
+ break;
+
+ case "xfedocommand":
+ // xfeDoCommand(openBrowser)
+ if (remoteParams[0].toLowerCase() != "openbrowser")
+ throw NS_ERROR_ABORT;
+
+ // Passing defaultArgs, so use NO_EXTERNAL_URIS
+ openWindow(null, this.chromeURL, "_blank",
+ "chrome,dialog=no,all" + this.getFeatures(cmdLine),
+ this.defaultArgs, NO_EXTERNAL_URIS);
+ break;
+
+ default:
+ // Somebody sent us a remote command we don't know how to process:
+ // just abort.
+ throw "Unknown remote command.";
+ }
+
+ cmdLine.preventDefault = true;
+ }
+ catch (e) {
+ Components.utils.reportError(e);
+ // If we had a -remote flag but failed to process it, throw
+ // NS_ERROR_ABORT so that the xremote code knows to return a failure
+ // back to the handling code.
+ throw NS_ERROR_ABORT;
+ }
+ }
+
+ var uriparam;
+ try {
+ while ((uriparam = cmdLine.handleFlagWithParam("new-window", false))) {
+ var uri = resolveURIInternal(cmdLine, uriparam);
+ if (!shouldLoadURI(uri))
+ continue;
+ openWindow(null, this.chromeURL, "_blank",
+ "chrome,dialog=no,all" + this.getFeatures(cmdLine),
+ uri.spec);
+ cmdLine.preventDefault = true;
+ }
+ }
+ catch (e) {
+ Components.utils.reportError(e);
+ }
+
+ try {
+ while ((uriparam = cmdLine.handleFlagWithParam("new-tab", false))) {
+ var uri = resolveURIInternal(cmdLine, uriparam);
+ handURIToExistingBrowser(uri, nsIBrowserDOMWindow.OPEN_NEWTAB, cmdLine);
+ cmdLine.preventDefault = true;
+ }
+ }
+ catch (e) {
+ Components.utils.reportError(e);
+ }
+
+ var chromeParam = cmdLine.handleFlagWithParam("chrome", false);
+ if (chromeParam) {
+
+ // Handle the old preference dialog URL separately (bug 285416)
+ if (chromeParam == "chrome://browser/content/pref/pref.xul") {
+ openPreferences();
+ cmdLine.preventDefault = true;
+ } else try {
+ // only load URIs which do not inherit chrome privs
+ var features = "chrome,dialog=no,all" + this.getFeatures(cmdLine);
+ var uri = resolveURIInternal(cmdLine, chromeParam);
+ var netutil = Components.classes["@mozilla.org/network/util;1"]
+ .getService(nsINetUtil);
+ if (!netutil.URIChainHasFlags(uri, URI_INHERITS_SECURITY_CONTEXT)) {
+ openWindow(null, uri.spec, "_blank", features);
+ cmdLine.preventDefault = true;
+ }
+ }
+ catch (e) {
+ Components.utils.reportError(e);
+ }
+ }
+ if (cmdLine.handleFlag("preferences", false)) {
+ openPreferences();
+ cmdLine.preventDefault = true;
+ }
+ if (cmdLine.handleFlag("silent", false))
+ cmdLine.preventDefault = true;
+
+ try {
+ var privateWindowParam = cmdLine.handleFlagWithParam("private-window", false);
+ if (privateWindowParam) {
+ let resolvedURI = resolveURIInternal(cmdLine, privateWindowParam);
+ handURIToExistingBrowser(resolvedURI, nsIBrowserDOMWindow.OPEN_NEWTAB, cmdLine, true);
+ cmdLine.preventDefault = true;
+ }
+ } catch (e) {
+ if (e.result != Components.results.NS_ERROR_INVALID_ARG) {
+ throw e;
+ }
+ // NS_ERROR_INVALID_ARG is thrown when flag exists, but has no param.
+ if (cmdLine.handleFlag("private-window", false)) {
+ openWindow(null, this.chromeURL, "_blank",
+ "chrome,dialog=no,private,all" + this.getFeatures(cmdLine),
+ "about:privatebrowsing");
+ cmdLine.preventDefault = true;
+ }
+ }
+
+ var searchParam = cmdLine.handleFlagWithParam("search", false);
+ if (searchParam) {
+ doSearch(searchParam, cmdLine);
+ cmdLine.preventDefault = true;
+ }
+
+ // The global PB Service consumes this flag, so only eat it in per-window
+ // PB builds.
+ if (cmdLine.handleFlag("private", false)) {
+ PrivateBrowsingUtils.enterTemporaryAutoStartMode();
+ }
+
+ var fileParam = cmdLine.handleFlagWithParam("file", false);
+ if (fileParam) {
+ var file = cmdLine.resolveFile(fileParam);
+ var ios = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ var uri = ios.newFileURI(file);
+ openWindow(null, this.chromeURL, "_blank",
+ "chrome,dialog=no,all" + this.getFeatures(cmdLine),
+ uri.spec);
+ cmdLine.preventDefault = true;
+ }
+
+#ifdef XP_WIN
+ // Handle "? searchterm" for Windows Vista start menu integration
+ for (var i = cmdLine.length - 1; i >= 0; --i) {
+ var param = cmdLine.getArgument(i);
+ if (param.match(/^\? /)) {
+ cmdLine.removeArguments(i, i);
+ cmdLine.preventDefault = true;
+
+ searchParam = param.substr(2);
+ doSearch(searchParam, cmdLine);
+ }
+ }
+#endif
+ },
+
+ helpInfo : " --browser Open a browser window.\n" +
+ " --new-window <url> Open <url> in a new window.\n" +
+ " --new-tab <url> Open <url> in a new tab.\n" +
+ " --private-window <url> Open <url> in a new private window.\n" +
+#ifdef XP_WIN
+ " --preferences Open Options dialog.\n" +
+#else
+ " --preferences Open Preferences dialog.\n" +
+#endif
+ " --search <term> Search <term> with your default search engine.\n",
+
+ /* nsIBrowserHandler */
+
+ get defaultArgs() {
+ var prefb = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(nsIPrefBranch);
+
+ if (!gFirstWindow) {
+ gFirstWindow = true;
+ if (PrivateBrowsingUtils.isInTemporaryAutoStartMode) {
+ return "about:privatebrowsing";
+ }
+ }
+
+ var overridePage = "";
+ var haveUpdateSession = false;
+ try {
+ // Read the old value of homepage_override.mstone before
+ // needHomepageOverride updates it, so that we can later add it to the
+ // URL if we do end up showing an overridePage. This makes it possible
+ // to have the overridePage's content vary depending on the version we're
+ // upgrading from.
+ let old_mstone = Services.prefs.getCharPref("browser.startup.homepage_override.mstone", "unknown");
+ let override = needHomepageOverride(prefb);
+ if (override != OVERRIDE_NONE) {
+ switch (override) {
+ case OVERRIDE_NEW_PROFILE:
+ // New profile.
+ overridePage = Services.urlFormatter.formatURLPref("startup.homepage_welcome_url");
+ break;
+ case OVERRIDE_NEW_MSTONE:
+ // Check whether we have a session to restore. If we do, we assume
+ // that this is an "update" session.
+ var ss = Components.classes["@mozilla.org/browser/sessionstartup;1"]
+ .getService(Components.interfaces.nsISessionStartup);
+ haveUpdateSession = ss.doRestore();
+ overridePage = Services.urlFormatter.formatURLPref("startup.homepage_override_url");
+ if (prefb.prefHasUserValue("app.update.postupdate"))
+ overridePage = getPostUpdateOverridePage(overridePage);
+
+ overridePage = overridePage.replace("%OLD_VERSION%", old_mstone);
+ break;
+ }
+ }
+ } catch (ex) {}
+
+ // formatURLPref might return "about:blank" if getting the pref fails
+ if (overridePage == "about:blank")
+ overridePage = "";
+
+ var startPage = "";
+ try {
+ var choice = prefb.getIntPref("browser.startup.page");
+ if (choice == 1 || choice == 3)
+ startPage = this.startPage;
+ } catch (e) {
+ Components.utils.reportError(e);
+ }
+
+ // Only show the startPage if we're not restoring an update session.
+ if (overridePage && startPage && !haveUpdateSession)
+ return overridePage + "|" + startPage;
+
+ return overridePage || startPage || "about:logopage";
+ },
+
+ get startPage() {
+ var uri = Services.prefs.getComplexValue("browser.startup.homepage",
+ nsIPrefLocalizedString).data;
+ if (!uri) {
+ Services.prefs.clearUserPref("browser.startup.homepage");
+ uri = Services.prefs.getComplexValue("browser.startup.homepage",
+ nsIPrefLocalizedString).data;
+ }
+ return uri;
+ },
+
+ mFeatures : null,
+
+ getFeatures : function bch_features(cmdLine) {
+ if (this.mFeatures === null) {
+ this.mFeatures = "";
+
+ try {
+ var width = cmdLine.handleFlagWithParam("width", false);
+ var height = cmdLine.handleFlagWithParam("height", false);
+
+ if (width)
+ this.mFeatures += ",width=" + width;
+ if (height)
+ this.mFeatures += ",height=" + height;
+ }
+ catch (e) {
+ }
+
+ // The global PB Service consumes this flag, so only eat it in per-window
+ // PB builds.
+ if (PrivateBrowsingUtils.isInTemporaryAutoStartMode) {
+ this.mFeatures = ",private";
+ }
+ }
+
+ return this.mFeatures;
+ },
+
+ /* nsIContentHandler */
+
+ handleContent : function bch_handleContent(contentType, context, request) {
+ try {
+ var webNavInfo = Components.classes["@mozilla.org/webnavigation-info;1"]
+ .getService(nsIWebNavigationInfo);
+ if (!webNavInfo.isTypeSupported(contentType, null)) {
+ throw NS_ERROR_WONT_HANDLE_CONTENT;
+ }
+ } catch (e) {
+ throw NS_ERROR_WONT_HANDLE_CONTENT;
+ }
+
+ request.QueryInterface(nsIChannel);
+ handURIToExistingBrowser(request.URI,
+ nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW, null);
+ request.cancel(NS_BINDING_ABORTED);
+ },
+
+ /* nsICommandLineValidator */
+ validate : function bch_validate(cmdLine) {
+ // Other handlers may use osint so only handle the osint flag if the url
+ // flag is also present and the command line is valid.
+ var osintFlagIdx = cmdLine.findFlag("osint", false);
+ var urlFlagIdx = cmdLine.findFlag("url", false);
+ if (urlFlagIdx > -1 && (osintFlagIdx > -1 ||
+ cmdLine.state == nsICommandLine.STATE_REMOTE_EXPLICIT)) {
+ var urlParam = cmdLine.getArgument(urlFlagIdx + 1);
+ if (cmdLine.length != urlFlagIdx + 2 || /firefoxurl:/.test(urlParam))
+ throw NS_ERROR_ABORT;
+ cmdLine.handleFlag("osint", false)
+ }
+ },
+};
+var gBrowserContentHandler = new nsBrowserContentHandler();
+
+function handURIToExistingBrowser(uri, location, cmdLine, forcePrivate)
+{
+ if (!shouldLoadURI(uri))
+ return;
+
+ // Unless using a private window is forced, open external links in private
+ // windows only if we're in perma-private mode.
+ var allowPrivate = forcePrivate || PrivateBrowsingUtils.permanentPrivateBrowsing;
+ var navWin = RecentWindow.getMostRecentBrowserWindow({private: allowPrivate});
+ if (!navWin) {
+ // if we couldn't load it in an existing window, open a new one
+ var features = "chrome,dialog=no,all" + gBrowserContentHandler.getFeatures(cmdLine);
+ if (forcePrivate) {
+ features += ",private";
+ }
+ openWindow(null, gBrowserContentHandler.chromeURL, "_blank", features, uri.spec);
+ return;
+ }
+
+ var navNav = navWin.QueryInterface(nsIInterfaceRequestor)
+ .getInterface(nsIWebNavigation);
+ var rootItem = navNav.QueryInterface(nsIDocShellTreeItem).rootTreeItem;
+ var rootWin = rootItem.QueryInterface(nsIInterfaceRequestor)
+ .getInterface(nsIDOMWindow);
+ var bwin = rootWin.QueryInterface(nsIDOMChromeWindow).browserDOMWindow;
+ bwin.openURI(uri, null, location,
+ nsIBrowserDOMWindow.OPEN_EXTERNAL);
+}
+
+function nsDefaultCommandLineHandler() {
+}
+
+nsDefaultCommandLineHandler.prototype = {
+ classID: Components.ID("{47cd0651-b1be-4a0f-b5c4-10e5a573ef71}"),
+
+ /* nsISupports */
+ QueryInterface : function dch_QI(iid) {
+ if (!iid.equals(nsISupports) &&
+ !iid.equals(nsICommandLineHandler))
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+
+ return this;
+ },
+
+#ifdef XP_WIN
+ _haveProfile: false,
+#endif
+
+ /* nsICommandLineHandler */
+ handle : function dch_handle(cmdLine) {
+ var urilist = [];
+
+#ifdef XP_WIN
+ // If we don't have a profile selected yet (e.g. the Profile Manager is
+ // displayed) we will crash if we open an url and then select a profile. To
+ // prevent this handle all url command line flags and set the command line's
+ // preventDefault to true to prevent the display of the ui. The initial
+ // command line will be retained when nsAppRunner calls LaunchChild though
+ // urls launched after the initial launch will be lost.
+ if (!this._haveProfile) {
+ try {
+ // This will throw when a profile has not been selected.
+ var fl = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIProperties);
+ var dir = fl.get("ProfD", Components.interfaces.nsILocalFile);
+ this._haveProfile = true;
+ }
+ catch (e) {
+ while ((ar = cmdLine.handleFlagWithParam("url", false))) { }
+ cmdLine.preventDefault = true;
+ }
+ }
+#endif
+
+ try {
+ var ar;
+ while ((ar = cmdLine.handleFlagWithParam("url", false))) {
+ var uri = resolveURIInternal(cmdLine, ar);
+ urilist.push(uri);
+ }
+ }
+ catch (e) {
+ Components.utils.reportError(e);
+ }
+
+ let count = cmdLine.length;
+
+ for (let i = 0; i < count; ++i) {
+ var curarg = cmdLine.getArgument(i);
+ if (curarg.match(/^-/)) {
+ Components.utils.reportError("Warning: unrecognized command line flag " + curarg + "\n");
+ // To emulate the pre-nsICommandLine behavior, we ignore
+ // the argument after an unrecognized flag.
+ ++i;
+ } else {
+ try {
+ urilist.push(resolveURIInternal(cmdLine, curarg));
+ }
+ catch (e) {
+ Components.utils.reportError("Error opening URI '" + curarg + "' from the command line: " + e + "\n");
+ }
+ }
+ }
+
+ if (urilist.length) {
+ if (cmdLine.state != nsICommandLine.STATE_INITIAL_LAUNCH &&
+ urilist.length == 1) {
+ // Try to find an existing window and load our URI into the
+ // current tab, new tab, or new window as prefs determine.
+ try {
+ handURIToExistingBrowser(urilist[0], nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW, cmdLine);
+ return;
+ }
+ catch (e) {
+ }
+ }
+
+ var URLlist = urilist.filter(shouldLoadURI).map(function (u) u.spec);
+ if (URLlist.length) {
+ openWindow(null, gBrowserContentHandler.chromeURL, "_blank",
+ "chrome,dialog=no,all" + gBrowserContentHandler.getFeatures(cmdLine),
+ URLlist);
+ }
+
+ }
+ else if (!cmdLine.preventDefault) {
+ // Passing defaultArgs, so use NO_EXTERNAL_URIS
+ openWindow(null, gBrowserContentHandler.chromeURL, "_blank",
+ "chrome,dialog=no,all" + gBrowserContentHandler.getFeatures(cmdLine),
+ gBrowserContentHandler.defaultArgs, NO_EXTERNAL_URIS);
+ }
+ },
+
+ helpInfo : "",
+};
+
+var components = [nsBrowserContentHandler, nsDefaultCommandLineHandler];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/components/nsBrowserGlue.js b/components/nsBrowserGlue.js
new file mode 100644
index 0000000..01a1338
--- /dev/null
+++ b/components/nsBrowserGlue.js
@@ -0,0 +1,2055 @@
+# -*- indent-tabs-mode: nil -*-
+# 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;
+
+const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+// Define Lazy Service Getters
+XPCOMUtils.defineLazyServiceGetter(this, "AlertsService",
+ "@mozilla.org/alerts-service;1", "nsIAlertsService");
+
+// Define Lazy Module Getters
+[
+ ["AddonManager", "resource://gre/modules/AddonManager.jsm"],
+ ["NetUtil", "resource://gre/modules/NetUtil.jsm"],
+ ["UserAgentOverrides", "resource://gre/modules/UserAgentOverrides.jsm"],
+ ["FileUtils", "resource://gre/modules/FileUtils.jsm"],
+ ["PlacesUtils", "resource://gre/modules/PlacesUtils.jsm"],
+ ["BookmarkHTMLUtils", "resource://gre/modules/BookmarkHTMLUtils.jsm"],
+ ["BookmarkJSONUtils", "resource://gre/modules/BookmarkJSONUtils.jsm"],
+ ["PageThumbs", "resource://gre/modules/PageThumbs.jsm"],
+ ["NewTabUtils", "resource://gre/modules/NewTabUtils.jsm"],
+ ["BrowserNewTabPreloader", "resource:///modules/BrowserNewTabPreloader.jsm"],
+#ifdef MOZ_WEBRTC
+ ["webrtcUI", "resource:///modules/webrtcUI.jsm"],
+#endif
+ ["PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm"],
+ ["RecentWindow", "resource:///modules/RecentWindow.jsm"],
+ ["Task", "resource://gre/modules/Task.jsm"],
+ ["PlacesBackups", "resource://gre/modules/PlacesBackups.jsm"],
+ ["OS", "resource://gre/modules/osfile.jsm"],
+ ["LoginManagerParent", "resource://gre/modules/LoginManagerParent.jsm"],
+ ["FormValidationHandler", "resource:///modules/FormValidationHandler.jsm"],
+ ["AutoCompletePopup", "resource:///modules/AutoCompletePopup.jsm"],
+ ["DateTimePickerHelper", "resource://gre/modules/DateTimePickerHelper.jsm"],
+ ["ShellService", "resource:///modules/ShellService.jsm"],
+].forEach(([name, resource]) => XPCOMUtils.defineLazyModuleGetter(this, name, resource));
+
+// Define Lazy Getters
+
+XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function() {
+ return Services.strings.createBundle('chrome://branding/locale/brand.properties');
+});
+
+XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
+ return Services.strings.createBundle('chrome://browser/locale/browser.properties');
+});
+
+const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser";
+const PREF_PLUGINS_UPDATEURL = "plugins.update.url";
+
+// We try to backup bookmarks at idle times, to avoid doing that at shutdown.
+// Number of idle seconds before trying to backup bookmarks. 15 minutes.
+const BOOKMARKS_BACKUP_IDLE_TIME = 15 * 60;
+// Minimum interval in milliseconds between backups.
+const BOOKMARKS_BACKUP_INTERVAL = 86400 * 1000;
+// Maximum number of backups to create. Old ones will be purged.
+const BOOKMARKS_BACKUP_MAX_BACKUPS = 10;
+
+// Factory object
+const BrowserGlueServiceFactory = {
+ _instance: null,
+ createInstance: function BGSF_createInstance(outer, iid) {
+ if (outer != null)
+ throw Components.results.NS_ERROR_NO_AGGREGATION;
+ return this._instance == null ?
+ this._instance = new BrowserGlue() : this._instance;
+ }
+};
+
+// Constructor
+
+function BrowserGlue() {
+ XPCOMUtils.defineLazyServiceGetter(this, "_idleService",
+ "@mozilla.org/widget/idleservice;1",
+ "nsIIdleService");
+
+ XPCOMUtils.defineLazyGetter(this, "_distributionCustomizer", function() {
+ Cu.import("resource:///modules/distribution.js");
+ return new DistributionCustomizer();
+ });
+
+ XPCOMUtils.defineLazyGetter(this, "_sanitizer",
+ function() {
+ let sanitizerScope = {};
+ Services.scriptloader.loadSubScript("chrome://browser/content/sanitize.js", sanitizerScope);
+ return sanitizerScope.Sanitizer;
+ });
+
+ this._init();
+}
+
+#ifndef XP_MACOSX
+# OS X has the concept of zero-window sessions and therefore ignores the
+# browser-lastwindow-close-* topics.
+#define OBSERVE_LASTWINDOW_CLOSE_TOPICS 1
+#endif
+
+BrowserGlue.prototype = {
+ _saveSession: false,
+ _isIdleObserver: false,
+ _isPlacesInitObserver: false,
+ _isPlacesLockedObserver: false,
+ _isPlacesShutdownObserver: false,
+ _isPlacesDatabaseLocked: false,
+ _migrationImportsDefaultBookmarks: false,
+
+ _setPrefToSaveSession: function BG__setPrefToSaveSession(aForce) {
+ if (!this._saveSession && !aForce)
+ return;
+
+ Services.prefs.setBoolPref("browser.sessionstore.resume_session_once", true);
+
+ // This method can be called via [NSApplication terminate:] on Mac, which
+ // ends up causing prefs not to be flushed to disk, so we need to do that
+ // explicitly here. See bug 497652.
+ Services.prefs.savePrefFile(null);
+ },
+
+#ifdef MOZ_SERVICES_SYNC
+ _setSyncAutoconnectDelay: function BG__setSyncAutoconnectDelay() {
+ // Assume that a non-zero value for services.sync.autoconnectDelay should override
+ if (Services.prefs.prefHasUserValue("services.sync.autoconnectDelay")) {
+ let prefDelay = Services.prefs.getIntPref("services.sync.autoconnectDelay");
+
+ if (prefDelay > 0)
+ return;
+ }
+
+ // delays are in seconds
+ const MAX_DELAY = 300;
+ let delay = 3;
+ let browserEnum = Services.wm.getEnumerator("navigator:browser");
+ while (browserEnum.hasMoreElements()) {
+ delay += browserEnum.getNext().gBrowser.tabs.length;
+ }
+ delay = delay <= MAX_DELAY ? delay : MAX_DELAY;
+
+ Cu.import("resource://services-sync/main.js");
+ Weave.Service.scheduler.delayedAutoConnect(delay);
+ },
+#endif
+
+ // nsIObserver implementation
+ observe: function BG_observe(subject, topic, data) {
+ switch (topic) {
+ case "notifications-open-settings":
+ this._openPermissions(subject);
+ break;
+ case "prefservice:after-app-defaults":
+ this._onAppDefaults();
+ break;
+ case "final-ui-startup":
+ this._finalUIStartup();
+ break;
+ case "browser-delayed-startup-finished":
+ this._onFirstWindowLoaded();
+ Services.obs.removeObserver(this, "browser-delayed-startup-finished");
+ break;
+ case "sessionstore-windows-restored":
+ this._onWindowsRestored();
+ break;
+ case "browser:purge-session-history":
+ // reset the console service's error buffer
+ Services.console.logStringMessage(null); // clear the console (in case it's open)
+ Services.console.reset();
+ break;
+ case "quit-application-requested":
+ this._onQuitRequest(subject, data);
+ break;
+ case "quit-application-granted":
+ // This pref must be set here because SessionStore will use its value
+ // on quit-application.
+ this._setPrefToSaveSession();
+ try {
+ let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].
+ getService(Ci.nsIAppStartup);
+ appStartup.trackStartupCrashEnd();
+ } catch (e) {
+ Cu.reportError("Could not end startup crash tracking in quit-application-granted: " + e);
+ }
+ DateTimePickerHelper.uninit();
+ break;
+#ifdef OBSERVE_LASTWINDOW_CLOSE_TOPICS
+ case "browser-lastwindow-close-requested":
+ // The application is not actually quitting, but the last full browser
+ // window is about to be closed.
+ this._onQuitRequest(subject, "lastwindow");
+ break;
+ case "browser-lastwindow-close-granted":
+ this._setPrefToSaveSession();
+ break;
+#endif
+#ifdef MOZ_SERVICES_SYNC
+ case "weave:service:ready":
+ this._setSyncAutoconnectDelay();
+ break;
+ case "weave:engine:clients:display-uri":
+ this._onDisplaySyncURI(subject);
+ break;
+#endif
+ case "session-save":
+ this._setPrefToSaveSession(true);
+ subject.QueryInterface(Ci.nsISupportsPRBool);
+ subject.data = true;
+ break;
+ case "places-init-complete":
+ if (!this._migrationImportsDefaultBookmarks)
+ this._initPlaces(false);
+
+ Services.obs.removeObserver(this, "places-init-complete");
+ this._isPlacesInitObserver = false;
+ // no longer needed, since history was initialized completely.
+ Services.obs.removeObserver(this, "places-database-locked");
+ this._isPlacesLockedObserver = false;
+ break;
+ case "places-database-locked":
+ this._isPlacesDatabaseLocked = true;
+ // Stop observing, so further attempts to load history service
+ // will not show the prompt.
+ Services.obs.removeObserver(this, "places-database-locked");
+ this._isPlacesLockedObserver = false;
+ break;
+ case "places-shutdown":
+ if (this._isPlacesShutdownObserver) {
+ Services.obs.removeObserver(this, "places-shutdown");
+ this._isPlacesShutdownObserver = false;
+ }
+ // places-shutdown is fired when the profile is about to disappear.
+ this._onPlacesShutdown();
+ break;
+ case "idle":
+ if ((this._idleService.idleTime > BOOKMARKS_BACKUP_IDLE_TIME * 1000) &&
+ this._shouldBackupBookmarks())
+ this._backupBookmarks();
+ break;
+ case "distribution-customization-complete":
+ Services.obs.removeObserver(this, "distribution-customization-complete");
+ // Customization has finished, we don't need the customizer anymore.
+ delete this._distributionCustomizer;
+ break;
+ case "browser-glue-test": // used by tests
+ if (data == "post-update-notification") {
+ if (Services.prefs.prefHasUserValue("app.update.postupdate"))
+ this._showUpdateNotification();
+ }
+ else if (data == "force-ui-migration") {
+ this._migrateUI();
+ }
+ else if (data == "force-distribution-customization") {
+ this._distributionCustomizer.applyPrefDefaults();
+ this._distributionCustomizer.applyCustomizations();
+ // To apply distribution bookmarks use "places-init-complete".
+ }
+ else if (data == "force-places-init") {
+ this._initPlaces(false);
+ }
+ break;
+ case "initial-migration-will-import-default-bookmarks":
+ this._migrationImportsDefaultBookmarks = true;
+ break;
+ case "initial-migration-did-import-default-bookmarks":
+ this._initPlaces(true);
+ break;
+ case "handle-xul-text-link":
+ let linkHandled = subject.QueryInterface(Ci.nsISupportsPRBool);
+ if (!linkHandled.data) {
+ let win = this.getMostRecentBrowserWindow();
+ if (win) {
+ data = JSON.parse(data);
+ win.openUILinkIn(data.href, "tab");
+ linkHandled.data = true;
+ }
+ }
+ break;
+ case "profile-before-change":
+ this._onProfileShutdown();
+ break;
+ case "browser-search-engine-modified":
+ if (data != "engine-default" && data != "engine-current") {
+ break;
+ }
+ // Enforce that the search service's defaultEngine is always equal to
+ // its currentEngine. The search service will notify us any time either
+ // of them are changed (either by directly setting the relevant prefs,
+ // i.e. if add-ons try to change this directly, or if the
+ // nsIBrowserSearchService setters are called).
+ // No need to initialize the search service, since it's guaranteed to be
+ // initialized already when this notification fires.
+ let ss = Services.search;
+ if (ss.currentEngine.name == ss.defaultEngine.name)
+ return;
+ if (data == "engine-current")
+ ss.defaultEngine = ss.currentEngine;
+ else
+ ss.currentEngine = ss.defaultEngine;
+ break;
+ case "browser-search-service":
+ if (data != "init-complete")
+ return;
+ Services.obs.removeObserver(this, "browser-search-service");
+ this._syncSearchEngines();
+ break;
+ }
+ },
+
+ _syncSearchEngines: function () {
+ // Only do this if the search service is already initialized. This function
+ // gets called in finalUIStartup and from a browser-search-service observer,
+ // to catch both cases (search service initialization occurring before and
+ // after final-ui-startup)
+ if (Services.search.isInitialized) {
+ Services.search.defaultEngine = Services.search.currentEngine;
+ }
+ },
+
+ // initialization (called on application startup)
+ _init: function BG__init() {
+ let os = Services.obs;
+ os.addObserver(this, "notifications-open-settings", false);
+ os.addObserver(this, "prefservice:after-app-defaults", false);
+ os.addObserver(this, "final-ui-startup", false);
+ os.addObserver(this, "browser-delayed-startup-finished", false);
+ os.addObserver(this, "sessionstore-windows-restored", false);
+ os.addObserver(this, "browser:purge-session-history", false);
+ os.addObserver(this, "quit-application-requested", false);
+ os.addObserver(this, "quit-application-granted", false);
+#ifdef OBSERVE_LASTWINDOW_CLOSE_TOPICS
+ os.addObserver(this, "browser-lastwindow-close-requested", false);
+ os.addObserver(this, "browser-lastwindow-close-granted", false);
+#endif
+#ifdef MOZ_SERVICES_SYNC
+ os.addObserver(this, "weave:service:ready", false);
+ os.addObserver(this, "weave:engine:clients:display-uri", false);
+#endif
+ os.addObserver(this, "session-save", false);
+ os.addObserver(this, "places-init-complete", false);
+ this._isPlacesInitObserver = true;
+ os.addObserver(this, "places-database-locked", false);
+ this._isPlacesLockedObserver = true;
+ os.addObserver(this, "distribution-customization-complete", false);
+ os.addObserver(this, "places-shutdown", false);
+ this._isPlacesShutdownObserver = true;
+ os.addObserver(this, "handle-xul-text-link", false);
+ os.addObserver(this, "profile-before-change", false);
+ os.addObserver(this, "browser-search-engine-modified", false);
+ os.addObserver(this, "browser-search-service", false);
+ },
+
+ // cleanup (called on application shutdown)
+ _dispose: function BG__dispose() {
+ let os = Services.obs;
+ os.removeObserver(this, "notifications-open-settings");
+ os.removeObserver(this, "prefservice:after-app-defaults");
+ os.removeObserver(this, "final-ui-startup");
+ os.removeObserver(this, "sessionstore-windows-restored");
+ os.removeObserver(this, "browser:purge-session-history");
+ os.removeObserver(this, "quit-application-requested");
+ os.removeObserver(this, "quit-application-granted");
+#ifdef OBSERVE_LASTWINDOW_CLOSE_TOPICS
+ os.removeObserver(this, "browser-lastwindow-close-requested");
+ os.removeObserver(this, "browser-lastwindow-close-granted");
+#endif
+#ifdef MOZ_SERVICES_SYNC
+ os.removeObserver(this, "weave:service:ready");
+ os.removeObserver(this, "weave:engine:clients:display-uri");
+#endif
+ os.removeObserver(this, "session-save");
+ if (this._isIdleObserver)
+ this._idleService.removeIdleObserver(this, BOOKMARKS_BACKUP_IDLE_TIME);
+ if (this._isPlacesInitObserver)
+ os.removeObserver(this, "places-init-complete");
+ if (this._isPlacesLockedObserver)
+ os.removeObserver(this, "places-database-locked");
+ if (this._isPlacesShutdownObserver)
+ os.removeObserver(this, "places-shutdown");
+ os.removeObserver(this, "handle-xul-text-link");
+ os.removeObserver(this, "profile-before-change");
+ os.removeObserver(this, "browser-search-engine-modified");
+ try {
+ os.removeObserver(this, "browser-search-service");
+ // may have already been removed by the observer
+ } catch (ex) {}
+ },
+
+ _onAppDefaults: function BG__onAppDefaults() {
+ // apply distribution customizations (prefs)
+ // other customizations are applied in _finalUIStartup()
+ this._distributionCustomizer.applyPrefDefaults();
+ },
+
+ // runs on startup, before the first command line handler is invoked
+ // (i.e. before the first window is opened)
+ _finalUIStartup: function BG__finalUIStartup() {
+ this._sanitizer.onStartup();
+ // check if we're in safe mode
+ if (Services.appinfo.inSafeMode) {
+ Services.ww.openWindow(null, "chrome://browser/content/safeMode.xul",
+ "_blank", "chrome,centerscreen,modal,resizable=no", null);
+ }
+
+ // apply distribution customizations
+ // prefs are applied in _onAppDefaults()
+ this._distributionCustomizer.applyCustomizations();
+
+ // handle any UI migration
+ this._migrateUI();
+
+ this._setUpUserAgentOverrides();
+
+ this._syncSearchEngines();
+
+ PageThumbs.init();
+ NewTabUtils.init();
+ BrowserNewTabPreloader.init();
+#ifdef MOZ_WEBRTC
+ webrtcUI.init();
+#endif
+ FormValidationHandler.init();
+
+ AutoCompletePopup.init();
+
+ LoginManagerParent.init();
+
+ Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
+ },
+
+ _setUpUserAgentOverrides: function BG__setUpUserAgentOverrides() {
+ UserAgentOverrides.init();
+
+ if (Services.prefs.getBoolPref("general.useragent.complexOverride.moodle")) {
+ UserAgentOverrides.addComplexOverride(function (aHttpChannel, aOriginalUA) {
+ let cookies;
+ try {
+ cookies = aHttpChannel.getRequestHeader("Cookie");
+ } catch (e) { /* no cookie sent */ }
+ if (cookies && cookies.indexOf("MoodleSession") > -1)
+ return aOriginalUA.replace(/Goanna\/[^ ]*/, "Goanna/20100101");
+ return null;
+ });
+ }
+ },
+
+ _trackSlowStartup: function () {
+ if (Services.startup.interrupted ||
+ Services.prefs.getBoolPref("browser.slowStartup.notificationDisabled"))
+ return;
+
+ let currentTime = Date.now() - Services.startup.getStartupInfo().process;
+ let averageTime = 0;
+ let samples = 0;
+ try {
+ averageTime = Services.prefs.getIntPref("browser.slowStartup.averageTime");
+ samples = Services.prefs.getIntPref("browser.slowStartup.samples");
+ } catch (e) { }
+
+ averageTime = (averageTime * samples + currentTime) / ++samples;
+
+ if (samples >= Services.prefs.getIntPref("browser.slowStartup.maxSamples")) {
+ if (averageTime > Services.prefs.getIntPref("browser.slowStartup.timeThreshold"))
+ this._showSlowStartupNotification();
+ averageTime = 0;
+ samples = 0;
+ }
+
+ Services.prefs.setIntPref("browser.slowStartup.averageTime", averageTime);
+ Services.prefs.setIntPref("browser.slowStartup.samples", samples);
+ },
+
+ _showSlowStartupNotification: function () {
+ let win = this.getMostRecentBrowserWindow();
+ if (!win)
+ return;
+
+ let productName = gBrandBundle.GetStringFromName("brandFullName");
+ let message = win.gNavigatorBundle.getFormattedString("slowStartup.message", [productName]);
+
+ let buttons = [
+ {
+ label: win.gNavigatorBundle.getString("slowStartup.helpButton.label"),
+ accessKey: win.gNavigatorBundle.getString("slowStartup.helpButton.accesskey"),
+ callback: function () {
+ win.openUILinkIn(Services.prefs.getCharPref("browser.slowstartup.help.url"), "tab");
+ }
+ },
+ {
+ label: win.gNavigatorBundle.getString("slowStartup.disableNotificationButton.label"),
+ accessKey: win.gNavigatorBundle.getString("slowStartup.disableNotificationButton.accesskey"),
+ callback: function () {
+ Services.prefs.setBoolPref("browser.slowStartup.notificationDisabled", true);
+ }
+ }
+ ];
+
+ let nb = win.document.getElementById("global-notificationbox");
+ nb.appendNotification(message, "slow-startup",
+ "chrome://browser/skin/slowStartup-16.png",
+ nb.PRIORITY_INFO_LOW, buttons);
+ },
+
+ // the first browser window has finished initializing
+ _onFirstWindowLoaded: function BG__onFirstWindowLoaded() {
+#ifdef XP_WIN
+ // For windows seven, initialize the jump list module.
+ const WINTASKBAR_CONTRACTID = "@mozilla.org/windows-taskbar;1";
+ if (WINTASKBAR_CONTRACTID in Cc &&
+ Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar).available) {
+ let temp = {};
+ Cu.import("resource:///modules/WindowsJumpLists.jsm", temp);
+ temp.WinTaskbarJumpList.startup();
+ }
+#endif
+
+ DateTimePickerHelper.init();
+
+ this._trackSlowStartup();
+ },
+
+ /**
+ * Profile shutdown handler (contains profile cleanup routines).
+ * All components depending on Places should be shut down in
+ * _onPlacesShutdown() and not here.
+ */
+ _onProfileShutdown: function BG__onProfileShutdown() {
+ BrowserNewTabPreloader.uninit();
+ UserAgentOverrides.uninit();
+#ifdef MOZ_WEBRTC
+ webrtcUI.uninit();
+#endif
+ FormValidationHandler.uninit();
+ AutoCompletePopup.uninit();
+ this._dispose();
+ },
+
+ // All initial windows have opened.
+ _onWindowsRestored: function BG__onWindowsRestored() {
+ // Show update notification, if needed.
+ if (Services.prefs.prefHasUserValue("app.update.postupdate"))
+ this._showUpdateNotification();
+
+ // Load the "more info" page for a locked places.sqlite
+ // This property is set earlier by places-database-locked topic.
+ if (this._isPlacesDatabaseLocked) {
+ this._showPlacesLockedNotificationBox();
+ }
+
+ // For any add-ons that were installed disabled and can be enabled offer
+ // them to the user.
+ let changedIDs = AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED);
+ if (changedIDs.length > 0) {
+ let win = this.getMostRecentBrowserWindow();
+ AddonManager.getAddonsByIDs(changedIDs, function(aAddons) {
+ aAddons.forEach(function(aAddon) {
+ // If the add-on isn't user disabled or can't be enabled then skip it.
+ if (!aAddon.userDisabled || !(aAddon.permissions & AddonManager.PERM_CAN_ENABLE))
+ return;
+
+ win.openUILinkIn("about:newaddon?id=" + aAddon.id, "tab");
+ })
+ });
+ }
+
+ // Perform default browser checking.
+ if (ShellService) {
+ let shouldCheck = ShellService.shouldCheckDefaultBrowser;
+
+ const skipDefaultBrowserCheck =
+ Services.prefs.getBoolPref("browser.shell.skipDefaultBrowserCheckOnFirstRun") &&
+ Services.prefs.getBoolPref("browser.shell.skipDefaultBrowserCheck");
+
+ const usePromptLimit = false;
+ let promptCount =
+ usePromptLimit ? Services.prefs.getIntPref("browser.shell.defaultBrowserCheckCount") : 0;
+
+ let willRecoverSession = false;
+ try {
+ let ss = Cc["@mozilla.org/browser/sessionstartup;1"].
+ getService(Ci.nsISessionStartup);
+ willRecoverSession =
+ (ss.sessionType == Ci.nsISessionStartup.RECOVER_SESSION);
+ }
+ catch (ex) { /* never mind; suppose SessionStore is broken */ }
+
+ // startup check, check all assoc
+ let isDefault = false;
+ let isDefaultError = false;
+ try {
+ isDefault = ShellService.isDefaultBrowser(true, false);
+ } catch (ex) {
+ isDefaultError = true;
+ }
+
+ if (isDefault) {
+ let now = (Math.floor(Date.now() / 1000)).toString();
+ Services.prefs.setCharPref("browser.shell.mostRecentDateSetAsDefault", now);
+ }
+
+ let willPrompt = shouldCheck && !isDefault && !willRecoverSession;
+
+ // Skip the "Set Default Browser" check during first-run or after the
+ // browser has been run a few times.
+ if (willPrompt) {
+ Services.tm.mainThread.dispatch(function() {
+ var win = this.getMostRecentBrowserWindow();
+ var brandBundle = win.document.getElementById("bundle_brand");
+ var shellBundle = win.document.getElementById("bundle_shell");
+
+ var brandShortName = brandBundle.getString("brandShortName");
+ var promptTitle = shellBundle.getString("setDefaultBrowserTitle");
+ var promptMessage = shellBundle.getFormattedString("setDefaultBrowserMessage",
+ [brandShortName]);
+ var checkboxLabel = shellBundle.getFormattedString("setDefaultBrowserDontAsk",
+ [brandShortName]);
+ var checkEveryTime = { value: shouldCheck };
+ var ps = Services.prompt;
+ var rv = ps.confirmEx(win, promptTitle, promptMessage,
+ ps.STD_YES_NO_BUTTONS,
+ null, null, null, checkboxLabel, checkEveryTime);
+ if (rv == 0) {
+ var claimAllTypes = true;
+#ifdef XP_WIN
+ try {
+ // In Windows 8+, the UI for selecting default protocol is much
+ // nicer than the UI for setting file type associations. So we
+ // only show the protocol association screen on Windows 8.
+ // Windows 8 is version 6.2.
+ let version = Services.sysinfo.getProperty("version");
+ claimAllTypes = (parseFloat(version) < 6.2);
+ } catch (ex) { }
+#endif
+ ShellService.setDefaultBrowser(claimAllTypes, false);
+ }
+ ShellService.shouldCheckDefaultBrowser = checkEveryTime.value;
+ }.bind(this), Ci.nsIThread.DISPATCH_NORMAL);
+ }
+ }
+ },
+
+ _onQuitRequest: function BG__onQuitRequest(aCancelQuit, aQuitType) {
+ // If user has already dismissed quit request, then do nothing
+ if ((aCancelQuit instanceof Ci.nsISupportsPRBool) && aCancelQuit.data)
+ return;
+
+ // There are several cases where we won't show a dialog here:
+ // 1. There is only 1 tab open in 1 window
+ // 2. The session will be restored at startup, indicated by
+ // browser.startup.page == 3 or browser.sessionstore.resume_session_once == true
+ // 3. browser.warnOnQuit == false
+ // 4. The browser is currently in Private Browsing mode
+ // 5. The browser will be restarted.
+ //
+ // Otherwise these are the conditions and the associated dialogs that will be shown:
+ // 1. aQuitType == "lastwindow" or "quit" and browser.showQuitWarning == true
+ // - The quit dialog will be shown
+ // 2. aQuitType == "lastwindow" && browser.tabs.warnOnClose == true
+ // - The "closing multiple tabs" dialog will be shown
+ //
+ // aQuitType == "lastwindow" is overloaded. "lastwindow" is used to indicate
+ // "the last window is closing but we're not quitting (a non-browser window is open)"
+ // and also "we're quitting by closing the last window".
+
+ if (aQuitType == "restart")
+ return;
+
+ var windowcount = 0;
+ var pagecount = 0;
+ var browserEnum = Services.wm.getEnumerator("navigator:browser");
+ let allWindowsPrivate = true;
+ while (browserEnum.hasMoreElements()) {
+ windowcount++;
+
+ var browser = browserEnum.getNext();
+ if (!PrivateBrowsingUtils.isWindowPrivate(browser))
+ allWindowsPrivate = false;
+ var tabbrowser = browser.document.getElementById("content");
+ if (tabbrowser)
+ pagecount += tabbrowser.browsers.length - tabbrowser._numPinnedTabs;
+ }
+
+ this._saveSession = false;
+ if (pagecount < 2)
+ return;
+
+ if (!aQuitType)
+ aQuitType = "quit";
+
+ // browser.warnOnQuit is a hidden global boolean to override all quit prompts
+ // browser.showQuitWarning specifically covers quitting
+ // browser.tabs.warnOnClose is the global "warn when closing multiple tabs" pref
+
+ var sessionWillBeRestored = Services.prefs.getIntPref("browser.startup.page") == 3 ||
+ Services.prefs.getBoolPref("browser.sessionstore.resume_session_once");
+ if (sessionWillBeRestored || !Services.prefs.getBoolPref("browser.warnOnQuit"))
+ return;
+
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+
+ // On last window close or quit && showQuitWarning, we want to show the
+ // quit warning.
+ if (!Services.prefs.getBoolPref("browser.showQuitWarning")) {
+ if (aQuitType == "lastwindow") {
+ // If aQuitType is "lastwindow" and we aren't showing the quit warning,
+ // we should show the window closing warning instead. warnAboutClosing
+ // tabs checks browser.tabs.warnOnClose and returns if it's ok to close
+ // the window. It doesn't actually close the window.
+ aCancelQuit.data =
+ !win.gBrowser.warnAboutClosingTabs(win.gBrowser.closingTabsEnum.ALL);
+ }
+ return;
+ }
+
+ let prompt = Services.prompt;
+ let quitBundle = Services.strings.createBundle("chrome://browser/locale/quitDialog.properties");
+
+ let appName = gBrandBundle.GetStringFromName("brandShortName");
+ let quitDialogTitle = quitBundle.formatStringFromName("quitDialogTitle",
+ [appName], 1);
+ let neverAskText = quitBundle.GetStringFromName("neverAsk2");
+ let neverAsk = {value: false};
+
+ let choice;
+ if (allWindowsPrivate) {
+ let text = quitBundle.formatStringFromName("messagePrivate", [appName], 1);
+ let flags = prompt.BUTTON_TITLE_IS_STRING * prompt.BUTTON_POS_0 +
+ prompt.BUTTON_TITLE_IS_STRING * prompt.BUTTON_POS_1 +
+ prompt.BUTTON_POS_0_DEFAULT;
+ choice = prompt.confirmEx(win, quitDialogTitle, text, flags,
+ quitBundle.GetStringFromName("quitTitle"),
+ quitBundle.GetStringFromName("cancelTitle"),
+ null,
+ neverAskText, neverAsk);
+
+ // The order of the buttons differs between the prompt.confirmEx calls
+ // here so we need to fix this for proper handling below.
+ if (choice == 0) {
+ choice = 2;
+ }
+ } else {
+ let text = quitBundle.formatStringFromName(
+ windowcount == 1 ? "messageNoWindows" : "message", [appName], 1);
+ let flags = prompt.BUTTON_TITLE_IS_STRING * prompt.BUTTON_POS_0 +
+ prompt.BUTTON_TITLE_IS_STRING * prompt.BUTTON_POS_1 +
+ prompt.BUTTON_TITLE_IS_STRING * prompt.BUTTON_POS_2 +
+ prompt.BUTTON_POS_0_DEFAULT;
+ choice = prompt.confirmEx(win, quitDialogTitle, text, flags,
+ quitBundle.GetStringFromName("saveTitle"),
+ quitBundle.GetStringFromName("cancelTitle"),
+ quitBundle.GetStringFromName("quitTitle"),
+ neverAskText, neverAsk);
+ }
+
+ switch (choice) {
+ case 2: // Quit
+ if (neverAsk.value)
+ Services.prefs.setBoolPref("browser.showQuitWarning", false);
+ break;
+ case 1: // Cancel
+ aCancelQuit.QueryInterface(Ci.nsISupportsPRBool);
+ aCancelQuit.data = true;
+ break;
+ case 0: // Save & Quit
+ this._saveSession = true;
+ if (neverAsk.value) {
+ // always save state when shutting down
+ Services.prefs.setIntPref("browser.startup.page", 3);
+ }
+ break;
+ }
+ },
+
+ _showUpdateNotification: function BG__showUpdateNotification() {
+ Services.prefs.clearUserPref("app.update.postupdate");
+
+ var um = Cc["@mozilla.org/updates/update-manager;1"].
+ getService(Ci.nsIUpdateManager);
+ try {
+ // If the updates.xml file is deleted then getUpdateAt will throw.
+ var update = um.getUpdateAt(0).QueryInterface(Ci.nsIPropertyBag);
+ }
+ catch (e) {
+ // This should never happen.
+ Cu.reportError("Unable to find update: " + e);
+ return;
+ }
+
+ var actions = update.getProperty("actions");
+ if (!actions || actions.indexOf("silent") != -1)
+ return;
+
+ var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].
+ getService(Ci.nsIURLFormatter);
+ var appName = gBrandBundle.GetStringFromName("brandShortName");
+
+ function getNotifyString(aPropData) {
+ var propValue = update.getProperty(aPropData.propName);
+ if (!propValue) {
+ if (aPropData.prefName)
+ propValue = formatter.formatURLPref(aPropData.prefName);
+ else if (aPropData.stringParams)
+ propValue = gBrowserBundle.formatStringFromName(aPropData.stringName,
+ aPropData.stringParams,
+ aPropData.stringParams.length);
+ else
+ propValue = gBrowserBundle.GetStringFromName(aPropData.stringName);
+ }
+ return propValue;
+ }
+
+ if (actions.indexOf("showNotification") != -1) {
+ let text = getNotifyString({propName: "notificationText",
+ stringName: "puNotifyText",
+ stringParams: [appName]});
+ let url = getNotifyString({propName: "notificationURL",
+ prefName: "startup.homepage_override_url"});
+ let label = getNotifyString({propName: "notificationButtonLabel",
+ stringName: "pu.notifyButton.label"});
+ let key = getNotifyString({propName: "notificationButtonAccessKey",
+ stringName: "pu.notifyButton.accesskey"});
+
+ let win = this.getMostRecentBrowserWindow();
+ let notifyBox = win.gBrowser.getNotificationBox();
+
+ let buttons = [
+ {
+ label: label,
+ accessKey: key,
+ popup: null,
+ callback: function(aNotificationBar, aButton) {
+ win.openUILinkIn(url, "tab");
+ }
+ }
+ ];
+
+ let notification = notifyBox.appendNotification(text, "post-update-notification",
+ null, notifyBox.PRIORITY_INFO_LOW,
+ buttons);
+ notification.persistence = -1; // Until user closes it
+ }
+
+ if (actions.indexOf("showAlert") == -1)
+ return;
+
+ let title = getNotifyString({propName: "alertTitle",
+ stringName: "puAlertTitle",
+ stringParams: [appName]});
+ let text = getNotifyString({propName: "alertText",
+ stringName: "puAlertText",
+ stringParams: [appName]});
+ let url = getNotifyString({propName: "alertURL",
+ prefName: "startup.homepage_override_url"});
+
+ var self = this;
+ function clickCallback(subject, topic, data) {
+ // This callback will be called twice but only once with this topic
+ if (topic != "alertclickcallback")
+ return;
+ let win = self.getMostRecentBrowserWindow();
+ win.openUILinkIn(data, "tab");
+ }
+
+ try {
+ // This will throw NS_ERROR_NOT_AVAILABLE if the notification cannot
+ // be displayed per the idl.
+ AlertsService.showAlertNotification(null, title, text,
+ true, url, clickCallback);
+ }
+ catch (e) {
+ Cu.reportError(e);
+ }
+ },
+
+ _showPluginUpdatePage: function BG__showPluginUpdatePage() {
+ // Pale Moon: disable this functionality from BrowserGlue, people are
+ // already notified if they visit a page with an outdated plugin, and
+ // they can check properly from the plugins page as well.
+
+// Services.prefs.setBoolPref(PREF_PLUGINS_NOTIFYUSER, false);
+//
+// var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].
+// getService(Ci.nsIURLFormatter);
+// var updateUrl = formatter.formatURLPref(PREF_PLUGINS_UPDATEURL);
+//
+// var win = this.getMostRecentBrowserWindow();
+// win.openUILinkIn(updateUrl, "tab");
+ },
+
+ /**
+ * Initialize Places
+ * - imports the bookmarks html file if bookmarks database is empty, try to
+ * restore bookmarks from a JSON/JSONLZ4 backup if the backend indicates
+ * that the database was corrupt.
+ *
+ * These prefs can be set up by the frontend:
+ *
+ * WARNING: setting these preferences to true will overwite existing bookmarks
+ *
+ * - browser.places.importBookmarksHTML
+ * Set to true will import the bookmarks.html file from the profile folder.
+ * - browser.places.smartBookmarksVersion
+ * Set during HTML import to indicate that Smart Bookmarks were created.
+ * Set to -1 to disable Smart Bookmarks creation.
+ * Set to 0 to restore current Smart Bookmarks.
+ * - browser.bookmarks.restore_default_bookmarks
+ * Set to true by safe-mode dialog to indicate we must restore default
+ * bookmarks.
+ */
+ _initPlaces: function BG__initPlaces(aInitialMigrationPerformed) {
+ // We must instantiate the history service since it will tell us if we
+ // need to import or restore bookmarks due to first-run, corruption or
+ // forced migration (due to a major schema change).
+ // If the database is corrupt or has been newly created we should
+ // import bookmarks.
+ var dbStatus = PlacesUtils.history.databaseStatus;
+ var importBookmarks = !aInitialMigrationPerformed &&
+ (dbStatus == PlacesUtils.history.DATABASE_STATUS_CREATE ||
+ dbStatus == PlacesUtils.history.DATABASE_STATUS_CORRUPT);
+
+ // Check if user or an extension has required to import bookmarks.html
+ var importBookmarksHTML = false;
+ try {
+ importBookmarksHTML =
+ Services.prefs.getBoolPref("browser.places.importBookmarksHTML");
+ if (importBookmarksHTML)
+ importBookmarks = true;
+ } catch(ex) {}
+
+ Task.spawn(function() {
+ // Check if Safe Mode or the user has required to restore bookmarks from
+ // default profile's bookmarks.html
+ var restoreDefaultBookmarks = false;
+ try {
+ restoreDefaultBookmarks =
+ Services.prefs.getBoolPref("browser.bookmarks.restore_default_bookmarks");
+ if (restoreDefaultBookmarks) {
+ // Ensure that we already have a bookmarks backup for today.
+ if (this._shouldBackupBookmarks())
+ yield this._backupBookmarks();
+ importBookmarks = true;
+ }
+ } catch(ex) {}
+
+ // If the user did not require to restore default bookmarks, or import
+ // from bookmarks.html, we will try to restore from JSON/JSONLZ4
+ if (importBookmarks && !restoreDefaultBookmarks && !importBookmarksHTML) {
+ // get latest JSON/JSONLZ4 backup
+ var bookmarksBackupFile = yield PlacesBackups.getMostRecentBackup();
+ if (bookmarksBackupFile) {
+ // restore from JSON/JSONLZ4 backup
+ yield BookmarkJSONUtils.importFromFile(bookmarksBackupFile, true);
+ importBookmarks = false;
+ }
+ else {
+ // We have created a new database but we don't have any backup available
+ importBookmarks = true;
+ if (yield OS.File.exists(BookmarkHTMLUtils.defaultPath)) {
+ // If bookmarks.html is available in current profile import it...
+ importBookmarksHTML = true;
+ }
+ else {
+ // ...otherwise we will restore defaults
+ restoreDefaultBookmarks = true;
+ }
+ }
+ }
+
+ // If bookmarks are not imported, then initialize smart bookmarks. This
+ // happens during a common startup.
+ // Otherwise, if any kind of import runs, smart bookmarks creation should be
+ // delayed till the import operations has finished. Not doing so would
+ // cause them to be overwritten by the newly imported bookmarks.
+ if (!importBookmarks) {
+ // Now apply distribution customized bookmarks.
+ // This should always run after Places initialization.
+ try {
+ this._distributionCustomizer.applyBookmarks();
+ this.ensurePlacesDefaultQueriesInitialized();
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ else {
+ // An import operation is about to run.
+ // Don't try to recreate smart bookmarks if autoExportHTML is true or
+ // smart bookmarks are disabled.
+ var autoExportHTML = Services.prefs.getBoolPref("browser.bookmarks.autoExportHTML", false);
+ var smartBookmarksVersion = Services.prefs.getIntPref("browser.places.smartBookmarksVersion", 0);
+ if (!autoExportHTML && smartBookmarksVersion != -1)
+ Services.prefs.setIntPref("browser.places.smartBookmarksVersion", 0);
+
+ var bookmarksUrl = null;
+ if (restoreDefaultBookmarks) {
+ // User wants to restore bookmarks.html file from default profile folder
+ bookmarksUrl = "resource:///defaults/profile/bookmarks.html";
+ }
+ else if (yield OS.File.exists(BookmarkHTMLUtils.defaultPath)) {
+ bookmarksUrl = OS.Path.toFileURI(BookmarkHTMLUtils.defaultPath);
+ }
+
+ if (bookmarksUrl) {
+ // Import from bookmarks.html file.
+ try {
+ BookmarkHTMLUtils.importFromURL(bookmarksUrl, true).then(null,
+ function onFailure() {
+ Cu.reportError(
+ new Error("Bookmarks.html file could be corrupt."));
+ }
+ ).then(
+ function onComplete() {
+ try {
+ // Now apply distribution customized bookmarks.
+ // This should always run after Places initialization.
+ this._distributionCustomizer.applyBookmarks();
+ // Ensure that smart bookmarks are created once the operation
+ // is complete.
+ this.ensurePlacesDefaultQueriesInitialized();
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }.bind(this)
+ );
+ } catch (e) {
+ Cu.reportError(
+ new Error("Bookmarks.html file could be corrupt." + "\n" +
+ e.message));
+ }
+ }
+ else {
+ Cu.reportError(new Error("Unable to find bookmarks.html file."));
+ }
+
+ // See #1083:
+ // "Delete all bookmarks except for backups" in Safe Mode doesn't work
+ var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ let observer = {
+ "observe": function() {
+ delete observer.timer;
+ // Reset preferences, so we won't try to import again at next run
+ if (importBookmarksHTML) {
+ Services.prefs.setBoolPref("browser.places.importBookmarksHTML", false);
+ }
+ if (restoreDefaultBookmarks) {
+ Services.prefs.setBoolPref("browser.bookmarks.restore_default_bookmarks",
+ false);
+ }
+ },
+ "timer": timer,
+ };
+ timer.init(observer, 100, Ci.nsITimer.TYPE_ONE_SHOT);
+ }
+
+ // Initialize bookmark archiving on idle.
+ // Once a day, either on idle or shutdown, bookmarks are backed up.
+ if (!this._isIdleObserver) {
+ this._idleService.addIdleObserver(this, BOOKMARKS_BACKUP_IDLE_TIME);
+ this._isIdleObserver = true;
+ }
+
+ }.bind(this)).catch(ex => {
+ Cu.reportError(ex);
+ }).then(result => {
+ // NB: deliberately after the catch so that we always do this, even if
+ // we threw halfway through initializing in the Task above.
+ Services.obs.notifyObservers(null, "places-browser-init-complete", "");
+ });
+ },
+
+ /**
+ * Places shut-down tasks
+ * - back up bookmarks if needed.
+ * - export bookmarks as HTML, if so configured.
+ * - finalize components depending on Places.
+ */
+ _onPlacesShutdown: function BG__onPlacesShutdown() {
+ this._sanitizer.onShutdown();
+ PageThumbs.uninit();
+
+ if (this._isIdleObserver) {
+ this._idleService.removeIdleObserver(this, BOOKMARKS_BACKUP_IDLE_TIME);
+ this._isIdleObserver = false;
+ }
+
+ let waitingForBackupToComplete = true;
+ if (this._shouldBackupBookmarks()) {
+ waitingForBackupToComplete = false;
+ this._backupBookmarks().then(
+ function onSuccess() {
+ waitingForBackupToComplete = true;
+ },
+ function onFailure() {
+ Cu.reportError("Unable to backup bookmarks.");
+ waitingForBackupToComplete = true;
+ }
+ );
+ }
+
+ // Backup bookmarks to bookmarks.html to support apps that depend
+ // on the legacy format.
+ let waitingForHTMLExportToComplete = true;
+ // If this fails to get the preference value, we don't export.
+ if (Services.prefs.getBoolPref("browser.bookmarks.autoExportHTML")) {
+ // Exceptionally, since this is a non-default setting and HTML format is
+ // discouraged in favor of the JSON/JSONLZ4 backups, we spin the event
+ // loop on shutdown, to wait for the export to finish. We cannot safely
+ // spin the event loop on shutdown until we include a watchdog to prevent
+ // potential hangs (bug 518683). The asynchronous shutdown operations
+ // will then be handled by a shutdown service (bug 435058).
+ waitingForHTMLExportToComplete = false;
+ BookmarkHTMLUtils.exportToFile(BookmarkHTMLUtils.defaultPath).then(
+ function onSuccess() {
+ waitingForHTMLExportToComplete = true;
+ },
+ function onFailure() {
+ Cu.reportError("Unable to auto export html.");
+ waitingForHTMLExportToComplete = true;
+ }
+ );
+ }
+
+ let thread = Services.tm.currentThread;
+ while (!waitingForBackupToComplete || !waitingForHTMLExportToComplete) {
+ thread.processNextEvent(true);
+ }
+ },
+
+ /**
+ * Determine whether to backup bookmarks or not.
+ * @return true if bookmarks should be backed up, false if not.
+ */
+ _shouldBackupBookmarks: function BG__shouldBackupBookmarks() {
+ let lastBackupFile = PlacesBackups.getMostRecent();
+
+ // Should backup bookmarks if there are no backups or the maximum interval between
+ // backups elapsed.
+ return (!lastBackupFile ||
+ new Date() - PlacesBackups.getDateForFile(lastBackupFile) > BOOKMARKS_BACKUP_INTERVAL);
+ },
+
+ /**
+ * Backup bookmarks.
+ */
+ _backupBookmarks: function BG__backupBookmarks() {
+ return Task.spawn(function() {
+ // Backup bookmarks if there are no backups or the maximum interval between
+ // backups elapsed.
+ let maxBackups = BOOKMARKS_BACKUP_MAX_BACKUPS;
+ try {
+ maxBackups = Services.prefs.getIntPref("browser.bookmarks.max_backups");
+ }
+ catch(ex) { /* Use default. */ }
+
+ yield PlacesBackups.create(maxBackups); // Don't force creation.
+ });
+ },
+
+ /**
+ * Show the notificationBox for a locked places database.
+ */
+ _showPlacesLockedNotificationBox: function BG__showPlacesLockedNotificationBox() {
+ var applicationName = gBrandBundle.GetStringFromName("brandShortName");
+ var placesBundle = Services.strings.createBundle("chrome://browser/locale/places/places.properties");
+ var title = placesBundle.GetStringFromName("lockPrompt.title");
+ var text = placesBundle.formatStringFromName("lockPrompt.text", [applicationName], 1);
+ var buttonText = placesBundle.GetStringFromName("lockPromptInfoButton.label");
+ var accessKey = placesBundle.GetStringFromName("lockPromptInfoButton.accessKey");
+
+ var helpTopic = "places-locked";
+ var url = Cc["@mozilla.org/toolkit/URLFormatterService;1"].
+ getService(Components.interfaces.nsIURLFormatter).
+ formatURLPref("app.support.baseURL");
+ url += helpTopic;
+
+ var win = this.getMostRecentBrowserWindow();
+
+ var buttons = [
+ {
+ label: buttonText,
+ accessKey: accessKey,
+ popup: null,
+ callback: function(aNotificationBar, aButton) {
+ win.openUILinkIn(url, "tab");
+ }
+ }
+ ];
+
+ var notifyBox = win.gBrowser.getNotificationBox();
+ var notification = notifyBox.appendNotification(text, title, null,
+ notifyBox.PRIORITY_CRITICAL_MEDIUM,
+ buttons);
+ notification.persistence = -1; // Until user closes it
+ },
+
+ _migrateUI: function BG__migrateUI() {
+ const UI_VERSION = 19;
+ const BROWSER_DOCURL = "chrome://browser/content/browser.xul#";
+ let currentUIVersion = 0;
+ try {
+ currentUIVersion = Services.prefs.getIntPref("browser.migration.version");
+ } catch(ex) {}
+ if (currentUIVersion >= UI_VERSION)
+ return;
+
+ this._rdf = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
+ this._dataSource = this._rdf.GetDataSource("rdf:local-store");
+ this._dirty = false;
+
+ if (currentUIVersion < 2) {
+ // This code adds the customizable bookmarks button.
+ let currentsetResource = this._rdf.GetResource("currentset");
+ let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
+ let currentset = this._getPersist(toolbarResource, currentsetResource);
+ // Need to migrate only if toolbar is customized and the element is not found.
+ if (currentset &&
+ currentset.indexOf("bookmarks-menu-button-container") == -1) {
+ currentset += ",bookmarks-menu-button-container";
+ this._setPersist(toolbarResource, currentsetResource, currentset);
+ }
+ }
+
+ if (currentUIVersion < 3) {
+ // This code merges the reload/stop/go button into the url bar.
+ let currentsetResource = this._rdf.GetResource("currentset");
+ let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
+ let currentset = this._getPersist(toolbarResource, currentsetResource);
+ // Need to migrate only if toolbar is customized and all 3 elements are found.
+ if (currentset &&
+ currentset.indexOf("reload-button") != -1 &&
+ currentset.indexOf("stop-button") != -1 &&
+ currentset.indexOf("urlbar-container") != -1 &&
+ currentset.indexOf("urlbar-container,reload-button,stop-button") == -1) {
+ currentset = currentset.replace(/(^|,)reload-button($|,)/, "$1$2")
+ .replace(/(^|,)stop-button($|,)/, "$1$2")
+ .replace(/(^|,)urlbar-container($|,)/,
+ "$1urlbar-container,reload-button,stop-button$2");
+ this._setPersist(toolbarResource, currentsetResource, currentset);
+ }
+ }
+
+ if (currentUIVersion < 4) {
+ // This code moves the home button to the immediate left of the bookmarks menu button.
+ let currentsetResource = this._rdf.GetResource("currentset");
+ let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
+ let currentset = this._getPersist(toolbarResource, currentsetResource);
+ // Need to migrate only if toolbar is customized and the elements are found.
+ if (currentset &&
+ currentset.indexOf("home-button") != -1 &&
+ currentset.indexOf("bookmarks-menu-button-container") != -1) {
+ currentset = currentset.replace(/(^|,)home-button($|,)/, "$1$2")
+ .replace(/(^|,)bookmarks-menu-button-container($|,)/,
+ "$1home-button,bookmarks-menu-button-container$2");
+ this._setPersist(toolbarResource, currentsetResource, currentset);
+ }
+ }
+
+ if (currentUIVersion < 5) {
+ // This code uncollapses PersonalToolbar if its collapsed status is not
+ // persisted, and user customized it or changed default bookmarks.
+ let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "PersonalToolbar");
+ let collapsedResource = this._rdf.GetResource("collapsed");
+ let collapsed = this._getPersist(toolbarResource, collapsedResource);
+ // If the user does not have a persisted value for the toolbar's
+ // "collapsed" attribute, try to determine whether it's customized.
+ if (collapsed === null) {
+ // We consider the toolbar customized if it has more than
+ // 3 children, or if it has a persisted currentset value.
+ let currentsetResource = this._rdf.GetResource("currentset");
+ let toolbarIsCustomized = !!this._getPersist(toolbarResource,
+ currentsetResource);
+ function getToolbarFolderCount() {
+ let toolbarFolder =
+ PlacesUtils.getFolderContents(PlacesUtils.toolbarFolderId).root;
+ let toolbarChildCount = toolbarFolder.childCount;
+ toolbarFolder.containerOpen = false;
+ return toolbarChildCount;
+ }
+
+ if (toolbarIsCustomized || getToolbarFolderCount() > 3) {
+ this._setPersist(toolbarResource, collapsedResource, "false");
+ }
+ }
+ }
+
+ if (currentUIVersion < 6) {
+ // convert tabsontop attribute to pref
+ let toolboxResource = this._rdf.GetResource(BROWSER_DOCURL + "navigator-toolbox");
+ let tabsOnTopResource = this._rdf.GetResource("tabsontop");
+ let tabsOnTopAttribute = this._getPersist(toolboxResource, tabsOnTopResource);
+ if (tabsOnTopAttribute)
+ Services.prefs.setBoolPref("browser.tabs.onTop", tabsOnTopAttribute == "true");
+ }
+
+ // Migration at version 7 only occurred for users who wanted to try the new
+ // Downloads Panel feature before its release. Since migration at version
+ // 9 adds the button by default, this step has been removed.
+
+ if (currentUIVersion < 8) {
+ // Reset homepage pref for users who have it set to google.com/firefox
+ let uri = Services.prefs.getComplexValue("browser.startup.homepage",
+ Ci.nsIPrefLocalizedString).data;
+ if (uri && /^https?:\/\/(www\.)?google(\.\w{2,3}){1,2}\/firefox\/?$/.test(uri)) {
+ Services.prefs.clearUserPref("browser.startup.homepage");
+ }
+ }
+
+ if (currentUIVersion < 9) {
+ // This code adds the customizable downloads buttons.
+ let currentsetResource = this._rdf.GetResource("currentset");
+ let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
+ let currentset = this._getPersist(toolbarResource, currentsetResource);
+
+ // Since the Downloads button is located in the navigation bar by default,
+ // migration needs to happen only if the toolbar was customized using a
+ // previous UI version, and the button was not already placed on the
+ // toolbar manually.
+ if (currentset &&
+ currentset.indexOf("downloads-button") == -1) {
+ // The element is added either after the search bar or before the home
+ // button. As a last resort, the element is added just before the
+ // non-customizable window controls.
+ if (currentset.indexOf("search-container") != -1) {
+ currentset = currentset.replace(/(^|,)search-container($|,)/,
+ "$1search-container,downloads-button$2")
+ } else if (currentset.indexOf("home-button") != -1) {
+ currentset = currentset.replace(/(^|,)home-button($|,)/,
+ "$1downloads-button,home-button$2")
+ } else {
+ currentset = currentset.replace(/(^|,)window-controls($|,)/,
+ "$1downloads-button,window-controls$2")
+ }
+ this._setPersist(toolbarResource, currentsetResource, currentset);
+ }
+
+ Services.prefs.clearUserPref("browser.download.useToolkitUI");
+ Services.prefs.clearUserPref("browser.library.useNewDownloadsView");
+ }
+
+#ifdef XP_WIN
+ if (currentUIVersion < 10) {
+ // For Windows systems with display set to > 96dpi (i.e. systemDefaultScale
+ // will return a value > 1.0), we want to discard any saved full-zoom settings,
+ // as we'll now be scaling the content according to the system resolution
+ // scale factor (Windows "logical DPI" setting)
+ let sm = Cc["@mozilla.org/gfx/screenmanager;1"].getService(Ci.nsIScreenManager);
+ if (sm.systemDefaultScale > 1.0) {
+ let cps2 = Cc["@mozilla.org/content-pref/service;1"].
+ getService(Ci.nsIContentPrefService2);
+ cps2.removeByName("browser.content.full-zoom", null);
+ }
+ }
+#endif
+
+ if (currentUIVersion < 11) {
+ Services.prefs.clearUserPref("dom.disable_window_move_resize");
+ Services.prefs.clearUserPref("dom.disable_window_flip");
+ Services.prefs.clearUserPref("dom.event.contextmenu.enabled");
+ Services.prefs.clearUserPref("javascript.enabled");
+ Services.prefs.clearUserPref("permissions.default.image");
+ }
+
+ if (currentUIVersion < 12) {
+ // Remove bookmarks-menu-button-container, then place
+ // bookmarks-menu-button into its position.
+ let currentsetResource = this._rdf.GetResource("currentset");
+ let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
+ let currentset = this._getPersist(toolbarResource, currentsetResource);
+ // Need to migrate only if toolbar is customized.
+ if (currentset) {
+ if (currentset.contains("bookmarks-menu-button-container")) {
+ currentset = currentset.replace(/(^|,)bookmarks-menu-button-container($|,)/,
+ "$1bookmarks-menu-button$2");
+ this._setPersist(toolbarResource, currentsetResource, currentset);
+ }
+ }
+ }
+
+ if (currentUIVersion < 16) {
+ // Migrate Sync from pmsync.palemoon.net to pmsync.palemoon.org
+ try {
+ let syncURL = Services.prefs.getCharPref("services.sync.clusterURL");
+ let newSyncURL = syncURL.replace(/pmsync\.palemoon\.net/i,"pmsync.palemoon.org");
+ if (newSyncURL != syncURL) {
+ Services.prefs.setCharPref("services.sync.clusterURL", newSyncURL);
+ }
+ } catch(ex) {
+ // Pref not found: Sync not in use, nothing to do.
+ }
+ }
+
+ if (currentUIVersion < 17) {
+ this._notifyNotificationsUpgrade();
+ }
+
+ if (this._dirty)
+ this._dataSource.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush();
+
+ delete this._rdf;
+ delete this._dataSource;
+
+ if (currentUIVersion < 18) {
+ // Make sure the doNotTrack value conforms to the conversion from
+ // three-state to two-state. (This reverts a setting of "please track me"
+ // to the default "don't say anything").
+ try {
+ if (Services.prefs.getBoolPref("privacy.donottrackheader.enabled") &&
+ Services.prefs.getIntPref("privacy.donottrackheader.value") != 1) {
+ Services.prefs.clearUserPref("privacy.donottrackheader.enabled");
+ Services.prefs.clearUserPref("privacy.donottrackheader.value");
+ }
+ }
+ catch (ex) {}
+ }
+
+#ifndef MOZ_JXR
+ // Until JPEG-XR decoder is implemented (UXP #144)
+ if (currentUIVersion < 19) {
+ try {
+ let ihaPref = "image.http.accept";
+ let ihaValue = Services.prefs.getCharPref(ihaPref);
+ if (ihaValue.includes("image/jxr,")) {
+ Services.prefs.setCharPref(ihaPref, ihaValue.replace("image/jxr,", ""));
+ } else if (ihaValue.includes("image/jxr")) {
+ Services.prefs.clearUserPref(ihaPref);
+ }
+ }
+ catch (ex) {}
+ }
+#endif
+
+ // Update the migration version.
+ Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
+ },
+
+ _hasExistingNotificationPermission: function BG__hasExistingNotificationPermission() {
+ let enumerator = Services.perms.enumerator;
+ while (enumerator.hasMoreElements()) {
+ let permission = enumerator.getNext().QueryInterface(Ci.nsIPermission);
+ if (permission.type == "desktop-notification") {
+ return true;
+ }
+ }
+ return false;
+ },
+
+ _notifyNotificationsUpgrade: function BG__notifyNotificationsUpgrade() {
+ if (!this._hasExistingNotificationPermission()) {
+ return;
+ }
+ function clickCallback(subject, topic, data) {
+ if (topic != "alertclickcallback")
+ return;
+ let win = RecentWindow.getMostRecentBrowserWindow();
+ win.openUILinkIn(data, "tab");
+ }
+ // Show the application icon for XUL notifications. We assume system-level
+ // notifications will include their own icon.
+ let imageURL = this._hasSystemAlertsService() ? "" :
+ "chrome://branding/content/about-logo.png";
+ let title = gBrowserBundle.GetStringFromName("webNotifications.upgradeTitle");
+ let text = gBrowserBundle.GetStringFromName("webNotifications.upgradeBody");
+ let url = Services.urlFormatter.formatURLPref("browser.push.warning.infoURL");
+
+ try {
+ AlertsService.showAlertNotification(imageURL, title, text,
+ true, url, clickCallback);
+ }
+ catch (e) {
+ Cu.reportError(e);
+ }
+ },
+
+ _openPermissions: function(aPrincipal) {
+ var win = this.getMostRecentBrowserWindow();
+ var url = "about:permissions";
+ try {
+ url = url + "?filter=" + aPrincipal.URI.host;
+ }
+ catch (e) {}
+ win.openUILinkIn(url, "tab");
+ },
+
+ _hasSystemAlertsService: function() {
+ try {
+ return !!Cc["@mozilla.org/system-alerts-service;1"].getService(
+ Ci.nsIAlertsService);
+ } catch (e) {}
+ return false;
+ },
+
+ _getPersist: function BG__getPersist(aSource, aProperty) {
+ var target = this._dataSource.GetTarget(aSource, aProperty, true);
+ if (target instanceof Ci.nsIRDFLiteral)
+ return target.Value;
+ return null;
+ },
+
+ _setPersist: function BG__setPersist(aSource, aProperty, aTarget) {
+ this._dirty = true;
+ try {
+ var oldTarget = this._dataSource.GetTarget(aSource, aProperty, true);
+ if (oldTarget) {
+ if (aTarget)
+ this._dataSource.Change(aSource, aProperty, oldTarget, this._rdf.GetLiteral(aTarget));
+ else
+ this._dataSource.Unassert(aSource, aProperty, oldTarget);
+ }
+ else {
+ this._dataSource.Assert(aSource, aProperty, this._rdf.GetLiteral(aTarget), true);
+ }
+
+ // Add the entry to the persisted set for this document if it's not there.
+ // This code is mostly borrowed from XULDocument::Persist.
+ let docURL = aSource.ValueUTF8.split("#")[0];
+ let docResource = this._rdf.GetResource(docURL);
+ let persistResource = this._rdf.GetResource("http://home.netscape.com/NC-rdf#persist");
+ if (!this._dataSource.HasAssertion(docResource, persistResource, aSource, true)) {
+ this._dataSource.Assert(docResource, persistResource, aSource, true);
+ }
+ }
+ catch(ex) {}
+ },
+
+ // ------------------------------
+ // public nsIBrowserGlue members
+ // ------------------------------
+
+ sanitize: function BG_sanitize(aParentWindow) {
+ this._sanitizer.sanitize(aParentWindow);
+ },
+
+ ensurePlacesDefaultQueriesInitialized:
+ function BG_ensurePlacesDefaultQueriesInitialized() {
+ // This is actual version of the smart bookmarks, must be increased every
+ // time smart bookmarks change.
+ // When adding a new smart bookmark below, its newInVersion property must
+ // be set to the version it has been added in, we will compare its value
+ // to users' smartBookmarksVersion and add new smart bookmarks without
+ // recreating old deleted ones.
+ const SMART_BOOKMARKS_VERSION = 4;
+ const SMART_BOOKMARKS_ANNO = "Places/SmartBookmark";
+ const SMART_BOOKMARKS_PREF = "browser.places.smartBookmarksVersion";
+
+ // TODO bug 399268: should this be a pref?
+ const MAX_RESULTS = 10;
+
+ // Get current smart bookmarks version. If not set, create them.
+ let smartBookmarksCurrentVersion = Services.prefs.getIntPref(SMART_BOOKMARKS_PREF, 0);
+
+ // If version is current or smart bookmarks are disabled, just bail out.
+ if (smartBookmarksCurrentVersion == -1 ||
+ smartBookmarksCurrentVersion >= SMART_BOOKMARKS_VERSION) {
+ return;
+ }
+
+ let batch = {
+ runBatched: function BG_EPDQI_runBatched() {
+ let menuIndex = 0;
+ let toolbarIndex = 0;
+ let bundle = Services.strings.createBundle("chrome://browser/locale/places/places.properties");
+
+ let smartBookmarks = {
+ MostVisited: {
+ title: bundle.GetStringFromName("mostVisitedTitle"),
+ uri: NetUtil.newURI("place:sort=" +
+ Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_DESCENDING +
+ "&maxResults=" + MAX_RESULTS),
+ parent: PlacesUtils.toolbarFolderId,
+ position: toolbarIndex++,
+ newInVersion: 1
+ },
+ RecentlyBookmarked: {
+ title: bundle.GetStringFromName("recentlyBookmarkedTitle"),
+ uri: NetUtil.newURI("place:folder=BOOKMARKS_MENU" +
+ "&folder=UNFILED_BOOKMARKS" +
+ "&folder=TOOLBAR" +
+ "&queryType=" +
+ Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS +
+ "&sort=" +
+ Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING +
+ "&maxResults=" + MAX_RESULTS +
+ "&excludeQueries=1"),
+ parent: PlacesUtils.bookmarksMenuFolderId,
+ position: menuIndex++,
+ newInVersion: 1
+ },
+ RecentTags: {
+ title: bundle.GetStringFromName("recentTagsTitle"),
+ uri: NetUtil.newURI("place:"+
+ "type=" +
+ Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY +
+ "&sort=" +
+ Ci.nsINavHistoryQueryOptions.SORT_BY_LASTMODIFIED_DESCENDING +
+ "&maxResults=" + MAX_RESULTS),
+ parent: PlacesUtils.bookmarksMenuFolderId,
+ position: menuIndex++,
+ newInVersion: 1
+ }
+ };
+
+ // Set current itemId, parent and position if Smart Bookmark exists,
+ // we will use these informations to create the new version at the same
+ // position.
+ let smartBookmarkItemIds = PlacesUtils.annotations.getItemsWithAnnotation(SMART_BOOKMARKS_ANNO);
+ smartBookmarkItemIds.forEach(function (itemId) {
+ let queryId = PlacesUtils.annotations.getItemAnnotation(itemId, SMART_BOOKMARKS_ANNO);
+ if (queryId in smartBookmarks) {
+ let smartBookmark = smartBookmarks[queryId];
+ smartBookmark.itemId = itemId;
+ smartBookmark.parent = PlacesUtils.bookmarks.getFolderIdForItem(itemId);
+ smartBookmark.position = PlacesUtils.bookmarks.getItemIndex(itemId);
+ }
+ else {
+ // We don't remove old Smart Bookmarks because user could still
+ // find them useful, or could have personalized them.
+ // Instead we remove the Smart Bookmark annotation.
+ PlacesUtils.annotations.removeItemAnnotation(itemId, SMART_BOOKMARKS_ANNO);
+ }
+ });
+
+ for (let queryId in smartBookmarks) {
+ let smartBookmark = smartBookmarks[queryId];
+
+ // We update or create only changed or new smart bookmarks.
+ // Also we respect user choices, so we won't try to create a smart
+ // bookmark if it has been removed.
+ if (smartBookmarksCurrentVersion > 0 &&
+ smartBookmark.newInVersion <= smartBookmarksCurrentVersion &&
+ !smartBookmark.itemId)
+ continue;
+
+ // Remove old version of the smart bookmark if it exists, since it
+ // will be replaced in place.
+ if (smartBookmark.itemId) {
+ PlacesUtils.bookmarks.removeItem(smartBookmark.itemId);
+ }
+
+ // Create the new smart bookmark and store its updated itemId.
+ smartBookmark.itemId =
+ PlacesUtils.bookmarks.insertBookmark(smartBookmark.parent,
+ smartBookmark.uri,
+ smartBookmark.position,
+ smartBookmark.title);
+ PlacesUtils.annotations.setItemAnnotation(smartBookmark.itemId,
+ SMART_BOOKMARKS_ANNO,
+ queryId, 0,
+ PlacesUtils.annotations.EXPIRE_NEVER);
+ }
+
+ // If we are creating all Smart Bookmarks from ground up, add a
+ // separator below them in the bookmarks menu.
+ if (smartBookmarksCurrentVersion == 0 &&
+ smartBookmarkItemIds.length == 0) {
+ let id = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.bookmarksMenuFolderId,
+ menuIndex);
+ // Don't add a separator if the menu was empty or there is one already.
+ if (id != -1 &&
+ PlacesUtils.bookmarks.getItemType(id) != PlacesUtils.bookmarks.TYPE_SEPARATOR) {
+ PlacesUtils.bookmarks.insertSeparator(PlacesUtils.bookmarksMenuFolderId,
+ menuIndex);
+ }
+ }
+ }
+ };
+
+ try {
+ PlacesUtils.bookmarks.runInBatchMode(batch, null);
+ }
+ catch(ex) {
+ Components.utils.reportError(ex);
+ }
+ finally {
+ Services.prefs.setIntPref(SMART_BOOKMARKS_PREF, SMART_BOOKMARKS_VERSION);
+ Services.prefs.savePrefFile(null);
+ }
+ },
+
+ // this returns the most recent non-popup browser window
+ getMostRecentBrowserWindow: function BG_getMostRecentBrowserWindow() {
+ return RecentWindow.getMostRecentBrowserWindow();
+ },
+
+#ifdef MOZ_SERVICES_SYNC
+ /**
+ * Called as an observer when Sync's "display URI" notification is fired.
+ *
+ * We open the received URI in a background tab.
+ *
+ * Eventually, this will likely be replaced by a more robust tab syncing
+ * feature. This functionality is considered somewhat evil by UX because it
+ * opens a new tab automatically without any prompting. However, it is a
+ * lesser evil than sending a tab to a specific device (from e.g. Fennec)
+ * and having nothing happen on the receiving end.
+ */
+ _onDisplaySyncURI: function _onDisplaySyncURI(data) {
+ try {
+ let tabbrowser = RecentWindow.getMostRecentBrowserWindow({private: false}).gBrowser;
+
+ // The payload is wrapped weirdly because of how Sync does notifications.
+ tabbrowser.addTab(data.wrappedJSObject.object.uri);
+ } catch (ex) {
+ Cu.reportError("Error displaying tab received by Sync: " + ex);
+ }
+ },
+#endif
+
+ // for XPCOM
+ classID: Components.ID("{eab9012e-5f74-4cbc-b2b5-a590235513cc}"),
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference,
+ Ci.nsIBrowserGlue]),
+
+ // redefine the default factory for XPCOMUtils
+ _xpcom_factory: BrowserGlueServiceFactory,
+}
+
+function ContentPermissionPrompt() {}
+
+ContentPermissionPrompt.prototype = {
+ classID: Components.ID("{d8903bf6-68d5-4e97-bcd1-e4d3012f721a}"),
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionPrompt]),
+
+ _getChromeWindow: function CPP_getChromeWindow(aWindow) {
+ var chromeWin = aWindow
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow)
+ .QueryInterface(Ci.nsIDOMChromeWindow);
+ return chromeWin;
+ },
+
+ _getBrowserForRequest: function (aRequest) {
+ let requestingWindow = aRequest.window.top;
+ // find the requesting browser or iframe
+ let browser = requestingWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+ return browser;
+ },
+
+ /**
+ * Show a permission prompt.
+ *
+ * @param aRequest The permission request.
+ * @param aMessage The message to display on the prompt.
+ * @param aPermission The type of permission to prompt.
+ * @param aActions An array of actions of the form:
+ * [main action, secondary actions, ...]
+ * Actions are of the form { stringId, action, expireType, callback }
+ * Permission is granted if action is null or ALLOW_ACTION.
+ * @param aNotificationId The id of the PopupNotification.
+ * @param aAnchorId The id for the PopupNotification anchor.
+ * @param aOptions Options for the PopupNotification
+ */
+ _showPrompt: function CPP_showPrompt(aRequest, aMessage, aPermission, aActions,
+ aNotificationId, aAnchorId, aOptions) {
+ function onFullScreen() {
+ popup.remove();
+ }
+
+ var requestingWindow = aRequest.window.top;
+ var chromeWin = this._getChromeWindow(requestingWindow).wrappedJSObject;
+ var browser = chromeWin.gBrowser.getBrowserForDocument(requestingWindow.document);
+ if (!browser) {
+ // find the requesting browser or iframe
+ browser = requestingWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+ }
+ var requestPrincipal = aRequest.principal;
+
+ // Transform the prompt actions into PopupNotification actions.
+ var popupNotificationActions = [];
+ for (var i = 0; i < aActions.length; i++) {
+ let promptAction = aActions[i];
+
+ // Don't offer action in PB mode if the action remembers permission for more than a session.
+ if (PrivateBrowsingUtils.isWindowPrivate(chromeWin) &&
+ promptAction.expireType != Ci.nsIPermissionManager.EXPIRE_SESSION &&
+ promptAction.action) {
+ continue;
+ }
+
+ var action = {
+ label: gBrowserBundle.GetStringFromName(promptAction.stringId),
+ accessKey: gBrowserBundle.GetStringFromName(promptAction.stringId + ".accesskey"),
+ callback: function() {
+ if (promptAction.callback) {
+ promptAction.callback();
+ }
+
+ // Remember permissions.
+ if (promptAction.action) {
+ Services.perms.addFromPrincipal(requestPrincipal, aPermission,
+ promptAction.action, promptAction.expireType);
+ }
+
+ // Grant permission if action is null or ALLOW_ACTION.
+ if (!promptAction.action || promptAction.action == Ci.nsIPermissionManager.ALLOW_ACTION) {
+ aRequest.allow();
+ } else {
+ aRequest.cancel();
+ }
+ },
+ };
+
+ popupNotificationActions.push(action);
+ }
+
+ var mainAction = popupNotificationActions.length ?
+ popupNotificationActions[0] : null;
+ var secondaryActions = popupNotificationActions.splice(1);
+
+ if (aRequest.type == "pointerLock") {
+ // If there's no mainAction, this is the autoAllow warning prompt.
+ let autoAllow = !mainAction;
+
+ if (!aOptions)
+ aOptions = {};
+
+ aOptions.removeOnDismissal = autoAllow;
+ aOptions.eventCallback = type => {
+ if (type == "removed") {
+ browser.removeEventListener("mozfullscreenchange", onFullScreen, true);
+ if (autoAllow) {
+ aRequest.allow();
+ }
+ }
+ }
+
+ }
+
+ var popup = chromeWin.PopupNotifications.show(browser, aNotificationId, aMessage, aAnchorId,
+ mainAction, secondaryActions, aOptions);
+ if (aRequest.type == "pointerLock") {
+ // pointerLock is automatically allowed in fullscreen mode (and revoked
+ // upon exit), so if the page enters fullscreen mode after requesting
+ // pointerLock (but before the user has granted permission), we should
+ // remove the now-impotent notification.
+ browser.addEventListener("mozfullscreenchange", onFullScreen, true);
+ }
+ },
+
+ _promptGeo : function(aRequest) {
+ var requestingURI = aRequest.principal.URI;
+
+ var message;
+
+ // Share location action.
+ var actions = [{
+ stringId: "geolocation.shareLocation",
+ action: null,
+ expireType: null,
+ callback: function() {
+ // Telemetry stub (left here for safety and compatibility reasons)
+ },
+ }];
+
+ if (requestingURI.schemeIs("file")) {
+ message = gBrowserBundle.formatStringFromName("geolocation.shareWithFile",
+ [requestingURI.path], 1);
+ } else {
+ message = gBrowserBundle.formatStringFromName("geolocation.shareWithSite",
+ [requestingURI.host], 1);
+ // Always share location action.
+ actions.push({
+ stringId: "geolocation.alwaysShareLocation",
+ action: Ci.nsIPermissionManager.ALLOW_ACTION,
+ expireType: null,
+ callback: function() {
+ // Telemetry stub (left here for safety and compatibility reasons)
+ },
+ });
+
+ // Never share location action.
+ actions.push({
+ stringId: "geolocation.neverShareLocation",
+ action: Ci.nsIPermissionManager.DENY_ACTION,
+ expireType: null,
+ callback: function() {
+ // Telemetry stub (left here for safety and compatibility reasons)
+ },
+ });
+ }
+
+ var options = {
+ learnMoreURL: Services.urlFormatter.formatURLPref("browser.geolocation.warning.infoURL"),
+ };
+
+ this._showPrompt(aRequest, message, "geo", actions, "geolocation",
+ "geo-notification-icon", options);
+ },
+
+ _promptWebNotifications : function(aRequest) {
+ var requestingURI = aRequest.principal.URI;
+
+ var message = gBrowserBundle.formatStringFromName("webNotifications.showFromSite",
+ [requestingURI.host], 1);
+
+ var actions;
+
+ var browser = this._getBrowserForRequest(aRequest);
+ // Only show "allow for session" in PB mode, we don't
+ // support "allow for session" in non-PB mode.
+ if (PrivateBrowsingUtils.isBrowserPrivate(browser)) {
+ actions = [
+ {
+ stringId: "webNotifications.showForSession",
+ action: Ci.nsIPermissionManager.ALLOW_ACTION,
+ expireType: Ci.nsIPermissionManager.EXPIRE_SESSION,
+ callback: function() {},
+ },
+ ];
+ } else {
+ actions = [
+ {
+ stringId: "webNotifications.showForSession",
+ action: Ci.nsIPermissionManager.ALLOW_ACTION,
+ expireType: Ci.nsIPermissionManager.EXPIRE_SESSION,
+ callback: function() {},
+ },
+ {
+ stringId: "webNotifications.alwaysShow",
+ action: Ci.nsIPermissionManager.ALLOW_ACTION,
+ expireType: null,
+ callback: function() {},
+ },
+ {
+ stringId: "webNotifications.neverShow",
+ action: Ci.nsIPermissionManager.DENY_ACTION,
+ expireType: null,
+ callback: function() {},
+ },
+ ];
+ }
+ var options = {
+ learnMoreURL: Services.urlFormatter.formatURLPref("browser.push.warning.infoURL"),
+ };
+
+ this._showPrompt(aRequest, message, "desktop-notification", actions,
+ "web-notifications",
+ "web-notifications-notification-icon", options);
+ },
+
+ _promptPointerLock: function CPP_promtPointerLock(aRequest, autoAllow) {
+ let requestingURI = aRequest.principal.URI;
+
+ let originString = requestingURI.schemeIs("file") ? requestingURI.path : requestingURI.host;
+ let message = gBrowserBundle.formatStringFromName(autoAllow ?
+ "pointerLock.autoLock.title2" : "pointerLock.title2",
+ [originString], 1);
+ // If this is an autoAllow info prompt, offer no actions.
+ // _showPrompt() will allow the request when it's dismissed.
+ let actions = [];
+ if (!autoAllow) {
+ actions = [
+ {
+ stringId: "pointerLock.allow2",
+ action: null,
+ expireType: null,
+ callback: function() {},
+ },
+ {
+ stringId: "pointerLock.alwaysAllow",
+ action: Ci.nsIPermissionManager.ALLOW_ACTION,
+ expireType: null,
+ callback: function() {},
+ },
+ {
+ stringId: "pointerLock.neverAllow",
+ action: Ci.nsIPermissionManager.DENY_ACTION,
+ expireType: null,
+ callback: function() {},
+ },
+ ];
+ }
+
+ this._showPrompt(aRequest, message, "pointerLock", actions, "pointerLock",
+ "pointerLock-notification-icon", null);
+ },
+
+ prompt: function CPP_prompt(request) {
+ // Only allow exactly one permission rquest here.
+ let types = request.types.QueryInterface(Ci.nsIArray);
+ if (types.length != 1) {
+ request.cancel();
+ return;
+ }
+ let perm = types.queryElementAt(0, Ci.nsIContentPermissionType);
+
+ const kFeatureKeys = { "geolocation" : "geo",
+ "desktop-notification" : "desktop-notification",
+ "pointerLock" : "pointerLock",
+ };
+
+ // Make sure that we support the request.
+ if (!(perm.type in kFeatureKeys)) {
+ return;
+ }
+
+ var requestingPrincipal = request.principal;
+ var requestingURI = requestingPrincipal.URI;
+
+ // Ignore requests from non-nsIStandardURLs
+ if (!(requestingURI instanceof Ci.nsIStandardURL))
+ return;
+
+ var autoAllow = false;
+ var permissionKey = kFeatureKeys[perm.type];
+ var result = Services.perms.testExactPermissionFromPrincipal(requestingPrincipal, permissionKey);
+
+ if (result == Ci.nsIPermissionManager.DENY_ACTION) {
+ request.cancel();
+ return;
+ }
+
+ if (result == Ci.nsIPermissionManager.ALLOW_ACTION) {
+ autoAllow = true;
+ // For pointerLock, we still want to show a warning prompt.
+ if (request.type != "pointerLock") {
+ request.allow();
+ return;
+ }
+ }
+
+ // Show the prompt.
+ switch (perm.type) {
+ case "geolocation":
+ this._promptGeo(request);
+ break;
+ case "desktop-notification":
+ this._promptWebNotifications(request);
+ break;
+ case "pointerLock":
+ this._promptPointerLock(request, autoAllow);
+ break;
+ }
+ },
+
+};
+
+var components = [BrowserGlue, ContentPermissionPrompt];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/components/nsIBrowserGlue.idl b/components/nsIBrowserGlue.idl
new file mode 100644
index 0000000..1bb82a9
--- /dev/null
+++ b/components/nsIBrowserGlue.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+interface nsIDOMWindow;
+
+/**
+ * nsIBrowserGlue is a dirty and rather fluid interface to host shared utility
+ * methods used by browser UI code, but which are not local to a browser window.
+ * The component implementing this interface is meant to be a singleton
+ * (service) and should progressively replace some of the shared "glue" code
+ * scattered in browser/base/content (e.g. bits of utilOverlay.js,
+ * contentAreaUtils.js, globalOverlay.js, browser.js), avoiding dynamic
+ * inclusion and initialization of a ton of JS code for *each* window.
+ * Dued to its nature and origin, this interface won't probably be the most
+ * elegant or stable in the mozilla codebase, but its aim is rather pragmatic:
+ * 1) reducing the performance overhead which affects browser window load;
+ * 2) allow global hooks (e.g. startup and shutdown observers) which survive
+ * browser windows to accomplish browser-related activities, such as shutdown
+ * sanitization (see bug #284086)
+ *
+ */
+
+[scriptable, uuid(781df699-17dc-4237-b3d7-876ddb7085e3)]
+interface nsIBrowserGlue : nsISupports
+{
+ /**
+ * Deletes privacy sensitive data according to user preferences
+ *
+ * @param aParentWindow an optionally null window which is the parent of the
+ * sanitization dialog
+ *
+ */
+ void sanitize(in nsIDOMWindow aParentWindow);
+
+ /**
+ * Add Smart Bookmarks special queries to bookmarks menu and toolbar folder.
+ */
+ void ensurePlacesDefaultQueriesInitialized();
+
+ /**
+ * Gets the most recent window that's a browser (but not a popup)
+ */
+ nsIDOMWindow getMostRecentBrowserWindow();
+};
diff --git a/components/nsIBrowserHandler.idl b/components/nsIBrowserHandler.idl
new file mode 100644
index 0000000..74292f9
--- /dev/null
+++ b/components/nsIBrowserHandler.idl
@@ -0,0 +1,20 @@
+/* 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/. */
+
+#include "nsISupports.idl"
+
+interface nsICommandLine;
+
+[scriptable, uuid(8D3F5A9D-118D-4548-A137-CF7718679069)]
+interface nsIBrowserHandler : nsISupports
+{
+ attribute AUTF8String startPage;
+ attribute AUTF8String defaultArgs;
+
+ /**
+ * Extract the width and height specified on the command line, if present.
+ * @return A feature string with a prepended comma, e.g. ",width=500,height=400"
+ */
+ AUTF8String getFeatures(in nsICommandLine aCmdLine);
+};
diff --git a/components/pageinfo/feeds.js b/components/pageinfo/feeds.js
new file mode 100644
index 0000000..468d8c1
--- /dev/null
+++ b/components/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, "feed" in rels);
+ 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/components/pageinfo/feeds.xml b/components/pageinfo/feeds.xml
new file mode 100644
index 0000000..782c05a
--- /dev/null
+++ b/components/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/components/pageinfo/jar.mn b/components/pageinfo/jar.mn
new file mode 100644
index 0000000..229f991
--- /dev/null
+++ b/components/pageinfo/jar.mn
@@ -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/.
+
+browser.jar:
+* content/browser/pageinfo/pageInfo.xul
+ content/browser/pageinfo/pageInfo.js
+ content/browser/pageinfo/pageInfo.css
+ content/browser/pageinfo/pageInfo.xml
+ content/browser/pageinfo/feeds.js
+ content/browser/pageinfo/feeds.xml
+ content/browser/pageinfo/permissions.js
+ content/browser/pageinfo/security.js \ No newline at end of file
diff --git a/components/pageinfo/moz.build b/components/pageinfo/moz.build
new file mode 100644
index 0000000..2d64d50
--- /dev/null
+++ b/components/pageinfo/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/.
+
+JAR_MANIFESTS += ['jar.mn']
+
diff --git a/components/pageinfo/pageInfo.css b/components/pageinfo/pageInfo.css
new file mode 100644
index 0000000..622b56b
--- /dev/null
+++ b/components/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/components/pageinfo/pageInfo.js b/components/pageinfo/pageInfo.js
new file mode 100644
index 0000000..600174a
--- /dev/null
+++ b/components/pageinfo/pageInfo.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/. */
+
+var Cu = Components.utils;
+Cu.import("resource://gre/modules/LoadContextInfo.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+//******** 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 || treecol.index == COL_IMAGE_COUNT) {
+ 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 nsICacheStorageService = Components.interfaces.nsICacheStorageService;
+const nsICacheStorage = Components.interfaces.nsICacheStorage;
+const cacheService = Components.classes["@mozilla.org/netwerk/cache-storage-service;1"].getService(nsICacheStorageService);
+
+var loadContextInfo = LoadContextInfo.fromLoadContext(
+ window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebNavigation)
+ .QueryInterface(Components.interfaces.nsILoadContext), false);
+var diskStorage = cacheService.diskCacheStorage(loadContextInfo, 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
+function getClipboardHelper() {
+ try {
+ return Components.classes["@mozilla.org/widget/clipboardhelper;1"].getService(Components.interfaces.nsIClipboardHelper);
+ } catch(e) {
+ // do nothing, later code will handle the error
+ }
+}
+const gClipboardHelper = getClipboardHelper();
+
+// 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);
+
+ // Make sure the page info window gets focus even if a doorhanger might
+ // otherwise (async) steal it.
+ window.focus();
+}
+
+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(gDocument.nodePrincipal);
+
+ /* 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 checkCacheListener = {
+ onCacheEntryCheck: function(entry, appCache) {
+ return Components.interfaces.nsICacheEntryOpenCallback.ENTRY_WANTED;
+ },
+ onCacheEntryAvailable: function(entry, isNew, appCache, status) {
+ cb(entry);
+ },
+ get mainThreadOnly() { return true; }
+ };
+ diskStorage.asyncOpenURI(Services.io.newURI(key, null, null), "", nsICacheStorage.OPEN_READONLY, checkCacheListener);
+}
+
+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.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, "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 (permission.matchesURI(makeURI(url), true)) {
+ 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/components/pageinfo/pageInfo.xml b/components/pageinfo/pageInfo.xml
new file mode 100644
index 0000000..20d3300
--- /dev/null
+++ b/components/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/components/pageinfo/pageInfo.xul b/components/pageinfo/pageInfo.xul
new file mode 100644
index 0000000..c7c486a
--- /dev/null
+++ b/components/pageinfo/pageInfo.xul
@@ -0,0 +1,507 @@
+<?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_geoDef" oncommand="onCheckboxClick('geo');"/>
+ <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_geoToggle" oncommand="onRadioClick('geo');"/>
+ <command id="cmd_pluginsToggle" oncommand="onPluginRadioClick(event);"/>
+ </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#9" command="cmd_cookieToggle" label="&permAllowFirstPartyOnly;"/>
+ <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#0" command="cmd_desktop-notificationToggle" label="&permAskAlways;"/>
+ <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="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>
+ </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 ../../base/content/browserMountPoints.inc
+#endif
+
+</window>
diff --git a/components/pageinfo/permissions.js b/components/pageinfo/permissions.js
new file mode 100644
index 0000000..4f8382f
--- /dev/null
+++ b/components/pageinfo/permissions.js
@@ -0,0 +1,341 @@
+/* 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 DENY = nsIPermissionManager.DENY_ACTION; // 2
+const SESSION = nsICookiePermission.ACCESS_SESSION; // 8
+
+const IMAGE_DENY = 2;
+
+const COOKIE_DENY = 2;
+const COOKIE_SESSION = 2;
+
+var gPermURI;
+var gPermPrincipal;
+var gPrefs;
+var gUsageRequest;
+
+var gPermObj = {
+ image: function getImageDefaultPermission()
+ {
+ if (gPrefs.getIntPref("permissions.default.image") == IMAGE_DENY) {
+ return DENY;
+ }
+ return ALLOW;
+ },
+ popup: function getPopupDefaultPermission()
+ {
+ if (gPrefs.getBoolPref("dom.disable_open_during_load")) {
+ return DENY;
+ }
+ return ALLOW;
+ },
+ cookie: function getCookieDefaultPermission()
+ {
+ if (gPrefs.getIntPref("network.cookie.cookieBehavior") == COOKIE_DENY) {
+ return DENY;
+ }
+ if (gPrefs.getIntPref("network.cookie.lifetimePolicy") == COOKIE_SESSION) {
+ return SESSION;
+ }
+ return ALLOW;
+ },
+ "desktop-notification": function getNotificationDefaultPermission()
+ {
+ if (!gPrefs.getBoolPref("dom.webnotifications.enabled")) {
+ return DENY;
+ }
+ return UNKNOWN;
+ },
+ install: function getInstallDefaultPermission()
+ {
+ if (Services.prefs.getBoolPref("xpinstall.whitelist.required")) {
+ return DENY;
+ }
+ return ALLOW;
+ },
+ geo: function getGeoDefaultPermissions()
+ {
+ if (!gPrefs.getBoolPref("geo.enabled")) {
+ return DENY;
+ }
+ return ALLOW;
+ },
+ plugins: function getPluginsDefaultPermissions()
+ {
+ return UNKNOWN;
+ },
+};
+
+var permissionObserver = {
+ observe: function (aSubject, aTopic, aData)
+ {
+ if (aTopic == "perm-changed") {
+ var permission = aSubject.QueryInterface(
+ Components.interfaces.nsIPermission);
+ if (permission.matchesURI(gPermURI, true)) {
+ if (permission.type in gPermObj)
+ initRow(permission.type);
+ else if (permission.type.startsWith("plugin"))
+ setPluginsRadioState();
+ }
+ }
+ }
+};
+
+function onLoadPermission(principal)
+{
+ 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;
+ gPermPrincipal = principal;
+ var hostText = document.getElementById("hostText");
+ hostText.value = gPermURI.prePath;
+
+ 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");
+ // Desktop Notification, Geolocation and PointerLock permission consumers
+ // use testExactPermission, not testPermission.
+ var perm;
+ if (aPartId == "desktop-notification" || 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);
+}
+
+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, 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, aPartId);
+ } else {
+ permissionManager.add(gPermURI, aPartId, permission);
+ }
+}
+
+function setRadioState(aPartId, aValue)
+{
+ var radio = document.getElementById(aPartId + "#" + aValue);
+ radio.radioGroup.selectedItem = radio;
+}
+
+// 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(aPermissionString, aPluginObject) {
+ let permPluginTemplate = document.getElementById("permPluginTemplate")
+ .cloneNode(true);
+ permPluginTemplate.setAttribute("permString", aPermissionString);
+ permPluginTemplate.setAttribute("tooltiptext", aPluginObject.description);
+ let attrs = [];
+ attrs.push([".permPluginTemplateLabel", "value", aPluginObject.name]);
+ attrs.push([".permPluginTemplateRadioGroup", "id", aPermissionString + "RadioGroup"]);
+ attrs.push([".permPluginTemplateRadioDefault", "id", aPermissionString + "#0"]);
+ let permPluginTemplateRadioAsk = ".permPluginTemplateRadioAsk";
+ if (Services.prefs.getBoolPref("plugins.click_to_play") ||
+ aPluginObject.vulnerable) {
+ attrs.push([permPluginTemplateRadioAsk, "id", aPermissionString + "#3"]);
+ } else {
+ permPluginTemplate.querySelector(permPluginTemplateRadioAsk)
+ .setAttribute("disabled", "true");
+ }
+ attrs.push([".permPluginTemplateRadioAllow", "id", aPermissionString + "#1"]);
+ attrs.push([".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");
+ permPluginTemplate.removeAttribute("tooltiptext");
+ 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 tags = pluginHost.getPluginTags();
+
+ let permissionMap = new Map();
+
+ for (let plugin of tags) {
+ if (plugin.disabled) {
+ continue;
+ }
+ for (let mimeType of plugin.getMimeTypes()) {
+ if (mimeType == "application/x-shockwave-flash" && plugin.name != "Shockwave Flash") {
+ continue;
+ }
+ let permString = pluginHost.getPermissionStringForType(mimeType);
+ if (!permissionMap.has(permString)) {
+ let name = makeNicePluginName(plugin.name) + " " + plugin.version;
+ let vulnerable = false;
+ if (permString.startsWith("plugin-vulnerable:")) {
+ name += " \u2014 " + vulnerableLabel;
+ vulnerable = true;
+ }
+ permissionMap.set(permString, {
+ "name": name,
+ "description": plugin.description,
+ "vulnerable": vulnerable
+ });
+ }
+ }
+ }
+
+ // Tycho:
+ // let entries = [
+ // {
+ // "permission": item[0],
+ // "obj": item[1],
+ // }
+ // for (item of permissionMap)
+ // ];
+ let entries = [];
+ for (let item of permissionMap) {
+ entries.push({
+ "permission": item[0],
+ "obj": item[1]
+ });
+ }
+ entries.sort(function(a, b) {
+ return ((a.obj.name < b.obj.name) ? -1 : (a.obj.name == b.obj.name ? 0 : 1));
+ });
+
+ // Tycho:
+ // let permissionEntries = [
+ // fillInPluginPermissionTemplate(p.permission, p.obj) for (p of entries)
+ // ];
+ let permissionEntries = [];
+ entries.forEach(function (p) {
+ permissionEntries.push(fillInPluginPermissionTemplate(p.permission, p.obj));
+ });
+
+ 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/components/pageinfo/security.js b/components/pageinfo/security.js
new file mode 100644
index 0000000..e791ab9
--- /dev/null
+++ b/components/pageinfo/security.js
@@ -0,0 +1,378 @@
+/* -*- 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 isMixed =
+ (ui.state & (Components.interfaces.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT |
+ Components.interfaces.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT));
+ 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,
+ encryptionSuite : undefined,
+ version: undefined,
+ isBroken : isBroken,
+ isMixed : isMixed,
+ isEV : isEV,
+ cert : cert,
+ fullLocation : gWindow.location
+ };
+
+ var version;
+ try {
+ retval.encryptionAlgorithm = status.cipherName;
+ retval.encryptionStrength = status.secretKeyLength;
+ retval.encryptionSuite = status.cipherSuite;
+ version = status.protocolVersion;
+ }
+ catch (e) {
+ }
+
+ switch (version) {
+ case nsISSLStatus.SSL_VERSION_3:
+ retval.version = "SSL 3";
+ break;
+ case nsISSLStatus.TLS_VERSION_1:
+ retval.version = "TLS 1.0";
+ break;
+ case nsISSLStatus.TLS_VERSION_1_1:
+ retval.version = "TLS 1.1";
+ break;
+ case nsISSLStatus.TLS_VERSION_1_2:
+ retval.version = "TLS 1.2"
+ break;
+ case nsISSLStatus.TLS_VERSION_1_3:
+ retval.version = "TLS 1.3"
+ break;
+ }
+
+ return retval;
+ } else {
+ return {
+ hostName : hName,
+ cAName : "",
+ encryptionAlgorithm : "",
+ encryptionStrength : 0,
+ encryptionSuite : "",
+ version: "",
+ isBroken : isBroken,
+ isMixed : isMixed,
+ 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) {
+ if (info.isMixed) {
+ hdr = pkiBundle.getString("pageInfo_MixedContent");
+ } else {
+ hdr = pkiBundle.getFormattedString("pageInfo_BrokenEncryption",
+ [info.encryptionAlgorithm,
+ info.encryptionStrength + "",
+ info.version]);
+ }
+ msg1 = pkiBundle.getString("pageInfo_Privacy_Broken1");
+ msg2 = pkiBundle.getString("pageInfo_Privacy_None2");
+ }
+ else if (info.encryptionStrength > 0) {
+ hdr = pkiBundle.getFormattedString("pageInfo_EncryptionWithBitsAndProtocol",
+ [info.encryptionAlgorithm,
+ info.encryptionStrength + "",
+ info.version]);
+ msg1 = pkiBundle.getString("pageInfo_Privacy_Encrypted1");
+ msg2 = pkiBundle.getString("pageInfo_Privacy_Encrypted2");
+ security._cert = info.cert;
+ }
+ 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/components/permissions/aboutPermissions.css b/components/permissions/aboutPermissions.css
new file mode 100644
index 0000000..d73b6a8
--- /dev/null
+++ b/components/permissions/aboutPermissions.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/. */
+
+.site {
+ -moz-binding: url("chrome://browser/content/permissions/aboutPermissions.xml#site");
+}
+
+.pluginPermission {
+ -moz-binding: url("chrome://browser/content/permissions/aboutPermissions.xml#pluginPermission");
+}
diff --git a/components/permissions/aboutPermissions.js b/components/permissions/aboutPermissions.js
new file mode 100644
index 0000000..421b65a
--- /dev/null
+++ b/components/permissions/aboutPermissions.js
@@ -0,0 +1,1335 @@
+/* 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";
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/DownloadUtils.jsm");
+Cu.import("resource://gre/modules/AddonManager.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/ForgetAboutSite.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+ "resource://gre/modules/PluralForm.jsm");
+
+var gSecMan = Cc["@mozilla.org/scriptsecuritymanager;1"].
+ getService(Ci.nsIScriptSecurityManager);
+
+var gFaviconService = Cc["@mozilla.org/browser/favicon-service;1"].
+ getService(Ci.nsIFaviconService);
+
+var gPlacesDatabase = Cc["@mozilla.org/browser/nav-history-service;1"].
+ getService(Ci.nsPIPlacesDatabase).
+ DBConnection.
+ clone(true);
+
+var gSitesStmt = gPlacesDatabase.createAsyncStatement(
+ "SELECT url " +
+ "FROM moz_places " +
+ "WHERE rev_host > '.' " +
+ "AND visit_count > 0 " +
+ "GROUP BY rev_host " +
+ "ORDER BY MAX(frecency) DESC " +
+ "LIMIT :limit");
+
+var gVisitStmt = gPlacesDatabase.createAsyncStatement(
+ "SELECT SUM(visit_count) AS count " +
+ "FROM moz_places " +
+ "WHERE rev_host = :rev_host");
+
+var gFlash = {
+ name: "Shockwave Flash",
+ betterName: "Adobe Flash",
+ type: "application/x-shockwave-flash",
+};
+
+// XXX:
+// Is there a better way to do this rather than this hacky comparison?
+// Copied this from toolkit/components/passwordmgr/crypto-SDR.js
+const MASTER_PASSWORD_MESSAGE = "User canceled master password entry";
+
+/**
+ * Permission types that should be tested with testExactPermission, as opposed
+ * to testPermission. This is based on what consumers use to test these
+ * permissions.
+ */
+const TEST_EXACT_PERM_TYPES = ["desktop-notification", "geo", "pointerLock"];
+
+/**
+ * Site object represents a single site, uniquely identified by a principal.
+ */
+function Site(principal) {
+ this.principal = principal;
+ this.listitem = null;
+}
+
+Site.prototype = {
+ /**
+ * Gets the favicon to use for the site. The callback only gets called if
+ * a favicon is found for either the http URI or the https URI.
+ *
+ * @param aCallback
+ * A callback function that takes a favicon image URL as a parameter.
+ */
+ getFavicon: function(aCallback) {
+ function invokeCallback(aFaviconURI) {
+ try {
+ // Use getFaviconLinkForIcon to get image data from the database instead
+ // of using the favicon URI to fetch image data over the network.
+ aCallback(gFaviconService.getFaviconLinkForIcon(aFaviconURI).spec);
+ } catch (e) {
+ Cu.reportError("AboutPermissions: " + e);
+ }
+ }
+
+ // Get the favicon for the origin
+ gFaviconService.getFaviconURLForPage(this.principal.URI, function (aURI) {
+ if (aURI) {
+ invokeCallback(aURI);
+ }
+ }.bind(this));
+ },
+
+ /**
+ * Gets the number of history visits for the site.
+ *
+ * @param aCallback
+ * A function that takes the visit count (a number) as a parameter.
+ */
+ getVisitCount: function(aCallback) {
+ // XXX This won't be a very reliable system, as it will count both http: and https: visits
+ // Unfortunately, I don't think that there is a much better way to do it right now.
+ let rev_host = this.principal.URI.host.split("").reverse().join("") + ".";
+ gVisitStmt.params.rev_host = rev_host;
+ gVisitStmt.executeAsync({
+ handleResult: function(aResults) {
+ let row = aResults.getNextRow();
+ let count = row.getResultByName("count") || 0;
+ try {
+ aCallback(count);
+ } catch (e) {
+ Cu.reportError("AboutPermissions: " + e);
+ }
+ },
+ handleError: function(aError) {
+ Cu.reportError("AboutPermissions: " + aError);
+ },
+ handleCompletion: function(aReason) {
+ }
+ });
+ },
+
+ /**
+ * Gets the permission value stored for a specified permission type.
+ *
+ * @param aType
+ * The permission type string stored in permission manager.
+ * e.g. "cookie", "geo", "popup", "image"
+ * @param aResultObj
+ * An object that stores the permission value set for aType.
+ *
+ * @return A boolean indicating whether or not a permission is set.
+ */
+ getPermission: function(aType, aResultObj) {
+ // Password saving isn't a nsIPermissionManager permission type, so handle
+ // it seperately.
+ if (aType == "password") {
+ aResultObj.value = this.loginSavingEnabled
+ ? Ci.nsIPermissionManager.ALLOW_ACTION
+ : Ci.nsIPermissionManager.DENY_ACTION;
+ return true;
+ }
+
+ let permissionValue;
+ if (TEST_EXACT_PERM_TYPES.indexOf(aType) == -1) {
+ permissionValue = Services.perms.testPermissionFromPrincipal(this.principal, aType);
+ } else {
+ permissionValue = Services.perms.testExactPermissionFromPrincipal(this.principal, aType);
+ }
+ aResultObj.value = permissionValue;
+
+ if (aType.startsWith("plugin")) {
+ if (permissionValue == Ci.nsIPermissionManager.PROMPT_ACTION) {
+ aResultObj.value = Ci.nsIPermissionManager.UNKNOWN_ACTION;
+ return true;
+ }
+ }
+
+ return permissionValue != Ci.nsIPermissionManager.UNKNOWN_ACTION;
+ },
+
+ /**
+ * Sets a permission for the site given a permission type and value.
+ *
+ * @param aType
+ * The permission type string stored in permission manager.
+ * e.g. "cookie", "geo", "popup", "image"
+ * @param aPerm
+ * The permission value to set for the permission type. This should
+ * be one of the constants defined in nsIPermissionManager.
+ */
+ setPermission: function(aType, aPerm) {
+ // Password saving isn't a nsIPermissionManager permission type, so handle
+ // it seperately.
+ if (aType == "password") {
+ this.loginSavingEnabled = aPerm == Ci.nsIPermissionManager.ALLOW_ACTION;
+ return;
+ }
+
+ if (aType.startsWith("plugin")) {
+ if (aPerm == Ci.nsIPermissionManager.UNKNOWN_ACTION) {
+ aPerm = Ci.nsIPermissionManager.PROMPT_ACTION;
+ }
+ }
+
+ Services.perms.addFromPrincipal(this.principal, aType, aPerm);
+ },
+
+ /**
+ * Clears a user-set permission value for the site given a permission type.
+ *
+ * @param aType
+ * The permission type string stored in permission manager.
+ * e.g. "cookie", "geo", "popup", "image"
+ */
+ clearPermission: function(aType) {
+ Services.perms.removeFromPrincipal(this.principal, aType);
+ },
+
+ /**
+ * Gets logins stored for the site.
+ *
+ * @return An array of the logins stored for the site.
+ */
+ get logins() {
+ try {
+ let logins = Services.logins.findLogins({},
+ this.principal.originNoSuffix, "", "");
+ return logins;
+ } catch (e) {
+ if (!e.message.includes(MASTER_PASSWORD_MESSAGE)) {
+ Cu.reportError("AboutPermissions: " + e);
+ }
+ return [];
+ }
+ },
+
+ get loginSavingEnabled() {
+ // Only say that login saving is blocked if it is blocked for both
+ // http and https.
+ try {
+ return Services.logins.getLoginSavingEnabled(this.principal.originNoSuffix);
+ } catch (e) {
+ if (!e.message.includes(MASTER_PASSWORD_MESSAGE)) {
+ Cu.reportError("AboutPermissions: " + e);
+ }
+ return false;
+ }
+ },
+
+ set loginSavingEnabled(isEnabled) {
+ try {
+ Services.logins.setLoginSavingEnabled(this.principal.originNoSuffix, isEnabled);
+ } catch (e) {
+ if (!e.message.includes(MASTER_PASSWORD_MESSAGE)) {
+ Cu.reportError("AboutPermissions: " + e);
+ }
+ }
+ },
+
+ /**
+ * Gets cookies stored for the site and base domain.
+ *
+ * @return An array of the cookies set for the site and base domain.
+ */
+ get cookies() {
+ let cookies = [];
+ let enumerator = Services.cookies.enumerator;
+ while (enumerator.hasMoreElements()) {
+ let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
+ if (cookie.host.hasRootDomain(
+ AboutPermissions.domainFromHost(this.principal.URI.host))) {
+ cookies.push(cookie);
+ }
+ }
+ return cookies;
+ },
+
+ /**
+ * Removes a set of specific cookies from the browser.
+ */
+ clearCookies: function() {
+ this.cookies.forEach(function(aCookie) {
+ Services.cookies.remove(aCookie.host, aCookie.name, aCookie.path, false,
+ aCookie.originAttributes);
+ });
+ },
+
+ /**
+ * Removes all data from the browser corresponding to the site.
+ */
+ forgetSite: function() {
+ // XXX This removes data for an entire domain, rather than just
+ // an origin. This may produce confusing results, as data will
+ // be cleared for the http:// as well as the https:// domain
+ // if you try to forget the https:// site.
+ ForgetAboutSite.removeDataFromDomain(this.principal.URI.host)
+ .catch(Cu.reportError);
+ }
+}
+
+/**
+ * PermissionDefaults object keeps track of default permissions for sites based
+ * on global preferences.
+ *
+ * Inspired by pageinfo/permissions.js
+ */
+var PermissionDefaults = {
+ UNKNOWN: Ci.nsIPermissionManager.UNKNOWN_ACTION, // 0
+ ALLOW: Ci.nsIPermissionManager.ALLOW_ACTION, // 1
+ DENY: Ci.nsIPermissionManager.DENY_ACTION, // 2
+ SESSION: Ci.nsICookiePermission.ACCESS_SESSION, // 8
+
+ get password() {
+ if (Services.prefs.getBoolPref("signon.rememberSignons")) {
+ return this.ALLOW;
+ }
+ return this.DENY;
+ },
+ set password(aValue) {
+ let value = (aValue != this.DENY);
+ Services.prefs.setBoolPref("signon.rememberSignons", value);
+ },
+
+ IMAGE_ALLOW: 1,
+ IMAGE_DENY: 2,
+ IMAGE_ALLOW_FIRST_PARTY_ONLY: 3,
+
+ get image() {
+ if (Services.prefs.getIntPref("permissions.default.image")
+ == this.IMAGE_DENY) {
+ return this.IMAGE_DENY;
+ } else if (Services.prefs.getIntPref("permissions.default.image")
+ == this.IMAGE_ALLOW_FIRST_PARTY_ONLY) {
+ return this.IMAGE_ALLOW_FIRST_PARTY_ONLY;
+ }
+ return this.IMAGE_ALLOW;
+ },
+ set image(aValue) {
+ let value = this.IMAGE_ALLOW;
+ if (aValue == this.IMAGE_DENY) {
+ value = this.IMAGE_DENY;
+ } else if (aValue == this.IMAGE_ALLOW_FIRST_PARTY_ONLY) {
+ value = this.IMAGE_ALLOW_FIRST_PARTY_ONLY;
+ }
+ Services.prefs.setIntPref("permissions.default.image", value);
+ },
+
+ get popup() {
+ if (Services.prefs.getBoolPref("dom.disable_open_during_load")) {
+ return this.DENY;
+ }
+ return this.ALLOW;
+ },
+ set popup(aValue) {
+ let value = (aValue == this.DENY);
+ Services.prefs.setBoolPref("dom.disable_open_during_load", value);
+ },
+
+ // For use with network.cookie.* prefs.
+ COOKIE_ACCEPT: 0,
+ COOKIE_DENY: 2,
+ COOKIE_NORMAL: 0,
+ COOKIE_SESSION: 2,
+
+ get cookie() {
+ if (Services.prefs.getIntPref("network.cookie.cookieBehavior")
+ == this.COOKIE_DENY) {
+ return this.DENY;
+ }
+
+ if (Services.prefs.getIntPref("network.cookie.lifetimePolicy")
+ == this.COOKIE_SESSION) {
+ return this.SESSION;
+ }
+ return this.ALLOW;
+ },
+ set cookie(aValue) {
+ let value = (aValue == this.DENY) ? this.COOKIE_DENY : this.COOKIE_ACCEPT;
+ Services.prefs.setIntPref("network.cookie.cookieBehavior", value);
+
+ let lifetimeValue = aValue == this.SESSION ? this.COOKIE_SESSION :
+ this.COOKIE_NORMAL;
+ Services.prefs.setIntPref("network.cookie.lifetimePolicy", lifetimeValue);
+ },
+
+ get ["desktop-notification"]() {
+ if (!Services.prefs.getBoolPref("dom.webnotifications.enabled")) {
+ return this.DENY;
+ }
+ // We always ask for permission to enable notifications for a specific
+ // site, so there is no global ALLOW.
+ return this.UNKNOWN;
+ },
+ set ["desktop-notification"](aValue) {
+ let value = (aValue != this.DENY);
+ Services.prefs.setBoolPref("dom.webnotifications.enabled", value);
+ },
+
+ get install() {
+ if (Services.prefs.getBoolPref("xpinstall.whitelist.required")) {
+ return this.DENY;
+ }
+ return this.ALLOW;
+ },
+ set install(aValue) {
+ let value = (aValue == this.DENY);
+ Services.prefs.setBoolPref("xpinstall.whitelist.required", value);
+ },
+
+ get geo() {
+ if (!Services.prefs.getBoolPref("geo.enabled")) {
+ return this.DENY;
+ }
+ // We always ask for permission to share location with a specific site,
+ // so there is no global ALLOW.
+ return this.UNKNOWN;
+ },
+ set geo(aValue) {
+ let value = (aValue != this.DENY);
+ Services.prefs.setBoolPref("geo.enabled", value);
+ },
+}
+
+/**
+ * AboutPermissions manages the about:permissions page.
+ */
+var AboutPermissions = {
+ /**
+ * Maximum number of sites to return from the places database.
+ */
+ PLACES_SITES_LIMIT_MAX: 100,
+
+ /**
+ * When adding sites to the dom sites-list, divide workload into intervals.
+ */
+ LIST_BUILD_DELAY: 100, // delay between intervals
+
+ /**
+ * Stores a mapping of origin strings to Site objects.
+ */
+ _sites: {},
+
+ /**
+ * Using a getter for sitesFilter to avoid races with tests.
+ */
+ get sitesFilter () {
+ delete this.sitesFilter;
+ return this.sitesFilter = document.getElementById("sites-filter");
+ },
+
+ sitesList: null,
+ _selectedSite: null,
+
+ /**
+ * For testing, track initializations so we can send notifications.
+ */
+ _initPlacesDone: false,
+ _initServicesDone: false,
+
+ /**
+ * This reflects the permissions that we expose in the UI. These correspond
+ * to permission type strings in the permission manager, PermissionDefaults,
+ * and element ids in aboutPermissions.xul.
+ *
+ * Potential future additions: "sts/use", "sts/subd"
+ */
+ _supportedPermissions: ["password", "image", "popup", "cookie",
+ "desktop-notification", "install", "geo"],
+
+ /**
+ * Permissions that don't have a global "Allow" option.
+ */
+ _noGlobalAllow: ["desktop-notification", "geo"],
+
+ /**
+ * Permissions that don't have a global "Deny" option.
+ */
+ _noGlobalDeny: [],
+
+ _stringBundleBrowser: Services.strings
+ .createBundle("chrome://browser/locale/browser.properties"),
+
+ _stringBundleAboutPermissions: Services.strings.createBundle(
+ "chrome://browser/locale/permissions/aboutPermissions.properties"),
+
+ _initPart1: function() {
+ this.initPluginList();
+ this.cleanupPluginList();
+
+ this.getSitesFromPlaces();
+
+ this.enumerateServicesGenerator = this.getEnumerateServicesGenerator();
+ setTimeout(this.enumerateServicesDriver.bind(this), this.LIST_BUILD_DELAY);
+ },
+
+ _initPart2: function() {
+ this._supportedPermissions.forEach(function(aType) {
+ this.updatePermission(aType);
+ }, this);
+ },
+
+ /**
+ * Called on page load.
+ */
+ init: function() {
+ this.sitesList = document.getElementById("sites-list");
+
+ this._initPart1();
+
+ // Attach observers in case data changes while the page is open.
+ Services.prefs.addObserver("signon.rememberSignons", this, false);
+ Services.prefs.addObserver("permissions.default.image", this, false);
+ Services.prefs.addObserver("dom.disable_open_during_load", this, false);
+ Services.prefs.addObserver("network.cookie.", this, false);
+ Services.prefs.addObserver("dom.webnotifications.enabled", this, false);
+ Services.prefs.addObserver("xpinstall.whitelist.required", this, false);
+ Services.prefs.addObserver("geo.enabled", this, false);
+ Services.prefs.addObserver("plugins.click_to_play", this, false);
+ Services.prefs.addObserver("permissions.places-sites-limit", this, false);
+
+ Services.obs.addObserver(this, "perm-changed", false);
+ Services.obs.addObserver(this, "passwordmgr-storage-changed", false);
+ Services.obs.addObserver(this, "cookie-changed", false);
+ Services.obs.addObserver(this, "browser:purge-domain-data", false);
+ Services.obs.addObserver(this, "plugin-info-updated", false);
+ Services.obs.addObserver(this, "plugin-list-updated", false);
+ Services.obs.addObserver(this, "blocklist-updated", false);
+
+ this._observersInitialized = true;
+ Services.obs.notifyObservers(null, "browser-permissions-preinit", null);
+
+ this._initPart2();
+
+ // Process about:permissions?filter=<string>
+ // About URIs don't support query params, so do this manually
+ var loc = document.location.href;
+ var matches = /[?&]filter\=([^&]+)/i.exec(loc);
+ if (matches) {
+ this.sitesFilter.value = decodeURIComponent(matches[1]);
+ }
+ },
+
+ sitesReload: function() {
+ Object.getOwnPropertyNames(this._sites).forEach(function(prop) {
+ AboutPermissions.deleteFromSitesList(prop);
+ });
+ this._initPart1();
+ this._initPart2();
+ },
+
+ // XXX copied this from browser-plugins.js - is there a way to share?
+ // Map the plugin's name to a filtered version more suitable for user UI.
+ makeNicePluginName: function(aName) {
+ if (aName == gFlash.name) {
+ return gFlash.betterName;
+ }
+
+ // 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;
+ },
+
+ initPluginList: function() {
+ let pluginHost = Cc["@mozilla.org/plugin/host;1"]
+ .getService(Ci.nsIPluginHost);
+ let tags = pluginHost.getPluginTags();
+
+ let permissionMap = new Map();
+
+ let permissionEntries = [];
+ let XUL_NS =
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+ for (let plugin of tags) {
+ for (let mimeType of plugin.getMimeTypes()) {
+ if ((mimeType == gFlash.type) && (plugin.name != gFlash.name)) {
+ continue;
+ }
+ let permString = pluginHost.getPermissionStringForType(mimeType);
+ if (!permissionMap.has(permString)) {
+ let permissionEntry = document.createElementNS(XUL_NS, "box");
+ permissionEntry.setAttribute("label",
+ this.makeNicePluginName(plugin.name)
+ + " " + plugin.version);
+ permissionEntry.setAttribute("tooltiptext", plugin.description);
+ permissionEntry.setAttribute("vulnerable", "");
+ permissionEntry.setAttribute("mimeType", mimeType);
+ permissionEntry.setAttribute("permString", permString);
+ permissionEntry.setAttribute("class", "pluginPermission");
+ permissionEntry.setAttribute("id", permString + "-entry");
+ // If the plugin is disabled, it makes no sense to change its
+ // click-to-play status, so don't add it.
+ if (plugin.disabled) {
+ permissionEntry.hidden = true;
+ } else {
+ permissionEntry.hidden = false;
+ }
+ permissionEntries.push(permissionEntry);
+ this._supportedPermissions.push(permString);
+ this._noGlobalDeny.push(permString);
+ Object.defineProperty(PermissionDefaults, permString, {
+ get: function() {
+ if ((Services.prefs.getBoolPref("plugins.click_to_play") &&
+ plugin.clicktoplay) ||
+ permString.startsWith("plugin-vulnerable:")) {
+ return PermissionDefaults.UNKNOWN;
+ }
+ return PermissionDefaults.ALLOW;
+ },
+ set: function(aValue) {
+ this.clicktoplay = (aValue == PermissionDefaults.UNKNOWN);
+ }.bind(plugin),
+ configurable: true
+ });
+ permissionMap.set(permString, "");
+ }
+ }
+ }
+
+ if (permissionEntries.length > 0) {
+ permissionEntries.sort(function(entryA, entryB) {
+ let labelA = entryA.getAttribute("label");
+ let labelB = entryB.getAttribute("label");
+ return ((labelA < labelB) ? -1 : (labelA == labelB ? 0 : 1));
+ });
+ }
+
+ let pluginsBox = document.getElementById("plugins-box");
+ while (pluginsBox.hasChildNodes()) {
+ pluginsBox.removeChild(pluginsBox.firstChild);
+ }
+ for (let permissionEntry of permissionEntries) {
+ pluginsBox.appendChild(permissionEntry);
+ }
+ },
+
+ cleanupPluginList: function() {
+ let pluginsPrefItem = document.getElementById("plugins-pref-item");
+ let pluginsBox = document.getElementById("plugins-box");
+ let pluginsBoxEmpty = true;
+ let pluginsBoxSibling = pluginsBox.firstChild;
+ while (pluginsBoxSibling) {
+ if (!pluginsBoxSibling.hidden) {
+ pluginsBoxEmpty = false;
+ break;
+ }
+ pluginsBoxSibling = pluginsBoxSibling.nextSibling;
+ }
+ if (pluginsBoxEmpty) {
+ pluginsPrefItem.collapsed = true;
+ } else {
+ pluginsPrefItem.collapsed = false;
+ }
+ },
+
+ /**
+ * Called on page unload.
+ */
+ cleanUp: function() {
+ if (this._observersInitialized) {
+ Services.prefs.removeObserver("signon.rememberSignons", this, false);
+ Services.prefs.removeObserver("permissions.default.image", this, false);
+ Services.prefs.removeObserver("dom.disable_open_during_load", this, false);
+ Services.prefs.removeObserver("network.cookie.", this, false);
+ Services.prefs.removeObserver("dom.webnotifications.enabled", this, false);
+ Services.prefs.removeObserver("xpinstall.whitelist.required", this, false);
+ Services.prefs.removeObserver("geo.enabled", this, false);
+ Services.prefs.removeObserver("plugins.click_to_play", this, false);
+ Services.prefs.removeObserver("permissions.places-sites-limit", this, false);
+
+ Services.obs.removeObserver(this, "perm-changed");
+ Services.obs.removeObserver(this, "passwordmgr-storage-changed");
+ Services.obs.removeObserver(this, "cookie-changed");
+ Services.obs.removeObserver(this, "browser:purge-domain-data");
+ Services.obs.removeObserver(this, "plugin-info-updated");
+ Services.obs.removeObserver(this, "plugin-list-updated");
+ Services.obs.removeObserver(this, "blocklist-updated");
+ }
+
+ gSitesStmt.finalize();
+ gVisitStmt.finalize();
+ gPlacesDatabase.asyncClose(null);
+ },
+
+ observe: function(aSubject, aTopic, aData) {
+ switch(aTopic) {
+ case "perm-changed":
+ // Permissions changes only affect individual sites.
+ if (!this._selectedSite) {
+ break;
+ }
+ // aSubject is null when nsIPermisionManager::removeAll() is called.
+ if (!aSubject) {
+ this._supportedPermissions.forEach(function(aType) {
+ this.updatePermission(aType);
+ }, this);
+ break;
+ }
+ let permission = aSubject.QueryInterface(Ci.nsIPermission);
+ // We can't compare selectedSite.principal and permission.principal here
+ // because we need to handle the case where a parent domain was changed
+ // in a way that affects the subdomain.
+ if (this._supportedPermissions.indexOf(permission.type) != -1) {
+ this.updatePermission(permission.type);
+ }
+ break;
+ case "nsPref:changed":
+ if (aData == "permissions.places-sites-limit") {
+ this.sitesReload();
+ return;
+ }
+ let plugin = false;
+ if (aData.startsWith("plugin")) {
+ plugin = true;
+ }
+ if (plugin) {
+ this.initPluginList();
+ }
+ this._supportedPermissions.forEach(function(aType) {
+ if (!plugin || (plugin && aType.startsWith("plugin"))) {
+ this.updatePermission(aType);
+ }
+ }, this);
+ if (plugin) {
+ this.cleanupPluginList();
+ }
+ break;
+ case "passwordmgr-storage-changed":
+ this.updatePermission("password");
+ if (this._selectedSite) {
+ this.updatePasswordsCount();
+ }
+ break;
+ case "cookie-changed":
+ if (this._selectedSite) {
+ this.updateCookiesCount();
+ }
+ break;
+ case "browser:purge-domain-data":
+ this.deleteFromSitesList(aData);
+ break;
+ case "plugin-info-updated":
+ case "plugin-list-updated":
+ case "blocklist-updated":
+ this.initPluginList();
+ this._supportedPermissions.forEach(function(aType) {
+ if (aType.startsWith("plugin")) {
+ this.updatePermission(aType);
+ }
+ }, this);
+ this.cleanupPluginList();
+ break;
+ }
+ },
+
+ /**
+ * Creates Site objects for the top-frecency sites in the places database
+ * and stores them in _sites.
+ * The number of sites created is controlled by _placesSitesLimit.
+ */
+ getSitesFromPlaces: function() {
+ let _placesSitesLimit = Services.prefs.getIntPref(
+ "permissions.places-sites-limit");
+ if (_placesSitesLimit <= 0) {
+ return;
+ }
+ if (_placesSitesLimit > this.PLACES_SITES_LIMIT_MAX) {
+ _placesSitesLimit = this.PLACES_SITES_LIMIT_MAX;
+ }
+
+ gSitesStmt.params.limit = _placesSitesLimit;
+ gSitesStmt.executeAsync({
+ handleResult: function(aResults) {
+ AboutPermissions.startSitesListBatch();
+ let row;
+ while (row = aResults.getNextRow()) {
+ let spec = row.getResultByName("url");
+ let uri = NetUtil.newURI(spec);
+ let principal = gSecMan.getNoAppCodebasePrincipal(uri);
+
+ AboutPermissions.addPrincipal(principal);
+ }
+ AboutPermissions.endSitesListBatch();
+ },
+ handleError: function(aError) {
+ Cu.reportError("AboutPermissions: " + aError);
+ },
+ handleCompletion: function(aReason) {
+ // Notify oberservers for testing purposes.
+ AboutPermissions._initPlacesDone = true;
+ if (AboutPermissions._initServicesDone) {
+ Services.obs.notifyObservers(
+ null, "browser-permissions-initialized", null);
+ }
+ }
+ });
+ },
+
+ /**
+ * Drives getEnumerateServicesGenerator to work in intervals.
+ */
+ enumerateServicesDriver: function() {
+ if (this.enumerateServicesGenerator.next()) {
+ // Build top sitesList items faster so that the list never seems sparse
+ let delay = Math.min(this.sitesList.itemCount * 5, this.LIST_BUILD_DELAY);
+ setTimeout(this.enumerateServicesDriver.bind(this), delay);
+ } else {
+ this.enumerateServicesGenerator.close();
+ this._initServicesDone = true;
+ if (this._initPlacesDone) {
+ Services.obs.notifyObservers(
+ null, "browser-permissions-initialized", null);
+ }
+ }
+ },
+
+ /**
+ * Finds sites that have non-default permissions and creates Site objects
+ * for them if they are not already stored in _sites.
+ */
+ getEnumerateServicesGenerator: function() {
+ let itemCnt = 1;
+ let schemeChrome = "chrome";
+
+ try {
+ let logins = Services.logins.getAllLogins();
+ logins.forEach(function(aLogin) {
+ try {
+ // aLogin.hostname is a string in origin URL format
+ // (e.g. "http://foo.com").
+ // newURI will throw for add-ons logins stored in chrome:// URIs
+ // i.e.: "chrome://weave" (Sync)
+ if (!aLogin.hostname.startsWith(schemeChrome + ":")) {
+ let uri = NetUtil.newURI(aLogin.hostname);
+ let principal = gSecMan.getNoAppCodebasePrincipal(uri);
+ this.addPrincipal(principal);
+ }
+ } catch (e) {
+ Cu.reportError("AboutPermissions: " + e);
+ }
+ itemCnt++;
+ }, this);
+
+ let disabledHosts = Services.logins.getAllDisabledHosts();
+ disabledHosts.forEach(function(aHostname) {
+ try {
+ // aHostname is a string in origin URL format (e.g. "http://foo.com").
+ // newURI will throw for add-ons logins stored in chrome:// URIs
+ // i.e.: "chrome://weave" (Sync)
+ if (!aHostname.startsWith(schemeChrome + ":")) {
+ let uri = NetUtil.newURI(aHostname);
+ let principal = gSecMan.getNoAppCodebasePrincipal(uri);
+ this.addPrincipal(principal);
+ }
+ } catch (e) {
+ Cu.reportError("AboutPermissions: " + e);
+ }
+ itemCnt++;
+ }, this);
+ } catch (e) {
+ if (!e.message.includes(MASTER_PASSWORD_MESSAGE)) {
+ Cu.reportError("AboutPermissions: " + e);
+ }
+ }
+
+ let enumerator = Services.perms.enumerator;
+ while (enumerator.hasMoreElements()) {
+ let permission = enumerator.getNext().QueryInterface(Ci.nsIPermission);
+ // Only include sites with exceptions set for supported permission types.
+ if (this._supportedPermissions.indexOf(permission.type) != -1) {
+ this.addPrincipal(permission.principal);
+ }
+ itemCnt++;
+ }
+
+ yield false;
+ },
+
+ /**
+ * Creates a new Site and adds it to _sites if it's not already there.
+ *
+ * @param aPrincipal
+ * A principal.
+ */
+ addPrincipal: function(aPrincipal) {
+ if (aPrincipal.origin in this._sites) {
+ return;
+ }
+ let site = new Site(aPrincipal);
+ this._sites[aPrincipal.origin] = site;
+ this.addToSitesList(site);
+ },
+
+ /**
+ * Populates sites-list richlistbox with data from Site object.
+ *
+ * @param aSite
+ * A Site object.
+ */
+ addToSitesList: function(aSite) {
+ let item = document.createElement("richlistitem");
+ item.setAttribute("class", "site");
+ item.setAttribute("value", aSite.principal.origin);
+
+ aSite.getFavicon(function(aURL) {
+ item.setAttribute("favicon", aURL);
+ });
+ aSite.listitem = item;
+
+ // Make sure to only display relevant items when list is filtered.
+ let filterValue = this.sitesFilter.value.toLowerCase();
+ item.collapsed = aSite.principal.origin.toLowerCase().indexOf(filterValue) == -1;
+
+ (this._listFragment || this.sitesList).appendChild(item);
+ },
+
+ startSitesListBatch: function() {
+ if (!this._listFragment)
+ this._listFragment = document.createDocumentFragment();
+ },
+
+ endSitesListBatch: function() {
+ if (this._listFragment) {
+ this.sitesList.appendChild(this._listFragment);
+ this._listFragment = null;
+ }
+ },
+
+ /**
+ * Hides sites in richlistbox based on search text in sites-filter textbox.
+ */
+ filterSitesList: function() {
+ let siteItems = this.sitesList.children;
+ let filterValue = this.sitesFilter.value.toLowerCase();
+
+ if (filterValue == "") {
+ for (let i = 0, iLen = siteItems.length; i < iLen; i++) {
+ siteItems[i].collapsed = false;
+ }
+ return;
+ }
+
+ for (let i = 0, iLen = siteItems.length; i < iLen; i++) {
+ let siteValue = siteItems[i].value.toLowerCase();
+ siteItems[i].collapsed = siteValue.indexOf(filterValue) == -1;
+ }
+ },
+
+ /**
+ * Removes all evidence of the selected site. The "forget this site" observer
+ * will call deleteFromSitesList to update the UI.
+ */
+ forgetSite: function() {
+ this._selectedSite.forgetSite();
+ },
+
+ /**
+ * Deletes sites for a host and all of its sub-domains. Removes these sites
+ * from _sites and removes their corresponding elements from the DOM.
+ *
+ * @param aHost
+ * The host string corresponding to the site to delete.
+ */
+ deleteFromSitesList: function(aHost) {
+ for (let origin in this._sites) {
+ let site = this._sites[origin];
+ if (site.principal.URI.host.hasRootDomain(aHost)) {
+ if (site == this._selectedSite) {
+ // Replace site-specific interface with "All Sites" interface.
+ this.sitesList.selectedItem =
+ document.getElementById("all-sites-item");
+ }
+
+ this.sitesList.removeChild(site.listitem);
+ delete this._sites[site.principal.origin];
+ }
+ }
+ },
+
+ /**
+ * Shows interface for managing site-specific permissions.
+ */
+ onSitesListSelect: function(event) {
+ if (event.target.selectedItem.id == "all-sites-item") {
+ // Clear the header label value from the previously selected site.
+ document.getElementById("site-label").value = "";
+ this.manageDefaultPermissions();
+ return;
+ }
+
+ let origin = event.target.value;
+ let site = this._selectedSite = this._sites[origin];
+ document.getElementById("site-label").value = origin;
+ document.getElementById("header-deck").selectedPanel =
+ document.getElementById("site-header");
+
+ this.updateVisitCount();
+ this.updatePermissionsBox();
+ },
+
+ /**
+ * Shows interface for managing default permissions. This corresponds to
+ * the "All Sites" list item.
+ */
+ manageDefaultPermissions: function() {
+ this._selectedSite = null;
+
+ document.getElementById("header-deck").selectedPanel =
+ document.getElementById("defaults-header");
+
+ this.updatePermissionsBox();
+ },
+
+ /**
+ * Updates permissions interface based on selected site.
+ */
+ updatePermissionsBox: function() {
+ this._supportedPermissions.forEach(function(aType) {
+ this.updatePermission(aType);
+ }, this);
+
+ this.updatePasswordsCount();
+ this.updateCookiesCount();
+ },
+
+ /**
+ * Sets menulist for a given permission to the correct state, based on
+ * the stored permission.
+ *
+ * @param aType
+ * The permission type string stored in permission manager.
+ * e.g. "cookie", "geo", "popup", "image"
+ */
+ updatePermission: function(aType) {
+ let allowItem = document.getElementById(
+ aType + "-" + PermissionDefaults.ALLOW);
+ allowItem.hidden = !this._selectedSite &&
+ this._noGlobalAllow.indexOf(aType) != -1;
+ let denyItem = document.getElementById(
+ aType + "-" + PermissionDefaults.DENY);
+ denyItem.hidden = !this._selectedSite &&
+ this._noGlobalDeny.indexOf(aType) != -1;
+
+ let permissionMenulist = document.getElementById(aType + "-menulist");
+ let permissionSetDefault = document.getElementById(aType + "-set-default");
+ let permissionValue;
+ let permissionDefault;
+ let pluginPermissionEntry;
+ let elementsPrefSetDefault = document.querySelectorAll(".pref-set-default");
+ if (!this._selectedSite) {
+ let _visibility = "collapse";
+ for (let i = 0, iLen = elementsPrefSetDefault.length; i < iLen; i++) {
+ elementsPrefSetDefault[i].style.visibility = _visibility;
+ }
+ permissionSetDefault.style.visibility = _visibility;
+ // If there is no selected site, we are updating the default permissions
+ // interface.
+ permissionValue = PermissionDefaults[aType];
+ permissionDefault = permissionValue;
+ if (aType == "image") {
+ // (aType + "-3") corresponds to ALLOW_FIRST_PARTY_ONLY,
+ // which is reserved for global preferences only.
+ document.getElementById(aType + "-3").hidden = false;
+ } else if (aType == "cookie") {
+ // (aType + "-9") corresponds to ALLOW_FIRST_PARTY_ONLY,
+ // which is reserved for site-specific preferences only.
+ document.getElementById(aType + "-9").hidden = true;
+ } else if (aType.startsWith("plugin")) {
+ pluginPermissionEntry = document.getElementById(aType + "-entry");
+ pluginPermissionEntry.setAttribute("vulnerable", "");
+ let vulnerable = false;
+ if (pluginPermissionEntry.isBlocklisted()) {
+ permissionMenulist.disabled = true;
+ permissionMenulist.setAttribute("tooltiptext",
+ AboutPermissions._stringBundleAboutPermissions
+ .GetStringFromName("pluginBlocklisted"));
+ vulnerable = true;
+ } else {
+ permissionMenulist.disabled = false;
+ permissionMenulist.setAttribute("tooltiptext", "");
+ }
+ if (Services.prefs.getBoolPref("plugins.click_to_play") || vulnerable) {
+ document.getElementById(aType + "-0").disabled = false;
+ } else {
+ document.getElementById(aType + "-0").disabled = true;
+ }
+ }
+ } else {
+ let _visibility = "visible";
+ for (let i = 0, iLen = elementsPrefSetDefault.length; i < iLen; i++) {
+ elementsPrefSetDefault[i].style.visibility = _visibility;
+ }
+ permissionSetDefault.style.visibility = _visibility;
+ permissionDefault = PermissionDefaults[aType];
+ if (aType == "image") {
+ document.getElementById(aType + "-3").hidden = true;
+ } else if (aType == "cookie") {
+ document.getElementById(aType + "-9").hidden = false;
+ } else if (aType.startsWith("plugin")) {
+ pluginPermissionEntry = document.getElementById(aType + "-entry");
+ let permString = pluginPermissionEntry.getAttribute("permString");
+ let vulnerable = false;
+ if (permString.startsWith("plugin-vulnerable:")) {
+ let nameVulnerable = " \u2014 "
+ + AboutPermissions._stringBundleBrowser
+ .GetStringFromName("pluginActivateVulnerable.label");
+ pluginPermissionEntry.setAttribute("vulnerable", nameVulnerable);
+ vulnerable = true;
+ }
+ if (Services.prefs.getBoolPref("plugins.click_to_play") || vulnerable) {
+ document.getElementById(aType + "-0").disabled = false;
+ } else {
+ document.getElementById(aType + "-0").disabled = true;
+ }
+ permissionMenulist.disabled = false;
+ permissionMenulist.setAttribute("tooltiptext", "");
+ }
+ let result = {};
+ permissionValue = this._selectedSite.getPermission(aType, result) ?
+ result.value : permissionDefault;
+ }
+
+ if (aType == "image") {
+ if (document.getElementById(aType + "-" + permissionValue).hidden) {
+ // ALLOW
+ permissionValue = 1;
+ }
+ }
+ if (aType.startsWith("plugin")) {
+ if (document.getElementById(aType + "-" + permissionValue).disabled) {
+ // ALLOW
+ permissionValue = 1;
+ }
+ }
+
+ if (!aType.startsWith("plugin")) {
+ let _elementDefault = document.getElementById(aType + "-default");
+ if (!this._selectedSite || (permissionValue == permissionDefault)) {
+ _elementDefault.setAttribute("value", "");
+ } else {
+ _elementDefault.setAttribute("value", "*");
+ }
+ } else {
+ let _elementDefaultVisibility;
+ if (!this._selectedSite || (permissionValue == permissionDefault)) {
+ _elementDefaultVisibility = false;
+ } else {
+ _elementDefaultVisibility = true;
+ }
+ pluginPermissionEntry.setDefaultVisibility(_elementDefaultVisibility);
+ }
+
+ permissionMenulist.selectedItem = document.getElementById(
+ aType + "-" + permissionValue);
+ },
+
+ onPermissionCommand: function(event, _default) {
+ let pluginHost = Cc["@mozilla.org/plugin/host;1"]
+ .getService(Ci.nsIPluginHost);
+ let permissionMimeType = event.currentTarget.getAttribute("mimeType");
+ let permissionType = event.currentTarget.getAttribute("type");
+ let permissionValue = event.target.value;
+
+ if (!this._selectedSite) {
+ if (permissionType.startsWith("plugin")) {
+ let addonValue = AddonManager.STATE_ASK_TO_ACTIVATE;
+ switch(permissionValue) {
+ case "1":
+ addonValue = false;
+ break;
+ case "2":
+ addonValue = true;
+ break;
+ }
+
+ AddonManager.getAddonsByTypes(["plugin"], function(addons) {
+ for (let addon of addons) {
+ for (let type of addon.pluginMimeTypes) {
+ if ((type.type == gFlash.type) && (addon.name != gFlash.name)) {
+ continue;
+ }
+ if (type.type.toLowerCase() == permissionMimeType.toLowerCase()) {
+ addon.userDisabled = addonValue;
+ return;
+ }
+ }
+ }
+ });
+ } else {
+ // If there is no selected site, we are setting the default permission.
+ PermissionDefaults[permissionType] = permissionValue;
+ }
+ } else {
+ if (_default) {
+ this._selectedSite.clearPermission(permissionType);
+ } else {
+ this._selectedSite.setPermission(permissionType, permissionValue);
+ }
+ }
+ },
+
+ updateVisitCount: function() {
+ this._selectedSite.getVisitCount(function(aCount) {
+ let visitForm = AboutPermissions._stringBundleAboutPermissions
+ .GetStringFromName("visitCount");
+ let visitLabel = PluralForm.get(aCount, visitForm)
+ .replace("#1", aCount);
+ document.getElementById("site-visit-count").value = visitLabel;
+ });
+ },
+
+ updatePasswordsCount: function() {
+ if (!this._selectedSite) {
+ document.getElementById("passwords-count").hidden = true;
+ document.getElementById("passwords-manage-all-button").hidden = false;
+ return;
+ }
+
+ let passwordsCount = this._selectedSite.logins.length;
+ let passwordsForm = this._stringBundleAboutPermissions
+ .GetStringFromName("passwordsCount");
+ let passwordsLabel = PluralForm.get(passwordsCount, passwordsForm)
+ .replace("#1", passwordsCount);
+
+ document.getElementById("passwords-label").value = passwordsLabel;
+ document.getElementById("passwords-manage-button").disabled =
+ (passwordsCount < 1);
+ document.getElementById("passwords-manage-all-button").hidden = true;
+ document.getElementById("passwords-count").hidden = false;
+ },
+
+ /**
+ * Opens password manager dialog.
+ */
+ managePasswords: function() {
+ let selectedOrigin = "";
+ if (this._selectedSite) {
+ selectedOrigin = this._selectedSite.principal.URI.prePath;
+ }
+
+ let win = Services.wm.getMostRecentWindow("Toolkit:PasswordManager");
+ if (win) {
+ win.setFilter(selectedOrigin);
+ win.focus();
+ } else {
+ window.openDialog("chrome://passwordmgr/content/passwordManager.xul",
+ "Toolkit:PasswordManager", "",
+ {filterString : selectedOrigin});
+ }
+ },
+
+ domainFromHost: function(aHost) {
+ let domain = aHost;
+ try {
+ domain = Services.eTLD.getBaseDomainFromHost(aHost);
+ } catch (e) {
+ // getBaseDomainFromHost will fail if the host is an IP address
+ // or is empty.
+ }
+
+ return domain;
+ },
+
+ updateCookiesCount: function() {
+ if (!this._selectedSite) {
+ document.getElementById("cookies-count").hidden = true;
+ document.getElementById("cookies-clear-all-button").hidden = false;
+ document.getElementById("cookies-manage-all-button").hidden = false;
+ return;
+ }
+
+ let cookiesCount = this._selectedSite.cookies.length;
+ let cookiesForm = this._stringBundleAboutPermissions
+ .GetStringFromName("cookiesCount");
+ let cookiesLabel = PluralForm.get(cookiesCount, cookiesForm)
+ .replace("#1", cookiesCount);
+
+ document.getElementById("cookies-label").value = cookiesLabel;
+ document.getElementById("cookies-clear-button").disabled =
+ (cookiesCount < 1);
+ document.getElementById("cookies-manage-button").disabled =
+ (cookiesCount < 1);
+ document.getElementById("cookies-clear-all-button").hidden = true;
+ document.getElementById("cookies-manage-all-button").hidden = true;
+ document.getElementById("cookies-count").hidden = false;
+ },
+
+ /**
+ * Clears cookies for the selected site and base domain.
+ */
+ clearCookies: function() {
+ if (!this._selectedSite) {
+ return;
+ }
+ let site = this._selectedSite;
+ site.clearCookies(site.cookies);
+ this.updateCookiesCount();
+ },
+
+ /**
+ * Opens cookie manager dialog.
+ */
+ manageCookies: function() {
+ // Cookies are stored by-host, and thus we filter the cookie window
+ // using only the host of the selected principal's origin
+ let selectedHost = "";
+ let selectedDomain = "";
+ if (this._selectedSite) {
+ selectedHost = this._selectedSite.principal.URI.host;
+ selectedDomain = this.domainFromHost(selectedHost);
+ }
+
+ let win = Services.wm.getMostRecentWindow("Browser:Cookies");
+ if (win) {
+ win.gCookiesWindow.setFilter(selectedDomain);
+ win.focus();
+ } else {
+ window.openDialog("chrome://browser/content/preferences/cookies.xul",
+ "Browser:Cookies", "", {filterString : selectedDomain});
+ }
+ },
+
+ /**
+ * Focusses the filter box.
+ */
+ focusFilterBox: function() {
+ this.sitesFilter.focus();
+ }
+}
+
+// See toolkit/forgetaboutsite/ForgetAboutSite.jsm
+String.prototype.hasRootDomain = function(aDomain) {
+ let index = this.indexOf(aDomain);
+ if (index == -1) {
+ return false;
+ }
+
+ if (this == aDomain) {
+ return true;
+ }
+
+ let prevChar = this[index - 1];
+ return (index == (this.length - aDomain.length)) &&
+ (prevChar == "." || prevChar == "/");
+}
diff --git a/components/permissions/aboutPermissions.xml b/components/permissions/aboutPermissions.xml
new file mode 100644
index 0000000..2932ea0
--- /dev/null
+++ b/components/permissions/aboutPermissions.xml
@@ -0,0 +1,113 @@
+<?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 % aboutPermissionsDTD SYSTEM "chrome://browser/locale/permissions/aboutPermissions.dtd" >
+%aboutPermissionsDTD;
+]>
+
+<bindings 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="site" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+ <content>
+ <xul:hbox class="site-container" align="center" flex="1">
+ <xul:image xbl:inherits="src=favicon" class="site-favicon"/>
+ <xul:label xbl:inherits="value,selected" class="site-domain" crop="end" flex="1"/>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="pluginPermission">
+ <content>
+ <xul:hbox flex="1" align="baseline">
+ <xul:label xbl:inherits="value=label" class="plugins-label"/>
+ <xul:label xbl:inherits="value=vulnerable" class="plugins-vulnerable"/>
+ <xul:label xbl:inherits="value=default" anonid="plugins-default" class="plugins-default"/>
+ <xul:spacer flex="1"/>
+ <xul:menulist anonid="plugins-menulist"
+ class="pref-menulist"
+ oncommand="AboutPermissions.onPermissionCommand(event, false);">
+ <xul:menupopup>
+ <xul:menuitem anonid="ask" value="0" label="&permission.alwaysAsk;"/>
+ <xul:menuitem anonid="allow" value="1" label="&permission.allow;"/>
+ <xul:menuitem anonid="block" value="2" label="&permission.block;"/>
+ </xul:menupopup>
+ </xul:menulist>
+ <xul:button xbl:inherits="value=set-default"
+ anonid="plugins-set-default"
+ class="pref-set-default"
+ label="&permission.default;"
+ oncommand="AboutPermissions.onPermissionCommand(event, true);"/>
+ </xul:hbox>
+ </content>
+ <implementation>
+ <constructor><![CDATA[
+ let mimeType = this.getAttribute("mimeType");
+ let permString = this.getAttribute("permString");
+ let menulist = document.getAnonymousElementByAttribute(this, "anonid", "plugins-menulist");
+ menulist.setAttribute("id", permString + "-menulist");
+ menulist.setAttribute("mimeType", mimeType);
+ menulist.setAttribute("type", permString);
+ let askitem = document.getAnonymousElementByAttribute(this, "anonid", "ask");
+ askitem.setAttribute("id", permString + "-0");
+ let allowitem = document.getAnonymousElementByAttribute(this, "anonid", "allow");
+ allowitem.setAttribute("id", permString + "-1");
+ let blockitem = document.getAnonymousElementByAttribute(this, "anonid", "block");
+ blockitem.setAttribute("id", permString + "-2");
+ let _default = document.getAnonymousElementByAttribute(this, "anonid", "plugins-default");
+ this.setDefaultVisibility(false);
+ _default.setAttribute("value", "*");
+ let _setDefault = document.getAnonymousElementByAttribute(this, "anonid", "plugins-set-default");
+ _setDefault.setAttribute("id", permString + "-set-default");
+ _setDefault.setAttribute("class", "pref-set-default");
+ _setDefault.setAttribute("type", permString);
+ ]]>
+ </constructor>
+ <method name="setDefaultVisibility">
+ <parameter name="visibility" />
+ <body><![CDATA[
+ let _default = document.getAnonymousElementByAttribute(this, "anonid", "plugins-default");
+ if (visibility) {
+ _default.style.visibility = "visible";
+ } else {
+ _default.style.visibility = "hidden";
+ }
+ ]]>
+ </body>
+ </method>
+ <method name="isClickToPlay">
+ <body><![CDATA[
+ let pluginHost = Components.classes["@mozilla.org/plugin/host;1"]
+ .getService(Components.interfaces.nsIPluginHost);
+ let mimeType = this.getAttribute("mimeType");
+ return (pluginHost.getStateForType(mimeType)
+ == Components.interfaces.nsIPluginTag.STATE_CLICKTOPLAY);
+ ]]>
+ </body>
+ </method>
+ <method name="isBlocklisted">
+ <body><![CDATA[
+ let pluginHost = Components.classes["@mozilla.org/plugin/host;1"]
+ .getService(Components.interfaces.nsIPluginHost);
+ let blocklistService = Components.classes["@mozilla.org/extensions/blocklist;1"]
+ .getService(Components.interfaces.nsIBlocklistService);
+ let mimeType = this.getAttribute("mimeType");
+ let tags = pluginHost.getPluginTags();
+ let blocklistState = Components.interfaces.nsIBlocklistService.STATE_NOT_BLOCKED;
+ for (let plugin of tags) {
+ if (plugin.getMimeTypes()[0] == mimeType) {
+ blocklistState = blocklistService.getPluginBlocklistState(plugin);
+ break;
+ }
+ }
+ return (blocklistState == Components.interfaces.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE ||
+ blocklistState == Components.interfaces.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE);
+ ]]>
+ </body>
+ </method>
+ </implementation>
+ </binding>
+</bindings>
diff --git a/components/permissions/aboutPermissions.xul b/components/permissions/aboutPermissions.xul
new file mode 100644
index 0000000..dfee147
--- /dev/null
+++ b/components/permissions/aboutPermissions.xul
@@ -0,0 +1,313 @@
+<?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/content/permissions/aboutPermissions.css"?>
+<?xml-stylesheet href="chrome://browser/skin/permissions/aboutPermissions.css"?>
+
+<!DOCTYPE page [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
+%brandDTD;
+<!ENTITY % aboutPermissionsDTD SYSTEM "chrome://browser/locale/permissions/aboutPermissions.dtd" >
+%aboutPermissionsDTD;
+]>
+
+<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:xhtml="http://www.w3.org/1999/xhtml"
+ id="permissions-page" title="&permissionsManager.title;"
+ onload="AboutPermissions.init();"
+ onunload="AboutPermissions.cleanUp();"
+ disablefastfind="true"
+ role="application">
+
+ <script type="application/javascript"
+ src="chrome://browser/content/permissions/aboutPermissions.js"/>
+
+ <keyset>
+ <key key="&focusSearch.key;" modifiers="accel" oncommand="AboutPermissions.focusFilterBox();"/>
+ </keyset>
+
+ <hbox id="permissions-header">
+ <label id="permissions-pagetitle">&permissionsManager.title;</label>
+ </hbox>
+ <hbox flex="1" id="permissions-content" class="main-content">
+
+ <vbox id="sites-box">
+ <button id="sites-reload"
+ label="&permissions.sitesReload;"
+ oncommand="AboutPermissions.sitesReload();"/>
+ <textbox id="sites-filter"
+ emptytext="&sites.search;"
+ oncommand="AboutPermissions.filterSitesList();"
+ type="search"/>
+ <richlistbox id="sites-list"
+ flex="1"
+ class="list"
+ onselect="AboutPermissions.onSitesListSelect(event);">
+ <richlistitem id="all-sites-item"
+ class="site"
+ value="&sites.allSites;"/>
+ </richlistbox>
+ </vbox>
+
+ <vbox id="permissions-box" flex="1">
+
+ <deck id="header-deck">
+ <hbox id="site-header" class="pref-item" align="center">
+ <description id="site-description">
+ &header.site.start;<label id="site-label"/>&header.site.end;
+ </description>
+ <label id="site-visit-count"/>
+ <spacer flex="1"/>
+ <button id="forget-site-button"
+ label="&permissions.forgetSite;"
+ oncommand="AboutPermissions.forgetSite();"/>
+ </hbox>
+
+ <hbox id="defaults-header" class="pref-item" align="center">
+ <description id="defaults-description">
+ &header.defaults;
+ </description>
+ </hbox>
+ </deck>
+
+ <!-- Passwords -->
+ <hbox id="password-pref-item"
+ class="pref-item" align="top">
+ <image class="pref-icon" type="password"/>
+ <vbox>
+ <hbox>
+ <label class="pref-title" value="&password.label;"/>
+ <label id="password-default" class="pref-default" value="*"/>
+ </hbox>
+ <hbox align="center">
+ <menulist id="password-menulist"
+ class="pref-menulist"
+ type="password"
+ oncommand="AboutPermissions.onPermissionCommand(event, false);">
+ <menupopup>
+ <menuitem id="password-1" value="1" label="&permission.allow;"/>
+ <menuitem id="password-2" value="2" label="&permission.block;"/>
+ </menupopup>
+ </menulist>
+ <button id="password-set-default"
+ class="pref-set-default"
+ label="&permission.default;"
+ type="password"
+ oncommand="AboutPermissions.onPermissionCommand(event, true);"/>
+ <button id="passwords-manage-all-button"
+ label="&password.manage;"
+ oncommand="AboutPermissions.managePasswords();"/>
+ </hbox>
+ <hbox id="passwords-count" align="center">
+ <label id="passwords-label"/>
+ <button id="passwords-manage-button"
+ label="&password.manage;"
+ oncommand="AboutPermissions.managePasswords();"/>
+ </hbox>
+ </vbox>
+ </hbox>
+
+ <!-- Image Blocking -->
+ <hbox id="image-pref-item"
+ class="pref-item" align="top">
+ <image class="pref-icon" type="image"/>
+ <vbox>
+ <hbox>
+ <label class="pref-title" value="&image.label;"/>
+ <label id="image-default" class="pref-default" value="*"/>
+ </hbox>
+ <hbox>
+ <menulist id="image-menulist"
+ class="pref-menulist"
+ type="image"
+ oncommand="AboutPermissions.onPermissionCommand(event, false);">
+ <menupopup>
+ <menuitem id="image-1" value="1" label="&permission.allow;"/>
+ <menuitem id="image-2" value="2" label="&permission.block;"/>
+ <menuitem id="image-3" value="3" label="&permission.allowFirstPartyOnly;"/>
+ </menupopup>
+ </menulist>
+ <button id="image-set-default"
+ class="pref-set-default"
+ label="&permission.default;"
+ type="image"
+ oncommand="AboutPermissions.onPermissionCommand(event, true);"/>
+ </hbox>
+ </vbox>
+ </hbox>
+
+ <!-- Pop-up Blocking -->
+ <hbox id="popup-pref-item"
+ class="pref-item" align="top">
+ <image class="pref-icon" type="popup"/>
+ <vbox>
+ <hbox>
+ <label class="pref-title" value="&popup.label;"/>
+ <label id="popup-default" class="pref-default" value="*"/>
+ </hbox>
+ <hbox>
+ <menulist id="popup-menulist"
+ class="pref-menulist"
+ type="popup"
+ oncommand="AboutPermissions.onPermissionCommand(event, false);">
+ <menupopup>
+ <menuitem id="popup-1" value="1" label="&permission.allow;"/>
+ <menuitem id="popup-2" value="2" label="&permission.block;"/>
+ </menupopup>
+ </menulist>
+ <button id="popup-set-default"
+ class="pref-set-default"
+ label="&permission.default;"
+ type="popup"
+ oncommand="AboutPermissions.onPermissionCommand(event, true);"/>
+ </hbox>
+ </vbox>
+ </hbox>
+
+ <!-- Cookies -->
+ <hbox id="cookie-pref-item"
+ class="pref-item" align="top">
+ <image class="pref-icon" type="cookie"/>
+ <vbox>
+ <hbox>
+ <label class="pref-title" value="&cookie.label;"/>
+ <label id="cookie-default" class="pref-default" value="*"/>
+ </hbox>
+ <hbox align="center">
+ <menulist id="cookie-menulist"
+ class="pref-menulist"
+ type="cookie"
+ oncommand="AboutPermissions.onPermissionCommand(event, false);">
+ <menupopup>
+ <menuitem id="cookie-1" value="1" label="&permission.allow;"/>
+ <menuitem id="cookie-8" value="8" label="&permission.allowForSession;"/>
+ <menuitem id="cookie-9" value="9" label="&permission.allowFirstPartyOnly;"/>
+ <menuitem id="cookie-2" value="2" label="&permission.block;"/>
+ </menupopup>
+ </menulist>
+ <button id="cookie-set-default"
+ class="pref-set-default"
+ label="&permission.default;"
+ type="cookie"
+ oncommand="AboutPermissions.onPermissionCommand(event, true);"/>
+ <button id="cookies-clear-all-button"
+ label="&cookie.removeAll;"
+ oncommand="Services.cookies.removeAll();"/>
+ <button id="cookies-manage-all-button"
+ label="&cookie.manage;"
+ oncommand="AboutPermissions.manageCookies();"/>
+ </hbox>
+ <hbox id="cookies-count" align="center">
+ <label id="cookies-label"/>
+ <button id="cookies-clear-button"
+ label="&cookie.remove;"
+ oncommand="AboutPermissions.clearCookies();"/>
+ <button id="cookies-manage-button"
+ label="&cookie.manage;"
+ oncommand="AboutPermissions.manageCookies();"/>
+ </hbox>
+ </vbox>
+ </hbox>
+
+ <!-- Desktop Notifications -->
+ <hbox id="desktop-notification-pref-item"
+ class="pref-item" align="top">
+ <image class="pref-icon" type="desktop-notification"/>
+ <vbox>
+ <hbox>
+ <label class="pref-title" value="&desktop-notification.label;"/>
+ <label id="desktop-notification-default" class="pref-default" value="*"/>
+ </hbox>
+ <hbox>
+ <menulist id="desktop-notification-menulist"
+ class="pref-menulist"
+ type="desktop-notification"
+ oncommand="AboutPermissions.onPermissionCommand(event, false);">
+ <menupopup>
+ <menuitem id="desktop-notification-0" value="0" label="&permission.alwaysAsk;"/>
+ <menuitem id="desktop-notification-1" value="1" label="&permission.allow;"/>
+ <menuitem id="desktop-notification-2" value="2" label="&permission.block;"/>
+ </menupopup>
+ </menulist>
+ <button id="desktop-notification-set-default"
+ class="pref-set-default"
+ label="&permission.default;"
+ type="desktop-notification"
+ oncommand="AboutPermissions.onPermissionCommand(event, true);"/>
+ </hbox>
+ </vbox>
+ </hbox>
+
+ <!-- Addons Blocking -->
+ <hbox id="install-pref-item"
+ class="pref-item" align="top">
+ <image class="pref-icon" type="install"/>
+ <vbox>
+ <hbox>
+ <label class="pref-title" value="&install.label;"/>
+ <label id="install-default" class="pref-default" value="*"/>
+ </hbox>
+ <hbox>
+ <menulist id="install-menulist"
+ class="pref-menulist"
+ type="install"
+ oncommand="AboutPermissions.onPermissionCommand(event, false);">
+ <menupopup>
+ <menuitem id="install-1" value="1" label="&permission.allow;"/>
+ <menuitem id="install-2" value="2" label="&permission.block;"/>
+ </menupopup>
+ </menulist>
+ <button id="install-set-default"
+ class="pref-set-default"
+ label="&permission.default;"
+ type="install"
+ oncommand="AboutPermissions.onPermissionCommand(event, true);"/>
+ </hbox>
+ </vbox>
+ </hbox>
+
+ <!-- Geolocation -->
+ <hbox id="geo-pref-item"
+ class="pref-item" align="top">
+ <image class="pref-icon" type="geo"/>
+ <vbox>
+ <hbox>
+ <label class="pref-title" value="&geo.label;"/>
+ <label id="geo-default" class="pref-default" value="*"/>
+ </hbox>
+ <hbox>
+ <menulist id="geo-menulist"
+ class="pref-menulist"
+ type="geo"
+ oncommand="AboutPermissions.onPermissionCommand(event, false);">
+ <menupopup>
+ <menuitem id="geo-0" value="0" label="&permission.alwaysAsk;"/>
+ <menuitem id="geo-1" value="1" label="&permission.allow;"/>
+ <menuitem id="geo-2" value="2" label="&permission.block;"/>
+ </menupopup>
+ </menulist>
+ <button id="geo-set-default"
+ class="pref-set-default"
+ label="&permission.default;"
+ type="geo"
+ oncommand="AboutPermissions.onPermissionCommand(event, true);"/>
+ </hbox>
+ </vbox>
+ </hbox>
+
+ <!-- Opt-in activation of Plug-ins -->
+ <hbox id="plugins-pref-item"
+ class="pref-item" align="top">
+ <image class="pref-icon" type="plugins"/>
+ <vbox>
+ <label class="pref-title" value="&plugins.label;"/>
+ <vbox id="plugins-box"/>
+ </vbox>
+ </hbox>
+ </vbox>
+ </hbox>
+
+</page>
diff --git a/components/permissions/jar.mn b/components/permissions/jar.mn
new file mode 100644
index 0000000..c788938
--- /dev/null
+++ b/components/permissions/jar.mn
@@ -0,0 +1,9 @@
+# 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/permissions/aboutPermissions.xul
+ content/browser/permissions/aboutPermissions.js
+ content/browser/permissions/aboutPermissions.css
+ content/browser/permissions/aboutPermissions.xml
diff --git a/components/permissions/moz.build b/components/permissions/moz.build
new file mode 100644
index 0000000..3bbe672
--- /dev/null
+++ b/components/permissions/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/.
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/components/places/PlacesUIUtils.jsm b/components/places/PlacesUIUtils.jsm
new file mode 100644
index 0000000..e3a9e13
--- /dev/null
+++ b/components/places/PlacesUIUtils.jsm
@@ -0,0 +1,1373 @@
+/* -*- 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/. */
+
+this.EXPORTED_SYMBOLS = ["PlacesUIUtils"];
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+ "resource://gre/modules/PluralForm.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
+ "resource:///modules/RecentWindow.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "PlacesUtils", function() {
+ Cu.import("resource://gre/modules/PlacesUtils.jsm");
+ return PlacesUtils;
+});
+
+this.PlacesUIUtils = {
+ ORGANIZER_LEFTPANE_VERSION: 7,
+ ORGANIZER_FOLDER_ANNO: "PlacesOrganizer/OrganizerFolder",
+ ORGANIZER_QUERY_ANNO: "PlacesOrganizer/OrganizerQuery",
+
+ LOAD_IN_SIDEBAR_ANNO: "bookmarkProperties/loadInSidebar",
+ DESCRIPTION_ANNO: "bookmarkProperties/description",
+
+ TYPE_TAB_DROP: "application/x-moz-tabbrowser-tab",
+
+ /**
+ * Makes a URI from a spec, and do fixup
+ * @param aSpec
+ * The string spec of the URI
+ * @returns A URI object for the spec.
+ */
+ createFixedURI: function PUIU_createFixedURI(aSpec) {
+ return URIFixup.createFixupURI(aSpec, Ci.nsIURIFixup.FIXUP_FLAG_NONE);
+ },
+
+ getFormattedString: function PUIU_getFormattedString(key, params) {
+ return bundle.formatStringFromName(key, params, params.length);
+ },
+
+ /**
+ * Get a localized plural string for the specified key name and numeric value
+ * substituting parameters.
+ *
+ * @param aKey
+ * String, key for looking up the localized string in the bundle
+ * @param aNumber
+ * Number based on which the final localized form is looked up
+ * @param aParams
+ * Array whose items will substitute #1, #2,... #n parameters
+ * in the string.
+ *
+ * @see https://developer.mozilla.org/en/Localization_and_Plurals
+ * @return The localized plural string.
+ */
+ getPluralString: function PUIU_getPluralString(aKey, aNumber, aParams) {
+ let str = PluralForm.get(aNumber, bundle.GetStringFromName(aKey));
+
+ // Replace #1 with aParams[0], #2 with aParams[1], and so on.
+ return str.replace(/\#(\d+)/g, function (matchedId, matchedNumber) {
+ let param = aParams[parseInt(matchedNumber, 10) - 1];
+ return param !== undefined ? param : matchedId;
+ });
+ },
+
+ getString: function PUIU_getString(key) {
+ return bundle.GetStringFromName(key);
+ },
+
+ get _copyableAnnotations() [
+ this.DESCRIPTION_ANNO,
+ this.LOAD_IN_SIDEBAR_ANNO,
+ PlacesUtils.POST_DATA_ANNO,
+ PlacesUtils.READ_ONLY_ANNO,
+ ],
+
+ /**
+ * Get a transaction for copying a uri item (either a bookmark or a history
+ * entry) from one container to another.
+ *
+ * @param aData
+ * JSON object of dropped or pasted item properties
+ * @param aContainer
+ * The container being copied into
+ * @param aIndex
+ * The index within the container the item is copied to
+ * @return A nsITransaction object that performs the copy.
+ *
+ * @note Since a copy creates a completely new item, only some internal
+ * annotations are synced from the old one.
+ * @see this._copyableAnnotations for the list of copyable annotations.
+ */
+ _getURIItemCopyTransaction:
+ function PUIU__getURIItemCopyTransaction(aData, aContainer, aIndex)
+ {
+ let transactions = [];
+ if (aData.dateAdded) {
+ transactions.push(
+ new PlacesEditItemDateAddedTransaction(null, aData.dateAdded)
+ );
+ }
+ if (aData.lastModified) {
+ transactions.push(
+ new PlacesEditItemLastModifiedTransaction(null, aData.lastModified)
+ );
+ }
+
+ let keyword = aData.keyword || null;
+ let annos = [];
+ if (aData.annos) {
+ annos = aData.annos.filter(function (aAnno) {
+ return this._copyableAnnotations.indexOf(aAnno.name) != -1;
+ }, this);
+ }
+
+ return new PlacesCreateBookmarkTransaction(PlacesUtils._uri(aData.uri),
+ aContainer, aIndex, aData.title,
+ keyword, annos, transactions);
+ },
+
+ /**
+ * Gets a transaction for copying (recursively nesting to include children)
+ * a folder (or container) and its contents from one folder to another.
+ *
+ * @param aData
+ * Unwrapped dropped folder data - Obj containing folder and children
+ * @param aContainer
+ * The container we are copying into
+ * @param aIndex
+ * The index in the destination container to insert the new items
+ * @return A nsITransaction object that will perform the copy.
+ *
+ * @note Since a copy creates a completely new item, only some internal
+ * annotations are synced from the old one.
+ * @see this._copyableAnnotations for the list of copyable annotations.
+ */
+ _getFolderCopyTransaction(aData, aContainer, aIndex) {
+ function getChildItemsTransactions(aRoot) {
+ let transactions = [];
+ let index = aIndex;
+ for (let i = 0; i < aRoot.childCount; ++i) {
+ let child = aRoot.getChild(i);
+ // Temporary hacks until we switch to PlacesTransactions.jsm.
+ let isLivemark =
+ PlacesUtils.annotations.itemHasAnnotation(child.itemId,
+ PlacesUtils.LMANNO_FEEDURI);
+ let [node] = PlacesUtils.unwrapNodes(
+ PlacesUtils.wrapNode(child, PlacesUtils.TYPE_X_MOZ_PLACE, isLivemark),
+ PlacesUtils.TYPE_X_MOZ_PLACE
+ );
+
+ // Make sure that items are given the correct index, this will be
+ // passed by the transaction manager to the backend for the insertion.
+ // Insertion behaves differently for DEFAULT_INDEX (append).
+ if (aIndex != PlacesUtils.bookmarks.DEFAULT_INDEX) {
+ index = i;
+ }
+
+ if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER) {
+ if (node.livemark && node.annos) {
+ transactions.push(
+ PlacesUIUtils._getLivemarkCopyTransaction(node, aContainer, index)
+ );
+ }
+ else {
+ transactions.push(
+ PlacesUIUtils._getFolderCopyTransaction(node, aContainer, index)
+ );
+ }
+ }
+ else if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR) {
+ transactions.push(new PlacesCreateSeparatorTransaction(-1, index));
+ }
+ else if (node.type == PlacesUtils.TYPE_X_MOZ_PLACE) {
+ transactions.push(
+ PlacesUIUtils._getURIItemCopyTransaction(node, -1, index)
+ );
+ }
+ else {
+ throw new Error("Unexpected item under a bookmarks folder");
+ }
+ }
+ return transactions;
+ }
+
+ if (aContainer == PlacesUtils.tagsFolderId) { // Copying into a tag folder.
+ let transactions = [];
+ if (!aData.livemark && aData.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER) {
+ let {root} = PlacesUtils.getFolderContents(aData.id, false, false);
+ let urls = PlacesUtils.getURLsForContainerNode(root);
+ root.containerOpen = false;
+ for (let { uri } of urls) {
+ transactions.push(
+ new PlacesTagURITransaction(NetUtil.newURI(uri), [aData.title])
+ );
+ }
+ }
+ return new PlacesAggregatedTransaction("addTags", transactions);
+ }
+
+ if (aData.livemark && aData.annos) { // Copying a livemark.
+ return this._getLivemarkCopyTransaction(aData, aContainer, aIndex);
+ }
+
+ let {root} = PlacesUtils.getFolderContents(aData.id, false, false);
+ let transactions = getChildItemsTransactions(root);
+ root.containerOpen = false;
+
+ if (aData.dateAdded) {
+ transactions.push(
+ new PlacesEditItemDateAddedTransaction(null, aData.dateAdded)
+ );
+ }
+ if (aData.lastModified) {
+ transactions.push(
+ new PlacesEditItemLastModifiedTransaction(null, aData.lastModified)
+ );
+ }
+
+ let annos = [];
+ if (aData.annos) {
+ annos = aData.annos.filter(function (aAnno) {
+ return this._copyableAnnotations.indexOf(aAnno.name) != -1;
+ }, this);
+ }
+
+ return new PlacesCreateFolderTransaction(aData.title, aContainer, aIndex,
+ annos, transactions);
+ },
+
+ /**
+ * Gets a transaction for copying a live bookmark item from one container to
+ * another.
+ *
+ * @param aData
+ * Unwrapped live bookmarkmark data
+ * @param aContainer
+ * The container we are copying into
+ * @param aIndex
+ * The index in the destination container to insert the new items
+ * @return A nsITransaction object that will perform the copy.
+ *
+ * @note Since a copy creates a completely new item, only some internal
+ * annotations are synced from the old one.
+ * @see this._copyableAnnotations for the list of copyable annotations.
+ */
+ _getLivemarkCopyTransaction:
+ function PUIU__getLivemarkCopyTransaction(aData, aContainer, aIndex)
+ {
+ if (!aData.livemark || !aData.annos) {
+ throw new Error("node is not a livemark");
+ }
+
+ let feedURI, siteURI;
+ let annos = [];
+ if (aData.annos) {
+ annos = aData.annos.filter(function (aAnno) {
+ if (aAnno.name == PlacesUtils.LMANNO_FEEDURI) {
+ feedURI = PlacesUtils._uri(aAnno.value);
+ }
+ else if (aAnno.name == PlacesUtils.LMANNO_SITEURI) {
+ siteURI = PlacesUtils._uri(aAnno.value);
+ }
+ return this._copyableAnnotations.indexOf(aAnno.name) != -1
+ }, this);
+ }
+
+ return new PlacesCreateLivemarkTransaction(feedURI, siteURI, aData.title,
+ aContainer, aIndex, annos);
+ },
+
+ /**
+ * Test if a bookmark item = a live bookmark item.
+ *
+ * @param aItemId
+ * item identifier
+ * @return true if a live bookmark item, false otherwise.
+ *
+ * @note Maybe this should be removed later, see bug 1072833.
+ */
+ _isLivemark:
+ function PUIU__isLivemark(aItemId)
+ {
+ // Since this check may be done on each dragover event, it's worth maintaining
+ // a cache.
+ let self = PUIU__isLivemark;
+ if (!("ids" in self)) {
+ const LIVEMARK_ANNO = PlacesUtils.LMANNO_FEEDURI;
+
+ let idsVec = PlacesUtils.annotations.getItemsWithAnnotation(LIVEMARK_ANNO);
+ self.ids = new Set(idsVec);
+
+ let obs = Object.freeze({
+ QueryInterface: XPCOMUtils.generateQI(Ci.nsIAnnotationObserver),
+
+ onItemAnnotationSet(itemId, annoName) {
+ if (annoName == LIVEMARK_ANNO)
+ self.ids.add(itemId);
+ },
+
+ onItemAnnotationRemoved(itemId, annoName) {
+ // If annoName is set to an empty string, the item is gone.
+ if (annoName == LIVEMARK_ANNO || annoName == "")
+ self.ids.delete(itemId);
+ },
+
+ onPageAnnotationSet() { },
+ onPageAnnotationRemoved() { },
+ });
+ PlacesUtils.annotations.addObserver(obs);
+ PlacesUtils.registerShutdownFunction(() => {
+ PlacesUtils.annotations.removeObserver(obs);
+ });
+ }
+ return self.ids.has(aItemId);
+ },
+
+ /**
+ * Constructs a Transaction for the drop or paste of a blob of data into
+ * a container.
+ * @param data
+ * The unwrapped data blob of dropped or pasted data.
+ * @param type
+ * The content type of the data
+ * @param container
+ * The container the data was dropped or pasted into
+ * @param index
+ * The index within the container the item was dropped or pasted at
+ * @param copy
+ * The drag action was copy, so don't move folders or links.
+ * @returns An object implementing nsITransaction that can perform
+ * the move/insert.
+ */
+ makeTransaction:
+ function PUIU_makeTransaction(data, type, container, index, copy)
+ {
+ switch (data.type) {
+ case PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER:
+ if (copy) {
+ return this._getFolderCopyTransaction(data, container, index);
+ }
+
+ // Otherwise move the item.
+ return new PlacesMoveItemTransaction(data.id, container, index);
+ break;
+ case PlacesUtils.TYPE_X_MOZ_PLACE:
+ if (copy || data.id == -1) { // Id is -1 if the place is not bookmarked.
+ return this._getURIItemCopyTransaction(data, container, index);
+ }
+
+ // Otherwise move the item.
+ return new PlacesMoveItemTransaction(data.id, container, index);
+ break;
+ case PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR:
+ if (copy) {
+ // There is no data in a separator, so copying it just amounts to
+ // inserting a new separator.
+ return new PlacesCreateSeparatorTransaction(container, index);
+ }
+
+ // Otherwise move the item.
+ return new PlacesMoveItemTransaction(data.id, container, index);
+ break;
+ default:
+ if (type == PlacesUtils.TYPE_X_MOZ_URL ||
+ type == PlacesUtils.TYPE_UNICODE ||
+ type == this.TYPE_TAB_DROP) {
+ let title = type != PlacesUtils.TYPE_UNICODE ? data.title
+ : data.uri;
+ return new PlacesCreateBookmarkTransaction(PlacesUtils._uri(data.uri),
+ container, index, title);
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Shows the bookmark dialog corresponding to the specified info.
+ *
+ * @param aInfo
+ * Describes the item to be edited/added in the dialog.
+ * See documentation at the top of bookmarkProperties.js
+ * @param aWindow
+ * Owner window for the new dialog.
+ *
+ * @see documentation at the top of bookmarkProperties.js
+ * @return true if any transaction has been performed, false otherwise.
+ */
+ showBookmarkDialog:
+ function PUIU_showBookmarkDialog(aInfo, aParentWindow) {
+ // Preserve size attributes differently based on the fact the dialog has
+ // a folder picker or not, since it needs more horizontal space than the
+ // other controls.
+ let hasFolderPicker = !("hiddenRows" in aInfo) ||
+ aInfo.hiddenRows.indexOf("folderPicker") == -1;
+ // Use a different chrome url to persist different sizes.
+ let dialogURL = hasFolderPicker ?
+ "chrome://browser/content/places/bookmarkProperties2.xul" :
+ "chrome://browser/content/places/bookmarkProperties.xul";
+
+ let features = "centerscreen,chrome,modal,resizable=yes";
+ aParentWindow.openDialog(dialogURL, "", features, aInfo);
+ return ("performed" in aInfo && aInfo.performed);
+ },
+
+ _getTopBrowserWin: function PUIU__getTopBrowserWin() {
+ return RecentWindow.getMostRecentBrowserWindow();
+ },
+
+ /**
+ * Returns the closet ancestor places view for the given DOM node
+ * @param aNode
+ * a DOM node
+ * @return the closet ancestor places view if exists, null otherwsie.
+ */
+ getViewForNode: function PUIU_getViewForNode(aNode) {
+ let node = aNode;
+
+ // The view for a <menu> of which its associated menupopup is a places
+ // view, is the menupopup.
+ if (node.localName == "menu" && !node._placesNode &&
+ node.lastChild._placesView)
+ return node.lastChild._placesView;
+
+ while (node instanceof Ci.nsIDOMElement) {
+ if (node._placesView)
+ return node._placesView;
+ if (node.localName == "tree" && node.getAttribute("type") == "places")
+ return node;
+
+ node = node.parentNode;
+ }
+
+ return null;
+ },
+
+ /**
+ * By calling this before visiting an URL, the visit will be associated to a
+ * TRANSITION_TYPED transition (if there is no a referrer).
+ * This is used when visiting pages from the history menu, history sidebar,
+ * url bar, url autocomplete results, and history searches from the places
+ * organizer. If this is not called visits will be marked as
+ * TRANSITION_LINK.
+ */
+ markPageAsTyped: function PUIU_markPageAsTyped(aURL) {
+ PlacesUtils.history.markPageAsTyped(this.createFixedURI(aURL));
+ },
+
+ /**
+ * By calling this before visiting an URL, the visit will be associated to a
+ * TRANSITION_BOOKMARK transition.
+ * This is used when visiting pages from the bookmarks menu,
+ * personal toolbar, and bookmarks from within the places organizer.
+ * If this is not called visits will be marked as TRANSITION_LINK.
+ */
+ markPageAsFollowedBookmark: function PUIU_markPageAsFollowedBookmark(aURL) {
+ PlacesUtils.history.markPageAsFollowedBookmark(this.createFixedURI(aURL));
+ },
+
+ /**
+ * By calling this before visiting an URL, any visit in frames will be
+ * associated to a TRANSITION_FRAMED_LINK transition.
+ * This is actually used to distinguish user-initiated visits in frames
+ * so automatic visits can be correctly ignored.
+ */
+ markPageAsFollowedLink: function PUIU_markPageAsFollowedLink(aURL) {
+ PlacesUtils.history.markPageAsFollowedLink(this.createFixedURI(aURL));
+ },
+
+ /**
+ * Allows opening of javascript/data URI only if the given node is
+ * bookmarked (see bug 224521).
+ * @param aURINode
+ * a URI node
+ * @param aWindow
+ * a window on which a potential error alert is shown on.
+ * @return true if it's safe to open the node in the browser, false otherwise.
+ *
+ */
+ checkURLSecurity: function PUIU_checkURLSecurity(aURINode, aWindow) {
+ if (PlacesUtils.nodeIsBookmark(aURINode))
+ return true;
+
+ var uri = PlacesUtils._uri(aURINode.uri);
+ if (uri.schemeIs("javascript") || uri.schemeIs("data")) {
+ const BRANDING_BUNDLE_URI = "chrome://branding/locale/brand.properties";
+ var brandShortName = Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService).
+ createBundle(BRANDING_BUNDLE_URI).
+ GetStringFromName("brandShortName");
+
+ var errorStr = this.getString("load-js-data-url-error");
+ Services.prompt.alert(aWindow, brandShortName, errorStr);
+ return false;
+ }
+ return true;
+ },
+
+ /**
+ * Get the description associated with a document, as specified in a <META>
+ * element.
+ * @param doc
+ * A DOM Document to get a description for
+ * @returns A description string if a META element was discovered with a
+ * "description" or "httpequiv" attribute, empty string otherwise.
+ */
+ getDescriptionFromDocument: function PUIU_getDescriptionFromDocument(doc) {
+ var metaElements = doc.getElementsByTagName("META");
+ for (var i = 0; i < metaElements.length; ++i) {
+ if (metaElements[i].name.toLowerCase() == "description" ||
+ metaElements[i].httpEquiv.toLowerCase() == "description") {
+ return metaElements[i].content;
+ }
+ }
+ return "";
+ },
+
+ /**
+ * Retrieve the description of an item
+ * @param aItemId
+ * item identifier
+ * @returns the description of the given item, or an empty string if it is
+ * not set.
+ */
+ getItemDescription: function PUIU_getItemDescription(aItemId) {
+ if (PlacesUtils.annotations.itemHasAnnotation(aItemId, this.DESCRIPTION_ANNO))
+ return PlacesUtils.annotations.getItemAnnotation(aItemId, this.DESCRIPTION_ANNO);
+ return "";
+ },
+
+ /**
+ * Check whether or not the given node represents a removable entry (either in
+ * history or in bookmarks).
+ *
+ * @param aNode
+ * a node, except the root node of a query.
+ * @return true if the aNode represents a removable entry, false otherwise.
+ */
+ canUserRemove: function (aNode) {
+ let parentNode = aNode.parent;
+ if (!parentNode)
+ throw new Error("canUserRemove doesn't accept root nodes");
+
+ // If it's not a bookmark, we can remove it unless it's a child of a
+ // livemark.
+ if (aNode.itemId == -1) {
+ // Rather than executing a db query, checking the existence of the feedURI
+ // annotation, detect livemark children by the fact that they are the only
+ // direct non-bookmark children of bookmark folders.
+ return !PlacesUtils.nodeIsFolder(parentNode);
+ }
+
+ // Generally it's always possible to remove children of a query.
+ if (PlacesUtils.nodeIsQuery(parentNode))
+ return true;
+
+ // Otherwise it has to be a child of an editable folder.
+ return !this.isContentsReadOnly(parentNode);
+ },
+
+ /**
+ * DO NOT USE THIS API IN ADDONS. IT IS VERY LIKELY TO CHANGE WHEN THE SWITCH
+ * TO GUIDS IS COMPLETE (BUG 1071511).
+ *
+ * Check whether or not the given node or item-id points to a folder which
+ * should not be modified by the user (i.e. its children should be unremovable
+ * and unmovable, new children should be disallowed, etc).
+ * These semantics are not inherited, meaning that read-only folder may
+ * contain editable items (for instance, the places root is read-only, but all
+ * of its direct children aren't).
+ *
+ * You should only pass folder item ids or folder nodes for aNodeOrItemId.
+ * While this is only enforced for the node case (if an item id of a separator
+ * or a bookmark is passed, false is returned), it's considered the caller's
+ * job to ensure that it checks a folder.
+ * Also note that folder-shortcuts should only be passed as result nodes.
+ * Otherwise they are just treated as bookmarks (i.e. false is returned).
+ *
+ * @param aNodeOrItemId
+ * any item id or result node.
+ * @throws if aNodeOrItemId is neither an item id nor a folder result node.
+ * @note livemark "folders" are considered read-only (but see bug 1072833).
+ * @return true if aItemId points to a read-only folder, false otherwise.
+ */
+ isContentsReadOnly: function (aNodeOrItemId) {
+ let itemId;
+ if (typeof(aNodeOrItemId) == "number") {
+ itemId = aNodeOrItemId;
+ }
+ else if (PlacesUtils.nodeIsFolder(aNodeOrItemId)) {
+ itemId = PlacesUtils.getConcreteItemId(aNodeOrItemId);
+ }
+ else {
+ throw new Error("invalid value for aNodeOrItemId");
+ }
+
+ if (itemId == PlacesUtils.placesRootId || this._isLivemark(itemId))
+ return true;
+
+ // leftPaneFolderId, and as a result, allBookmarksFolderId, is a lazy getter
+ // performing at least a synchronous DB query (and on its very first call
+ // in a fresh profile, it also creates the entire structure).
+ // Therefore we don't want to this function, which is called very often by
+ // isCommandEnabled, to ever be the one that invokes it first, especially
+ // because isCommandEnabled may be called way before the left pane folder is
+ // even created (for example, if the user only uses the bookmarks menu or
+ // toolbar for managing bookmarks). To do so, we avoid comparing to those
+ // special folder if the lazy getter is still in place. This is safe merely
+ // because the only way to access the left pane contents goes through
+ // "resolving" the leftPaneFolderId getter.
+ if ("get" in Object.getOwnPropertyDescriptor(this, "leftPaneFolderId"))
+ return false;
+
+ return itemId == this.leftPaneFolderId ||
+ itemId == this.allBookmarksFolderId;
+ },
+
+ /**
+ * Gives the user a chance to cancel loading lots of tabs at once
+ */
+ _confirmOpenInTabs:
+ function PUIU__confirmOpenInTabs(numTabsToOpen, aWindow) {
+ const WARN_ON_OPEN_PREF = "browser.tabs.warnOnOpen";
+ var reallyOpen = true;
+
+ if (Services.prefs.getBoolPref(WARN_ON_OPEN_PREF)) {
+ if (numTabsToOpen >= Services.prefs.getIntPref("browser.tabs.maxOpenBeforeWarn")) {
+ // default to true: if it were false, we wouldn't get this far
+ var warnOnOpen = { value: true };
+
+ var messageKey = "tabs.openWarningMultipleBranded";
+ var openKey = "tabs.openButtonMultiple";
+ const BRANDING_BUNDLE_URI = "chrome://branding/locale/brand.properties";
+ var brandShortName = Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService).
+ createBundle(BRANDING_BUNDLE_URI).
+ GetStringFromName("brandShortName");
+
+ var buttonPressed = Services.prompt.confirmEx(
+ aWindow,
+ this.getString("tabs.openWarningTitle"),
+ this.getFormattedString(messageKey, [numTabsToOpen, brandShortName]),
+ (Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0) +
+ (Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1),
+ this.getString(openKey), null, null,
+ this.getFormattedString("tabs.openWarningPromptMeBranded",
+ [brandShortName]),
+ warnOnOpen
+ );
+
+ reallyOpen = (buttonPressed == 0);
+ // don't set the pref unless they press OK and it's false
+ if (reallyOpen && !warnOnOpen.value)
+ Services.prefs.setBoolPref(WARN_ON_OPEN_PREF, false);
+ }
+ }
+
+ return reallyOpen;
+ },
+
+ /** aItemsToOpen needs to be an array of objects of the form:
+ * {uri: string, isBookmark: boolean}
+ */
+ _openTabset: function PUIU__openTabset(aItemsToOpen, aEvent, aWindow) {
+ if (!aItemsToOpen.length)
+ return;
+
+ // Prefer the caller window if it's a browser window, otherwise use
+ // the top browser window.
+ var browserWindow = null;
+ browserWindow =
+ aWindow && aWindow.document.documentElement.getAttribute("windowtype") == "navigator:browser" ?
+ aWindow : this._getTopBrowserWin();
+
+ var urls = [];
+ let skipMarking = browserWindow && PrivateBrowsingUtils.isWindowPrivate(browserWindow);
+ for (let item of aItemsToOpen) {
+ urls.push(item.uri);
+ if (skipMarking) {
+ continue;
+ }
+
+ if (item.isBookmark)
+ this.markPageAsFollowedBookmark(item.uri);
+ else
+ this.markPageAsTyped(item.uri);
+ }
+
+ // whereToOpenLink doesn't return "window" when there's no browser window
+ // open (Bug 630255).
+ var where = browserWindow ?
+ browserWindow.whereToOpenLink(aEvent, false, true) : "window";
+ if (where == "window") {
+ // There is no browser window open, thus open a new one.
+ var uriList = PlacesUtils.toISupportsString(urls.join("|"));
+ var args = Cc["@mozilla.org/supports-array;1"].
+ createInstance(Ci.nsISupportsArray);
+ args.AppendElement(uriList);
+ browserWindow = Services.ww.openWindow(aWindow,
+ "chrome://browser/content/browser.xul",
+ null, "chrome,dialog=no,all", args);
+ return;
+ }
+
+ var loadInBackground = where == "tabshifted" ? true : false;
+ // For consistency, we want all the bookmarks to open in new tabs, instead
+ // of having one of them replace the currently focused tab. Hence we call
+ // loadTabs with aReplace set to false.
+ browserWindow.gBrowser.loadTabs(urls, loadInBackground, false);
+ },
+
+ openLiveMarkNodesInTabs:
+ function PUIU_openLiveMarkNodesInTabs(aNode, aEvent, aView) {
+ let window = aView.ownerWindow;
+
+ PlacesUtils.livemarks.getLivemark({id: aNode.itemId})
+ .then(aLivemark => {
+ urlsToOpen = [];
+
+ let nodes = aLivemark.getNodesForContainer(aNode);
+ for (let node of nodes) {
+ urlsToOpen.push({uri: node.uri, isBookmark: false});
+ }
+
+ if (this._confirmOpenInTabs(urlsToOpen.length, window)) {
+ this._openTabset(urlsToOpen, aEvent, window);
+ }
+ }, Cu.reportError);
+ },
+
+ openContainerNodeInTabs:
+ function PUIU_openContainerInTabs(aNode, aEvent, aView) {
+ let window = aView.ownerWindow;
+
+ let urlsToOpen = PlacesUtils.getURLsForContainerNode(aNode);
+ if (this._confirmOpenInTabs(urlsToOpen.length, window)) {
+ this._openTabset(urlsToOpen, aEvent, window);
+ }
+ },
+
+ openURINodesInTabs: function PUIU_openURINodesInTabs(aNodes, aEvent, aView) {
+ let window = aView.ownerWindow;
+
+ let urlsToOpen = [];
+ for (var i=0; i < aNodes.length; i++) {
+ // Skip over separators and folders.
+ if (PlacesUtils.nodeIsURI(aNodes[i]))
+ urlsToOpen.push({uri: aNodes[i].uri, isBookmark: PlacesUtils.nodeIsBookmark(aNodes[i])});
+ }
+ this._openTabset(urlsToOpen, aEvent, window);
+ },
+
+ /**
+ * Loads the node's URL in the appropriate tab or window or as a web
+ * panel given the user's preference specified by modifier keys tracked by a
+ * DOM mouse/key event.
+ * @param aNode
+ * An uri result node.
+ * @param aEvent
+ * The DOM mouse/key event with modifier keys set that track the
+ * user's preferred destination window or tab.
+ * @param aView
+ * The controller associated with aNode.
+ */
+ openNodeWithEvent:
+ function PUIU_openNodeWithEvent(aNode, aEvent, aView) {
+ let window = aView.ownerWindow;
+ this._openNodeIn(aNode, window.whereToOpenLink(aEvent, false, true), window);
+ },
+
+ /**
+ * Loads the node's URL in the appropriate tab or window or as a
+ * web panel.
+ * see also openUILinkIn
+ */
+ openNodeIn: function PUIU_openNodeIn(aNode, aWhere, aView, aPrivate) {
+ let window = aView.ownerWindow;
+ this._openNodeIn(aNode, aWhere, window, aPrivate);
+ },
+
+ _openNodeIn: function PUIU_openNodeIn(aNode, aWhere, aWindow, aPrivate=false) {
+ if (aNode && PlacesUtils.nodeIsURI(aNode) &&
+ this.checkURLSecurity(aNode, aWindow)) {
+ let isBookmark = PlacesUtils.nodeIsBookmark(aNode);
+
+ if (!PrivateBrowsingUtils.isWindowPrivate(aWindow)) {
+ if (isBookmark)
+ this.markPageAsFollowedBookmark(aNode.uri);
+ else
+ this.markPageAsTyped(aNode.uri);
+ }
+
+ // Check whether the node is a bookmark which should be opened as
+ // a web panel
+ if (aWhere == "current" && isBookmark) {
+ if (PlacesUtils.annotations
+ .itemHasAnnotation(aNode.itemId, this.LOAD_IN_SIDEBAR_ANNO)) {
+ let browserWin = this._getTopBrowserWin();
+ if (browserWin) {
+ browserWin.openWebPanel(aNode.title, aNode.uri);
+ return;
+ }
+ }
+ }
+ aWindow.openUILinkIn(aNode.uri, aWhere, {
+ inBackground: Services.prefs.getBoolPref("browser.tabs.loadBookmarksInBackground"),
+ private: aPrivate,
+ });
+ }
+ },
+
+ /**
+ * Helper for guessing scheme from an url string.
+ * Used to avoid nsIURI overhead in frequently called UI functions.
+ *
+ * @param aUrlString the url to guess the scheme from.
+ *
+ * @return guessed scheme for this url string.
+ *
+ * @note this is not supposed be perfect, so use it only for UI purposes.
+ */
+ guessUrlSchemeForUI: function PUIU_guessUrlSchemeForUI(aUrlString) {
+ return aUrlString.substr(0, aUrlString.indexOf(":"));
+ },
+
+ getBestTitle: function PUIU_getBestTitle(aNode, aDoNotCutTitle) {
+ var title;
+ if (!aNode.title && PlacesUtils.nodeIsURI(aNode)) {
+ // if node title is empty, try to set the label using host and filename
+ // PlacesUtils._uri() will throw if aNode.uri is not a valid URI
+ try {
+ var uri = PlacesUtils._uri(aNode.uri);
+ var host = uri.host;
+ var fileName = uri.QueryInterface(Ci.nsIURL).fileName;
+ // if fileName is empty, use path to distinguish labels
+ if (aDoNotCutTitle) {
+ title = host + uri.path;
+ } else {
+ title = host + (fileName ?
+ (host ? "/" + this.ellipsis + "/" : "") + fileName :
+ uri.path);
+ }
+ }
+ catch (e) {
+ // Use (no title) for non-standard URIs (data:, javascript:, ...)
+ title = "";
+ }
+ }
+ else
+ title = aNode.title;
+
+ return title || this.getString("noTitle");
+ },
+
+ get leftPaneQueries() {
+ // build the map
+ this.leftPaneFolderId;
+ return this.leftPaneQueries;
+ },
+
+ // Get the folder id for the organizer left-pane folder.
+ get leftPaneFolderId() {
+ let leftPaneRoot = -1;
+ let allBookmarksId;
+
+ // Shortcuts to services.
+ let bs = PlacesUtils.bookmarks;
+ let as = PlacesUtils.annotations;
+
+ // This is the list of the left pane queries.
+ let queries = {
+ "PlacesRoot": { title: "" },
+ "History": { title: this.getString("OrganizerQueryHistory") },
+ "Downloads": { title: this.getString("OrganizerQueryDownloads") },
+ "Tags": { title: this.getString("OrganizerQueryTags") },
+ "AllBookmarks": { title: this.getString("OrganizerQueryAllBookmarks") },
+ "BookmarksToolbar":
+ { title: null,
+ concreteTitle: PlacesUtils.getString("BookmarksToolbarFolderTitle"),
+ concreteId: PlacesUtils.toolbarFolderId },
+ "BookmarksMenu":
+ { title: null,
+ concreteTitle: PlacesUtils.getString("BookmarksMenuFolderTitle"),
+ concreteId: PlacesUtils.bookmarksMenuFolderId },
+ "UnfiledBookmarks":
+ { title: null,
+ concreteTitle: PlacesUtils.getString("UnsortedBookmarksFolderTitle"),
+ concreteId: PlacesUtils.unfiledBookmarksFolderId },
+ };
+ // All queries but PlacesRoot.
+ const EXPECTED_QUERY_COUNT = 7;
+
+ // Removes an item and associated annotations, ignoring eventual errors.
+ function safeRemoveItem(aItemId) {
+ try {
+ if (as.itemHasAnnotation(aItemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO) &&
+ !(as.getItemAnnotation(aItemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO) in queries)) {
+ // Some extension annotated their roots with our query annotation,
+ // so we should not delete them.
+ return;
+ }
+ // removeItemAnnotation does not check if item exists, nor the anno,
+ // so this is safe to do.
+ as.removeItemAnnotation(aItemId, PlacesUIUtils.ORGANIZER_FOLDER_ANNO);
+ as.removeItemAnnotation(aItemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO);
+ // This will throw if the annotation is an orphan.
+ bs.removeItem(aItemId);
+ }
+ catch(e) { /* orphan anno */ }
+ }
+
+ // Returns true if item really exists, false otherwise.
+ function itemExists(aItemId) {
+ try {
+ bs.getItemIndex(aItemId);
+ return true;
+ }
+ catch(e) {
+ return false;
+ }
+ }
+
+ // Get all items marked as being the left pane folder.
+ let items = as.getItemsWithAnnotation(this.ORGANIZER_FOLDER_ANNO);
+ if (items.length > 1) {
+ // Something went wrong, we cannot have more than one left pane folder,
+ // remove all left pane folders and continue. We will create a new one.
+ items.forEach(safeRemoveItem);
+ }
+ else if (items.length == 1 && items[0] != -1) {
+ leftPaneRoot = items[0];
+ // Check that organizer left pane root is valid.
+ let version = as.getItemAnnotation(leftPaneRoot, this.ORGANIZER_FOLDER_ANNO);
+ if (version != this.ORGANIZER_LEFTPANE_VERSION ||
+ !itemExists(leftPaneRoot)) {
+ // Invalid root, we must rebuild the left pane.
+ safeRemoveItem(leftPaneRoot);
+ leftPaneRoot = -1;
+ }
+ }
+
+ if (leftPaneRoot != -1) {
+ // A valid left pane folder has been found.
+ // Build the leftPaneQueries Map. This is used to quickly access them,
+ // associating a mnemonic name to the real item ids.
+ delete this.leftPaneQueries;
+ this.leftPaneQueries = {};
+
+ let items = as.getItemsWithAnnotation(this.ORGANIZER_QUERY_ANNO);
+ // While looping through queries we will also check for their validity.
+ let queriesCount = 0;
+ for (let i = 0; i < items.length; i++) {
+ let queryName = as.getItemAnnotation(items[i], this.ORGANIZER_QUERY_ANNO);
+
+ // Some extension did use our annotation to decorate their items
+ // with icons, so we should check only our elements, to avoid dataloss.
+ if (!(queryName in queries))
+ continue;
+
+ let query = queries[queryName];
+ query.itemId = items[i];
+
+ if (!itemExists(query.itemId)) {
+ // Orphan annotation, bail out and create a new left pane root.
+ break;
+ }
+
+ // Check that all queries have valid parents.
+ let parentId = bs.getFolderIdForItem(query.itemId);
+ if (items.indexOf(parentId) == -1 && parentId != leftPaneRoot) {
+ // The parent is not part of the left pane, bail out and create a new
+ // left pane root.
+ break;
+ }
+
+ // Titles could have been corrupted or the user could have changed his
+ // locale. Check title and eventually fix it.
+ if (bs.getItemTitle(query.itemId) != query.title)
+ bs.setItemTitle(query.itemId, query.title);
+ if ("concreteId" in query) {
+ if (bs.getItemTitle(query.concreteId) != query.concreteTitle)
+ bs.setItemTitle(query.concreteId, query.concreteTitle);
+ }
+
+ // Add the query to our cache.
+ this.leftPaneQueries[queryName] = query.itemId;
+ queriesCount++;
+ }
+
+ if (queriesCount != EXPECTED_QUERY_COUNT) {
+ // Queries number is wrong, so the left pane must be corrupt.
+ // Note: we can't just remove the leftPaneRoot, because some query could
+ // have a bad parent, so we have to remove all items one by one.
+ items.forEach(safeRemoveItem);
+ safeRemoveItem(leftPaneRoot);
+ }
+ else {
+ // Everything is fine, return the current left pane folder.
+ delete this.leftPaneFolderId;
+ return this.leftPaneFolderId = leftPaneRoot;
+ }
+ }
+
+ // Create a new left pane folder.
+ var callback = {
+ // Helper to create an organizer special query.
+ create_query: function CB_create_query(aQueryName, aParentId, aQueryUrl) {
+ let itemId = bs.insertBookmark(aParentId,
+ PlacesUtils._uri(aQueryUrl),
+ bs.DEFAULT_INDEX,
+ queries[aQueryName].title);
+ // Mark as special organizer query.
+ as.setItemAnnotation(itemId, PlacesUIUtils.ORGANIZER_QUERY_ANNO, aQueryName,
+ 0, as.EXPIRE_NEVER);
+ // We should never backup this, since it changes between profiles.
+ as.setItemAnnotation(itemId, PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO, 1,
+ 0, as.EXPIRE_NEVER);
+ // Add to the queries map.
+ PlacesUIUtils.leftPaneQueries[aQueryName] = itemId;
+ return itemId;
+ },
+
+ // Helper to create an organizer special folder.
+ create_folder: function CB_create_folder(aFolderName, aParentId, aIsRoot) {
+ // Left Pane Root Folder.
+ let folderId = bs.createFolder(aParentId,
+ queries[aFolderName].title,
+ bs.DEFAULT_INDEX);
+ // We should never backup this, since it changes between profiles.
+ as.setItemAnnotation(folderId, PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO, 1,
+ 0, as.EXPIRE_NEVER);
+
+ if (aIsRoot) {
+ // Mark as special left pane root.
+ as.setItemAnnotation(folderId, PlacesUIUtils.ORGANIZER_FOLDER_ANNO,
+ PlacesUIUtils.ORGANIZER_LEFTPANE_VERSION,
+ 0, as.EXPIRE_NEVER);
+ }
+ else {
+ // Mark as special organizer folder.
+ as.setItemAnnotation(folderId, PlacesUIUtils.ORGANIZER_QUERY_ANNO, aFolderName,
+ 0, as.EXPIRE_NEVER);
+ PlacesUIUtils.leftPaneQueries[aFolderName] = folderId;
+ }
+ return folderId;
+ },
+
+ runBatched: function CB_runBatched(aUserData) {
+ delete PlacesUIUtils.leftPaneQueries;
+ PlacesUIUtils.leftPaneQueries = { };
+
+ // Left Pane Root Folder.
+ leftPaneRoot = this.create_folder("PlacesRoot", bs.placesRoot, true);
+
+ // History Query.
+ this.create_query("History", leftPaneRoot,
+ "place:type=" +
+ Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY +
+ "&sort=" +
+ Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING);
+
+ // Downloads.
+ this.create_query("Downloads", leftPaneRoot,
+ "place:transition=" +
+ Ci.nsINavHistoryService.TRANSITION_DOWNLOAD +
+ "&sort=" +
+ Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING);
+
+ // Tags Query.
+ this.create_query("Tags", leftPaneRoot,
+ "place:type=" +
+ Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY +
+ "&sort=" +
+ Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_ASCENDING);
+
+ // All Bookmarks Folder.
+ allBookmarksId = this.create_folder("AllBookmarks", leftPaneRoot, false);
+
+ // All Bookmarks->Bookmarks Toolbar Query.
+ this.create_query("BookmarksToolbar", allBookmarksId,
+ "place:folder=TOOLBAR");
+
+ // All Bookmarks->Bookmarks Menu Query.
+ this.create_query("BookmarksMenu", allBookmarksId,
+ "place:folder=BOOKMARKS_MENU");
+
+ // All Bookmarks->Unfiled Bookmarks Query.
+ this.create_query("UnfiledBookmarks", allBookmarksId,
+ "place:folder=UNFILED_BOOKMARKS");
+ }
+ };
+ bs.runInBatchMode(callback, null);
+ // Maybe: PlacesUtils.bookmarks.runInBatchMode(callback, null); ?
+
+ delete this.leftPaneFolderId;
+ return this.leftPaneFolderId = leftPaneRoot;
+ },
+
+ /**
+ * Get the folder id for the organizer left-pane folder.
+ */
+ get allBookmarksFolderId() {
+ // ensure the left-pane root is initialized;
+ this.leftPaneFolderId;
+ delete this.allBookmarksFolderId;
+ return this.allBookmarksFolderId = this.leftPaneQueries["AllBookmarks"];
+ },
+
+ /**
+ * If an item is a left-pane query, returns the name of the query
+ * or an empty string if not.
+ *
+ * @param aItemId id of a container
+ * @returns the name of the query, or empty string if not a left-pane query
+ */
+ getLeftPaneQueryNameFromId: function PUIU_getLeftPaneQueryNameFromId(aItemId) {
+ var queryName = "";
+ // If the let pane hasn't been built, use the annotation service
+ // directly, to avoid building the left pane too early.
+ if (Object.getOwnPropertyDescriptor(this, "leftPaneFolderId").value === undefined) {
+ try {
+ queryName = PlacesUtils.annotations.
+ getItemAnnotation(aItemId, this.ORGANIZER_QUERY_ANNO);
+ }
+ catch (ex) {
+ // doesn't have the annotation
+ queryName = "";
+ }
+ }
+ else {
+ // If the left pane has already been built, use the name->id map
+ // cached in PlacesUIUtils.
+ for (let [name, id] in Iterator(this.leftPaneQueries)) {
+ if (aItemId == id)
+ queryName = name;
+ }
+ }
+ return queryName;
+ },
+
+ /**
+ * Returns the passed URL with a #moz-resolution fragment
+ * for the specified dimensions and devicePixelRatio.
+ *
+ * @param aWindow
+ * A window from where we want to get the device
+ * pixel Ratio
+ *
+ * @param aURL
+ * The URL where we should add the fragment
+ *
+ * @param aWidth
+ * The target image width
+ *
+ * @param aHeight
+ * The target image height
+ *
+ * @return The URL with the fragment at the end
+ */
+ getImageURLForResolution:
+ function PUIU_getImageURLForResolution(aWindow, aURL, aWidth, aHeight) {
+ return aURL;
+ }
+};
+
+XPCOMUtils.defineLazyServiceGetter(PlacesUIUtils, "RDF",
+ "@mozilla.org/rdf/rdf-service;1",
+ "nsIRDFService");
+
+XPCOMUtils.defineLazyGetter(PlacesUIUtils, "localStore", function() {
+ return PlacesUIUtils.RDF.GetDataSource("rdf:local-store");
+});
+
+XPCOMUtils.defineLazyGetter(PlacesUIUtils, "ellipsis", function() {
+ return Services.prefs.getComplexValue("intl.ellipsis",
+ Ci.nsIPrefLocalizedString).data;
+});
+
+XPCOMUtils.defineLazyServiceGetter(this, "URIFixup",
+ "@mozilla.org/docshell/urifixup;1",
+ "nsIURIFixup");
+
+XPCOMUtils.defineLazyGetter(this, "bundle", function() {
+ const PLACES_STRING_BUNDLE_URI =
+ "chrome://browser/locale/places/places.properties";
+ return Cc["@mozilla.org/intl/stringbundle;1"].
+ getService(Ci.nsIStringBundleService).
+ createBundle(PLACES_STRING_BUNDLE_URI);
+});
+
+XPCOMUtils.defineLazyServiceGetter(this, "focusManager",
+ "@mozilla.org/focus-manager;1",
+ "nsIFocusManager");
+
+/**
+ * This is a compatibility shim for old PUIU.ptm users.
+ *
+ * If you're looking for transactions and writing new code using them, directly
+ * use the transactions objects exported by the PlacesUtils.jsm module.
+ *
+ * This object will be removed once enough users are converted to the new API.
+ */
+XPCOMUtils.defineLazyGetter(PlacesUIUtils, "ptm", function() {
+ // Ensure PlacesUtils is imported in scope.
+ PlacesUtils;
+
+ return {
+ aggregateTransactions: function(aName, aTransactions)
+ new PlacesAggregatedTransaction(aName, aTransactions),
+
+ createFolder: function(aName, aContainer, aIndex, aAnnotations,
+ aChildItemsTransactions)
+ new PlacesCreateFolderTransaction(aName, aContainer, aIndex, aAnnotations,
+ aChildItemsTransactions),
+
+ createItem: function(aURI, aContainer, aIndex, aTitle, aKeyword,
+ aAnnotations, aChildTransactions)
+ new PlacesCreateBookmarkTransaction(aURI, aContainer, aIndex, aTitle,
+ aKeyword, aAnnotations,
+ aChildTransactions),
+
+ createSeparator: function(aContainer, aIndex)
+ new PlacesCreateSeparatorTransaction(aContainer, aIndex),
+
+ createLivemark: function(aFeedURI, aSiteURI, aName, aContainer, aIndex,
+ aAnnotations)
+ new PlacesCreateLivemarkTransaction(aFeedURI, aSiteURI, aName, aContainer,
+ aIndex, aAnnotations),
+
+ moveItem: function(aItemId, aNewContainer, aNewIndex)
+ new PlacesMoveItemTransaction(aItemId, aNewContainer, aNewIndex),
+
+ removeItem: function(aItemId)
+ new PlacesRemoveItemTransaction(aItemId),
+
+ editItemTitle: function(aItemId, aNewTitle)
+ new PlacesEditItemTitleTransaction(aItemId, aNewTitle),
+
+ editBookmarkURI: function(aItemId, aNewURI)
+ new PlacesEditBookmarkURITransaction(aItemId, aNewURI),
+
+ setItemAnnotation: function(aItemId, aAnnotationObject)
+ new PlacesSetItemAnnotationTransaction(aItemId, aAnnotationObject),
+
+ setPageAnnotation: function(aURI, aAnnotationObject)
+ new PlacesSetPageAnnotationTransaction(aURI, aAnnotationObject),
+
+ editBookmarkKeyword: function(aItemId, aNewKeyword)
+ new PlacesEditBookmarkKeywordTransaction(aItemId, aNewKeyword),
+
+ editBookmarkPostData: function(aItemId, aPostData)
+ new PlacesEditBookmarkPostDataTransaction(aItemId, aPostData),
+
+ editLivemarkSiteURI: function(aLivemarkId, aSiteURI)
+ new PlacesEditLivemarkSiteURITransaction(aLivemarkId, aSiteURI),
+
+ editLivemarkFeedURI: function(aLivemarkId, aFeedURI)
+ new PlacesEditLivemarkFeedURITransaction(aLivemarkId, aFeedURI),
+
+ editItemDateAdded: function(aItemId, aNewDateAdded)
+ new PlacesEditItemDateAddedTransaction(aItemId, aNewDateAdded),
+
+ editItemLastModified: function(aItemId, aNewLastModified)
+ new PlacesEditItemLastModifiedTransaction(aItemId, aNewLastModified),
+
+ sortFolderByName: function(aFolderId)
+ new PlacesSortFolderByNameTransaction(aFolderId),
+
+ tagURI: function(aURI, aTags)
+ new PlacesTagURITransaction(aURI, aTags),
+
+ untagURI: function(aURI, aTags)
+ new PlacesUntagURITransaction(aURI, aTags),
+
+ /**
+ * Transaction for setting/unsetting Load-in-sidebar annotation.
+ *
+ * @param aBookmarkId
+ * id of the bookmark where to set Load-in-sidebar annotation.
+ * @param aLoadInSidebar
+ * boolean value.
+ * @returns nsITransaction object.
+ */
+ setLoadInSidebar: function(aItemId, aLoadInSidebar)
+ {
+ let annoObj = { name: PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO,
+ type: Ci.nsIAnnotationService.TYPE_INT32,
+ flags: 0,
+ value: aLoadInSidebar,
+ expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
+ return new PlacesSetItemAnnotationTransaction(aItemId, annoObj);
+ },
+
+ /**
+ * Transaction for editing the description of a bookmark or a folder.
+ *
+ * @param aItemId
+ * id of the item to edit.
+ * @param aDescription
+ * new description.
+ * @returns nsITransaction object.
+ */
+ editItemDescription: function(aItemId, aDescription)
+ {
+ let annoObj = { name: PlacesUIUtils.DESCRIPTION_ANNO,
+ type: Ci.nsIAnnotationService.TYPE_STRING,
+ flags: 0,
+ value: aDescription,
+ expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
+ return new PlacesSetItemAnnotationTransaction(aItemId, annoObj);
+ },
+
+ ////////////////////////////////////////////////////////////////////////////
+ //// nsITransactionManager forwarders.
+
+ beginBatch: function()
+ PlacesUtils.transactionManager.beginBatch(null),
+
+ endBatch: function()
+ PlacesUtils.transactionManager.endBatch(false),
+
+ doTransaction: function(txn)
+ PlacesUtils.transactionManager.doTransaction(txn),
+
+ undoTransaction: function()
+ PlacesUtils.transactionManager.undoTransaction(),
+
+ redoTransaction: function()
+ PlacesUtils.transactionManager.redoTransaction(),
+
+ get numberOfUndoItems()
+ PlacesUtils.transactionManager.numberOfUndoItems,
+ get numberOfRedoItems()
+ PlacesUtils.transactionManager.numberOfRedoItems,
+ get maxTransactionCount()
+ PlacesUtils.transactionManager.maxTransactionCount,
+ set maxTransactionCount(val)
+ PlacesUtils.transactionManager.maxTransactionCount = val,
+
+ clear: function()
+ PlacesUtils.transactionManager.clear(),
+
+ peekUndoStack: function()
+ PlacesUtils.transactionManager.peekUndoStack(),
+
+ peekRedoStack: function()
+ PlacesUtils.transactionManager.peekRedoStack(),
+
+ getUndoStack: function()
+ PlacesUtils.transactionManager.getUndoStack(),
+
+ getRedoStack: function()
+ PlacesUtils.transactionManager.getRedoStack(),
+
+ AddListener: function(aListener)
+ PlacesUtils.transactionManager.AddListener(aListener),
+
+ RemoveListener: function(aListener)
+ PlacesUtils.transactionManager.RemoveListener(aListener)
+ }
+});
diff --git a/components/places/content/bookmarkProperties.js b/components/places/content/bookmarkProperties.js
new file mode 100644
index 0000000..e1d1077
--- /dev/null
+++ b/components/places/content/bookmarkProperties.js
@@ -0,0 +1,675 @@
+/* -*- 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/. */
+
+/**
+ * The panel is initialized based on data given in the js object passed
+ * as window.arguments[0]. The object must have the following fields set:
+ * @ action (String). Possible values:
+ * - "add" - for adding a new item.
+ * @ type (String). Possible values:
+ * - "bookmark"
+ * @ loadBookmarkInSidebar - optional, the default state for the
+ * "Load this bookmark in the sidebar" field.
+ * - "folder"
+ * @ URIList (Array of nsIURI objects) - optional, list of uris to
+ * be bookmarked under the new folder.
+ * - "livemark"
+ * @ uri (nsIURI object) - optional, the default uri for the new item.
+ * The property is not used for the "folder with items" type.
+ * @ title (String) - optional, the default title for the new item.
+ * @ description (String) - optional, the default description for the new
+ * item.
+ * @ defaultInsertionPoint (InsertionPoint JS object) - optional, the
+ * default insertion point for the new item.
+ * @ keyword (String) - optional, the default keyword for the new item.
+ * @ postData (String) - optional, POST data to accompany the keyword.
+ * @ charSet (String) - optional, character-set to accompany the keyword.
+ * Notes:
+ * 1) If |uri| is set for a bookmark/livemark item and |title| isn't,
+ * the dialog will query the history tables for the title associated
+ * with the given uri. If the dialog is set to adding a folder with
+ * bookmark items under it (see URIList), a default static title is
+ * used ("[Folder Name]").
+ * 2) The index field of the default insertion point is ignored if
+ * the folder picker is shown.
+ * - "edit" - for editing a bookmark item or a folder.
+ * @ type (String). Possible values:
+ * - "bookmark"
+ * @ itemId (Integer) - the id of the bookmark item.
+ * - "folder" (also applies to livemarks)
+ * @ itemId (Integer) - the id of the folder.
+ * @ hiddenRows (Strings array) - optional, list of rows to be hidden
+ * regardless of the item edited or added by the dialog.
+ * Possible values:
+ * - "title"
+ * - "location"
+ * - "description"
+ * - "keyword"
+ * - "tags"
+ * - "loadInSidebar"
+ * - "feedLocation"
+ * - "siteLocation"
+ * - "folderPicker" - hides both the tree and the menu.
+ * @ readOnly (Boolean) - optional, states if the panel should be read-only
+ *
+ * window.arguments[0].performed is set to true if any transaction has
+ * been performed by the dialog.
+ */
+
+Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+const BOOKMARK_ITEM = 0;
+const BOOKMARK_FOLDER = 1;
+const LIVEMARK_CONTAINER = 2;
+
+const ACTION_EDIT = 0;
+const ACTION_ADD = 1;
+
+var elementsHeight = new Map();
+
+var BookmarkPropertiesPanel = {
+
+ /** UI Text Strings */
+ __strings: null,
+ get _strings() {
+ if (!this.__strings) {
+ this.__strings = document.getElementById("stringBundle");
+ }
+ return this.__strings;
+ },
+
+ _action: null,
+ _itemType: null,
+ _itemId: -1,
+ _uri: null,
+ _loadInSidebar: false,
+ _title: "",
+ _description: "",
+ _URIs: [],
+ _keyword: "",
+ _postData: null,
+ _charSet: "",
+ _feedURI: null,
+ _siteURI: null,
+
+ _defaultInsertionPoint: null,
+ _hiddenRows: [],
+ _batching: false,
+ _readOnly: false,
+
+ /**
+ * This method returns the correct label for the dialog's "accept"
+ * button based on the variant of the dialog.
+ */
+ _getAcceptLabel: function BPP__getAcceptLabel() {
+ if (this._action == ACTION_ADD) {
+ if (this._URIs.length)
+ return this._strings.getString("dialogAcceptLabelAddMulti");
+
+ if (this._itemType == LIVEMARK_CONTAINER)
+ return this._strings.getString("dialogAcceptLabelAddLivemark");
+
+ if (this._dummyItem || this._loadInSidebar)
+ return this._strings.getString("dialogAcceptLabelAddItem");
+
+ return this._strings.getString("dialogAcceptLabelSaveItem");
+ }
+ return this._strings.getString("dialogAcceptLabelEdit");
+ },
+
+ /**
+ * This method returns the correct title for the current variant
+ * of this dialog.
+ */
+ _getDialogTitle: function BPP__getDialogTitle() {
+ if (this._action == ACTION_ADD) {
+ if (this._itemType == BOOKMARK_ITEM)
+ return this._strings.getString("dialogTitleAddBookmark");
+ if (this._itemType == LIVEMARK_CONTAINER)
+ return this._strings.getString("dialogTitleAddLivemark");
+
+ // add folder
+ NS_ASSERT(this._itemType == BOOKMARK_FOLDER, "Unknown item type");
+ if (this._URIs.length)
+ return this._strings.getString("dialogTitleAddMulti");
+
+ return this._strings.getString("dialogTitleAddFolder");
+ }
+ if (this._action == ACTION_EDIT) {
+ return this._strings.getFormattedString("dialogTitleEdit", [this._title]);
+ }
+ return "";
+ },
+
+ /**
+ * Determines the initial data for the item edited or added by this dialog
+ */
+ _determineItemInfo: function BPP__determineItemInfo() {
+ var dialogInfo = window.arguments[0];
+ this._action = dialogInfo.action == "add" ? ACTION_ADD : ACTION_EDIT;
+ this._hiddenRows = dialogInfo.hiddenRows ? dialogInfo.hiddenRows : [];
+ if (this._action == ACTION_ADD) {
+ NS_ASSERT("type" in dialogInfo, "missing type property for add action");
+
+ if ("title" in dialogInfo)
+ this._title = dialogInfo.title;
+
+ if ("defaultInsertionPoint" in dialogInfo) {
+ this._defaultInsertionPoint = dialogInfo.defaultInsertionPoint;
+ }
+ else
+ this._defaultInsertionPoint =
+ new InsertionPoint(PlacesUtils.bookmarksMenuFolderId,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ Ci.nsITreeView.DROP_ON);
+
+ switch (dialogInfo.type) {
+ case "bookmark":
+ this._itemType = BOOKMARK_ITEM;
+ if ("uri" in dialogInfo) {
+ NS_ASSERT(dialogInfo.uri instanceof Ci.nsIURI,
+ "uri property should be a uri object");
+ this._uri = dialogInfo.uri;
+ if (typeof(this._title) != "string") {
+ this._title = this._getURITitleFromHistory(this._uri) ||
+ this._uri.spec;
+ }
+ }
+ else {
+ this._uri = PlacesUtils._uri("about:blank");
+ this._title = this._strings.getString("newBookmarkDefault");
+ this._dummyItem = true;
+ }
+
+ if ("loadBookmarkInSidebar" in dialogInfo)
+ this._loadInSidebar = dialogInfo.loadBookmarkInSidebar;
+
+ if ("keyword" in dialogInfo) {
+ this._keyword = dialogInfo.keyword;
+ this._isAddKeywordDialog = true;
+ if ("postData" in dialogInfo)
+ this._postData = dialogInfo.postData;
+ if ("charSet" in dialogInfo)
+ this._charSet = dialogInfo.charSet;
+ }
+ break;
+
+ case "folder":
+ this._itemType = BOOKMARK_FOLDER;
+ if (!this._title) {
+ if ("URIList" in dialogInfo) {
+ this._title = this._strings.getString("bookmarkAllTabsDefault");
+ this._URIs = dialogInfo.URIList;
+ }
+ else
+ this._title = this._strings.getString("newFolderDefault");
+ this._dummyItem = true;
+ }
+ break;
+
+ case "livemark":
+ this._itemType = LIVEMARK_CONTAINER;
+ if ("feedURI" in dialogInfo)
+ this._feedURI = dialogInfo.feedURI;
+ if ("siteURI" in dialogInfo)
+ this._siteURI = dialogInfo.siteURI;
+
+ if (!this._title) {
+ if (this._feedURI) {
+ this._title = this._getURITitleFromHistory(this._feedURI) ||
+ this._feedURI.spec;
+ }
+ else
+ this._title = this._strings.getString("newLivemarkDefault");
+ }
+ }
+
+ if ("description" in dialogInfo)
+ this._description = dialogInfo.description;
+ }
+ else { // edit
+ NS_ASSERT("itemId" in dialogInfo);
+ this._itemId = dialogInfo.itemId;
+ this._title = PlacesUtils.bookmarks.getItemTitle(this._itemId);
+ this._readOnly = !!dialogInfo.readOnly;
+
+ switch (dialogInfo.type) {
+ case "bookmark":
+ this._itemType = BOOKMARK_ITEM;
+
+ this._uri = PlacesUtils.bookmarks.getBookmarkURI(this._itemId);
+ // keyword
+ this._keyword = PlacesUtils.bookmarks
+ .getKeywordForBookmark(this._itemId);
+ // Load In Sidebar
+ this._loadInSidebar = PlacesUtils.annotations
+ .itemHasAnnotation(this._itemId,
+ PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO);
+ break;
+
+ case "folder":
+ this._itemType = BOOKMARK_FOLDER;
+ PlacesUtils.livemarks.getLivemark({ id: this._itemId })
+ .then(aLivemark => {
+ this._itemType = LIVEMARK_CONTAINER;
+ this._feedURI = aLivemark.feedURI;
+ this._siteURI = aLivemark.siteURI;
+ this._fillEditProperties();
+
+ let acceptButton = document.documentElement.getButton("accept");
+ acceptButton.disabled = !this._inputIsValid();
+
+ let newHeight = window.outerHeight +
+ this._element("descriptionField").boxObject.height;
+ window.resizeTo(window.outerWidth, newHeight);
+ }, () => undefined);
+
+ break;
+ }
+
+ // Description
+ if (PlacesUtils.annotations
+ .itemHasAnnotation(this._itemId, PlacesUIUtils.DESCRIPTION_ANNO)) {
+ this._description = PlacesUtils.annotations
+ .getItemAnnotation(this._itemId,
+ PlacesUIUtils.DESCRIPTION_ANNO);
+ }
+ }
+ },
+
+ /**
+ * This method returns the title string corresponding to a given URI.
+ * If none is available from the bookmark service (probably because
+ * the given URI doesn't appear in bookmarks or history), we synthesize
+ * a title from the first 100 characters of the URI.
+ *
+ * @param aURI
+ * nsIURI object for which we want the title
+ *
+ * @returns a title string
+ */
+ _getURITitleFromHistory: function BPP__getURITitleFromHistory(aURI) {
+ NS_ASSERT(aURI instanceof Ci.nsIURI);
+
+ // get the title from History
+ return PlacesUtils.history.getPageTitle(aURI);
+ },
+
+ /**
+ * This method should be called by the onload of the Bookmark Properties
+ * dialog to initialize the state of the panel.
+ */
+ onDialogLoad: Task.async(function* BPP_onDialogLoad() {
+ this._determineItemInfo();
+
+ document.title = this._getDialogTitle();
+ var acceptButton = document.documentElement.getButton("accept");
+ acceptButton.label = this._getAcceptLabel();
+
+ // Do not use sizeToContent, otherwise, due to bug 90276, the dialog will
+ // grow at every opening.
+ // Since elements can be uncollapsed asynchronously, we must observe their
+ // mutations and resize the dialog using a cached element size.
+ this._height = window.outerHeight;
+ this._mutationObserver = new MutationObserver(mutations => {
+ for (let mutation of mutations) {
+ let target = mutation.target;
+ let id = target.id;
+ if (!/^editBMPanel_.*(Row|Checkbox)$/.test(id))
+ continue;
+
+ let collapsed = target.getAttribute("collapsed") === "true";
+ let wasCollapsed = mutation.oldValue === "true";
+ if (collapsed == wasCollapsed)
+ continue;
+
+ if (collapsed) {
+ this._height -= elementsHeight.get(id);
+ elementsHeight.delete(id);
+ } else {
+ elementsHeight.set(id, target.boxObject.height);
+ this._height += elementsHeight.get(id);
+ }
+ window.resizeTo(window.outerWidth, this._height);
+ }
+ });
+
+ this._mutationObserver.observe(document,
+ { subtree: true,
+ attributeOldValue: true,
+ attributeFilter: ["collapsed"] });
+
+ // Some controls are flexible and we want to update their cached size when
+ // the dialog is resized.
+ window.addEventListener("resize", this);
+
+ this._beginBatch();
+
+ switch (this._action) {
+ case ACTION_EDIT:
+ this._fillEditProperties();
+ acceptButton.disabled = this._readOnly;
+ break;
+ case ACTION_ADD:
+ yield this._fillAddProperties();
+ // if this is an uri related dialog disable accept button until
+ // the user fills an uri value.
+ if (this._itemType == BOOKMARK_ITEM)
+ acceptButton.disabled = !this._inputIsValid();
+ break;
+ }
+
+ if (!this._readOnly) {
+ // Listen on uri fields to enable accept button if input is valid
+ if (this._itemType == BOOKMARK_ITEM) {
+ this._element("locationField")
+ .addEventListener("input", this, false);
+ if (this._isAddKeywordDialog) {
+ this._element("keywordField")
+ .addEventListener("input", this, false);
+ }
+ }
+ else if (this._itemType == LIVEMARK_CONTAINER) {
+ this._element("feedLocationField")
+ .addEventListener("input", this, false);
+ this._element("siteLocationField")
+ .addEventListener("input", this, false);
+ }
+ }
+
+ // Ensure the Name Picker textbox is focused on load
+ var namePickerElem = document.getElementById('editBMPanel_namePicker');
+ namePickerElem.focus();
+ namePickerElem.select();
+ }),
+
+ // nsIDOMEventListener
+ handleEvent: function BPP_handleEvent(aEvent) {
+ var target = aEvent.target;
+ switch (aEvent.type) {
+ case "input":
+ if (target.id == "editBMPanel_locationField" ||
+ target.id == "editBMPanel_feedLocationField" ||
+ target.id == "editBMPanel_siteLocationField" ||
+ target.id == "editBMPanel_keywordField") {
+ // Check uri fields to enable accept button if input is valid
+ document.documentElement
+ .getButton("accept").disabled = !this._inputIsValid();
+ }
+ break;
+ case "resize":
+ for (let [id, oldHeight] of elementsHeight) {
+ let newHeight = document.getElementById(id).boxObject.height;
+ this._height += - oldHeight + newHeight;
+ elementsHeight.set(id, newHeight);
+ }
+ break;
+ }
+ },
+
+ _beginBatch: function BPP__beginBatch() {
+ if (this._batching)
+ return;
+
+ PlacesUtils.transactionManager.beginBatch(null);
+ this._batching = true;
+ },
+
+ _endBatch: function BPP__endBatch() {
+ if (!this._batching)
+ return;
+
+ PlacesUtils.transactionManager.endBatch(false);
+ this._batching = false;
+ },
+
+ _fillEditProperties: function BPP__fillEditProperties() {
+ gEditItemOverlay.initPanel(this._itemId,
+ { hiddenRows: this._hiddenRows,
+ forceReadOnly: this._readOnly });
+ },
+
+ _fillAddProperties: Task.async(function* BPP__fillAddProperties() {
+ yield this._createNewItem();
+ // Edit the new item
+ gEditItemOverlay.initPanel(this._itemId,
+ { hiddenRows: this._hiddenRows });
+ // Empty location field if the uri is about:blank, this way inserting a new
+ // url will be easier for the user, Accept button will be automatically
+ // disabled by the input listener until the user fills the field.
+ var locationField = this._element("locationField");
+ if (locationField.value == "about:blank")
+ locationField.value = "";
+ }),
+
+ // nsISupports
+ QueryInterface: function BPP_QueryInterface(aIID) {
+ if (aIID.equals(Ci.nsIDOMEventListener) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_NOINTERFACE;
+ },
+
+ _element: function BPP__element(aID) {
+ return document.getElementById("editBMPanel_" + aID);
+ },
+
+ onDialogUnload: function BPP_onDialogUnload() {
+ // gEditItemOverlay does not exist anymore here, so don't rely on it.
+ this._mutationObserver.disconnect();
+ delete this._mutationObserver;
+
+ window.removeEventListener("resize", this);
+
+ // Calling removeEventListener with arguments which do not identify any
+ // currently registered EventListener on the EventTarget has no effect.
+ this._element("locationField")
+ .removeEventListener("input", this, false);
+ this._element("feedLocationField")
+ .removeEventListener("input", this, false);
+ this._element("siteLocationField")
+ .removeEventListener("input", this, false);
+ },
+
+ onDialogAccept: function BPP_onDialogAccept() {
+ // We must blur current focused element to save its changes correctly
+ document.commandDispatcher.focusedElement.blur();
+ // The order here is important! We have to uninit the panel first, otherwise
+ // late changes could force it to commit more transactions.
+ gEditItemOverlay.uninitPanel(true);
+ this._endBatch();
+ window.arguments[0].performed = true;
+ },
+
+ onDialogCancel: function BPP_onDialogCancel() {
+ // The order here is important! We have to uninit the panel first, otherwise
+ // changes done as part of Undo may change the panel contents and by
+ // that force it to commit more transactions.
+ gEditItemOverlay.uninitPanel(true);
+ this._endBatch();
+ PlacesUtils.transactionManager.undoTransaction();
+ window.arguments[0].performed = false;
+ },
+
+ /**
+ * This method checks to see if the input fields are in a valid state.
+ *
+ * @returns true if the input is valid, false otherwise
+ */
+ _inputIsValid: function BPP__inputIsValid() {
+ if (this._itemType == BOOKMARK_ITEM &&
+ !this._containsValidURI("locationField"))
+ return false;
+ if (this._isAddKeywordDialog && !this._element("keywordField").value.length)
+ return false;
+
+ return true;
+ },
+
+ /**
+ * Determines whether the XUL textbox with the given ID contains a
+ * string that can be converted into an nsIURI.
+ *
+ * @param aTextboxID
+ * the ID of the textbox element whose contents we'll test
+ *
+ * @returns true if the textbox contains a valid URI string, false otherwise
+ */
+ _containsValidURI: function BPP__containsValidURI(aTextboxID) {
+ try {
+ var value = this._element(aTextboxID).value;
+ if (value) {
+ PlacesUIUtils.createFixedURI(value);
+ return true;
+ }
+ } catch (e) { }
+ return false;
+ },
+
+ /**
+ * [New Item Mode] Get the insertion point details for the new item, given
+ * dialog state and opening arguments.
+ *
+ * The container-identifier and insertion-index are returned separately in
+ * the form of [containerIdentifier, insertionIndex]
+ */
+ _getInsertionPointDetails: function BPP__getInsertionPointDetails() {
+ var containerId = this._defaultInsertionPoint.itemId;
+ var indexInContainer = this._defaultInsertionPoint.index;
+
+ return [containerId, indexInContainer];
+ },
+
+ /**
+ * Returns a transaction for creating a new bookmark item representing the
+ * various fields and opening arguments of the dialog.
+ */
+ _getCreateNewBookmarkTransaction:
+ function BPP__getCreateNewBookmarkTransaction(aContainer, aIndex) {
+ var annotations = [];
+ var childTransactions = [];
+
+ if (this._description) {
+ let annoObj = { name : PlacesUIUtils.DESCRIPTION_ANNO,
+ type : Ci.nsIAnnotationService.TYPE_STRING,
+ flags : 0,
+ value : this._description,
+ expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
+ let editItemTxn = new PlacesSetItemAnnotationTransaction(-1, annoObj);
+ childTransactions.push(editItemTxn);
+ }
+
+ if (this._loadInSidebar) {
+ let annoObj = { name : PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO,
+ value : true };
+ let setLoadTxn = new PlacesSetItemAnnotationTransaction(-1, annoObj);
+ childTransactions.push(setLoadTxn);
+ }
+
+ if (this._postData) {
+ let postDataTxn = new PlacesEditBookmarkPostDataTransaction(-1, this._postData);
+ childTransactions.push(postDataTxn);
+ }
+
+ //XXX TODO: this should be in a transaction!
+ if (this._charSet && !PrivateBrowsingUtils.isWindowPrivate(window))
+ PlacesUtils.setCharsetForURI(this._uri, this._charSet);
+
+ let createTxn = new PlacesCreateBookmarkTransaction(this._uri,
+ aContainer,
+ aIndex,
+ this._title,
+ this._keyword,
+ annotations,
+ childTransactions);
+
+ return new PlacesAggregatedTransaction(this._getDialogTitle(),
+ [createTxn]);
+ },
+
+ /**
+ * Returns a childItems-transactions array representing the URIList with
+ * which the dialog has been opened.
+ */
+ _getTransactionsForURIList: function BPP__getTransactionsForURIList() {
+ var transactions = [];
+ for (var i = 0; i < this._URIs.length; ++i) {
+ var uri = this._URIs[i];
+ var title = this._getURITitleFromHistory(uri);
+ var createTxn = new PlacesCreateBookmarkTransaction(uri, -1,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ title);
+ transactions.push(createTxn);
+ }
+ return transactions;
+ },
+
+ /**
+ * Returns a transaction for creating a new folder item representing the
+ * various fields and opening arguments of the dialog.
+ */
+ _getCreateNewFolderTransaction:
+ function BPP__getCreateNewFolderTransaction(aContainer, aIndex) {
+ var annotations = [];
+ var childItemsTransactions;
+ if (this._URIs.length)
+ childItemsTransactions = this._getTransactionsForURIList();
+
+ if (this._description)
+ annotations.push(this._getDescriptionAnnotation(this._description));
+
+ return new PlacesCreateFolderTransaction(this._title, aContainer,
+ aIndex, annotations,
+ childItemsTransactions);
+ },
+
+ /**
+ * Returns a transaction for creating a new live-bookmark item representing
+ * the various fields and opening arguments of the dialog.
+ */
+ _getCreateNewLivemarkTransaction:
+ function BPP__getCreateNewLivemarkTransaction(aContainer, aIndex) {
+ return new PlacesCreateLivemarkTransaction(this._feedURI, this._siteURI,
+ this._title,
+ aContainer, aIndex);
+ },
+
+ /**
+ * Dialog-accept code-path for creating a new item (any type)
+ */
+ _createNewItem: Task.async(function* BPP__getCreateItemTransaction() {
+ var [container, index] = this._getInsertionPointDetails();
+ var txn;
+
+ switch (this._itemType) {
+ case BOOKMARK_FOLDER:
+ txn = this._getCreateNewFolderTransaction(container, index);
+ break;
+ case LIVEMARK_CONTAINER:
+ txn = this._getCreateNewLivemarkTransaction(container, index);
+ break;
+ default: // BOOKMARK_ITEM
+ txn = this._getCreateNewBookmarkTransaction(container, index);
+ }
+
+ PlacesUtils.transactionManager.doTransaction(txn);
+ // This is a temporary hack until we use PlacesTransactions.jsm
+ if (txn._promise) {
+ yield txn._promise;
+ }
+
+ let folderGuid = yield PlacesUtils.promiseItemGuid(container);
+ let bm = yield PlacesUtils.bookmarks.fetch({
+ parentGuid: folderGuid,
+ index: index
+ });
+ this._itemId = yield PlacesUtils.promiseItemId(bm.guid);
+ })
+};
diff --git a/components/places/content/bookmarkProperties.xul b/components/places/content/bookmarkProperties.xul
new file mode 100644
index 0000000..2c04f8b
--- /dev/null
+++ b/components/places/content/bookmarkProperties.xul
@@ -0,0 +1,43 @@
+<?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/"?>
+<?xml-stylesheet href="chrome://browser/skin/places/editBookmarkOverlay.css"?>
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+<?xml-stylesheet href="chrome://browser/content/places/places.css"?>
+
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/places/editBookmarkOverlay.xul"?>
+
+<!DOCTYPE dialog [
+ <!ENTITY % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd">
+ %editBookmarkOverlayDTD;
+]>
+
+<dialog id="bookmarkproperties"
+ buttons="accept, cancel"
+ buttoniconaccept="save"
+ ondialogaccept="BookmarkPropertiesPanel.onDialogAccept();"
+ ondialogcancel="BookmarkPropertiesPanel.onDialogCancel();"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="BookmarkPropertiesPanel.onDialogLoad();"
+ onunload="BookmarkPropertiesPanel.onDialogUnload();"
+ style="min-width: 30em;"
+ persist="screenX screenY width">
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="stringBundle"
+ src="chrome://browser/locale/places/bookmarkProperties.properties"/>
+ </stringbundleset>
+
+ <script type="application/javascript"
+ src="chrome://browser/content/places/editBookmarkOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/places/bookmarkProperties.js"/>
+
+<vbox id="editBookmarkPanelContent"/>
+
+</dialog>
diff --git a/components/places/content/bookmarksPanel.js b/components/places/content/bookmarksPanel.js
new file mode 100644
index 0000000..c964bd0
--- /dev/null
+++ b/components/places/content/bookmarksPanel.js
@@ -0,0 +1,25 @@
+/* -*- 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/. */
+
+function init() {
+ document.getElementById("bookmarks-view").place =
+ "place:queryType=1&folder=" + window.top.PlacesUIUtils.allBookmarksFolderId;
+}
+
+function searchBookmarks(aSearchString) {
+ var tree = document.getElementById('bookmarks-view');
+ if (!aSearchString)
+ tree.place = tree.place;
+ else
+ tree.applyFilter(aSearchString,
+ [PlacesUtils.bookmarksMenuFolderId,
+ PlacesUtils.unfiledBookmarksFolderId,
+ PlacesUtils.toolbarFolderId]);
+}
+
+window.addEventListener("SidebarFocused",
+ function()
+ document.getElementById("search-box").focus(),
+ false);
diff --git a/components/places/content/bookmarksPanel.xul b/components/places/content/bookmarksPanel.xul
new file mode 100644
index 0000000..45744bb
--- /dev/null
+++ b/components/places/content/bookmarksPanel.xul
@@ -0,0 +1,55 @@
+<?xml version="1.0"?> <!-- -*- Mode: SGML; indent-tabs-mode: nil; -*- -->
+<!-- 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"?>
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+
+<!DOCTYPE page SYSTEM "chrome://browser/locale/places/places.dtd">
+
+<page id="bookmarksPanel"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="init();"
+ onunload="SidebarUtils.setMouseoverURL('');">
+
+ <script type="application/javascript"
+ src="chrome://browser/content/bookmarks/sidebarUtils.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/bookmarks/bookmarksPanel.js"/>
+
+ <commandset id="placesCommands"/>
+ <commandset id="editMenuCommands"/>
+ <keyset id="placesCommandKeys"/>
+ <menupopup id="placesContext"/>
+
+ <!-- Bookmarks and history tooltip -->
+ <tooltip id="bhTooltip"/>
+
+ <hbox id="sidebar-search-container" align="center">
+ <label id="sidebar-search-label"
+ value="&search.label;" accesskey="&search.accesskey;" control="search-box"/>
+ <textbox id="search-box" flex="1" type="search" class="compact"
+ aria-controls="bookmarks-view"
+ oncommand="searchBookmarks(this.value);"/>
+ </hbox>
+
+ <tree id="bookmarks-view" class="sidebar-placesTree" type="places"
+ flex="1"
+ hidecolumnpicker="true"
+ context="placesContext"
+ onkeypress="SidebarUtils.handleTreeKeyPress(event);"
+ onclick="SidebarUtils.handleTreeClick(this, event, true);"
+ onmousemove="SidebarUtils.handleTreeMouseMove(event);"
+ onmouseout="SidebarUtils.setMouseoverURL('');">
+ <treecols>
+ <treecol id="title" flex="1" primary="true" hideheader="true"/>
+ </treecols>
+ <treechildren id="bookmarks-view-children" view="bookmarks-view"
+ class="sidebar-placesTreechildren" flex="1" tooltip="bhTooltip"/>
+ </tree>
+</page>
diff --git a/components/places/content/browserPlacesViews.js b/components/places/content/browserPlacesViews.js
new file mode 100644
index 0000000..8b90dd2
--- /dev/null
+++ b/components/places/content/browserPlacesViews.js
@@ -0,0 +1,1754 @@
+/* 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");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+/**
+ * The base view implements everything that's common to the toolbar and
+ * menu views.
+ */
+function PlacesViewBase(aPlace) {
+ this.place = aPlace;
+ this._controller = new PlacesController(this);
+ this._viewElt.controllers.appendController(this._controller);
+}
+
+PlacesViewBase.prototype = {
+ // The xul element that holds the entire view.
+ _viewElt: null,
+ get viewElt() this._viewElt,
+
+ get associatedElement() this._viewElt,
+
+ get controllers() this._viewElt.controllers,
+
+ // The xul element that represents the root container.
+ _rootElt: null,
+
+ // Set to true for views that are represented by native widgets (i.e.
+ // the native mac menu).
+ _nativeView: false,
+
+ QueryInterface: XPCOMUtils.generateQI(
+ [Components.interfaces.nsINavHistoryResultObserver,
+ Components.interfaces.nsISupportsWeakReference]),
+
+ _place: "",
+ get place() this._place,
+ set place(val) {
+ this._place = val;
+
+ let history = PlacesUtils.history;
+ let queries = { }, options = { };
+ history.queryStringToQueries(val, queries, { }, options);
+ if (!queries.value.length)
+ queries.value = [history.getNewQuery()];
+
+ let result = history.executeQueries(queries.value, queries.value.length,
+ options.value);
+ result.addObserver(this, false);
+ return val;
+ },
+
+ _result: null,
+ get result() this._result,
+ set result(val) {
+ if (this._result == val)
+ return val;
+
+ if (this._result) {
+ this._result.removeObserver(this);
+ this._resultNode.containerOpen = false;
+ }
+
+ if (this._rootElt.localName == "menupopup")
+ this._rootElt._built = false;
+
+ this._result = val;
+ if (val) {
+ this._resultNode = val.root;
+ this._rootElt._placesNode = this._resultNode;
+ this._domNodes = new Map();
+ this._domNodes.set(this._resultNode, this._rootElt);
+
+ // This calls _rebuild through invalidateContainer.
+ this._resultNode.containerOpen = true;
+ }
+ else {
+ this._resultNode = null;
+ delete this._domNodes;
+ }
+
+ return val;
+ },
+
+ /**
+ * Gets the DOM node used for the given places node.
+ *
+ * @param aPlacesNode
+ * a places result node.
+ * @throws if there is no DOM node set for aPlacesNode.
+ */
+ _getDOMNodeForPlacesNode:
+ function PVB__getDOMNodeForPlacesNode(aPlacesNode) {
+ let node = this._domNodes.get(aPlacesNode, null);
+ if (!node) {
+ throw new Error("No DOM node set for aPlacesNode.\nnode.type: " +
+ aPlacesNode.type + ". node.parent: " + aPlacesNode);
+ }
+ return node;
+ },
+
+ get controller() this._controller,
+
+ get selType() "single",
+ selectItems: function() { },
+ selectAll: function() { },
+
+ get selectedNode() {
+ if (this._contextMenuShown) {
+ let anchor = this._contextMenuShown.triggerNode;
+ if (!anchor)
+ return null;
+
+ if (anchor._placesNode)
+ return this._rootElt == anchor ? null : anchor._placesNode;
+
+ anchor = anchor.parentNode;
+ return this._rootElt == anchor ? null : (anchor._placesNode || null);
+ }
+ return null;
+ },
+
+ get hasSelection() this.selectedNode != null,
+
+ get selectedNodes() {
+ let selectedNode = this.selectedNode;
+ return selectedNode ? [selectedNode] : [];
+ },
+
+ get removableSelectionRanges() {
+ // On static content the current selectedNode would be the selection's
+ // parent node. We don't want to allow removing a node when the
+ // selection is not explicit.
+ if (document.popupNode &&
+ (document.popupNode == "menupopup" || !document.popupNode._placesNode))
+ return [];
+
+ return [this.selectedNodes];
+ },
+
+ get draggableSelection() [this._draggedElt],
+
+ get insertionPoint() {
+ // There is no insertion point for history queries, so bail out now and
+ // save a lot of work when updating commands.
+ let resultNode = this._resultNode;
+ if (PlacesUtils.nodeIsQuery(resultNode) &&
+ PlacesUtils.asQuery(resultNode).queryOptions.queryType ==
+ Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY)
+ return null;
+
+ // By default, the insertion point is at the top level, at the end.
+ let index = PlacesUtils.bookmarks.DEFAULT_INDEX;
+ let container = this._resultNode;
+ let orientation = Ci.nsITreeView.DROP_BEFORE;
+ let isTag = false;
+
+ let selectedNode = this.selectedNode;
+ if (selectedNode) {
+ let popup = document.popupNode;
+ if (!popup._placesNode || popup._placesNode == this._resultNode ||
+ popup._placesNode.itemId == -1) {
+ // If a static menuitem is selected, or if the root node is selected,
+ // the insertion point is inside the folder, at the end.
+ container = selectedNode;
+ orientation = Ci.nsITreeView.DROP_ON;
+ }
+ else {
+ // In all other cases the insertion point is before that node.
+ container = selectedNode.parent;
+ index = container.getChildIndex(selectedNode);
+ isTag = PlacesUtils.nodeIsTagQuery(container);
+ }
+ }
+
+ if (PlacesControllerDragHelper.disallowInsertion(container))
+ return null;
+
+ return new InsertionPoint(PlacesUtils.getConcreteItemId(container),
+ index, orientation, isTag);
+ },
+
+ buildContextMenu: function PVB_buildContextMenu(aPopup) {
+ this._contextMenuShown = aPopup;
+ window.updateCommands("places");
+ return this.controller.buildContextMenu(aPopup);
+ },
+
+ destroyContextMenu: function PVB_destroyContextMenu(aPopup) {
+ this._contextMenuShown = null;
+ },
+
+ _cleanPopup: function PVB_cleanPopup(aPopup, aDelay) {
+ // Remove Places nodes from the popup.
+ let child = aPopup._startMarker;
+ while (child.nextSibling != aPopup._endMarker) {
+ let sibling = child.nextSibling;
+ if (sibling._placesNode && !aDelay) {
+ aPopup.removeChild(sibling);
+ }
+ else if (sibling._placesNode && aDelay) {
+ // HACK (bug 733419): the popups originating from the OS X native
+ // menubar don't live-update while open, thus we don't clean it
+ // until the next popupshowing, to avoid zombie menuitems.
+ if (!aPopup._delayedRemovals)
+ aPopup._delayedRemovals = [];
+ aPopup._delayedRemovals.push(sibling);
+ child = child.nextSibling;
+ }
+ else {
+ child = child.nextSibling;
+ }
+ }
+ },
+
+ _rebuildPopup: function PVB__rebuildPopup(aPopup) {
+ let resultNode = aPopup._placesNode;
+ if (!resultNode.containerOpen)
+ return;
+
+ if (this.controller.hasCachedLivemarkInfo(resultNode)) {
+ this._setEmptyPopupStatus(aPopup, false);
+ aPopup._built = true;
+ this._populateLivemarkPopup(aPopup);
+ return;
+ }
+
+ this._cleanPopup(aPopup);
+
+ let cc = resultNode.childCount;
+ if (cc > 0) {
+ this._setEmptyPopupStatus(aPopup, false);
+
+ for (let i = 0; i < cc; ++i) {
+ let child = resultNode.getChild(i);
+ this._insertNewItemToPopup(child, aPopup, null);
+ }
+ }
+ else {
+ this._setEmptyPopupStatus(aPopup, true);
+ }
+ aPopup._built = true;
+ },
+
+ _removeChild: function PVB__removeChild(aChild) {
+ // If document.popupNode pointed to this child, null it out,
+ // otherwise controller's command-updating may rely on the removed
+ // item still being "selected".
+ if (document.popupNode == aChild)
+ document.popupNode = null;
+
+ aChild.parentNode.removeChild(aChild);
+ },
+
+ _setEmptyPopupStatus:
+ function PVB__setEmptyPopupStatus(aPopup, aEmpty) {
+ if (!aPopup._emptyMenuitem) {
+ let label = PlacesUIUtils.getString("bookmarksMenuEmptyFolder");
+ aPopup._emptyMenuitem = document.createElement("menuitem");
+ aPopup._emptyMenuitem.setAttribute("label", label);
+ aPopup._emptyMenuitem.setAttribute("disabled", true);
+ }
+
+ if (aEmpty) {
+ aPopup.setAttribute("emptyplacesresult", "true");
+ // Don't add the menuitem if there is static content.
+ if (!aPopup._startMarker.previousSibling &&
+ !aPopup._endMarker.nextSibling)
+ aPopup.insertBefore(aPopup._emptyMenuitem, aPopup._endMarker);
+ }
+ else {
+ aPopup.removeAttribute("emptyplacesresult");
+ try {
+ aPopup.removeChild(aPopup._emptyMenuitem);
+ } catch (ex) {}
+ }
+ },
+
+ _createMenuItemForPlacesNode:
+ function PVB__createMenuItemForPlacesNode(aPlacesNode) {
+ this._domNodes.delete(aPlacesNode);
+
+ let element;
+ let type = aPlacesNode.type;
+ if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
+ element = document.createElement("menuseparator");
+ }
+ else {
+ let itemId = aPlacesNode.itemId;
+ if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_URI) {
+ element = document.createElement("menuitem");
+ element.className = "menuitem-iconic bookmark-item menuitem-with-favicon";
+ element.setAttribute("scheme",
+ PlacesUIUtils.guessUrlSchemeForUI(aPlacesNode.uri));
+ }
+ else if (PlacesUtils.containerTypes.indexOf(type) != -1) {
+ element = document.createElement("menu");
+ element.setAttribute("container", "true");
+
+ if (aPlacesNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY) {
+ element.setAttribute("query", "true");
+ if (PlacesUtils.nodeIsTagQuery(aPlacesNode))
+ element.setAttribute("tagContainer", "true");
+ else if (PlacesUtils.nodeIsDay(aPlacesNode))
+ element.setAttribute("dayContainer", "true");
+ else if (PlacesUtils.nodeIsHost(aPlacesNode))
+ element.setAttribute("hostContainer", "true");
+ }
+ else if (itemId != -1) {
+ PlacesUtils.livemarks.getLivemark({ id: itemId })
+ .then(aLivemark => {
+ element.setAttribute("livemark", "true");
+#ifdef XP_MACOSX
+ // OS X native menubar doesn't track list-style-images since
+ // it doesn't have a frame (bug 733415). Thus enforce updating.
+ element.setAttribute("image", "");
+ element.removeAttribute("image");
+#endif
+ this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
+ }, () => undefined);
+ }
+
+ let popup = document.createElement("menupopup");
+ popup._placesNode = PlacesUtils.asContainer(aPlacesNode);
+
+ if (!this._nativeView) {
+ popup.setAttribute("placespopup", "true");
+ }
+
+#ifdef XP_MACOSX
+ // No context menu on mac.
+ popup.setAttribute("context", "placesContext");
+#endif
+ element.appendChild(popup);
+ element.className = "menu-iconic bookmark-item";
+
+ this._domNodes.set(aPlacesNode, popup);
+ }
+ else
+ throw "Unexpected node";
+
+ element.setAttribute("label", PlacesUIUtils.getBestTitle(aPlacesNode));
+
+ let icon = aPlacesNode.icon;
+ if (icon)
+ element.setAttribute("image",
+ PlacesUIUtils.getImageURLForResolution(window, icon));
+ }
+
+ element._placesNode = aPlacesNode;
+ if (!this._domNodes.has(aPlacesNode))
+ this._domNodes.set(aPlacesNode, element);
+
+ return element;
+ },
+
+ _insertNewItemToPopup:
+ function PVB__insertNewItemToPopup(aNewChild, aPopup, aBefore) {
+ let element = this._createMenuItemForPlacesNode(aNewChild);
+ let before = aBefore || aPopup._endMarker;
+ aPopup.insertBefore(element, before);
+ return element;
+ },
+
+ _setLivemarkSiteURIMenuItem:
+ function PVB__setLivemarkSiteURIMenuItem(aPopup) {
+ let livemarkInfo = this.controller.getCachedLivemarkInfo(aPopup._placesNode);
+ let siteUrl = livemarkInfo && livemarkInfo.siteURI ?
+ livemarkInfo.siteURI.spec : null;
+ if (!siteUrl && aPopup._siteURIMenuitem) {
+ aPopup.removeChild(aPopup._siteURIMenuitem);
+ aPopup._siteURIMenuitem = null;
+ aPopup.removeChild(aPopup._siteURIMenuseparator);
+ aPopup._siteURIMenuseparator = null;
+ }
+ else if (siteUrl && !aPopup._siteURIMenuitem) {
+ // Add "Open (Feed Name)" menuitem.
+ aPopup._siteURIMenuitem = document.createElement("menuitem");
+ aPopup._siteURIMenuitem.className = "openlivemarksite-menuitem";
+ aPopup._siteURIMenuitem.setAttribute("targetURI", siteUrl);
+ aPopup._siteURIMenuitem.setAttribute("oncommand",
+ "openUILink(this.getAttribute('targetURI'), event);");
+
+ // If a user middle-clicks this item we serve the oncommand event.
+ // We are using checkForMiddleClick because of Bug 246720.
+ // Note: stopPropagation is needed to avoid serving middle-click
+ // with BT_onClick that would open all items in tabs.
+ aPopup._siteURIMenuitem.setAttribute("onclick",
+ "checkForMiddleClick(this, event); event.stopPropagation();");
+ let label =
+ PlacesUIUtils.getFormattedString("menuOpenLivemarkOrigin.label",
+ [aPopup.parentNode.getAttribute("label")])
+ aPopup._siteURIMenuitem.setAttribute("label", label);
+ aPopup.insertBefore(aPopup._siteURIMenuitem, aPopup._startMarker);
+
+ aPopup._siteURIMenuseparator = document.createElement("menuseparator");
+ aPopup.insertBefore(aPopup._siteURIMenuseparator, aPopup._startMarker);
+ }
+ },
+
+ /**
+ * Add, update or remove the livemark status menuitem.
+ * @param aPopup
+ * The livemark container popup
+ * @param aStatus
+ * The livemark status
+ */
+ _setLivemarkStatusMenuItem:
+ function PVB_setLivemarkStatusMenuItem(aPopup, aStatus) {
+ let statusMenuitem = aPopup._statusMenuitem;
+ if (!statusMenuitem) {
+ // Create the status menuitem and cache it in the popup object.
+ statusMenuitem = document.createElement("menuitem");
+ statusMenuitem.className = "livemarkstatus-menuitem";
+ statusMenuitem.setAttribute("disabled", true);
+ aPopup._statusMenuitem = statusMenuitem;
+ }
+
+ if (aStatus == Ci.mozILivemark.STATUS_LOADING ||
+ aStatus == Ci.mozILivemark.STATUS_FAILED) {
+ // Status has changed, update the cached status menuitem.
+ let stringId = aStatus == Ci.mozILivemark.STATUS_LOADING ?
+ "bookmarksLivemarkLoading" : "bookmarksLivemarkFailed";
+ statusMenuitem.setAttribute("label", PlacesUIUtils.getString(stringId));
+ if (aPopup._startMarker.nextSibling != statusMenuitem)
+ aPopup.insertBefore(statusMenuitem, aPopup._startMarker.nextSibling);
+ }
+ else {
+ // The livemark has finished loading.
+ if (aPopup._statusMenuitem.parentNode == aPopup)
+ aPopup.removeChild(aPopup._statusMenuitem);
+ }
+ },
+
+ toggleCutNode: function PVB_toggleCutNode(aPlacesNode, aValue) {
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+
+ // We may get the popup for menus, but we need the menu itself.
+ if (elt.localName == "menupopup")
+ elt = elt.parentNode;
+ if (aValue)
+ elt.setAttribute("cutting", "true");
+ else
+ elt.removeAttribute("cutting");
+ },
+
+ nodeURIChanged: function PVB_nodeURIChanged(aPlacesNode, aURIString) {
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+
+ // Here we need the <menu>.
+ if (elt.localName == "menupopup")
+ elt = elt.parentNode;
+
+ elt.setAttribute("scheme", PlacesUIUtils.guessUrlSchemeForUI(aURIString));
+ },
+
+ nodeIconChanged: function PVB_nodeIconChanged(aPlacesNode) {
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+
+ // There's no UI representation for the root node, thus there's nothing to
+ // be done when the icon changes.
+ if (elt == this._rootElt)
+ return;
+
+ // Here we need the <menu>.
+ if (elt.localName == "menupopup")
+ elt = elt.parentNode;
+
+ let icon = aPlacesNode.icon;
+ if (!icon)
+ elt.removeAttribute("image");
+ else if (icon != elt.getAttribute("image"))
+ elt.setAttribute("image",
+ PlacesUIUtils.getImageURLForResolution(window, icon));
+ },
+
+ nodeAnnotationChanged:
+ function PVB_nodeAnnotationChanged(aPlacesNode, aAnno) {
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+
+ // All livemarks have a feedURI, so use it as our indicator of a livemark
+ // being modified.
+ if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
+ let menu = elt.parentNode;
+ if (!menu.hasAttribute("livemark")) {
+ menu.setAttribute("livemark", "true");
+#ifdef XP_MACOSX
+ // OS X native menubar doesn't track list-style-images since
+ // it doesn't have a frame (bug 733415). Thus enforce updating.
+ menu.setAttribute("image", "");
+ menu.removeAttribute("image");
+#endif
+ }
+
+ PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId })
+ .then(aLivemark => {
+ // Controller will use this to build the meta data for the node.
+ this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
+ this.invalidateContainer(aPlacesNode);
+ }, () => undefined);
+ }
+ },
+
+ nodeTitleChanged:
+ function PVB_nodeTitleChanged(aPlacesNode, aNewTitle) {
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+
+ // There's no UI representation for the root node, thus there's
+ // nothing to be done when the title changes.
+ if (elt == this._rootElt)
+ return;
+
+ // Here we need the <menu>.
+ if (elt.localName == "menupopup")
+ elt = elt.parentNode;
+
+ if (!aNewTitle && elt.localName != "toolbarbutton") {
+ // Many users consider toolbars as shortcuts containers, so explicitly
+ // allow empty labels on toolbarbuttons. For any other element try to be
+ // smarter, guessing a title from the uri.
+ elt.setAttribute("label", PlacesUIUtils.getBestTitle(aPlacesNode));
+ }
+ else {
+ elt.setAttribute("label", aNewTitle);
+ }
+ },
+
+ nodeRemoved:
+ function PVB_nodeRemoved(aParentPlacesNode, aPlacesNode, aIndex) {
+ let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+
+ // Here we need the <menu>.
+ if (elt.localName == "menupopup")
+ elt = elt.parentNode;
+
+ if (parentElt._built) {
+ parentElt.removeChild(elt);
+
+ // Figure out if we need to show the "<Empty>" menu-item.
+ // TODO Bug 517701: This doesn't seem to handle the case of an empty
+ // root.
+ if (parentElt._startMarker.nextSibling == parentElt._endMarker)
+ this._setEmptyPopupStatus(parentElt, true);
+ }
+ },
+
+ nodeHistoryDetailsChanged:
+ function PVB_nodeHistoryDetailsChanged(aPlacesNode, aTime, aCount) {
+ if (aPlacesNode.parent &&
+ this.controller.hasCachedLivemarkInfo(aPlacesNode.parent)) {
+ // Find the node in the parent.
+ let popup = this._getDOMNodeForPlacesNode(aPlacesNode.parent);
+ for (let child = popup._startMarker.nextSibling;
+ child != popup._endMarker;
+ child = child.nextSibling) {
+ if (child._placesNode && child._placesNode.uri == aPlacesNode.uri) {
+ if (aCount)
+ child.setAttribute("visited", "true");
+ else
+ child.removeAttribute("visited");
+ break;
+ }
+ }
+ }
+ },
+
+ nodeTagsChanged: function() { },
+ nodeDateAddedChanged: function() { },
+ nodeLastModifiedChanged: function() { },
+ nodeKeywordChanged: function() { },
+ sortingChanged: function() { },
+ batching: function() { },
+
+ nodeInserted:
+ function PVB_nodeInserted(aParentPlacesNode, aPlacesNode, aIndex) {
+ let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
+ if (!parentElt._built)
+ return;
+
+ let index = Array.indexOf(parentElt.childNodes, parentElt._startMarker) +
+ aIndex + 1;
+ this._insertNewItemToPopup(aPlacesNode, parentElt,
+ parentElt.childNodes[index]);
+ this._setEmptyPopupStatus(parentElt, false);
+ },
+
+ nodeMoved:
+ function PBV_nodeMoved(aPlacesNode,
+ aOldParentPlacesNode, aOldIndex,
+ aNewParentPlacesNode, aNewIndex) {
+ // Note: the current implementation of moveItem does not actually
+ // use this notification when the item in question is moved from one
+ // folder to another. Instead, it calls nodeRemoved and nodeInserted
+ // for the two folders. Thus, we can assume old-parent == new-parent.
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+
+ // Here we need the <menu>.
+ if (elt.localName == "menupopup")
+ elt = elt.parentNode;
+
+ // If our root node is a folder, it might be moved. There's nothing
+ // we need to do in that case.
+ if (elt == this._rootElt)
+ return;
+
+ let parentElt = this._getDOMNodeForPlacesNode(aNewParentPlacesNode);
+ if (parentElt._built) {
+ // Move the node.
+ parentElt.removeChild(elt);
+ let index = Array.indexOf(parentElt.childNodes, parentElt._startMarker) +
+ aNewIndex + 1;
+ parentElt.insertBefore(elt, parentElt.childNodes[index]);
+ }
+ },
+
+ containerStateChanged:
+ function PVB_containerStateChanged(aPlacesNode, aOldState, aNewState) {
+ if (aNewState == Ci.nsINavHistoryContainerResultNode.STATE_OPENED ||
+ aNewState == Ci.nsINavHistoryContainerResultNode.STATE_CLOSED) {
+ this.invalidateContainer(aPlacesNode);
+
+ if (PlacesUtils.nodeIsFolder(aPlacesNode)) {
+ let queryOptions = PlacesUtils.asQuery(this._result.root).queryOptions;
+ if (queryOptions.excludeItems) {
+ return;
+ }
+
+ PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId })
+ .then(aLivemark => {
+ let shouldInvalidate =
+ !this.controller.hasCachedLivemarkInfo(aPlacesNode);
+ this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
+ if (aNewState == Ci.nsINavHistoryContainerResultNode.STATE_OPENED) {
+ aLivemark.registerForUpdates(aPlacesNode, this);
+ // Prioritize the current livemark.
+ aLivemark.reload();
+ PlacesUtils.livemarks.reloadLivemarks();
+ if (shouldInvalidate)
+ this.invalidateContainer(aPlacesNode);
+ }
+ else {
+ aLivemark.unregisterForUpdates(aPlacesNode);
+ }
+ }, () => undefined);
+ }
+ }
+ },
+
+ _populateLivemarkPopup: function PVB__populateLivemarkPopup(aPopup)
+ {
+ this._setLivemarkSiteURIMenuItem(aPopup);
+ // Show the loading status only if there are no entries yet.
+ if (aPopup._startMarker.nextSibling == aPopup._endMarker)
+ this._setLivemarkStatusMenuItem(aPopup, Ci.mozILivemark.STATUS_LOADING);
+
+ PlacesUtils.livemarks.getLivemark({ id: aPopup._placesNode.itemId })
+ .then(aLivemark => {
+ let placesNode = aPopup._placesNode;
+ if (!placesNode.containerOpen)
+ return;
+
+ if (aLivemark.status != Ci.mozILivemark.STATUS_LOADING)
+ this._setLivemarkStatusMenuItem(aPopup, aLivemark.status);
+ this._cleanPopup(aPopup,
+ this._nativeView && aPopup.parentNode.hasAttribute("open"));
+
+ let children = aLivemark.getNodesForContainer(placesNode);
+ for (let i = 0; i < children.length; i++) {
+ let child = children[i];
+ this.nodeInserted(placesNode, child, i);
+ if (child.accessCount)
+ this._getDOMNodeForPlacesNode(child).setAttribute("visited", true);
+ else
+ this._getDOMNodeForPlacesNode(child).removeAttribute("visited");
+ }
+ }, Components.utils.reportError);
+ },
+
+ invalidateContainer: function PVB_invalidateContainer(aPlacesNode) {
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+ elt._built = false;
+
+ // If the menupopup is open we should live-update it.
+ if (elt.parentNode.open)
+ this._rebuildPopup(elt);
+ },
+
+ uninit: function PVB_uninit() {
+ if (this._result) {
+ this._result.removeObserver(this);
+ this._resultNode.containerOpen = false;
+ this._resultNode = null;
+ this._result = null;
+ }
+
+ if (this._controller) {
+ this._controller.terminate();
+ // Removing the controller will fail if it is already no longer there.
+ // This can happen if the view element was removed/reinserted without
+ // our knowledge. There is no way to check for that having happened
+ // without the possibility of an exception. :-(
+ try {
+ this._viewElt.controllers.removeController(this._controller);
+ } catch (ex) {
+ } finally {
+ this._controller = null;
+ }
+ }
+
+ delete this._viewElt._placesView;
+ },
+
+ get isRTL() {
+ if ("_isRTL" in this)
+ return this._isRTL;
+
+ return this._isRTL = document.defaultView
+ .getComputedStyle(this.viewElt, "")
+ .direction == "rtl";
+ },
+
+ get ownerWindow() window,
+
+ /**
+ * Adds an "Open All in Tabs" menuitem to the bottom of the popup.
+ * @param aPopup
+ * a Places popup.
+ */
+ _mayAddCommandsItems: function PVB__mayAddCommandsItems(aPopup) {
+ // The command items are never added to the root popup.
+ if (aPopup == this._rootElt)
+ return;
+
+ let hasMultipleURIs = false;
+
+ // Check if the popup contains at least 2 menuitems with places nodes.
+ // We don't currently support opening multiple uri nodes when they are not
+ // populated by the result.
+ if (aPopup._placesNode.childCount > 0) {
+ let currentChild = aPopup.firstChild;
+ let numURINodes = 0;
+ while (currentChild) {
+ if (currentChild.localName == "menuitem" && currentChild._placesNode) {
+ if (++numURINodes == 2)
+ break;
+ }
+ currentChild = currentChild.nextSibling;
+ }
+ hasMultipleURIs = numURINodes > 1;
+ }
+
+ let isLiveMark = false;
+ if (this.controller.hasCachedLivemarkInfo(aPopup._placesNode)) {
+ hasMultipleURIs = true;
+ isLiveMark = true;
+ }
+
+ if (!hasMultipleURIs) {
+ // We don't have to show any option.
+ if (aPopup._endOptOpenAllInTabs) {
+ aPopup.removeChild(aPopup._endOptOpenAllInTabs);
+ aPopup._endOptOpenAllInTabs = null;
+
+ aPopup.removeChild(aPopup._endOptSeparator);
+ aPopup._endOptSeparator = null;
+ }
+ }
+ else if (!aPopup._endOptOpenAllInTabs) {
+ // Create a separator before options.
+ aPopup._endOptSeparator = document.createElement("menuseparator");
+ aPopup._endOptSeparator.className = "bookmarks-actions-menuseparator";
+ aPopup.appendChild(aPopup._endOptSeparator);
+
+ // Add the "Open All in Tabs" menuitem.
+ aPopup._endOptOpenAllInTabs = document.createElement("menuitem");
+ aPopup._endOptOpenAllInTabs.className = "openintabs-menuitem";
+ if (isLiveMark) {
+ aPopup._endOptOpenAllInTabs.setAttribute("oncommand",
+ "PlacesUIUtils.openLiveMarkNodesInTabs(this.parentNode._placesNode, event, " +
+ "PlacesUIUtils.getViewForNode(this));");
+ } else {
+ aPopup._endOptOpenAllInTabs.setAttribute("oncommand",
+ "PlacesUIUtils.openContainerNodeInTabs(this.parentNode._placesNode, event, " +
+ "PlacesUIUtils.getViewForNode(this));");
+ }
+ aPopup._endOptOpenAllInTabs.setAttribute("onclick",
+ "checkForMiddleClick(this, event); event.stopPropagation();");
+ aPopup._endOptOpenAllInTabs.setAttribute("label",
+ gNavigatorBundle.getString("menuOpenAllInTabs.label"));
+ aPopup.appendChild(aPopup._endOptOpenAllInTabs);
+ }
+ },
+
+ _ensureMarkers: function PVB__ensureMarkers(aPopup) {
+ if (aPopup._startMarker)
+ return;
+
+ // _startMarker is an hidden menuseparator that lives before places nodes.
+ aPopup._startMarker = document.createElement("menuseparator");
+ aPopup._startMarker.hidden = true;
+ aPopup.insertBefore(aPopup._startMarker, aPopup.firstChild);
+
+ // _endMarker is an hidden menuseparator that lives after places nodes.
+ aPopup._endMarker = document.createElement("menuseparator");
+ aPopup._endMarker.hidden = true;
+ aPopup.appendChild(aPopup._endMarker);
+
+ // Move the markers to the right position.
+ let firstNonStaticNodeFound = false;
+ for (let i = 0; i < aPopup.childNodes.length; i++) {
+ let child = aPopup.childNodes[i];
+ // Menus that have static content at the end, but are initially empty,
+ // use a special "builder" attribute to figure out where to start
+ // inserting places nodes.
+ if (child.getAttribute("builder") == "end") {
+ aPopup.insertBefore(aPopup._endMarker, child);
+ break;
+ }
+
+ if (child._placesNode && !firstNonStaticNodeFound) {
+ firstNonStaticNodeFound = true;
+ aPopup.insertBefore(aPopup._startMarker, child);
+ }
+ }
+ if (!firstNonStaticNodeFound) {
+ aPopup.insertBefore(aPopup._startMarker, aPopup._endMarker);
+ }
+ },
+
+ _onPopupShowing: function PVB__onPopupShowing(aEvent) {
+ // Avoid handling popupshowing of inner views.
+ let popup = aEvent.originalTarget;
+
+ this._ensureMarkers(popup);
+
+ // Remove any delayed element, see _cleanPopup for details.
+ if ("_delayedRemovals" in popup) {
+ while (popup._delayedRemovals.length > 0) {
+ popup.removeChild(popup._delayedRemovals.shift());
+ }
+ }
+
+ if (popup._placesNode && PlacesUIUtils.getViewForNode(popup) == this) {
+ if (!popup._placesNode.containerOpen)
+ popup._placesNode.containerOpen = true;
+ if (!popup._built)
+ this._rebuildPopup(popup);
+
+ this._mayAddCommandsItems(popup);
+ }
+ },
+
+ _addEventListeners:
+ function PVB__addEventListeners(aObject, aEventNames, aCapturing) {
+ for (let i = 0; i < aEventNames.length; i++) {
+ aObject.addEventListener(aEventNames[i], this, aCapturing);
+ }
+ },
+
+ _removeEventListeners:
+ function PVB__removeEventListeners(aObject, aEventNames, aCapturing) {
+ for (let i = 0; i < aEventNames.length; i++) {
+ aObject.removeEventListener(aEventNames[i], this, aCapturing);
+ }
+ },
+};
+
+function PlacesToolbar(aPlace) {
+ let startTime = Date.now();
+ // Add some smart getters for our elements.
+ let thisView = this;
+ [
+ ["_viewElt", "PlacesToolbar"],
+ ["_rootElt", "PlacesToolbarItems"],
+ ["_dropIndicator", "PlacesToolbarDropIndicator"],
+ ["_chevron", "PlacesChevron"],
+ ["_chevronPopup", "PlacesChevronPopup"]
+ ].forEach(function (elementGlobal) {
+ let [name, id] = elementGlobal;
+ thisView.__defineGetter__(name, function () {
+ let element = document.getElementById(id);
+ if (!element)
+ return null;
+
+ delete thisView[name];
+ return thisView[name] = element;
+ });
+ });
+
+ this._viewElt._placesView = this;
+
+ this._addEventListeners(this._viewElt, this._cbEvents, false);
+ this._addEventListeners(this._rootElt, ["popupshowing", "popuphidden"], true);
+ this._addEventListeners(this._rootElt, ["overflow", "underflow"], true);
+ this._addEventListeners(window, ["resize", "unload"], false);
+
+ // If personal-bookmarks has been dragged to the tabs toolbar,
+ // we have to track addition and removals of tabs, to properly
+ // recalculate the available space for bookmarks.
+ // TODO (bug 734730): Use a performant mutation listener when available.
+ if (this._viewElt.parentNode.parentNode == document.getElementById("TabsToolbar")) {
+ this._addEventListeners(gBrowser.tabContainer, ["TabOpen", "TabClose"], false);
+ }
+
+ PlacesViewBase.call(this, aPlace);
+}
+
+PlacesToolbar.prototype = {
+ __proto__: PlacesViewBase.prototype,
+
+ _cbEvents: ["dragstart", "dragover", "dragexit", "dragend", "drop",
+ "mousemove", "mouseover", "mouseout"],
+
+ QueryInterface: function PT_QueryInterface(aIID) {
+ if (aIID.equals(Ci.nsIDOMEventListener) ||
+ aIID.equals(Ci.nsITimerCallback))
+ return this;
+
+ return PlacesViewBase.prototype.QueryInterface.apply(this, arguments);
+ },
+
+ uninit: function PT_uninit() {
+ this._removeEventListeners(this._viewElt, this._cbEvents, false);
+ this._removeEventListeners(this._rootElt, ["popupshowing", "popuphidden"],
+ true);
+ this._removeEventListeners(this._rootElt, ["overflow", "underflow"], true);
+ this._removeEventListeners(window, ["resize", "unload"], false);
+ this._removeEventListeners(gBrowser.tabContainer, ["TabOpen", "TabClose"], false);
+
+ PlacesViewBase.prototype.uninit.apply(this, arguments);
+ },
+
+ _openedMenuButton: null,
+ _allowPopupShowing: true,
+
+ _rebuild: function PT__rebuild() {
+ // Clear out references to existing nodes, since they will be removed
+ // and re-added.
+ if (this._overFolder.elt)
+ this._clearOverFolder();
+
+ this._openedMenuButton = null;
+ while (this._rootElt.hasChildNodes()) {
+ this._rootElt.removeChild(this._rootElt.firstChild);
+ }
+
+ let cc = this._resultNode.childCount;
+ for (let i = 0; i < cc; ++i) {
+ this._insertNewItem(this._resultNode.getChild(i), null);
+ }
+
+ if (this._chevronPopup.hasAttribute("type")) {
+ // Chevron has already been initialized, but since we are forcing
+ // a rebuild of the toolbar, it has to be rebuilt.
+ // Otherwise, it will be initialized when the toolbar overflows.
+ this._chevronPopup.place = this.place;
+ }
+ },
+
+ _insertNewItem:
+ function PT__insertNewItem(aChild, aBefore) {
+ this._domNodes.delete(aChild);
+
+ let type = aChild.type;
+ let button;
+ if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
+ button = document.createElement("toolbarseparator");
+ }
+ else {
+ button = document.createElement("toolbarbutton");
+ button.className = "bookmark-item";
+ button.setAttribute("label", aChild.title || "");
+ let icon = aChild.icon;
+ if (icon)
+ button.setAttribute("image",
+ PlacesUIUtils.getImageURLForResolution(window, icon));
+
+ if (PlacesUtils.containerTypes.indexOf(type) != -1) {
+ button.setAttribute("type", "menu");
+ button.setAttribute("container", "true");
+
+ if (PlacesUtils.nodeIsQuery(aChild)) {
+ button.setAttribute("query", "true");
+ if (PlacesUtils.nodeIsTagQuery(aChild))
+ button.setAttribute("tagContainer", "true");
+ }
+ else if (PlacesUtils.nodeIsFolder(aChild)) {
+ PlacesUtils.livemarks.getLivemark({ id: aChild.itemId })
+ .then(aLivemark => {
+ button.setAttribute("livemark", "true");
+ this.controller.cacheLivemarkInfo(aChild, aLivemark);
+ }, () => undefined);
+ }
+
+ let popup = document.createElement("menupopup");
+ popup.setAttribute("placespopup", "true");
+ button.appendChild(popup);
+ popup._placesNode = PlacesUtils.asContainer(aChild);
+#ifndef XP_MACOSX
+ popup.setAttribute("context", "placesContext");
+#endif
+
+ this._domNodes.set(aChild, popup);
+ }
+ else if (PlacesUtils.nodeIsURI(aChild)) {
+ button.setAttribute("scheme",
+ PlacesUIUtils.guessUrlSchemeForUI(aChild.uri));
+ }
+ }
+
+ button._placesNode = aChild;
+ if (!this._domNodes.has(aChild))
+ this._domNodes.set(aChild, button);
+
+ if (aBefore)
+ this._rootElt.insertBefore(button, aBefore);
+ else
+ this._rootElt.appendChild(button);
+ },
+
+ _updateChevronPopupNodesVisibility:
+ function PT__updateChevronPopupNodesVisibility() {
+ for (let i = 0, node = this._chevronPopup._startMarker.nextSibling;
+ node != this._chevronPopup._endMarker;
+ i++, node = node.nextSibling) {
+ node.hidden = this._rootElt.childNodes[i].style.visibility != "hidden";
+ }
+ },
+
+ _onChevronPopupShowing:
+ function PT__onChevronPopupShowing(aEvent) {
+ // Handle popupshowing only for the chevron popup, not for nested ones.
+ if (aEvent.target != this._chevronPopup)
+ return;
+
+ if (!this._chevron._placesView)
+ this._chevron._placesView = new PlacesMenu(aEvent, this.place);
+
+ this._updateChevronPopupNodesVisibility();
+ },
+
+ handleEvent: function PT_handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "unload":
+ this.uninit();
+ break;
+ case "resize":
+ // This handler updates nodes visibility in both the toolbar
+ // and the chevron popup when a window resize does not change
+ // the overflow status of the toolbar.
+ this.updateChevron();
+ break;
+ case "overflow":
+ if (aEvent.target != aEvent.currentTarget)
+ return;
+
+ // Ignore purely vertical overflows.
+ if (aEvent.detail == 0)
+ return;
+
+ // Attach the popup binding to the chevron popup if it has not yet
+ // been initialized.
+ if (!this._chevronPopup.hasAttribute("type")) {
+ this._chevronPopup.setAttribute("place", this.place);
+ this._chevronPopup.setAttribute("type", "places");
+ }
+ this._chevron.collapsed = false;
+ this.updateChevron();
+ break;
+ case "underflow":
+ if (aEvent.target != aEvent.currentTarget)
+ return;
+
+ // Ignore purely vertical underflows.
+ if (aEvent.detail == 0)
+ return;
+
+ this.updateChevron();
+ this._chevron.collapsed = true;
+ break;
+ case "TabOpen":
+ case "TabClose":
+ this.updateChevron();
+ break;
+ case "dragstart":
+ this._onDragStart(aEvent);
+ break;
+ case "dragover":
+ this._onDragOver(aEvent);
+ break;
+ case "dragexit":
+ this._onDragExit(aEvent);
+ break;
+ case "dragend":
+ this._onDragEnd(aEvent);
+ break;
+ case "drop":
+ this._onDrop(aEvent);
+ break;
+ case "mouseover":
+ this._onMouseOver(aEvent);
+ break;
+ case "mousemove":
+ this._onMouseMove(aEvent);
+ break;
+ case "mouseout":
+ this._onMouseOut(aEvent);
+ break;
+ case "popupshowing":
+ this._onPopupShowing(aEvent);
+ break;
+ case "popuphidden":
+ this._onPopupHidden(aEvent);
+ break;
+ default:
+ throw "Trying to handle unexpected event.";
+ }
+ },
+
+ updateChevron: function PT_updateChevron() {
+ // If the chevron is collapsed there's nothing to update.
+ if (this._chevron.collapsed)
+ return;
+
+ // Update the chevron on a timer. This will avoid repeated work when
+ // lot of changes happen in a small timeframe.
+ if (this._updateChevronTimer)
+ this._updateChevronTimer.cancel();
+
+ this._updateChevronTimer = this._setTimer(100);
+ },
+
+ _updateChevronTimerCallback: function PT__updateChevronTimerCallback() {
+ let scrollRect = this._rootElt.getBoundingClientRect();
+ let childOverflowed = false;
+ for (let i = 0; i < this._rootElt.childNodes.length; i++) {
+ let child = this._rootElt.childNodes[i];
+ // Once a child overflows, all the next ones will.
+ if (!childOverflowed) {
+ let childRect = child.getBoundingClientRect();
+ childOverflowed = this.isRTL ? (childRect.left < scrollRect.left)
+ : (childRect.right > scrollRect.right);
+
+ }
+ child.style.visibility = childOverflowed ? "hidden" : "visible";
+ }
+
+ // We rebuild the chevron on popupShowing, so if it is open
+ // we must update it.
+ if (this._chevron.open)
+ this._updateChevronPopupNodesVisibility();
+ },
+
+ nodeInserted:
+ function PT_nodeInserted(aParentPlacesNode, aPlacesNode, aIndex) {
+ let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
+ if (parentElt == this._rootElt) {
+ let children = this._rootElt.childNodes;
+ this._insertNewItem(aPlacesNode,
+ aIndex < children.length ? children[aIndex] : null);
+ this.updateChevron();
+ return;
+ }
+
+ PlacesViewBase.prototype.nodeInserted.apply(this, arguments);
+ },
+
+ nodeRemoved:
+ function PT_nodeRemoved(aParentPlacesNode, aPlacesNode, aIndex) {
+ let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+
+ // Here we need the <menu>.
+ if (elt.localName == "menupopup")
+ elt = elt.parentNode;
+
+ if (parentElt == this._rootElt) {
+ this._removeChild(elt);
+ this.updateChevron();
+ return;
+ }
+
+ PlacesViewBase.prototype.nodeRemoved.apply(this, arguments);
+ },
+
+ nodeMoved:
+ function PT_nodeMoved(aPlacesNode,
+ aOldParentPlacesNode, aOldIndex,
+ aNewParentPlacesNode, aNewIndex) {
+ let parentElt = this._getDOMNodeForPlacesNode(aNewParentPlacesNode);
+ if (parentElt == this._rootElt) {
+ // Container is on the toolbar.
+
+ // Move the element.
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+
+ // Here we need the <menu>.
+ if (elt.localName == "menupopup")
+ elt = elt.parentNode;
+
+ this._removeChild(elt);
+ this._rootElt.insertBefore(elt, this._rootElt.childNodes[aNewIndex]);
+
+ // The chevron view may get nodeMoved after the toolbar. In such a case,
+ // we should ensure (by manually swapping menuitems) that the actual nodes
+ // are in the final position before updateChevron tries to updates their
+ // visibility, or the chevron may go out of sync.
+ // Luckily updateChevron runs on a timer, so, by the time it updates
+ // nodes, the menu has already handled the notification.
+
+ this.updateChevron();
+ return;
+ }
+
+ PlacesViewBase.prototype.nodeMoved.apply(this, arguments);
+ },
+
+ nodeAnnotationChanged:
+ function PT_nodeAnnotationChanged(aPlacesNode, aAnno) {
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+ if (elt == this._rootElt)
+ return;
+
+ // We're notified for the menupopup, not the containing toolbarbutton.
+ if (elt.localName == "menupopup")
+ elt = elt.parentNode;
+
+ if (elt.parentNode == this._rootElt) {
+ // Node is on the toolbar.
+
+ // All livemarks have a feedURI, so use it as our indicator.
+ if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
+ elt.setAttribute("livemark", true);
+
+ PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId })
+ .then(aLivemark => {
+ this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
+ this.invalidateContainer(aPlacesNode);
+ }, Components.utils.reportError);
+ }
+ }
+ else {
+ // Node is in a submenu.
+ PlacesViewBase.prototype.nodeAnnotationChanged.apply(this, arguments);
+ }
+ },
+
+ nodeTitleChanged: function PT_nodeTitleChanged(aPlacesNode, aNewTitle) {
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+
+ // There's no UI representation for the root node, thus there's
+ // nothing to be done when the title changes.
+ if (elt == this._rootElt)
+ return;
+
+ PlacesViewBase.prototype.nodeTitleChanged.apply(this, arguments);
+
+ // Here we need the <menu>.
+ if (elt.localName == "menupopup")
+ elt = elt.parentNode;
+
+ if (elt.parentNode == this._rootElt) {
+ // Node is on the toolbar
+ this.updateChevron();
+ }
+ },
+
+ invalidateContainer: function PT_invalidateContainer(aPlacesNode) {
+ let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
+ if (elt == this._rootElt) {
+ // Container is the toolbar itself.
+ this._rebuild();
+ return;
+ }
+
+ PlacesViewBase.prototype.invalidateContainer.apply(this, arguments);
+ },
+
+ _overFolder: { elt: null,
+ openTimer: null,
+ hoverTime: 350,
+ closeTimer: null },
+
+ _clearOverFolder: function PT__clearOverFolder() {
+ // The mouse is no longer dragging over the stored menubutton.
+ // Close the menubutton, clear out drag styles, and clear all
+ // timers for opening/closing it.
+ if (this._overFolder.elt && this._overFolder.elt.lastChild) {
+ if (!this._overFolder.elt.lastChild.hasAttribute("dragover")) {
+ this._overFolder.elt.lastChild.hidePopup();
+ }
+ this._overFolder.elt.removeAttribute("dragover");
+ this._overFolder.elt = null;
+ }
+ if (this._overFolder.openTimer) {
+ this._overFolder.openTimer.cancel();
+ this._overFolder.openTimer = null;
+ }
+ if (this._overFolder.closeTimer) {
+ this._overFolder.closeTimer.cancel();
+ this._overFolder.closeTimer = null;
+ }
+ },
+
+ /**
+ * This function returns information about where to drop when dragging over
+ * the toolbar. The returned object has the following properties:
+ * - ip: the insertion point for the bookmarks service.
+ * - beforeIndex: child index to drop before, for the drop indicator.
+ * - folderElt: the folder to drop into, if applicable.
+ */
+ _getDropPoint: function PT__getDropPoint(aEvent) {
+ let result = this.result;
+ if (!PlacesUtils.nodeIsFolder(this._resultNode))
+ return null;
+
+ let dropPoint = { ip: null, beforeIndex: null, folderElt: null };
+ let elt = aEvent.target;
+ if (elt._placesNode && elt != this._rootElt &&
+ elt.localName != "menupopup") {
+ let eltRect = elt.getBoundingClientRect();
+ let eltIndex = Array.indexOf(this._rootElt.childNodes, elt);
+ if (PlacesUtils.nodeIsFolder(elt._placesNode) &&
+ !PlacesUIUtils.isContentsReadOnly(elt._placesNode)) {
+ // This is a folder.
+ // If we are in the middle of it, drop inside it.
+ // Otherwise, drop before it, with regards to RTL mode.
+ let threshold = eltRect.width * 0.25;
+ if (this.isRTL ? (aEvent.clientX > eltRect.right - threshold)
+ : (aEvent.clientX < eltRect.left + threshold)) {
+ // Drop before this folder.
+ dropPoint.ip =
+ new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode),
+ eltIndex, Ci.nsITreeView.DROP_BEFORE);
+ dropPoint.beforeIndex = eltIndex;
+ }
+ else if (this.isRTL ? (aEvent.clientX > eltRect.left + threshold)
+ : (aEvent.clientX < eltRect.right - threshold)) {
+ // Drop inside this folder.
+ dropPoint.ip =
+ new InsertionPoint(PlacesUtils.getConcreteItemId(elt._placesNode),
+ -1, Ci.nsITreeView.DROP_ON,
+ PlacesUtils.nodeIsTagQuery(elt._placesNode));
+ dropPoint.beforeIndex = eltIndex;
+ dropPoint.folderElt = elt;
+ }
+ else {
+ // Drop after this folder.
+ let beforeIndex =
+ (eltIndex == this._rootElt.childNodes.length - 1) ?
+ -1 : eltIndex + 1;
+
+ dropPoint.ip =
+ new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode),
+ beforeIndex, Ci.nsITreeView.DROP_BEFORE);
+ dropPoint.beforeIndex = beforeIndex;
+ }
+ }
+ else {
+ // This is a non-folder node or a read-only folder.
+ // Drop before it with regards to RTL mode.
+ let threshold = eltRect.width * 0.5;
+ if (this.isRTL ? (aEvent.clientX > eltRect.left + threshold)
+ : (aEvent.clientX < eltRect.left + threshold)) {
+ // Drop before this bookmark.
+ dropPoint.ip =
+ new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode),
+ eltIndex, Ci.nsITreeView.DROP_BEFORE);
+ dropPoint.beforeIndex = eltIndex;
+ }
+ else {
+ // Drop after this bookmark.
+ let beforeIndex =
+ eltIndex == this._rootElt.childNodes.length - 1 ?
+ -1 : eltIndex + 1;
+ dropPoint.ip =
+ new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode),
+ beforeIndex, Ci.nsITreeView.DROP_BEFORE);
+ dropPoint.beforeIndex = beforeIndex;
+ }
+ }
+ }
+ else {
+ // We are most likely dragging on the empty area of the
+ // toolbar, we should drop after the last node.
+ dropPoint.ip =
+ new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode),
+ -1, Ci.nsITreeView.DROP_BEFORE);
+ dropPoint.beforeIndex = -1;
+ }
+
+ return dropPoint;
+ },
+
+ _setTimer: function PT_setTimer(aTime) {
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(this, aTime, timer.TYPE_ONE_SHOT);
+ return timer;
+ },
+
+ notify: function PT_notify(aTimer) {
+ if (aTimer == this._updateChevronTimer) {
+ this._updateChevronTimer = null;
+ this._updateChevronTimerCallback();
+ }
+
+ // * Timer to turn off indicator bar.
+ else if (aTimer == this._ibTimer) {
+ this._dropIndicator.collapsed = true;
+ this._ibTimer = null;
+ }
+
+ // * Timer to open a menubutton that's being dragged over.
+ else if (aTimer == this._overFolder.openTimer) {
+ // Set the autoopen attribute on the folder's menupopup so that
+ // the menu will automatically close when the mouse drags off of it.
+ this._overFolder.elt.lastChild.setAttribute("autoopened", "true");
+ this._overFolder.elt.open = true;
+ this._overFolder.openTimer = null;
+ }
+
+ // * Timer to close a menubutton that's been dragged off of.
+ else if (aTimer == this._overFolder.closeTimer) {
+ // Close the menubutton if we are not dragging over it or one of
+ // its children. The autoopened attribute will let the menu know to
+ // close later if the menu is still being dragged over.
+ let currentPlacesNode = PlacesControllerDragHelper.currentDropTarget;
+ let inHierarchy = false;
+ while (currentPlacesNode) {
+ if (currentPlacesNode == this._rootElt) {
+ inHierarchy = true;
+ break;
+ }
+ currentPlacesNode = currentPlacesNode.parentNode;
+ }
+ // The _clearOverFolder() function will close the menu for
+ // _overFolder.elt. So null it out if we don't want to close it.
+ if (inHierarchy)
+ this._overFolder.elt = null;
+
+ // Clear out the folder and all associated timers.
+ this._clearOverFolder();
+ }
+ },
+
+ _onMouseOver: function PT__onMouseOver(aEvent) {
+ let button = aEvent.target;
+ if (button.parentNode == this._rootElt && button._placesNode &&
+ PlacesUtils.nodeIsURI(button._placesNode))
+ window.XULBrowserWindow.setOverLink(aEvent.target._placesNode.uri, null);
+ },
+
+ _onMouseOut: function PT__onMouseOut(aEvent) {
+ window.XULBrowserWindow.setOverLink("", null);
+ },
+
+ _cleanupDragDetails: function PT__cleanupDragDetails() {
+ // Called on dragend and drop.
+ PlacesControllerDragHelper.currentDropTarget = null;
+ this._draggedElt = null;
+ if (this._ibTimer)
+ this._ibTimer.cancel();
+
+ this._dropIndicator.collapsed = true;
+ },
+
+ _onDragStart: function PT__onDragStart(aEvent) {
+ // Sub menus have their own d&d handlers.
+ let draggedElt = aEvent.target;
+ if (draggedElt.parentNode != this._rootElt || !draggedElt._placesNode)
+ return;
+
+ if (draggedElt.localName == "toolbarbutton" &&
+ draggedElt.getAttribute("type") == "menu") {
+ // If the drag gesture on a container is toward down we open instead
+ // of dragging.
+ let translateY = this._cachedMouseMoveEvent.clientY - aEvent.clientY;
+ let translateX = this._cachedMouseMoveEvent.clientX - aEvent.clientX;
+ if ((translateY) >= Math.abs(translateX/2)) {
+ // Don't start the drag.
+ aEvent.preventDefault();
+ // Open the menu.
+ draggedElt.open = true;
+ return;
+ }
+
+ // If the menu is open, close it.
+ if (draggedElt.open) {
+ draggedElt.lastChild.hidePopup();
+ draggedElt.open = false;
+ }
+ }
+
+ // Activate the view and cache the dragged element.
+ this._draggedElt = draggedElt._placesNode;
+ this._rootElt.focus();
+
+ this._controller.setDataTransfer(aEvent);
+ aEvent.stopPropagation();
+ },
+
+ _onDragOver: function PT__onDragOver(aEvent) {
+ // Cache the dataTransfer
+ PlacesControllerDragHelper.currentDropTarget = aEvent.target;
+ let dt = aEvent.dataTransfer;
+
+ let dropPoint = this._getDropPoint(aEvent);
+ if (!dropPoint || !dropPoint.ip ||
+ !PlacesControllerDragHelper.canDrop(dropPoint.ip, dt)) {
+ this._dropIndicator.collapsed = true;
+ aEvent.stopPropagation();
+ return;
+ }
+
+ if (this._ibTimer) {
+ this._ibTimer.cancel();
+ this._ibTimer = null;
+ }
+
+ if (dropPoint.folderElt || aEvent.originalTarget == this._chevron) {
+ // Dropping over a menubutton or chevron button.
+ // Set styles and timer to open relative menupopup.
+ let overElt = dropPoint.folderElt || this._chevron;
+ if (this._overFolder.elt != overElt) {
+ this._clearOverFolder();
+ this._overFolder.elt = overElt;
+ this._overFolder.openTimer = this._setTimer(this._overFolder.hoverTime);
+ }
+ if (!this._overFolder.elt.hasAttribute("dragover"))
+ this._overFolder.elt.setAttribute("dragover", "true");
+
+ this._dropIndicator.collapsed = true;
+ }
+ else {
+ // Dragging over a normal toolbarbutton,
+ // show indicator bar and move it to the appropriate drop point.
+ let ind = this._dropIndicator;
+ let halfInd = ind.clientWidth / 2;
+ let translateX;
+ if (this.isRTL) {
+ halfInd = Math.ceil(halfInd);
+ translateX = 0 - this._rootElt.getBoundingClientRect().right - halfInd;
+ if (this._rootElt.firstChild) {
+ if (dropPoint.beforeIndex == -1)
+ translateX += this._rootElt.lastChild.getBoundingClientRect().left;
+ else {
+ translateX += this._rootElt.childNodes[dropPoint.beforeIndex]
+ .getBoundingClientRect().right;
+ }
+ }
+ }
+ else {
+ halfInd = Math.floor(halfInd);
+ translateX = 0 - this._rootElt.getBoundingClientRect().left +
+ halfInd;
+ if (this._rootElt.firstChild) {
+ if (dropPoint.beforeIndex == -1)
+ translateX += this._rootElt.lastChild.getBoundingClientRect().right;
+ else {
+ translateX += this._rootElt.childNodes[dropPoint.beforeIndex]
+ .getBoundingClientRect().left;
+ }
+ }
+ }
+
+ ind.style.transform = "translate(" + Math.round(translateX) + "px)";
+ ind.style.MozMarginStart = (-ind.clientWidth) + "px";
+ ind.collapsed = false;
+
+ // Clear out old folder information.
+ this._clearOverFolder();
+ }
+
+ aEvent.preventDefault();
+ aEvent.stopPropagation();
+ },
+
+ _onDrop: function PT__onDrop(aEvent) {
+ PlacesControllerDragHelper.currentDropTarget = aEvent.target;
+
+ let dropPoint = this._getDropPoint(aEvent);
+ if (dropPoint && dropPoint.ip) {
+ PlacesControllerDragHelper.onDrop(dropPoint.ip, aEvent.dataTransfer)
+ aEvent.preventDefault();
+ }
+
+ this._cleanupDragDetails();
+ aEvent.stopPropagation();
+ },
+
+ _onDragExit: function PT__onDragExit(aEvent) {
+ PlacesControllerDragHelper.currentDropTarget = null;
+
+ // Set timer to turn off indicator bar (if we turn it off
+ // here, dragenter might be called immediately after, creating
+ // flicker).
+ if (this._ibTimer)
+ this._ibTimer.cancel();
+ this._ibTimer = this._setTimer(10);
+
+ // If we hovered over a folder, close it now.
+ if (this._overFolder.elt)
+ this._overFolder.closeTimer = this._setTimer(this._overFolder.hoverTime);
+ },
+
+ _onDragEnd: function PT_onDragEnd(aEvent) {
+ this._cleanupDragDetails();
+ },
+
+ _onPopupShowing: function PT__onPopupShowing(aEvent) {
+ if (!this._allowPopupShowing) {
+ this._allowPopupShowing = true;
+ aEvent.preventDefault();
+ return;
+ }
+
+ let parent = aEvent.target.parentNode;
+ if (parent.localName == "toolbarbutton")
+ this._openedMenuButton = parent;
+
+ PlacesViewBase.prototype._onPopupShowing.apply(this, arguments);
+ },
+
+ _onPopupHidden: function PT__onPopupHidden(aEvent) {
+ let popup = aEvent.target;
+ let placesNode = popup._placesNode;
+ // Avoid handling popuphidden of inner views
+ if (placesNode && PlacesUIUtils.getViewForNode(popup) == this) {
+ // UI performance: folder queries are cheap, keep the resultnode open
+ // so we don't rebuild its contents whenever the popup is reopened.
+ // Though, we want to always close feed containers so their expiration
+ // status will be checked at next opening.
+ if (!PlacesUtils.nodeIsFolder(placesNode) ||
+ this.controller.hasCachedLivemarkInfo(placesNode)) {
+ placesNode.containerOpen = false;
+ }
+ }
+
+ let parent = popup.parentNode;
+ if (parent.localName == "toolbarbutton") {
+ this._openedMenuButton = null;
+ // Clear the dragover attribute if present, if we are dragging into a
+ // folder in the hierachy of current opened popup we don't clear
+ // this attribute on clearOverFolder. See Notify for closeTimer.
+ if (parent.hasAttribute("dragover"))
+ parent.removeAttribute("dragover");
+ }
+ },
+
+ _onMouseMove: function PT__onMouseMove(aEvent) {
+ // Used in dragStart to prevent dragging folders when dragging down.
+ this._cachedMouseMoveEvent = aEvent;
+
+ if (this._openedMenuButton == null ||
+ PlacesControllerDragHelper.getSession())
+ return;
+
+ let target = aEvent.originalTarget;
+ if (this._openedMenuButton != target &&
+ target.localName == "toolbarbutton" &&
+ target.type == "menu") {
+ this._openedMenuButton.open = false;
+ target.open = true;
+ }
+ }
+};
+
+/**
+ * View for Places menus. This object should be created during the first
+ * popupshowing that's dispatched on the menu.
+ */
+function PlacesMenu(aPopupShowingEvent, aPlace) {
+ this._rootElt = aPopupShowingEvent.target; // <menupopup>
+ this._viewElt = this._rootElt.parentNode; // <menu>
+ this._viewElt._placesView = this;
+ this._addEventListeners(this._rootElt, ["popupshowing", "popuphidden"], true);
+ this._addEventListeners(window, ["unload"], false);
+
+#ifdef XP_MACOSX
+ // Must walk up to support views in sub-menus, like Bookmarks Toolbar menu.
+ for (let elt = this._viewElt.parentNode; elt; elt = elt.parentNode) {
+ if (elt.localName == "menubar") {
+ this._nativeView = true;
+ break;
+ }
+ }
+#endif
+
+ PlacesViewBase.call(this, aPlace);
+ this._onPopupShowing(aPopupShowingEvent);
+}
+
+PlacesMenu.prototype = {
+ __proto__: PlacesViewBase.prototype,
+
+ QueryInterface: function PM_QueryInterface(aIID) {
+ if (aIID.equals(Ci.nsIDOMEventListener))
+ return this;
+
+ return PlacesViewBase.prototype.QueryInterface.apply(this, arguments);
+ },
+
+ _removeChild: function PM_removeChild(aChild) {
+ PlacesViewBase.prototype._removeChild.apply(this, arguments);
+ },
+
+ uninit: function PM_uninit() {
+ this._removeEventListeners(this._rootElt, ["popupshowing", "popuphidden"],
+ true);
+ this._removeEventListeners(window, ["unload"], false);
+
+ PlacesViewBase.prototype.uninit.apply(this, arguments);
+ },
+
+ handleEvent: function PM_handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "unload":
+ this.uninit();
+ break;
+ case "popupshowing":
+ this._onPopupShowing(aEvent);
+ break;
+ case "popuphidden":
+ this._onPopupHidden(aEvent);
+ break;
+ }
+ },
+
+ _onPopupHidden: function PM__onPopupHidden(aEvent) {
+ // Avoid handling popuphidden of inner views.
+ let popup = aEvent.originalTarget;
+ let placesNode = popup._placesNode;
+ if (!placesNode || PlacesUIUtils.getViewForNode(popup) != this)
+ return;
+
+ // UI performance: folder queries are cheap, keep the resultnode open
+ // so we don't rebuild its contents whenever the popup is reopened.
+ // Though, we want to always close feed containers so their expiration
+ // status will be checked at next opening.
+ if (!PlacesUtils.nodeIsFolder(placesNode) ||
+ this.controller.hasCachedLivemarkInfo(placesNode))
+ placesNode.containerOpen = false;
+
+ // The autoopened attribute is set for folders which have been
+ // automatically opened when dragged over. Turn off this attribute
+ // when the folder closes because it is no longer applicable.
+ popup.removeAttribute("autoopened");
+ popup.removeAttribute("dragstart");
+ }
+};
+
diff --git a/components/places/content/controller.js b/components/places/content/controller.js
new file mode 100644
index 0000000..f4e272e
--- /dev/null
+++ b/components/places/content/controller.js
@@ -0,0 +1,1895 @@
+/* -*- 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/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/ForgetAboutSite.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+// XXXmano: we should move most/all of these constants to PlacesUtils
+const ORGANIZER_ROOT_BOOKMARKS = "place:folder=BOOKMARKS_MENU&excludeItems=1&queryType=1";
+
+// No change to the view, preserve current selection
+const RELOAD_ACTION_NOTHING = 0;
+// Inserting items new to the view, select the inserted rows
+const RELOAD_ACTION_INSERT = 1;
+// Removing items from the view, select the first item after the last selected
+const RELOAD_ACTION_REMOVE = 2;
+// Moving items within a view, don't treat the dropped items as additional
+// rows.
+const RELOAD_ACTION_MOVE = 3;
+
+// When removing a bunch of pages we split them in chunks to give some breath
+// to the main-thread.
+const REMOVE_PAGES_CHUNKLEN = 300;
+
+/**
+ * Represents an insertion point within a container where we can insert
+ * items.
+ * @param aItemId
+ * The identifier of the parent container
+ * @param aIndex
+ * The index within the container where we should insert
+ * @param aOrientation
+ * The orientation of the insertion. NOTE: the adjustments to the
+ * insertion point to accommodate the orientation should be done by
+ * the person who constructs the IP, not the user. The orientation
+ * is provided for informational purposes only!
+ * @param [optional] aIsTag
+ * Indicates if parent container is a tag
+ * @param [optional] aDropNearItemId
+ * When defined we will calculate index based on this itemId
+ * @constructor
+ */
+function InsertionPoint(aItemId, aIndex, aOrientation, aIsTag,
+ aDropNearItemId) {
+ this.itemId = aItemId;
+ this._index = aIndex;
+ this.orientation = aOrientation;
+ this.isTag = aIsTag;
+ this.dropNearItemId = aDropNearItemId;
+}
+
+InsertionPoint.prototype = {
+ set index(val) {
+ return this._index = val;
+ },
+
+ get index() {
+ if (this.dropNearItemId > 0) {
+ // If dropNearItemId is set up we must calculate the real index of
+ // the item near which we will drop.
+ var index = PlacesUtils.bookmarks.getItemIndex(this.dropNearItemId);
+ return this.orientation == Ci.nsITreeView.DROP_BEFORE ? index : index + 1;
+ }
+ return this._index;
+ }
+};
+
+/**
+ * Places Controller
+ */
+
+function PlacesController(aView) {
+ this._view = aView;
+ XPCOMUtils.defineLazyServiceGetter(this, "clipboard",
+ "@mozilla.org/widget/clipboard;1",
+ "nsIClipboard");
+ XPCOMUtils.defineLazyGetter(this, "profileName", function () {
+ return Services.dirsvc.get("ProfD", Ci.nsIFile).leafName;
+ });
+
+ this._cachedLivemarkInfoObjects = new Map();
+}
+
+PlacesController.prototype = {
+ /**
+ * The places view.
+ */
+ _view: null,
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIClipboardOwner
+ ]),
+
+ // nsIClipboardOwner
+ LosingOwnership: function PC_LosingOwnership (aXferable) {
+ this.cutNodes = [];
+ },
+
+ terminate: function PC_terminate() {
+ this._releaseClipboardOwnership();
+ },
+
+ supportsCommand: function PC_supportsCommand(aCommand) {
+ // Non-Places specific commands that we also support
+ switch (aCommand) {
+ case "cmd_undo":
+ case "cmd_redo":
+ case "cmd_cut":
+ case "cmd_copy":
+ case "cmd_paste":
+ case "cmd_delete":
+ case "cmd_selectAll":
+ return true;
+ }
+
+ // All other Places Commands are prefixed with "placesCmd_" ... this
+ // filters out other commands that we do _not_ support (see 329587).
+ const CMD_PREFIX = "placesCmd_";
+ return (aCommand.substr(0, CMD_PREFIX.length) == CMD_PREFIX);
+ },
+
+ isCommandEnabled: function PC_isCommandEnabled(aCommand) {
+ switch (aCommand) {
+ case "cmd_undo":
+ return PlacesUtils.transactionManager.numberOfUndoItems > 0;
+ case "cmd_redo":
+ return PlacesUtils.transactionManager.numberOfRedoItems > 0;
+ case "cmd_cut":
+ case "placesCmd_cut":
+ case "placesCmd_moveBookmarks":
+ for (let node of this._view.selectedNodes) {
+ // If selection includes history nodes or tags-as-bookmark, disallow
+ // cutting.
+ if (node.itemId == -1 ||
+ (node.parent && PlacesUtils.nodeIsTagQuery(node.parent))) {
+ return false;
+ }
+ }
+ // Otherwise fall through to the cmd_delete check.
+ case "cmd_delete":
+ case "placesCmd_delete":
+ case "placesCmd_deleteDataHost":
+ return this._hasRemovableSelection();
+ case "cmd_copy":
+ case "placesCmd_copy":
+ return this._view.hasSelection;
+ case "cmd_paste":
+ case "placesCmd_paste":
+ return this._canInsert(true) && this._isClipboardDataPasteable();
+ case "cmd_selectAll":
+ if (this._view.selType != "single") {
+ let rootNode = this._view.result.root;
+ if (rootNode.containerOpen && rootNode.childCount > 0)
+ return true;
+ }
+ return false;
+ case "placesCmd_open":
+ case "placesCmd_open:window":
+ case "placesCmd_open:privatewindow":
+ case "placesCmd_open:tab":
+ var selectedNode = this._view.selectedNode;
+ return selectedNode && PlacesUtils.nodeIsURI(selectedNode);
+ case "placesCmd_new:folder":
+ case "placesCmd_new:livemark":
+ return this._canInsert();
+ case "placesCmd_new:bookmark":
+ return this._canInsert();
+ case "placesCmd_new:separator":
+ return this._canInsert() &&
+ !PlacesUtils.asQuery(this._view.result.root).queryOptions.excludeItems &&
+ this._view.result.sortingMode ==
+ Ci.nsINavHistoryQueryOptions.SORT_BY_NONE;
+ case "placesCmd_show:info":
+ var selectedNode = this._view.selectedNode;
+ return selectedNode && PlacesUtils.getConcreteItemId(selectedNode) != -1
+ case "placesCmd_reload":
+ // Livemark containers
+ var selectedNode = this._view.selectedNode;
+ return selectedNode && this.hasCachedLivemarkInfo(selectedNode);
+ case "placesCmd_sortBy:name":
+ var selectedNode = this._view.selectedNode;
+ return selectedNode &&
+ PlacesUtils.nodeIsFolder(selectedNode) &&
+ !PlacesUIUtils.isContentsReadOnly(selectedNode) &&
+ this._view.result.sortingMode ==
+ Ci.nsINavHistoryQueryOptions.SORT_BY_NONE;
+ case "placesCmd_createBookmark":
+ var node = this._view.selectedNode;
+ return node && PlacesUtils.nodeIsURI(node) && node.itemId == -1;
+ case "placesCmd_openParentFolder":
+ return true;
+ default:
+ return false;
+ }
+ },
+
+ doCommand: function PC_doCommand(aCommand) {
+ switch (aCommand) {
+ case "cmd_undo":
+ PlacesUtils.transactionManager.undoTransaction();
+ break;
+ case "cmd_redo":
+ PlacesUtils.transactionManager.redoTransaction();
+ break;
+ case "cmd_cut":
+ case "placesCmd_cut":
+ this.cut();
+ break;
+ case "cmd_copy":
+ case "placesCmd_copy":
+ this.copy();
+ break;
+ case "cmd_paste":
+ case "placesCmd_paste":
+ this.paste();
+ break;
+ case "cmd_delete":
+ case "placesCmd_delete":
+ this.remove("Remove Selection");
+ break;
+ case "placesCmd_deleteDataHost":
+ var host;
+ if (PlacesUtils.nodeIsHost(this._view.selectedNode)) {
+ var queries = this._view.selectedNode.getQueries();
+ host = queries[0].domain;
+ }
+ else
+ host = NetUtil.newURI(this._view.selectedNode.uri).host;
+ ForgetAboutSite.removeDataFromDomain(host)
+ .catch(Components.utils.reportError);
+ break;
+ case "cmd_selectAll":
+ this.selectAll();
+ break;
+ case "placesCmd_open":
+ PlacesUIUtils.openNodeIn(this._view.selectedNode, "current", this._view);
+ break;
+ case "placesCmd_open:window":
+ PlacesUIUtils.openNodeIn(this._view.selectedNode, "window", this._view);
+ break;
+ case "placesCmd_open:privatewindow":
+ PlacesUIUtils.openNodeIn(this._view.selectedNode, "window", this._view, true);
+ break;
+ case "placesCmd_open:tab":
+ PlacesUIUtils.openNodeIn(this._view.selectedNode, "tab", this._view);
+ break;
+ case "placesCmd_new:folder":
+ this.newItem("folder");
+ break;
+ case "placesCmd_new:bookmark":
+ this.newItem("bookmark");
+ break;
+ case "placesCmd_new:livemark":
+ this.newItem("livemark");
+ break;
+ case "placesCmd_new:separator":
+ this.newSeparator();
+ break;
+ case "placesCmd_show:info":
+ this.showBookmarkPropertiesForSelection();
+ break;
+ case "placesCmd_moveBookmarks":
+ this.moveSelectedBookmarks();
+ break;
+ case "placesCmd_reload":
+ this.reloadSelectedLivemark();
+ break;
+ case "placesCmd_sortBy:name":
+ this.sortFolderByName();
+ break;
+ case "placesCmd_createBookmark":
+ let node = this._view.selectedNode;
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: "bookmark"
+ , hiddenRows: [ "description"
+ , "keyword"
+ , "location"
+ , "loadInSidebar" ]
+ , uri: NetUtil.newURI(node.uri)
+ , title: node.title
+ }, window.top);
+ break;
+ case "placesCmd_openParentFolder":
+ this.openParentFolder();
+ break;
+ }
+ },
+
+ onEvent: function PC_onEvent(eventName) { },
+
+
+ /**
+ * Determine whether or not the selection can be removed, either by the
+ * delete or cut operations based on whether or not any of its contents
+ * are non-removable. We don't need to worry about recursion here since it
+ * is a policy decision that a removable item not be placed inside a non-
+ * removable item.
+ * @returns true if all nodes in the selection can be removed,
+ * false otherwise.
+ */
+ _hasRemovableSelection() {
+ var ranges = this._view.removableSelectionRanges;
+ if (!ranges.length)
+ return false;
+
+ var root = this._view.result.root;
+
+ for (var j = 0; j < ranges.length; j++) {
+ var nodes = ranges[j];
+ for (var i = 0; i < nodes.length; ++i) {
+ // Disallow removing the view's root node
+ if (nodes[i] == root)
+ return false;
+
+ if (!PlacesUIUtils.canUserRemove(nodes[i]))
+ return false;
+ }
+ }
+
+ return true;
+ },
+
+ /**
+ * Determines whether or not nodes can be inserted relative to the selection.
+ */
+ _canInsert: function PC__canInsert(isPaste) {
+ var ip = this._view.insertionPoint;
+ return ip != null && (isPaste || ip.isTag != true);
+ },
+
+ /**
+ * Looks at the data on the clipboard to see if it is paste-able.
+ * Paste-able data is:
+ * - in a format that the view can receive
+ * @returns true if: - clipboard data is of a TYPE_X_MOZ_PLACE_* flavor,
+ - clipboard data is of type TEXT_UNICODE and
+ is a valid URI.
+ */
+ _isClipboardDataPasteable: function PC__isClipboardDataPasteable() {
+ // if the clipboard contains TYPE_X_MOZ_PLACE_* data, it is definitely
+ // pasteable, with no need to unwrap all the nodes.
+
+ var flavors = PlacesControllerDragHelper.placesFlavors;
+ var clipboard = this.clipboard;
+ var hasPlacesData =
+ clipboard.hasDataMatchingFlavors(flavors, flavors.length,
+ Ci.nsIClipboard.kGlobalClipboard);
+ if (hasPlacesData)
+ return this._view.insertionPoint != null;
+
+ // if the clipboard doesn't have TYPE_X_MOZ_PLACE_* data, we also allow
+ // pasting of valid "text/unicode" and "text/x-moz-url" data
+ var xferable = Cc["@mozilla.org/widget/transferable;1"].
+ createInstance(Ci.nsITransferable);
+ xferable.init(null);
+
+ xferable.addDataFlavor(PlacesUtils.TYPE_X_MOZ_URL);
+ xferable.addDataFlavor(PlacesUtils.TYPE_UNICODE);
+ clipboard.getData(xferable, Ci.nsIClipboard.kGlobalClipboard);
+
+ try {
+ // getAnyTransferData will throw if no data is available.
+ var data = { }, type = { };
+ xferable.getAnyTransferData(type, data, { });
+ data = data.value.QueryInterface(Ci.nsISupportsString).data;
+ if (type.value != PlacesUtils.TYPE_X_MOZ_URL &&
+ type.value != PlacesUtils.TYPE_UNICODE)
+ return false;
+
+ // unwrapNodes() will throw if the data blob is malformed.
+ var unwrappedNodes = PlacesUtils.unwrapNodes(data, type.value);
+ return this._view.insertionPoint != null;
+ }
+ catch (e) {
+ // getAnyTransferData or unwrapNodes failed
+ return false;
+ }
+ },
+
+ /**
+ * Gathers information about the selected nodes according to the following
+ * rules:
+ * "link" node is a URI
+ * "bookmark" node is a bookmark
+ * "livemarkChild" node is a child of a livemark
+ * "tagChild" node is a child of a tag
+ * "folder" node is a folder
+ * "query" node is a query
+ * "separator" node is a separator line
+ * "host" node is a host
+ *
+ * @returns an array of objects corresponding the selected nodes. Each
+ * object has each of the properties above set if its corresponding
+ * node matches the rule. In addition, the annotations names for each
+ * node are set on its corresponding object as properties.
+ * Notes:
+ * 1) This can be slow, so don't call it anywhere performance critical!
+ */
+ _buildSelectionMetadata: function PC__buildSelectionMetadata() {
+ var metadata = [];
+ var nodes = this._view.selectedNodes;
+
+ for (var i = 0; i < nodes.length; i++) {
+ var nodeData = {};
+ var node = nodes[i];
+ var nodeType = node.type;
+ var uri = null;
+
+ // We don't use the nodeIs* methods here to avoid going through the type
+ // property way too often
+ switch (nodeType) {
+ case Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY:
+ nodeData["query"] = true;
+ if (node.parent) {
+ switch (PlacesUtils.asQuery(node.parent).queryOptions.resultType) {
+ case Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY:
+ nodeData["host"] = true;
+ break;
+ case Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY:
+ case Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY:
+ nodeData["day"] = true;
+ break;
+ }
+ }
+ break;
+ case Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER:
+ case Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT:
+ nodeData["folder"] = true;
+ break;
+ case Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR:
+ nodeData["separator"] = true;
+ break;
+ case Ci.nsINavHistoryResultNode.RESULT_TYPE_URI:
+ nodeData["link"] = true;
+ uri = NetUtil.newURI(node.uri);
+ if (PlacesUtils.nodeIsBookmark(node)) {
+ nodeData["bookmark"] = true;
+ var parentNode = node.parent;
+ if (parentNode) {
+ if (PlacesUtils.nodeIsTagQuery(parentNode))
+ nodeData["tagChild"] = true;
+ }
+ } else {
+ var parentNode = node.parent;
+ if (parentNode) {
+ if (this.hasCachedLivemarkInfo(parentNode))
+ nodeData["livemarkChild"] = true;
+ }
+ }
+ break;
+ }
+
+ // annotations
+ if (uri) {
+ let names = PlacesUtils.annotations.getPageAnnotationNames(uri);
+ for (let j = 0; j < names.length; ++j)
+ nodeData[names[j]] = true;
+ }
+
+ // For items also include the item-specific annotations
+ if (node.itemId != -1) {
+ let names = PlacesUtils.annotations
+ .getItemAnnotationNames(node.itemId);
+ for (let j = 0; j < names.length; ++j)
+ nodeData[names[j]] = true;
+ }
+ metadata.push(nodeData);
+ }
+
+ return metadata;
+ },
+
+ /**
+ * Determines if a context-menu item should be shown
+ * @param aMenuItem
+ * the context menu item
+ * @param aMetaData
+ * meta data about the selection
+ * @returns true if the conditions (see buildContextMenu) are satisfied
+ * and the item can be displayed, false otherwise.
+ */
+ _shouldShowMenuItem: function PC__shouldShowMenuItem(aMenuItem, aMetaData) {
+ var selectiontype = aMenuItem.getAttribute("selectiontype");
+ if (!selectiontype) {
+ selectiontype = "single|multiple";
+ }
+ var selectionTypes = selectiontype.split("|");
+ if (selectionTypes.indexOf("any") != -1) {
+ return true;
+ }
+ var count = aMetaData.length;
+ if (count > 1 && selectionTypes.indexOf("multiple") == -1)
+ return false;
+ if (count == 1 && selectionTypes.indexOf("single") == -1)
+ return false;
+ // NB: if there is no selection, we show the item if (and only if)
+ // the selectiontype includes 'none' - the metadata list will be
+ // empty so none of the other criteria will apply anyway.
+ if (count == 0)
+ return selectionTypes.indexOf("none") != -1;
+
+ var forceHideAttr = aMenuItem.getAttribute("forcehideselection");
+ if (forceHideAttr) {
+ var forceHideRules = forceHideAttr.split("|");
+ for (let i = 0; i < aMetaData.length; ++i) {
+ for (let j = 0; j < forceHideRules.length; ++j) {
+ if (forceHideRules[j] in aMetaData[i])
+ return false;
+ }
+ }
+ }
+
+ var selectionAttr = aMenuItem.getAttribute("selection");
+ if (!selectionAttr) {
+ return !aMenuItem.hidden;
+ }
+
+ if (selectionAttr == "any")
+ return true;
+
+ var showRules = selectionAttr.split("|");
+ var anyMatched = false;
+ function metaDataNodeMatches(metaDataNode, rules) {
+ for (var i = 0; i < rules.length; i++) {
+ if (rules[i] in metaDataNode)
+ return true;
+ }
+ return false;
+ }
+
+ for (var i = 0; i < aMetaData.length; ++i) {
+ if (metaDataNodeMatches(aMetaData[i], showRules))
+ anyMatched = true;
+ else
+ return false;
+ }
+ return anyMatched;
+ },
+
+ /**
+ * Detects information (meta-data rules) about the current selection in the
+ * view (see _buildSelectionMetadata) and sets the visibility state for each
+ * of the menu-items in the given popup with the following rules applied:
+ * 1) The "selectiontype" attribute may be set on a menu-item to "single"
+ * if the menu-item should be visible only if there is a single node
+ * selected, or to "multiple" if the menu-item should be visible only if
+ * multiple nodes are selected, or to "none" if the menuitems should be
+ * visible for if there are no selected nodes, or to a |-separated
+ * combination of these.
+ * If the attribute is not set or set to an invalid value, the menu-item
+ * may be visible irrespective of the selection.
+ * 2) The "selection" attribute may be set on a menu-item to the various
+ * meta-data rules for which it may be visible. The rules should be
+ * separated with the | character.
+ * 3) A menu-item may be visible only if at least one of the rules set in
+ * its selection attribute apply to each of the selected nodes in the
+ * view.
+ * 4) The "forcehideselection" attribute may be set on a menu-item to rules
+ * for which it should be hidden. This attribute takes priority over the
+ * selection attribute. A menu-item would be hidden if at least one of the
+ * given rules apply to one of the selected nodes. The rules should be
+ * separated with the | character.
+ * 5) The "hideifnoinsertionpoint" attribute may be set on a menu-item to
+ * true if it should be hidden when there's no insertion point
+ * 6) The visibility state of a menu-item is unchanged if none of these
+ * attribute are set.
+ * 7) These attributes should not be set on separators for which the
+ * visibility state is "auto-detected."
+ * 8) The "hideifprivatebrowsing" attribute may be set on a menu-item to
+ * true if it should be hidden inside the private browsing mode
+ * @param aPopup
+ * The menupopup to build children into.
+ * @return true if at least one item is visible, false otherwise.
+ */
+ buildContextMenu: function PC_buildContextMenu(aPopup) {
+ var metadata = this._buildSelectionMetadata();
+ var ip = this._view.insertionPoint;
+ var noIp = !ip || ip.isTag;
+
+ var separator = null;
+ var visibleItemsBeforeSep = false;
+ var usableItemCount = 0;
+ for (var i = 0; i < aPopup.childNodes.length; ++i) {
+ var item = aPopup.childNodes[i];
+ if (item.localName != "menuseparator") {
+ // We allow pasting into tag containers, so special case that.
+ var hideIfNoIP = item.getAttribute("hideifnoinsertionpoint") == "true" &&
+ noIp && !(ip && ip.isTag && item.id == "placesContext_paste");
+ // Show the "Open Containing Folder" menu-item only when the context is
+ // in the Library or in the Sidebar, and only when there's no insertion
+ // point.
+ var hideParentFolderItem = item.id == "placesContext_openParentFolder" &&
+ (!/tree/i.test(this._view.localName) || ip);
+ var hideIfPrivate = item.getAttribute("hideifprivatebrowsing") == "true" &&
+ PrivateBrowsingUtils.isWindowPrivate(window);
+ var shouldHideItem = hideIfNoIP || hideIfPrivate || hideParentFolderItem ||
+ !this._shouldShowMenuItem(item, metadata);
+ item.hidden = item.disabled = shouldHideItem;
+
+ if (!item.hidden) {
+ visibleItemsBeforeSep = true;
+ usableItemCount++;
+
+ // Show the separator above the menu-item if any
+ if (separator) {
+ separator.hidden = false;
+ separator = null;
+ }
+ }
+ }
+ else { // menuseparator
+ // Initially hide it. It will be unhidden if there will be at least one
+ // visible menu-item above and below it.
+ item.hidden = true;
+
+ // We won't show the separator at all if no items are visible above it
+ if (visibleItemsBeforeSep)
+ separator = item;
+
+ // New separator, count again:
+ visibleItemsBeforeSep = false;
+ }
+ }
+
+ // Set Open Folder/Links In Tabs items enabled state if they're visible
+ if (usableItemCount > 0) {
+ var openContainerInTabsItem = document.getElementById("placesContext_openContainer:tabs");
+ if (!openContainerInTabsItem.hidden) {
+ var containerToUse = this._view.selectedNode || this._view.result.root;
+ if (PlacesUtils.nodeIsContainer(containerToUse)) {
+ if (!PlacesUtils.hasChildURIs(containerToUse, true)) {
+ openContainerInTabsItem.disabled = true;
+ // Ensure that we don't display the menu if nothing is enabled:
+ usableItemCount--;
+ }
+ }
+ }
+ }
+
+ return usableItemCount > 0;
+ },
+
+ /**
+ * Select all links in the current view.
+ */
+ selectAll: function PC_selectAll() {
+ this._view.selectAll();
+ },
+
+ /**
+ * Opens the bookmark properties for the selected URI Node.
+ */
+ showBookmarkPropertiesForSelection:
+ function PC_showBookmarkPropertiesForSelection() {
+ var node = this._view.selectedNode;
+ if (!node)
+ return;
+
+ var itemType = PlacesUtils.nodeIsFolder(node) ||
+ PlacesUtils.nodeIsTagQuery(node) ? "folder" : "bookmark";
+ var concreteId = PlacesUtils.getConcreteItemId(node);
+ var isRootItem = PlacesUtils.isRootItem(concreteId);
+ var itemId = node.itemId;
+ if (isRootItem || PlacesUtils.nodeIsTagQuery(node)) {
+ // If this is a root or the Tags query we use the concrete itemId to catch
+ // the correct title for the node.
+ itemId = concreteId;
+ }
+
+ PlacesUIUtils.showBookmarkDialog({ action: "edit"
+ , type: itemType
+ , itemId: itemId
+ , readOnly: isRootItem
+ , hiddenRows: [ "folderPicker" ]
+ }, window.top);
+ },
+
+ /**
+ * This method can be run on a URI parameter to ensure that it didn't
+ * receive a string instead of an nsIURI object.
+ */
+ _assertURINotString: function PC__assertURINotString(value) {
+ NS_ASSERT((typeof(value) == "object") && !(value instanceof String),
+ "This method should be passed a URI as a nsIURI object, not as a string.");
+ },
+
+ /**
+ * Reloads the selected livemark if any.
+ */
+ reloadSelectedLivemark: function PC_reloadSelectedLivemark() {
+ var selectedNode = this._view.selectedNode;
+ if (selectedNode) {
+ let itemId = selectedNode.itemId;
+ PlacesUtils.livemarks.getLivemark({ id: itemId })
+ .then(aLivemark => {
+ aLivemark.reload(true);
+ }, Components.utils.reportError);
+ }
+ },
+
+ /**
+ * Opens the links in the selected folder, or the selected links in new tabs.
+ */
+ openSelectionInTabs: function PC_openLinksInTabs(aEvent) {
+ var node = this._view.selectedNode;
+ var nodes = this._view.selectedNodes;
+ // In the case of no selection, open the root node:
+ if (!node && !nodes.length) {
+ node = this._view.result.root;
+ }
+ if (node && PlacesUtils.nodeIsContainer(node))
+ PlacesUIUtils.openContainerNodeInTabs(node, aEvent, this._view);
+ else
+ PlacesUIUtils.openURINodesInTabs(nodes, aEvent, this._view);
+ },
+
+ /**
+ * Shows the Add Bookmark UI for the current insertion point.
+ *
+ * @param aType
+ * the type of the new item (bookmark/livemark/folder)
+ */
+ newItem: function PC_newItem(aType) {
+ let ip = this._view.insertionPoint;
+ if (!ip)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+
+ let performed =
+ PlacesUIUtils.showBookmarkDialog({ action: "add"
+ , type: aType
+ , defaultInsertionPoint: ip
+ , hiddenRows: [ "folderPicker" ]
+ }, window.top);
+ if (performed) {
+ // Select the new item.
+ let insertedNodeId = PlacesUtils.bookmarks
+ .getIdForItemAt(ip.itemId, ip.index);
+ this._view.selectItems([insertedNodeId], false);
+ }
+ },
+
+ /**
+ * Create a new Bookmark separator somewhere.
+ */
+ newSeparator: function PC_newSeparator() {
+ var ip = this._view.insertionPoint;
+ if (!ip)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+ var txn = new PlacesCreateSeparatorTransaction(ip.itemId, ip.index);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ // select the new item
+ var insertedNodeId = PlacesUtils.bookmarks
+ .getIdForItemAt(ip.itemId, ip.index);
+ this._view.selectItems([insertedNodeId], false);
+ },
+
+ /**
+ * Opens a dialog for moving the selected nodes.
+ */
+ moveSelectedBookmarks: function PC_moveBookmarks() {
+ window.openDialog("chrome://browser/content/places/moveBookmarks.xul",
+ "", "chrome, modal",
+ this._view.selectedNodes);
+ },
+
+ /**
+ * Sort the selected folder by name.
+ */
+ sortFolderByName: function PC_sortFolderByName() {
+ var itemId = PlacesUtils.getConcreteItemId(this._view.selectedNode);
+ var txn = new PlacesSortFolderByNameTransaction(itemId);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ },
+
+ /**
+ * Open the parent folder for the selected bookmarks search result.
+ */
+ openParentFolder: function PC_openParentFolder() {
+ var view;
+ if (!document.popupNode) {
+ view = document.commandDispatcher.focusedElement;
+ } else {
+ view = PlacesUIUtils.getViewForNode(document.popupNode); // XULElement
+ }
+ if (!view || view.getAttribute("type") != "places")
+ return;
+ var node = view.selectedNode; // nsINavHistoryResultNode
+ var aItemId = node.itemId;
+ var aFolderItemId = this.getParentFolderByItemId(aItemId);
+ if (aFolderItemId)
+ this.selectFolderByItemId(view, aFolderItemId, aItemId);
+ },
+
+ getParentFolderByItemId: function PC_getParentFolderByItemId(aItemId) {
+ var bmsvc = Components.classes["@mozilla.org/browser/nav-bookmarks-service;1"].
+ getService(Components.interfaces.nsINavBookmarksService);
+ var parentFolderId = bmsvc.getFolderIdForItem(aItemId);
+
+ return parentFolderId;
+ },
+
+ selectItems2: function PC_selectItems2(view, aIDs) {
+ var ids = aIDs; // Don't manipulate the caller's array.
+
+ // Array of nodes found by findNodes which are to be selected
+ var nodes = [];
+
+ // Array of nodes found by findNodes which should be opened
+ var nodesToOpen = [];
+
+ // A set of URIs of container-nodes that were previously searched,
+ // and thus shouldn't be searched again. This is empty at the initial
+ // start of the recursion and gets filled in as the recursion
+ // progresses.
+ var nodesURIChecked = [];
+
+ /**
+ * Recursively search through a node's children for items
+ * with the given IDs. When a matching item is found, remove its ID
+ * from the IDs array, and add the found node to the nodes dictionary.
+ *
+ * NOTE: This method will leave open any node that had matching items
+ * in its subtree.
+ */
+ function findNodes(node) {
+ var foundOne = false;
+ // See if node matches an ID we wanted; add to results.
+ // For simple folder queries, check both itemId and the concrete
+ // item id.
+ var index = ids.indexOf(node.itemId);
+ if (index == -1 &&
+ node.type == Components.interfaces.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT) {
+ index = ids.indexOf(PlacesUtils.asQuery(node).folderItemId); //xxx Bug 556739 3.7a5pre
+ }
+
+ if (index != -1) {
+ nodes.push(node);
+ foundOne = true;
+ ids.splice(index, 1);
+ }
+
+ if (ids.length == 0 || !PlacesUtils.nodeIsContainer(node) ||
+ nodesURIChecked.indexOf(node.uri) != -1)
+ return foundOne;
+
+ nodesURIChecked.push(node.uri);
+ PlacesUtils.asContainer(node); // xxx Bug 556739 3.7a6pre
+
+ // Remember the beginning state so that we can re-close
+ // this node if we don't find any additional results here.
+ var previousOpenness = node.containerOpen;
+ node.containerOpen = true;
+ for (var child = 0; child < node.childCount && ids.length > 0;
+ child++) {
+ var childNode = node.getChild(child);
+ var found = findNodes(childNode);
+ if (!foundOne)
+ foundOne = found;
+ }
+
+ // If we didn't find any additional matches in this node's
+ // subtree, revert the node to its previous openness.
+ if (foundOne)
+ nodesToOpen.unshift(node);
+ node.containerOpen = previousOpenness;
+ return foundOne;
+ } // findNodes
+
+ // Disable notifications while looking for nodes.
+ let result = view.result;
+ let didSuppressNotifications = result.suppressNotifications;
+ if (!didSuppressNotifications)
+ result.suppressNotifications = true
+ try {
+ findNodes(view.result.root);
+ }
+ finally {
+ if (!didSuppressNotifications)
+ result.suppressNotifications = false;
+ }
+
+ // For all the nodes we've found, highlight the corresponding
+ // index in the tree.
+ var resultview = view.view;
+ var selection = resultview.selection;
+ selection.selectEventsSuppressed = true;
+ selection.clearSelection();
+ // Open nodes containing found items.
+ for (var i = 0; i < nodesToOpen.length; i++) {
+ nodesToOpen[i].containerOpen = true;
+ }
+ for (var i = 0; i < nodes.length; i++) {
+ if (PlacesUtils.nodeIsContainer(nodes[i]))
+ continue;
+
+ var index = resultview.treeIndexForNode(nodes[i]);
+ selection.rangedSelect(index, index, true);
+ }
+ selection.selectEventsSuppressed = false;
+ },
+
+ selectFolderByItemId: function PC_selectFolderByItemId(view, aFolderItemId, aItemId) {
+ // Library
+ if (view.getAttribute("id") == "placeContent") {
+ view = document.getElementById("placesList");
+ // Select a folder node in folder pane.
+ this.selectItems2(view, [aFolderItemId]);
+ view.selectItems([aFolderItemId]);
+ if (view.currentIndex)
+ view.treeBoxObject.ensureRowIsVisible(view.currentIndex);
+ // Reselect child node.
+ setTimeout(function(aItemId, view) {
+ var aView = view.ownerDocument.getElementById("placeContent");
+ aView.selectItems([aItemId]);
+ if (aView.currentIndex)
+ aView.treeBoxObject.ensureRowIsVisible(aView.currentIndex);
+ }, 0, aItemId, view);
+ return;
+ }
+
+ // Bookmarks Sidebar
+ if (!view)
+ return;
+ view.place = view.place;
+
+ if ('FlatBookmarksOverlay' in window) {
+ var sidebarwin = view.ownerDocument.defaultView;
+ var searchBox = sidebarwin.document.getElementById("search-box");
+ searchBox.value = "";
+ searchBox.doCommand();
+ sidebarwin.FlatBookmarks._setTreePlace(sidebarwin.FlatBookmarks._makePlaceForFolder(aFolderItemId));
+ view.selectItems([aItemId]);
+ var tbo = view.treeBoxObject;
+ tbo.ensureRowIsVisible(view.currentIndex);
+ view.focus();
+ return;
+ }
+
+ view.findNode = function flatChildNodes(node, aIDs) {
+ var ids = aIDs; // Don't manipulate the caller's array.
+
+ // Array of nodes found by findNodes which are to be selected
+ var nodes = [];
+
+ // Array of nodes found by findNodes which should be opened
+ var nodesToOpen = [];
+
+ // A set of URIs of container-nodes that were previously searched,
+ // and thus shouldn't be searched again. This is empty at the initial
+ // start of the recursion and gets filled in as the recursion
+ // progresses.
+ var nodesURIChecked = [];
+
+ /**
+ * Recursively search through a node's children for items
+ * with the given IDs. When a matching item is found, remove its ID
+ * from the IDs array, and add the found node to the nodes dictionary.
+ *
+ * NOTE: This method will leave open any node that had matching items
+ * in its subtree.
+ */
+ function findNodes(node) {
+ var foundOne = false;
+ // See if node matches an ID we wanted; add to results.
+ // For simple folder queries, check both itemId and the concrete
+ // item id.
+ var index = ids.indexOf(node.itemId);
+ if (index == -1 &&
+ node.type == Components.interfaces.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT) {
+ index = ids.indexOf(PlacesUtils.asQuery(node).folderItemId); // xxx Bug 556739 3.7a5pre
+ }
+
+ if (index != -1) {
+ nodes.push(node);
+ foundOne = true;
+ ids.splice(index, 1);
+ }
+
+ if (ids.length == 0 || !PlacesUtils.nodeIsContainer(node) ||
+ nodesURIChecked.indexOf(node.uri) != -1)
+ return foundOne;
+
+ nodesURIChecked.push(node.uri);
+ PlacesUtils.asContainer(node); // xxx Bug 556739 3.7a6pre
+ // Remember the beginning state so that we can re-close
+ // this node if we don't find any additional results here.
+ var previousOpenness = node.containerOpen;
+ node.containerOpen = true;
+ for (var child = 0; child < node.childCount && ids.length > 0;
+ child++) {
+ var childNode = node.getChild(child);
+ if (PlacesUtils.nodeIsQuery(childNode))
+ continue;
+ var found = findNodes(childNode);
+ if (!foundOne)
+ foundOne = found;
+ }
+
+ // If we didn't find any additional matches in this node's
+ // subtree, revert the node to its previous openness.
+ if (foundOne)
+ nodesToOpen.unshift(node);
+ node.containerOpen = previousOpenness;
+ return foundOne;
+ } // findNodes
+
+ // Disable notifications while looking for nodes.
+ let result = this.result;
+ let didSuppressNotifications = result.suppressNotifications;
+ if (!didSuppressNotifications)
+ result.suppressNotifications = true
+ try {
+ findNodes(this.result.root);
+ }
+ finally {
+ if (!didSuppressNotifications)
+ result.suppressNotifications = false;
+ }
+
+ // Open nodes containing found items.
+ for (var i = 0; i < nodesToOpen.length; i++) {
+ nodesToOpen[i].containerOpen = true;
+ }
+ return nodes;
+ }; // findNode
+
+ // For all the nodes we've found, highlight the corresponding
+ // index in the tree.
+ var resultview = view.view;
+ var selection = view.view.selection;
+ selection.selectEventsSuppressed = true;
+ selection.clearSelection();
+ var nodes = view.findNode(view.result.root, [aFolderItemId]);
+ if (nodes.length > 0) {
+ var index = resultview.treeIndexForNode(nodes[0]);
+ nodes = view.findNode(nodes[0], [aItemId]);
+ if (nodes.length > 0) {
+ index = resultview.treeIndexForNode(nodes[0]);
+ selection.rangedSelect(index, index, true);
+ }
+ }
+ selection.selectEventsSuppressed = false;
+
+ var tbo = view.treeBoxObject;
+ tbo.ensureRowIsVisible(view.currentIndex);
+ view.focus();
+ return;
+ },
+
+ /**
+ * Walk the list of folders we're removing in this delete operation, and
+ * see if the selected node specified is already implicitly being removed
+ * because it is a child of that folder.
+ * @param node
+ * Node to check for containment.
+ * @param pastFolders
+ * List of folders the calling function has already traversed
+ * @returns true if the node should be skipped, false otherwise.
+ */
+ _shouldSkipNode: function PC_shouldSkipNode(node, pastFolders) {
+ /**
+ * Determines if a node is contained by another node within a resultset.
+ * @param node
+ * The node to check for containment for
+ * @param parent
+ * The parent container to check for containment in
+ * @returns true if node is a member of parent's children, false otherwise.
+ */
+ function isContainedBy(node, parent) {
+ var cursor = node.parent;
+ while (cursor) {
+ if (cursor == parent)
+ return true;
+ cursor = cursor.parent;
+ }
+ return false;
+ }
+
+ for (var j = 0; j < pastFolders.length; ++j) {
+ if (isContainedBy(node, pastFolders[j]))
+ return true;
+ }
+ return false;
+ },
+
+ /**
+ * Creates a set of transactions for the removal of a range of items.
+ * A range is an array of adjacent nodes in a view.
+ * @param [in] range
+ * An array of nodes to remove. Should all be adjacent.
+ * @param [out] transactions
+ * An array of transactions.
+ * @param [optional] removedFolders
+ * An array of folder nodes that have already been removed.
+ */
+ _removeRange: function PC__removeRange(range, transactions, removedFolders) {
+ NS_ASSERT(transactions instanceof Array, "Must pass a transactions array");
+ if (!removedFolders)
+ removedFolders = [];
+
+ for (var i = 0; i < range.length; ++i) {
+ var node = range[i];
+ if (this._shouldSkipNode(node, removedFolders))
+ continue;
+
+ if (PlacesUtils.nodeIsTagQuery(node.parent)) {
+ // This is a uri node inside a tag container. It needs a special
+ // untag transaction.
+ var tagItemId = PlacesUtils.getConcreteItemId(node.parent);
+ var uri = NetUtil.newURI(node.uri);
+ let txn = new PlacesUntagURITransaction(uri, [tagItemId]);
+ transactions.push(txn);
+ }
+ else if (PlacesUtils.nodeIsTagQuery(node) && node.parent &&
+ PlacesUtils.nodeIsQuery(node.parent) &&
+ PlacesUtils.asQuery(node.parent).queryOptions.resultType ==
+ Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY) {
+ // This is a tag container.
+ // Untag all URIs tagged with this tag only if the tag container is
+ // child of the "Tags" query in the library, in all other places we
+ // must only remove the query node.
+ var tag = node.title;
+ var URIs = PlacesUtils.tagging.getURIsForTag(tag);
+ for (var j = 0; j < URIs.length; j++) {
+ let txn = new PlacesUntagURITransaction(URIs[j], [tag]);
+ transactions.push(txn);
+ }
+ }
+ else if (PlacesUtils.nodeIsURI(node) &&
+ PlacesUtils.nodeIsQuery(node.parent) &&
+ PlacesUtils.asQuery(node.parent).queryOptions.queryType ==
+ Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) {
+ // This is a uri node inside an history query.
+ PlacesUtils.bhistory.removePage(NetUtil.newURI(node.uri));
+ // History deletes are not undoable, so we don't have a transaction.
+ }
+ else if (node.itemId == -1 &&
+ PlacesUtils.nodeIsQuery(node) &&
+ PlacesUtils.asQuery(node).queryOptions.queryType ==
+ Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) {
+ // This is a dynamically generated history query, like queries
+ // grouped by site, time or both. Dynamically generated queries don't
+ // have an itemId even if they are descendants of a bookmark.
+ this._removeHistoryContainer(node);
+ // History deletes are not undoable, so we don't have a transaction.
+ }
+ else {
+ // This is a common bookmark item.
+ if (PlacesUtils.nodeIsFolder(node)) {
+ // If this is a folder we add it to our array of folders, used
+ // to skip nodes that are children of an already removed folder.
+ removedFolders.push(node);
+ }
+ let txn = new PlacesRemoveItemTransaction(node.itemId);
+ transactions.push(txn);
+ }
+ }
+ },
+
+ /**
+ * Removes the set of selected ranges from bookmarks.
+ * @param txnName
+ * See |remove|.
+ */
+ _removeRowsFromBookmarks: function PC__removeRowsFromBookmarks(txnName) {
+ var ranges = this._view.removableSelectionRanges;
+ var transactions = [];
+ var removedFolders = [];
+
+ for (var i = 0; i < ranges.length; i++)
+ this._removeRange(ranges[i], transactions, removedFolders);
+
+ if (transactions.length > 0) {
+ var txn = new PlacesAggregatedTransaction(txnName, transactions);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ }
+ },
+
+ /**
+ * Removes the set of selected ranges from history.
+ *
+ * @note history deletes are not undoable.
+ */
+ _removeRowsFromHistory: function PC__removeRowsFromHistory() {
+ let nodes = this._view.selectedNodes;
+ let URIs = [];
+ for (let i = 0; i < nodes.length; ++i) {
+ let node = nodes[i];
+ if (PlacesUtils.nodeIsURI(node)) {
+ let uri = NetUtil.newURI(node.uri);
+ // Avoid duplicates.
+ if (URIs.indexOf(uri) < 0) {
+ URIs.push(uri);
+ }
+ }
+ else if (PlacesUtils.nodeIsQuery(node) &&
+ PlacesUtils.asQuery(node).queryOptions.queryType ==
+ Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) {
+ this._removeHistoryContainer(node);
+ }
+ }
+
+ // Do removal in chunks to give some breath to main-thread.
+ function pagesChunkGenerator(aURIs) {
+ while (aURIs.length) {
+ let URIslice = aURIs.splice(0, REMOVE_PAGES_CHUNKLEN);
+ PlacesUtils.bhistory.removePages(URIslice, URIslice.length);
+ Services.tm.mainThread.dispatch(function() {
+ try {
+ gen.next();
+ } catch (ex if ex instanceof StopIteration) {}
+ }, Ci.nsIThread.DISPATCH_NORMAL);
+ yield;
+ }
+ }
+ let gen = pagesChunkGenerator(URIs);
+ gen.next();
+ },
+
+ /**
+ * Removes history visits for an history container node.
+ * @param [in] aContainerNode
+ * The container node to remove.
+ *
+ * @note history deletes are not undoable.
+ */
+ _removeHistoryContainer: function PC__removeHistoryContainer(aContainerNode) {
+ if (PlacesUtils.nodeIsHost(aContainerNode)) {
+ // Site container.
+ PlacesUtils.bhistory.removePagesFromHost(aContainerNode.title, true);
+ }
+ else if (PlacesUtils.nodeIsDay(aContainerNode)) {
+ // Day container.
+ let query = aContainerNode.getQueries()[0];
+ let beginTime = query.beginTime;
+ let endTime = query.endTime;
+ NS_ASSERT(query && beginTime && endTime,
+ "A valid date container query should exist!");
+ // We want to exclude beginTime from the removal because
+ // removePagesByTimeframe includes both extremes, while date containers
+ // exclude the lower extreme. So, if we would not exclude it, we would
+ // end up removing more history than requested.
+ PlacesUtils.bhistory.removePagesByTimeframe(beginTime + 1, endTime);
+ }
+ },
+
+ /**
+ * Removes the selection
+ * @param aTxnName
+ * A name for the transaction if this is being performed
+ * as part of another operation.
+ */
+ remove: function PC_remove(aTxnName) {
+ if (!this._hasRemovableSelection())
+ return;
+
+ NS_ASSERT(aTxnName !== undefined, "Must supply Transaction Name");
+
+ var root = this._view.result.root;
+
+ if (PlacesUtils.nodeIsFolder(root))
+ this._removeRowsFromBookmarks(aTxnName);
+ else if (PlacesUtils.nodeIsQuery(root)) {
+ var queryType = PlacesUtils.asQuery(root).queryOptions.queryType;
+ if (queryType == Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS)
+ this._removeRowsFromBookmarks(aTxnName);
+ else if (queryType == Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY)
+ this._removeRowsFromHistory();
+ else
+ NS_ASSERT(false, "implement support for QUERY_TYPE_UNIFIED");
+ }
+ else
+ NS_ASSERT(false, "unexpected root");
+ },
+
+ /**
+ * Fills a DataTransfer object with the content of the selection that can be
+ * dropped elsewhere.
+ * @param aEvent
+ * The dragstart event.
+ */
+ setDataTransfer: function PC_setDataTransfer(aEvent) {
+ let dt = aEvent.dataTransfer;
+ let doCopy = ["copyLink", "copy", "link"].indexOf(dt.effectAllowed) != -1;
+
+ let result = this._view.result;
+ let didSuppressNotifications = result.suppressNotifications;
+ if (!didSuppressNotifications)
+ result.suppressNotifications = true;
+
+ function addData(type, index, feedURI) {
+ let wrapNode = PlacesUtils.wrapNode(node, type, feedURI);
+ dt.mozSetDataAt(type, wrapNode, index);
+ }
+
+ function addURIData(index, feedURI) {
+ addData(PlacesUtils.TYPE_X_MOZ_URL, index, feedURI);
+ addData(PlacesUtils.TYPE_UNICODE, index, feedURI);
+ addData(PlacesUtils.TYPE_HTML, index, feedURI);
+ }
+
+ try {
+ let nodes = this._view.draggableSelection;
+ for (let i = 0; i < nodes.length; ++i) {
+ var node = nodes[i];
+
+ // This order is _important_! It controls how this and other
+ // applications select data to be inserted based on type.
+ addData(PlacesUtils.TYPE_X_MOZ_PLACE, i);
+
+ // Drop the feed uri for livemark containers
+ let livemarkInfo = this.getCachedLivemarkInfo(node);
+ if (livemarkInfo) {
+ addURIData(i, livemarkInfo.feedURI.spec);
+ }
+ else if (node.uri) {
+ addURIData(i);
+ }
+ }
+ }
+ finally {
+ if (!didSuppressNotifications)
+ result.suppressNotifications = false;
+ }
+ },
+
+ get clipboardAction () {
+ let action = {};
+ let actionOwner;
+ try {
+ let xferable = Cc["@mozilla.org/widget/transferable;1"].
+ createInstance(Ci.nsITransferable);
+ xferable.init(null);
+ xferable.addDataFlavor(PlacesUtils.TYPE_X_MOZ_PLACE_ACTION)
+ this.clipboard.getData(xferable, Ci.nsIClipboard.kGlobalClipboard);
+ xferable.getTransferData(PlacesUtils.TYPE_X_MOZ_PLACE_ACTION, action, {});
+ [action, actionOwner] =
+ action.value.QueryInterface(Ci.nsISupportsString).data.split(",");
+ } catch(ex) {
+ // Paste from external sources don't have any associated action, just
+ // fallback to a copy action.
+ return "copy";
+ }
+ // For cuts also check who inited the action, since cuts across different
+ // instances should instead be handled as copies (The sources are not
+ // available for this instance).
+ if (action == "cut" && actionOwner != this.profileName)
+ action = "copy";
+
+ return action;
+ },
+
+ _releaseClipboardOwnership: function PC__releaseClipboardOwnership() {
+ if (this.cutNodes.length > 0) {
+ // This clears the logical clipboard, doesn't remove data.
+ this.clipboard.emptyClipboard(Ci.nsIClipboard.kGlobalClipboard);
+ }
+ },
+
+ _clearClipboard: function PC__clearClipboard() {
+ let xferable = Cc["@mozilla.org/widget/transferable;1"].
+ createInstance(Ci.nsITransferable);
+ xferable.init(null);
+ // Empty transferables may cause crashes, so just add an unknown type.
+ const TYPE = "text/x-moz-place-empty";
+ xferable.addDataFlavor(TYPE);
+ xferable.setTransferData(TYPE, PlacesUtils.toISupportsString(""), 0);
+ this.clipboard.setData(xferable, null, Ci.nsIClipboard.kGlobalClipboard);
+ },
+
+ _populateClipboard: function PC__populateClipboard(aNodes, aAction) {
+ // This order is _important_! It controls how this and other applications
+ // select data to be inserted based on type.
+ let contents = [
+ { type: PlacesUtils.TYPE_X_MOZ_PLACE, entries: [] },
+ { type: PlacesUtils.TYPE_X_MOZ_URL, entries: [] },
+ { type: PlacesUtils.TYPE_HTML, entries: [] },
+ { type: PlacesUtils.TYPE_UNICODE, entries: [] },
+ ];
+
+ // Avoid handling descendants of a copied node, the transactions take care
+ // of them automatically.
+ let copiedFolders = [];
+ aNodes.forEach(function (node) {
+ if (this._shouldSkipNode(node, copiedFolders))
+ return;
+ if (PlacesUtils.nodeIsFolder(node))
+ copiedFolders.push(node);
+
+ let livemarkInfo = this.getCachedLivemarkInfo(node);
+ let feedURI = livemarkInfo && livemarkInfo.feedURI.spec;
+
+ contents.forEach(function (content) {
+ content.entries.push(
+ PlacesUtils.wrapNode(node, content.type, feedURI)
+ );
+ });
+ }, this);
+
+ function addData(type, data) {
+ xferable.addDataFlavor(type);
+ xferable.setTransferData(type, PlacesUtils.toISupportsString(data),
+ data.length * 2);
+ }
+
+ let xferable = Cc["@mozilla.org/widget/transferable;1"].
+ createInstance(Ci.nsITransferable);
+ xferable.init(null);
+ let hasData = false;
+ // This order matters here! It controls how this and other applications
+ // select data to be inserted based on type.
+ contents.forEach(function (content) {
+ if (content.entries.length > 0) {
+ hasData = true;
+ let glue =
+ content.type == PlacesUtils.TYPE_X_MOZ_PLACE ? "," : PlacesUtils.endl;
+ addData(content.type, content.entries.join(glue));
+ }
+ });
+
+ // Track the exected action in the xferable. This must be the last flavor
+ // since it's the least preferred one.
+ // Enqueue a unique instance identifier to distinguish operations across
+ // concurrent instances of the application.
+ addData(PlacesUtils.TYPE_X_MOZ_PLACE_ACTION, aAction + "," + this.profileName);
+
+ if (hasData) {
+ this.clipboard.setData(xferable,
+ this.cutNodes.length > 0 ? this : null,
+ Ci.nsIClipboard.kGlobalClipboard);
+ }
+ },
+
+ _cutNodes: [],
+ get cutNodes() this._cutNodes,
+ set cutNodes(aNodes) {
+ let self = this;
+ function updateCutNodes(aValue) {
+ self._cutNodes.forEach(function (aNode) {
+ self._view.toggleCutNode(aNode, aValue);
+ });
+ }
+
+ updateCutNodes(false);
+ this._cutNodes = aNodes;
+ updateCutNodes(true);
+ return aNodes;
+ },
+
+ /**
+ * Copy Bookmarks and Folders to the clipboard
+ */
+ copy: function PC_copy() {
+ let result = this._view.result;
+ let didSuppressNotifications = result.suppressNotifications;
+ if (!didSuppressNotifications)
+ result.suppressNotifications = true;
+ try {
+ this._populateClipboard(this._view.selectedNodes, "copy");
+ }
+ finally {
+ if (!didSuppressNotifications)
+ result.suppressNotifications = false;
+ }
+ },
+
+ /**
+ * Cut Bookmarks and Folders to the clipboard
+ */
+ cut: function PC_cut() {
+ let result = this._view.result;
+ let didSuppressNotifications = result.suppressNotifications;
+ if (!didSuppressNotifications)
+ result.suppressNotifications = true;
+ try {
+ this._populateClipboard(this._view.selectedNodes, "cut");
+ this.cutNodes = this._view.selectedNodes;
+ }
+ finally {
+ if (!didSuppressNotifications)
+ result.suppressNotifications = false;
+ }
+ },
+
+ /**
+ * Paste Bookmarks and Folders from the clipboard
+ */
+ paste: function PC_paste() {
+ // No reason to proceed if there isn't a valid insertion point.
+ let ip = this._view.insertionPoint;
+ if (!ip)
+ throw Cr.NS_ERROR_NOT_AVAILABLE;
+
+ let action = this.clipboardAction;
+
+ let xferable = Cc["@mozilla.org/widget/transferable;1"].
+ createInstance(Ci.nsITransferable);
+ xferable.init(null);
+ // This order matters here! It controls the preferred flavors for this
+ // paste operation.
+ [ PlacesUtils.TYPE_X_MOZ_PLACE,
+ PlacesUtils.TYPE_X_MOZ_URL,
+ PlacesUtils.TYPE_UNICODE,
+ ].forEach(function (type) xferable.addDataFlavor(type));
+
+ this.clipboard.getData(xferable, Ci.nsIClipboard.kGlobalClipboard);
+
+ // Now get the clipboard contents, in the best available flavor.
+ let data = {}, type = {}, items = [];
+ try {
+ xferable.getAnyTransferData(type, data, {});
+ data = data.value.QueryInterface(Ci.nsISupportsString).data;
+ type = type.value;
+ items = PlacesUtils.unwrapNodes(data, type);
+ } catch(ex) {
+ // No supported data exists or nodes unwrap failed, just bail out.
+ return;
+ }
+
+ let transactions = [];
+ let insertionIndex = ip.index;
+ for (let i = 0; i < items.length; ++i) {
+ if (ip.isTag) {
+ // Pasting into a tag container means tagging the item, regardless of
+ // the requested action.
+ let tagTxn = new PlacesTagURITransaction(NetUtil.newURI(items[i].uri),
+ [ip.itemId]);
+ transactions.push(tagTxn);
+ continue;
+ }
+
+ // Adjust index to make sure items are pasted in the correct position.
+ // If index is DEFAULT_INDEX, items are just appended.
+ if (ip.index != PlacesUtils.bookmarks.DEFAULT_INDEX)
+ insertionIndex = ip.index + i;
+
+ transactions.push(
+ PlacesUIUtils.makeTransaction(items[i], type, ip.itemId,
+ insertionIndex, action == "copy")
+ );
+ }
+
+ let aggregatedTxn = new PlacesAggregatedTransaction("Paste", transactions);
+ PlacesUtils.transactionManager.doTransaction(aggregatedTxn);
+
+ // Cut/past operations are not repeatable, so clear the clipboard.
+ if (action == "cut") {
+ this._clearClipboard();
+ }
+
+ // Select the pasted items, they should be consecutive.
+ let insertedNodeIds = [];
+ for (let i = 0; i < transactions.length; ++i) {
+ insertedNodeIds.push(
+ PlacesUtils.bookmarks.getIdForItemAt(ip.itemId, ip.index + i)
+ );
+ }
+ if (insertedNodeIds.length > 0)
+ this._view.selectItems(insertedNodeIds, false);
+ },
+
+ /**
+ * Cache the livemark info for a node. This allows the controller and the
+ * views to treat the given node as a livemark.
+ * @param aNode
+ * a places result node.
+ * @param aLivemarkInfo
+ * a mozILivemarkInfo object.
+ */
+ cacheLivemarkInfo: function PC_cacheLivemarkInfo(aNode, aLivemarkInfo) {
+ this._cachedLivemarkInfoObjects.set(aNode, aLivemarkInfo);
+ },
+
+ /**
+ * Returns whether or not there's cached mozILivemarkInfo object for a node.
+ * @param aNode
+ * a places result node.
+ * @return true if there's a cached mozILivemarkInfo object for
+ * aNode, false otherwise.
+ */
+ hasCachedLivemarkInfo: function PC_hasCachedLivemarkInfo(aNode)
+ this._cachedLivemarkInfoObjects.has(aNode),
+
+ /**
+ * Returns the cached livemark info for a node, if set by cacheLivemarkInfo,
+ * null otherwise.
+ * @param aNode
+ * a places result node.
+ * @return the mozILivemarkInfo object for aNode, if set, null otherwise.
+ */
+ getCachedLivemarkInfo: function PC_getCachedLivemarkInfo(aNode)
+ this._cachedLivemarkInfoObjects.get(aNode, null)
+};
+
+/**
+ * Handles drag and drop operations for views. Note that this is view agnostic!
+ * You should not use PlacesController._view within these methods, since
+ * the view that the item(s) have been dropped on was not necessarily active.
+ * Drop functions are passed the view that is being dropped on.
+ */
+var PlacesControllerDragHelper = {
+ /**
+ * DOM Element currently being dragged over
+ */
+ currentDropTarget: null,
+
+ /**
+ * Determines if the mouse is currently being dragged over a child node of
+ * this menu. This is necessary so that the menu doesn't close while the
+ * mouse is dragging over one of its submenus
+ * @param node
+ * The container node
+ * @returns true if the user is dragging over a node within the hierarchy of
+ * the container, false otherwise.
+ */
+ draggingOverChildNode: function PCDH_draggingOverChildNode(node) {
+ let currentNode = this.currentDropTarget;
+ while (currentNode) {
+ if (currentNode == node)
+ return true;
+ currentNode = currentNode.parentNode;
+ }
+ return false;
+ },
+
+ /**
+ * @returns The current active drag session. Returns null if there is none.
+ */
+ getSession: function PCDH__getSession() {
+ return this.dragService.getCurrentSession();
+ },
+
+ /**
+ * Extract the first accepted flavor from a list of flavors.
+ * @param aFlavors
+ * The flavors list of type nsIDOMDOMStringList.
+ */
+ getFirstValidFlavor: function PCDH_getFirstValidFlavor(aFlavors) {
+ for (let i = 0; i < aFlavors.length; i++) {
+ if (this.GENERIC_VIEW_DROP_TYPES.indexOf(aFlavors[i]) != -1)
+ return aFlavors[i];
+ }
+
+ // If no supported flavor is found, check if data includes text/plain
+ // contents. If so, request them as text/unicode, a conversion will happen
+ // automatically.
+ if (aFlavors.contains("text/plain")) {
+ return PlacesUtils.TYPE_UNICODE;
+ }
+
+ return null;
+ },
+
+ /**
+ * Determines whether or not the data currently being dragged can be dropped
+ * on a places view.
+ * @param ip
+ * The insertion point where the items should be dropped.
+ */
+ canDrop: function PCDH_canDrop(ip, dt) {
+ let dropCount = dt.mozItemCount;
+
+ // Check every dragged item.
+ for (let i = 0; i < dropCount; i++) {
+ let flavor = this.getFirstValidFlavor(dt.mozTypesAt(i));
+ if (!flavor)
+ return false;
+
+ // Urls can be dropped on any insertionpoint.
+ // XXXmano: remember that this method is called for each dragover event!
+ // Thus we shouldn't use unwrapNodes here at all if possible.
+ // I think it would be OK to accept bogus data here (e.g. text which was
+ // somehow wrapped as TAB_DROP_TYPE, this is not in our control, and
+ // will just case the actual drop to be a no-op), and only rule out valid
+ // expected cases, which are either unsupported flavors, or items which
+ // cannot be dropped in the current insertionpoint. The last case will
+ // likely force us to use unwrapNodes for the private data types of
+ // places.
+ if (flavor == TAB_DROP_TYPE)
+ continue;
+
+ let data = dt.mozGetDataAt(flavor, i);
+ let dragged;
+ try {
+ dragged = PlacesUtils.unwrapNodes(data, flavor)[0];
+ }
+ catch (e) {
+ return false;
+ }
+
+ // Only bookmarks and urls can be dropped into tag containers.
+ if (ip.isTag && ip.orientation == Ci.nsITreeView.DROP_ON &&
+ dragged.type != PlacesUtils.TYPE_X_MOZ_URL &&
+ (dragged.type != PlacesUtils.TYPE_X_MOZ_PLACE ||
+ (dragged.uri && dragged.uri.startsWith("place:")) ))
+ return false;
+
+ // The following loop disallows the dropping of a folder on itself or
+ // on any of its descendants.
+ if (dragged.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER ||
+ (dragged.uri && dragged.uri.startsWith("place:")) ) {
+ let parentId = ip.itemId;
+ while (parentId != PlacesUtils.placesRootId) {
+ if (dragged.concreteId == parentId || dragged.id == parentId)
+ return false;
+ parentId = PlacesUtils.bookmarks.getFolderIdForItem(parentId);
+ }
+ }
+ }
+ return true;
+ },
+
+
+ /**
+ * Determines if a node can be moved.
+ *
+ * @param aNode
+ * A nsINavHistoryResultNode node.
+ * @returns True if the node can be moved, false otherwise.
+ */
+ canMoveNode:
+ function PCDH_canMoveNode(aNode) {
+ // Only bookmark items are movable.
+ if (aNode.itemId == -1)
+ return false;
+
+ // Once tags and bookmarked are divorced, the tag-query check should be
+ // removed.
+ let parentNode = aNode.parent;
+ return parentNode != null &&
+ !(PlacesUtils.nodeIsFolder(parentNode) &&
+ PlacesUIUtils.isContentsReadOnly(parentNode)) &&
+ !PlacesUtils.nodeIsTagQuery(parentNode);
+ },
+
+ /**
+ * Handles the drop of one or more items onto a view.
+ * @param insertionPoint
+ * The insertion point where the items should be dropped
+ */
+ onDrop: function PCDH_onDrop(insertionPoint, dt) {
+ let doCopy = ["copy", "link"].indexOf(dt.dropEffect) != -1;
+
+ let transactions = [];
+ let dropCount = dt.mozItemCount;
+ let movedCount = 0;
+ for (let i = 0; i < dropCount; ++i) {
+ let flavor = this.getFirstValidFlavor(dt.mozTypesAt(i));
+ if (!flavor)
+ return;
+
+ let data = dt.mozGetDataAt(flavor, i);
+ let unwrapped;
+ if (flavor != TAB_DROP_TYPE) {
+ // There's only ever one in the D&D case.
+ unwrapped = PlacesUtils.unwrapNodes(data, flavor)[0];
+ }
+ else if (data instanceof XULElement && data.localName == "tab" &&
+ data.ownerDocument.defaultView instanceof ChromeWindow) {
+ let uri = data.linkedBrowser.currentURI;
+ let spec = uri ? uri.spec : "about:blank";
+ let title = data.label;
+ unwrapped = { uri: spec,
+ title: data.label,
+ type: PlacesUtils.TYPE_X_MOZ_URL};
+ }
+ else
+ throw("bogus data was passed as a tab");
+
+ let index = insertionPoint.index;
+
+ // Adjust insertion index to prevent reversal of dragged items. When you
+ // drag multiple elts upward: need to increment index or each successive
+ // elt will be inserted at the same index, each above the previous.
+ let dragginUp = insertionPoint.itemId == unwrapped.parent &&
+ index < PlacesUtils.bookmarks.getItemIndex(unwrapped.id);
+ if (index != -1 && dragginUp)
+ index += movedCount++;
+
+ // If dragging over a tag container we should tag the item.
+ if (insertionPoint.isTag &&
+ insertionPoint.orientation == Ci.nsITreeView.DROP_ON) {
+ let uri = NetUtil.newURI(unwrapped.uri);
+ let tagItemId = insertionPoint.itemId;
+ let tagTxn = new PlacesTagURITransaction(uri, [tagItemId]);
+ transactions.push(tagTxn);
+ }
+ else {
+ transactions.push(PlacesUIUtils.makeTransaction(unwrapped,
+ flavor, insertionPoint.itemId,
+ index, doCopy));
+ }
+ }
+
+ let txn = new PlacesAggregatedTransaction("DropItems", transactions);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ },
+
+ /**
+ * Checks if we can insert into a container.
+ * @param aContainer
+ * The container were we are want to drop
+ */
+ disallowInsertion: function(aContainer) {
+ NS_ASSERT(aContainer, "empty container");
+ // Allow dropping into Tag containers and editable folders.
+ return !PlacesUtils.nodeIsTagQuery(aContainer) &&
+ (!PlacesUtils.nodeIsFolder(aContainer) ||
+ PlacesUIUtils.isContentsReadOnly(aContainer));
+ },
+
+ placesFlavors: [PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER,
+ PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR,
+ PlacesUtils.TYPE_X_MOZ_PLACE],
+
+ // The order matters.
+ GENERIC_VIEW_DROP_TYPES: [PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER,
+ PlacesUtils.TYPE_X_MOZ_PLACE_SEPARATOR,
+ PlacesUtils.TYPE_X_MOZ_PLACE,
+ PlacesUtils.TYPE_X_MOZ_URL,
+ TAB_DROP_TYPE,
+ PlacesUtils.TYPE_UNICODE],
+};
+
+
+XPCOMUtils.defineLazyServiceGetter(PlacesControllerDragHelper, "dragService",
+ "@mozilla.org/widget/dragservice;1",
+ "nsIDragService");
+
+function goUpdatePlacesCommands() {
+ // Get the controller for one of the places commands.
+ var placesController = doGetPlacesControllerForCommand("placesCmd_open");
+ function updatePlacesCommand(aCommand) {
+ goSetCommandEnabled(aCommand, placesController &&
+ placesController.isCommandEnabled(aCommand));
+ }
+
+ updatePlacesCommand("placesCmd_open");
+ updatePlacesCommand("placesCmd_open:window");
+ updatePlacesCommand("placesCmd_open:privatewindow");
+ updatePlacesCommand("placesCmd_open:tab");
+ updatePlacesCommand("placesCmd_new:folder");
+ updatePlacesCommand("placesCmd_new:bookmark");
+ updatePlacesCommand("placesCmd_new:livemark");
+ updatePlacesCommand("placesCmd_new:separator");
+ updatePlacesCommand("placesCmd_show:info");
+ updatePlacesCommand("placesCmd_moveBookmarks");
+ updatePlacesCommand("placesCmd_reload");
+ updatePlacesCommand("placesCmd_sortBy:name");
+ updatePlacesCommand("placesCmd_openParentFolder");
+ updatePlacesCommand("placesCmd_cut");
+ updatePlacesCommand("placesCmd_copy");
+ updatePlacesCommand("placesCmd_paste");
+ updatePlacesCommand("placesCmd_delete");
+}
+
+function doGetPlacesControllerForCommand(aCommand)
+{
+ // A context menu may be built for non-focusable views. Thus, we first try
+ // to look for a view associated with document.popupNode
+ let popupNode;
+ try {
+ popupNode = document.popupNode;
+ } catch (e) {
+ // The document went away (bug 797307).
+ return null;
+ }
+ if (popupNode) {
+ let view = PlacesUIUtils.getViewForNode(popupNode);
+ if (view && view._contextMenuShown)
+ return view.controllers.getControllerForCommand(aCommand);
+ }
+
+ // When we're not building a context menu, only focusable views
+ // are possible. Thus, we can safely use the command dispatcher.
+ let controller = top.document.commandDispatcher
+ .getControllerForCommand(aCommand);
+ if (controller)
+ return controller;
+
+ return null;
+}
+
+function goDoPlacesCommand(aCommand)
+{
+ let controller = doGetPlacesControllerForCommand(aCommand);
+ if (controller && controller.isCommandEnabled(aCommand))
+ controller.doCommand(aCommand);
+}
+
diff --git a/components/places/content/downloadsViewOverlay.xul b/components/places/content/downloadsViewOverlay.xul
new file mode 100644
index 0000000..8e2d775
--- /dev/null
+++ b/components/places/content/downloadsViewOverlay.xul
@@ -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/. -->
+
+<?xul-overlay href="chrome://browser/content/downloads/allDownloadsViewOverlay.xul"?>
+
+<!DOCTYPE overlay [
+<!ENTITY % downloadsDTD SYSTEM "chrome://browser/locale/downloads/downloads.dtd">
+%downloadsDTD;
+]>
+
+<overlay id="downloadsViewOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <script type="application/javascript"><![CDATA[
+ const DOWNLOADS_QUERY = "place:transition=" +
+ Components.interfaces.nsINavHistoryService.TRANSITION_DOWNLOAD +
+ "&sort=" +
+ Components.interfaces.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING;
+
+ ContentArea.setContentViewForQueryString(DOWNLOADS_QUERY,
+ function() new DownloadsPlacesView(document.getElementById("downloadsRichListBox"), false),
+ { showDetailsPane: false,
+ toolbarSet: "back-button, forward-button, organizeButton, clearDownloadsButton, libraryToolbarSpacer, searchFilter" });
+ ]]></script>
+
+ <window id="places">
+ <commandset id="downloadCommands"/>
+ <menupopup id="downloadsContextMenu"/>
+ </window>
+
+ <deck id="placesViewsDeck">
+ <richlistbox id="downloadsRichListBox"/>
+ </deck>
+
+ <toolbar id="placesToolbar">
+ <toolbarbutton id="clearDownloadsButton"
+#ifdef XP_MACOSX
+ class="tabbable"
+#endif
+ insertbefore="libraryToolbarSpacer"
+ label="&clearDownloadsButton.label;"
+ command="downloadsCmd_clearDownloads"
+ tooltiptext="&clearDownloadsButton.tooltip;"/>
+ </toolbar>
+
+</overlay>
diff --git a/components/places/content/editBookmarkOverlay.js b/components/places/content/editBookmarkOverlay.js
new file mode 100644
index 0000000..69d7d32
--- /dev/null
+++ b/components/places/content/editBookmarkOverlay.js
@@ -0,0 +1,1063 @@
+/* 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 LAST_USED_ANNO = "bookmarkPropertiesDialog/folderLastUsed";
+const MAX_FOLDER_ITEM_IN_MENU_LIST = 5;
+
+var gEditItemOverlay = {
+ _uri: null,
+ _itemId: -1,
+ _itemIds: [],
+ _uris: [],
+ _tags: [],
+ _allTags: [],
+ _keyword: null,
+ _multiEdit: false,
+ _itemType: -1,
+ _readOnly: false,
+ _hiddenRows: [],
+ _onPanelReady: false,
+ _observersAdded: false,
+ _staticFoldersListBuilt: false,
+ _initialized: false,
+ _titleOverride: "",
+
+ // the first field which was edited after this panel was initialized for
+ // a certain item
+ _firstEditedField: "",
+
+ get itemId() {
+ return this._itemId;
+ },
+
+ get uri() {
+ return this._uri;
+ },
+
+ get multiEdit() {
+ return this._multiEdit;
+ },
+
+ /**
+ * Determines the initial data for the item edited or added by this dialog
+ */
+ _determineInfo: function EIO__determineInfo(aInfo) {
+ // hidden rows
+ if (aInfo && aInfo.hiddenRows)
+ this._hiddenRows = aInfo.hiddenRows;
+ else
+ this._hiddenRows.splice(0, this._hiddenRows.length);
+ // force-read-only
+ this._readOnly = aInfo && aInfo.forceReadOnly;
+ this._titleOverride = aInfo && aInfo.titleOverride ? aInfo.titleOverride
+ : "";
+ this._onPanelReady = aInfo && aInfo.onPanelReady;
+ },
+
+ _showHideRows: function EIO__showHideRows() {
+ var isBookmark = this._itemId != -1 &&
+ this._itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK;
+ var isQuery = false;
+ if (this._uri)
+ isQuery = this._uri.schemeIs("place");
+
+ this._element("nameRow").collapsed = this._hiddenRows.indexOf("name") != -1;
+ this._element("folderRow").collapsed =
+ this._hiddenRows.indexOf("folderPicker") != -1 || this._readOnly;
+ this._element("tagsRow").collapsed = !this._uri ||
+ this._hiddenRows.indexOf("tags") != -1 || isQuery;
+ // Collapse the tag selector if the item does not accept tags.
+ if (!this._element("tagsSelectorRow").collapsed &&
+ this._element("tagsRow").collapsed)
+ this.toggleTagsSelector();
+ this._element("descriptionRow").collapsed =
+ this._hiddenRows.indexOf("description") != -1 || this._readOnly;
+ this._element("keywordRow").collapsed = !isBookmark || this._readOnly ||
+ this._hiddenRows.indexOf("keyword") != -1 || isQuery;
+ this._element("locationRow").collapsed = !(this._uri && !isQuery) ||
+ this._hiddenRows.indexOf("location") != -1;
+ this._element("loadInSidebarCheckbox").collapsed = !isBookmark || isQuery ||
+ this._readOnly || this._hiddenRows.indexOf("loadInSidebar") != -1;
+ this._element("feedLocationRow").collapsed = !this._isLivemark ||
+ this._hiddenRows.indexOf("feedLocation") != -1;
+ this._element("siteLocationRow").collapsed = !this._isLivemark ||
+ this._hiddenRows.indexOf("siteLocation") != -1;
+ this._element("selectionCount").hidden = !this._multiEdit;
+ },
+
+ /**
+ * Initialize the panel
+ * @param aFor
+ * Either a places-itemId (of a bookmark, folder or a live bookmark),
+ * an array of itemIds (used for bulk tagging), or a URI object (in
+ * which case, the panel would be initialized in read-only mode).
+ * @param [optional] aInfo
+ * JS object which stores additional info for the panel
+ * initialization. The following properties may bet set:
+ * * hiddenRows (Strings array): list of rows to be hidden regardless
+ * of the item edited. Possible values: "title", "location",
+ * "description", "keyword", "loadInSidebar", "feedLocation",
+ * "siteLocation", folderPicker"
+ * * forceReadOnly - set this flag to initialize the panel to its
+ * read-only (view) mode even if the given item is editable.
+ */
+ initPanel: function EIO_initPanel(aFor, aInfo) {
+ // For sanity ensure that the implementer has uninited the panel before
+ // trying to init it again, or we could end up leaking due to observers.
+ if (this._initialized)
+ this.uninitPanel(false);
+
+ var aItemIdList;
+ if (Array.isArray(aFor)) {
+ aItemIdList = aFor;
+ aFor = aItemIdList[0];
+ }
+ else if (this._multiEdit) {
+ this._multiEdit = false;
+ this._tags = [];
+ this._uris = [];
+ this._allTags = [];
+ this._itemIds = [];
+ this._element("selectionCount").hidden = true;
+ }
+
+ this._folderMenuList = this._element("folderMenuList");
+ this._folderTree = this._element("folderTree");
+
+ this._determineInfo(aInfo);
+ if (aFor instanceof Ci.nsIURI) {
+ this._itemId = -1;
+ this._uri = aFor;
+ this._readOnly = true;
+ }
+ else {
+ this._itemId = aFor;
+ // We can't store information on invalid itemIds.
+ this._readOnly = this._readOnly || this._itemId == -1;
+
+ var containerId = PlacesUtils.bookmarks.getFolderIdForItem(this._itemId);
+ this._itemType = PlacesUtils.bookmarks.getItemType(this._itemId);
+ if (this._itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK) {
+ this._uri = PlacesUtils.bookmarks.getBookmarkURI(this._itemId);
+ this._keyword = PlacesUtils.bookmarks.getKeywordForBookmark(this._itemId);
+ this._initTextField("keywordField", this._keyword);
+ this._element("loadInSidebarCheckbox").checked =
+ PlacesUtils.annotations.itemHasAnnotation(this._itemId,
+ PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO);
+ }
+ else {
+ this._uri = null;
+ this._isLivemark = false;
+ PlacesUtils.livemarks.getLivemark({id: this._itemId })
+ .then(aLivemark => {
+ this._isLivemark = true;
+ this._initTextField("feedLocationField", aLivemark.feedURI.spec, true);
+ this._initTextField("siteLocationField", aLivemark.siteURI ? aLivemark.siteURI.spec : "", true);
+ this._showHideRows();
+ }, () => undefined);
+ }
+
+ // folder picker
+ this._initFolderMenuList(containerId);
+
+ // description field
+ this._initTextField("descriptionField",
+ PlacesUIUtils.getItemDescription(this._itemId));
+ }
+
+ if (this._itemId == -1 ||
+ this._itemType == Ci.nsINavBookmarksService.TYPE_BOOKMARK) {
+ this._isLivemark = false;
+
+ this._initTextField("locationField", this._uri.spec);
+ if (!aItemIdList) {
+ var tags = PlacesUtils.tagging.getTagsForURI(this._uri).join(", ");
+ this._initTextField("tagsField", tags, false);
+ }
+ else {
+ this._multiEdit = true;
+ this._allTags = [];
+ this._itemIds = aItemIdList;
+ for (var i = 0; i < aItemIdList.length; i++) {
+ if (aItemIdList[i] instanceof Ci.nsIURI) {
+ this._uris[i] = aItemIdList[i];
+ this._itemIds[i] = -1;
+ }
+ else
+ this._uris[i] = PlacesUtils.bookmarks.getBookmarkURI(this._itemIds[i]);
+ this._tags[i] = PlacesUtils.tagging.getTagsForURI(this._uris[i]);
+ }
+ this._allTags = this._getCommonTags();
+ this._initTextField("tagsField", this._allTags.join(", "), false);
+ this._element("itemsCountText").value =
+ PlacesUIUtils.getPluralString("detailsPane.itemsCountLabel",
+ this._itemIds.length,
+ [this._itemIds.length]);
+ }
+
+ // tags selector
+ this._rebuildTagsSelectorList();
+ }
+
+ // name picker
+ this._initNamePicker();
+
+ this._showHideRows();
+
+ // observe changes
+ if (!this._observersAdded) {
+ // Single bookmarks observe any change. History entries and multiEdit
+ // observe only tags changes, through bookmarks.
+ if (this._itemId != -1 || this._uri || this._multiEdit)
+ PlacesUtils.bookmarks.addObserver(this, false);
+
+ this._element("namePicker").addEventListener("blur", this);
+ this._element("locationField").addEventListener("blur", this);
+ this._element("tagsField").addEventListener("blur", this);
+ this._element("keywordField").addEventListener("blur", this);
+ this._element("descriptionField").addEventListener("blur", this);
+ window.addEventListener("unload", this, false);
+ this._observersAdded = true;
+ }
+
+ let focusElement = () => {
+ this._initialized = true;
+ };
+
+ if (this._onPanelReady) {
+ this._onPanelReady(focusElement);
+ } else {
+ focusElement();
+ }
+ },
+
+ /**
+ * Finds tags that are in common among this._tags entries that track tags
+ * for each selected uri.
+ * The tags arrays should be kept up-to-date for this to work properly.
+ *
+ * @return array of common tags for the selected uris.
+ */
+ _getCommonTags: function() {
+ return this._tags[0].filter(
+ function (aTag) this._tags.every(
+ function (aTags) aTags.indexOf(aTag) != -1
+ ), this
+ );
+ },
+
+ _initTextField: function(aTextFieldId, aValue, aReadOnly) {
+ var field = this._element(aTextFieldId);
+ field.readOnly = aReadOnly !== undefined ? aReadOnly : this._readOnly;
+
+ if (field.value != aValue) {
+ field.value = aValue;
+ this._editorTransactionManagerClear(field);
+ }
+ },
+
+ /**
+ * Appends a menu-item representing a bookmarks folder to a menu-popup.
+ * @param aMenupopup
+ * The popup to which the menu-item should be added.
+ * @param aFolderId
+ * The identifier of the bookmarks folder.
+ * @return the new menu item.
+ */
+ _appendFolderItemToMenupopup:
+ function EIO__appendFolderItemToMenuList(aMenupopup, aFolderId) {
+ // First make sure the folders-separator is visible
+ this._element("foldersSeparator").hidden = false;
+
+ var folderMenuItem = document.createElement("menuitem");
+ var folderTitle = PlacesUtils.bookmarks.getItemTitle(aFolderId)
+ folderMenuItem.folderId = aFolderId;
+ folderMenuItem.setAttribute("label", folderTitle);
+ folderMenuItem.className = "menuitem-iconic folder-icon";
+ aMenupopup.appendChild(folderMenuItem);
+ return folderMenuItem;
+ },
+
+ _initFolderMenuList: function EIO__initFolderMenuList(aSelectedFolder) {
+ // clean up first
+ var menupopup = this._folderMenuList.menupopup;
+ while (menupopup.childNodes.length > 6)
+ menupopup.removeChild(menupopup.lastChild);
+
+ const bms = PlacesUtils.bookmarks;
+ const annos = PlacesUtils.annotations;
+
+ // Build the static list
+ var unfiledItem = this._element("unfiledRootItem");
+ if (!this._staticFoldersListBuilt) {
+ unfiledItem.label = bms.getItemTitle(PlacesUtils.unfiledBookmarksFolderId);
+ unfiledItem.folderId = PlacesUtils.unfiledBookmarksFolderId;
+ var bmMenuItem = this._element("bmRootItem");
+ bmMenuItem.label = bms.getItemTitle(PlacesUtils.bookmarksMenuFolderId);
+ bmMenuItem.folderId = PlacesUtils.bookmarksMenuFolderId;
+ var toolbarItem = this._element("toolbarFolderItem");
+ toolbarItem.label = bms.getItemTitle(PlacesUtils.toolbarFolderId);
+ toolbarItem.folderId = PlacesUtils.toolbarFolderId;
+ this._staticFoldersListBuilt = true;
+ }
+
+ // List of recently used folders:
+ var folderIds = annos.getItemsWithAnnotation(LAST_USED_ANNO);
+
+ /**
+ * The value of the LAST_USED_ANNO annotation is the time (in the form of
+ * Date.getTime) at which the folder has been last used.
+ *
+ * First we build the annotated folders array, each item has both the
+ * folder identifier and the time at which it was last-used by this dialog
+ * set. Then we sort it descendingly based on the time field.
+ */
+ this._recentFolders = [];
+ for (var i = 0; i < folderIds.length; i++) {
+ var lastUsed = annos.getItemAnnotation(folderIds[i], LAST_USED_ANNO);
+ this._recentFolders.push({ folderId: folderIds[i], lastUsed: lastUsed });
+ }
+ this._recentFolders.sort(function(a, b) {
+ if (b.lastUsed < a.lastUsed)
+ return -1;
+ if (b.lastUsed > a.lastUsed)
+ return 1;
+ return 0;
+ });
+
+ var numberOfItems = Math.min(MAX_FOLDER_ITEM_IN_MENU_LIST,
+ this._recentFolders.length);
+ for (var i = 0; i < numberOfItems; i++) {
+ this._appendFolderItemToMenupopup(menupopup,
+ this._recentFolders[i].folderId);
+ }
+
+ var defaultItem = this._getFolderMenuItem(aSelectedFolder);
+ this._folderMenuList.selectedItem = defaultItem;
+
+ // Set a selectedIndex attribute to show special icons
+ this._folderMenuList.setAttribute("selectedIndex",
+ this._folderMenuList.selectedIndex);
+
+ // Hide the folders-separator if no folder is annotated as recently-used
+ this._element("foldersSeparator").hidden = (menupopup.childNodes.length <= 6);
+ this._folderMenuList.disabled = this._readOnly;
+ },
+
+ QueryInterface: function EIO_QueryInterface(aIID) {
+ if (aIID.equals(Ci.nsIDOMEventListener) ||
+ aIID.equals(Ci.nsINavBookmarkObserver) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ _element: function EIO__element(aID) {
+ return document.getElementById("editBMPanel_" + aID);
+ },
+
+ _editorTransactionManagerClear: function EIO__editorTransactionManagerClear(aItem) {
+ // Clear the editor's undo stack
+ let transactionManager;
+ try {
+ transactionManager = aItem.editor.transactionManager;
+ } catch (e) {
+ // When retrieving the transaction manager, editor may be null resulting
+ // in a TypeError. Additionally, the transaction manager may not
+ // exist yet, which causes access to it to throw NS_ERROR_FAILURE.
+ // In either event, the transaction manager doesn't exist it, so we
+ // don't need to worry about clearing it.
+ if (!(e instanceof TypeError) && e.result != Cr.NS_ERROR_FAILURE) {
+ throw e;
+ }
+ }
+ if (transactionManager) {
+ transactionManager.clear();
+ }
+ },
+
+ _getItemStaticTitle: function EIO__getItemStaticTitle() {
+ if (this._titleOverride)
+ return this._titleOverride;
+
+ let title = "";
+ if (this._itemId == -1) {
+ title = PlacesUtils.history.getPageTitle(this._uri);
+ }
+ else {
+ title = PlacesUtils.bookmarks.getItemTitle(this._itemId);
+ }
+ return title;
+ },
+
+ _initNamePicker: function EIO_initNamePicker() {
+ var namePicker = this._element("namePicker");
+ namePicker.value = this._getItemStaticTitle();
+ namePicker.readOnly = this._readOnly;
+ this._editorTransactionManagerClear(namePicker);
+ },
+
+ uninitPanel: function EIO_uninitPanel(aHideCollapsibleElements) {
+ if (aHideCollapsibleElements) {
+ // hide the folder tree if it was previously visible
+ var folderTreeRow = this._element("folderTreeRow");
+ if (!folderTreeRow.collapsed)
+ this.toggleFolderTreeVisibility();
+
+ // hide the tag selector if it was previously visible
+ var tagsSelectorRow = this._element("tagsSelectorRow");
+ if (!tagsSelectorRow.collapsed)
+ this.toggleTagsSelector();
+ }
+
+ if (this._observersAdded) {
+ if (this._itemId != -1 || this._uri || this._multiEdit)
+ PlacesUtils.bookmarks.removeObserver(this);
+
+ this._element("namePicker").removeEventListener("blur", this);
+ this._element("locationField").removeEventListener("blur", this);
+ this._element("tagsField").removeEventListener("blur", this);
+ this._element("keywordField").removeEventListener("blur", this);
+ this._element("descriptionField").removeEventListener("blur", this);
+
+ this._observersAdded = false;
+ }
+
+ this._itemId = -1;
+ this._uri = null;
+ this._uris = [];
+ this._tags = [];
+ this._allTags = [];
+ this._itemIds = [];
+ this._multiEdit = false;
+ this._firstEditedField = "";
+ this._initialized = false;
+ this._titleOverride = "";
+ this._readOnly = false;
+ },
+
+ onTagsFieldBlur: function EIO_onTagsFieldBlur() {
+ if (this._updateTags()) // if anything has changed
+ this._mayUpdateFirstEditField("tagsField");
+ },
+
+ _updateTags: function EIO__updateTags() {
+ if (this._multiEdit)
+ return this._updateMultipleTagsForItems();
+ return this._updateSingleTagForItem();
+ },
+
+ _updateSingleTagForItem: function EIO__updateSingleTagForItem() {
+ var currentTags = PlacesUtils.tagging.getTagsForURI(this._uri);
+ var tags = this._getTagsArrayFromTagField();
+ if (tags.length > 0 || currentTags.length > 0) {
+ var tagsToRemove = [];
+ var tagsToAdd = [];
+ var txns = [];
+ for (var i = 0; i < currentTags.length; i++) {
+ if (tags.indexOf(currentTags[i]) == -1)
+ tagsToRemove.push(currentTags[i]);
+ }
+ for (var i = 0; i < tags.length; i++) {
+ if (currentTags.indexOf(tags[i]) == -1)
+ tagsToAdd.push(tags[i]);
+ }
+
+ if (tagsToRemove.length > 0) {
+ let untagTxn = new PlacesUntagURITransaction(this._uri, tagsToRemove);
+ txns.push(untagTxn);
+ }
+ if (tagsToAdd.length > 0) {
+ let tagTxn = new PlacesTagURITransaction(this._uri, tagsToAdd);
+ txns.push(tagTxn);
+ }
+
+ if (txns.length > 0) {
+ let aggregate = new PlacesAggregatedTransaction("Update tags", txns);
+ PlacesUtils.transactionManager.doTransaction(aggregate);
+
+ // Ensure the tagsField is in sync, clean it up from empty tags
+ var tags = PlacesUtils.tagging.getTagsForURI(this._uri).join(", ");
+ this._initTextField("tagsField", tags, false);
+ return true;
+ }
+ }
+ return false;
+ },
+
+ /**
+ * Stores the first-edit field for this dialog, if the passed-in field
+ * is indeed the first edited field
+ * @param aNewField
+ * the id of the field that may be set (without the "editBMPanel_"
+ * prefix)
+ */
+ _mayUpdateFirstEditField: function EIO__mayUpdateFirstEditField(aNewField) {
+ // * The first-edit-field behavior is not applied in the multi-edit case
+ // * if this._firstEditedField is already set, this is not the first field,
+ // so there's nothing to do
+ if (this._multiEdit || this._firstEditedField)
+ return;
+
+ this._firstEditedField = aNewField;
+
+ // set the pref
+ var prefs = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+ prefs.setCharPref("browser.bookmarks.editDialog.firstEditField", aNewField);
+ },
+
+ _updateMultipleTagsForItems: function EIO__updateMultipleTagsForItems() {
+ var tags = this._getTagsArrayFromTagField();
+ if (tags.length > 0 || this._allTags.length > 0) {
+ var tagsToRemove = [];
+ var tagsToAdd = [];
+ var txns = [];
+ for (var i = 0; i < this._allTags.length; i++) {
+ if (tags.indexOf(this._allTags[i]) == -1)
+ tagsToRemove.push(this._allTags[i]);
+ }
+ for (var i = 0; i < this._tags.length; i++) {
+ tagsToAdd[i] = [];
+ for (var j = 0; j < tags.length; j++) {
+ if (this._tags[i].indexOf(tags[j]) == -1)
+ tagsToAdd[i].push(tags[j]);
+ }
+ }
+
+ if (tagsToAdd.length > 0) {
+ for (let i = 0; i < this._uris.length; i++) {
+ if (tagsToAdd[i].length > 0) {
+ let tagTxn = new PlacesTagURITransaction(this._uris[i],
+ tagsToAdd[i]);
+ txns.push(tagTxn);
+ }
+ }
+ }
+ if (tagsToRemove.length > 0) {
+ for (let i = 0; i < this._uris.length; i++) {
+ let untagTxn = new PlacesUntagURITransaction(this._uris[i],
+ tagsToRemove);
+ txns.push(untagTxn);
+ }
+ }
+
+ if (txns.length > 0) {
+ let aggregate = new PlacesAggregatedTransaction("Update tags", txns);
+ PlacesUtils.transactionManager.doTransaction(aggregate);
+
+ this._allTags = tags;
+ this._tags = [];
+ for (let i = 0; i < this._uris.length; i++) {
+ this._tags[i] = PlacesUtils.tagging.getTagsForURI(this._uris[i]);
+ }
+
+ // Ensure the tagsField is in sync, clean it up from empty tags
+ this._initTextField("tagsField", tags, false);
+ return true;
+ }
+ }
+ return false;
+ },
+
+ onNamePickerBlur: function EIO_onNamePickerBlur() {
+ if (this._itemId == -1)
+ return;
+
+ var namePicker = this._element("namePicker")
+
+ // Here we update either the item title or its cached static title
+ var newTitle = namePicker.value;
+ if (!newTitle &&
+ PlacesUtils.bookmarks.getFolderIdForItem(this._itemId) == PlacesUtils.tagsFolderId) {
+ // We don't allow setting an empty title for a tag, restore the old one.
+ this._initNamePicker();
+ }
+ else if (this._getItemStaticTitle() != newTitle) {
+ this._mayUpdateFirstEditField("namePicker");
+ let txn = new PlacesEditItemTitleTransaction(this._itemId, newTitle);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ }
+ },
+
+ onDescriptionFieldBlur: function EIO_onDescriptionFieldBlur() {
+ var description = this._element("descriptionField").value;
+ if (description != PlacesUIUtils.getItemDescription(this._itemId)) {
+ var annoObj = { name : PlacesUIUtils.DESCRIPTION_ANNO,
+ type : Ci.nsIAnnotationService.TYPE_STRING,
+ flags : 0,
+ value : description,
+ expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
+ var txn = new PlacesSetItemAnnotationTransaction(this._itemId, annoObj);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ }
+ },
+
+ onLocationFieldBlur: function EIO_onLocationFieldBlur() {
+ var uri;
+ try {
+ uri = PlacesUIUtils.createFixedURI(this._element("locationField").value);
+ }
+ catch(ex) { return; }
+
+ if (!this._uri.equals(uri)) {
+ var txn = new PlacesEditBookmarkURITransaction(this._itemId, uri);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ this._uri = uri;
+ }
+ },
+
+ onKeywordFieldBlur: function EIO_onKeywordFieldBlur() {
+ let oldKeyword = this._keyword;
+ let keyword = this._keyword = this._element("keywordField").value;
+ if (keyword != oldKeyword) {
+ let txn = new PlacesEditBookmarkKeywordTransaction(this._itemId,
+ keyword,
+ null,
+ oldKeyword);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ }
+ },
+
+ onLoadInSidebarCheckboxCommand:
+ function EIO_onLoadInSidebarCheckboxCommand() {
+ let annoObj = { name : PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO };
+ if (this._element("loadInSidebarCheckbox").checked)
+ annoObj.value = true;
+ let txn = new PlacesSetItemAnnotationTransaction(this._itemId, annoObj);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ },
+
+ toggleFolderTreeVisibility: function EIO_toggleFolderTreeVisibility() {
+ var expander = this._element("foldersExpander");
+ var folderTreeRow = this._element("folderTreeRow");
+ if (!folderTreeRow.collapsed) {
+ expander.className = "expander-down";
+ expander.setAttribute("tooltiptext",
+ expander.getAttribute("tooltiptextdown"));
+ folderTreeRow.collapsed = true;
+ this._element("chooseFolderSeparator").hidden =
+ this._element("chooseFolderMenuItem").hidden = false;
+ }
+ else {
+ expander.className = "expander-up"
+ expander.setAttribute("tooltiptext",
+ expander.getAttribute("tooltiptextup"));
+ folderTreeRow.collapsed = false;
+
+ // XXXmano: Ideally we would only do this once, but for some odd reason,
+ // the editable mode set on this tree, together with its collapsed state
+ // breaks the view.
+ const FOLDER_TREE_PLACE_URI =
+ "place:excludeItems=1&excludeQueries=1&excludeReadOnlyFolders=1&folder=" +
+ PlacesUIUtils.allBookmarksFolderId;
+ this._folderTree.place = FOLDER_TREE_PLACE_URI;
+
+ this._element("chooseFolderSeparator").hidden =
+ this._element("chooseFolderMenuItem").hidden = true;
+ var currentFolder = this._getFolderIdFromMenuList();
+ this._folderTree.selectItems([currentFolder]);
+ this._folderTree.focus();
+ }
+ },
+
+ _getFolderIdFromMenuList:
+ function EIO__getFolderIdFromMenuList() {
+ var selectedItem = this._folderMenuList.selectedItem;
+ NS_ASSERT("folderId" in selectedItem,
+ "Invalid menuitem in the folders-menulist");
+ return selectedItem.folderId;
+ },
+
+ /**
+ * Get the corresponding menu-item in the folder-menu-list for a bookmarks
+ * folder if such an item exists. Otherwise, this creates a menu-item for the
+ * folder. If the items-count limit (see MAX_FOLDERS_IN_MENU_LIST) is reached,
+ * the new item replaces the last menu-item.
+ * @param aFolderId
+ * The identifier of the bookmarks folder.
+ */
+ _getFolderMenuItem:
+ function EIO__getFolderMenuItem(aFolderId) {
+ var menupopup = this._folderMenuList.menupopup;
+
+ for (let i = 0; i < menupopup.childNodes.length; i++) {
+ if ("folderId" in menupopup.childNodes[i] &&
+ menupopup.childNodes[i].folderId == aFolderId)
+ return menupopup.childNodes[i];
+ }
+
+ // 3 special folders + separator + folder-items-count limit
+ if (menupopup.childNodes.length == 4 + MAX_FOLDER_ITEM_IN_MENU_LIST)
+ menupopup.removeChild(menupopup.lastChild);
+
+ return this._appendFolderItemToMenupopup(menupopup, aFolderId);
+ },
+
+ onFolderMenuListCommand: function EIO_onFolderMenuListCommand(aEvent) {
+ // Set a selectedIndex attribute to show special icons
+ this._folderMenuList.setAttribute("selectedIndex",
+ this._folderMenuList.selectedIndex);
+
+ if (aEvent.target.id == "editBMPanel_chooseFolderMenuItem") {
+ // reset the selection back to where it was and expand the tree
+ // (this menu-item is hidden when the tree is already visible
+ var container = PlacesUtils.bookmarks.getFolderIdForItem(this._itemId);
+ var item = this._getFolderMenuItem(container);
+ this._folderMenuList.selectedItem = item;
+ // XXXmano HACK: setTimeout 100, otherwise focus goes back to the
+ // menulist right away
+ setTimeout(function(self) self.toggleFolderTreeVisibility(), 100, this);
+ return;
+ }
+
+ // Move the item
+ var container = this._getFolderIdFromMenuList();
+ if (PlacesUtils.bookmarks.getFolderIdForItem(this._itemId) != container) {
+ var txn = new PlacesMoveItemTransaction(this._itemId,
+ container,
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ PlacesUtils.transactionManager.doTransaction(txn);
+
+ // Mark the containing folder as recently-used if it isn't in the
+ // static list
+ if (container != PlacesUtils.unfiledBookmarksFolderId &&
+ container != PlacesUtils.toolbarFolderId &&
+ container != PlacesUtils.bookmarksMenuFolderId)
+ this._markFolderAsRecentlyUsed(container);
+ }
+
+ // Update folder-tree selection
+ var folderTreeRow = this._element("folderTreeRow");
+ if (!folderTreeRow.collapsed) {
+ var selectedNode = this._folderTree.selectedNode;
+ if (!selectedNode ||
+ PlacesUtils.getConcreteItemId(selectedNode) != container)
+ this._folderTree.selectItems([container]);
+ }
+ },
+
+ onFolderTreeSelect: function EIO_onFolderTreeSelect() {
+ var selectedNode = this._folderTree.selectedNode;
+
+ // Disable the "New Folder" button if we cannot create a new folder
+ this._element("newFolderButton")
+ .disabled = !this._folderTree.insertionPoint || !selectedNode;
+
+ if (!selectedNode)
+ return;
+
+ var folderId = PlacesUtils.getConcreteItemId(selectedNode);
+ if (this._getFolderIdFromMenuList() == folderId)
+ return;
+
+ var folderItem = this._getFolderMenuItem(folderId);
+ this._folderMenuList.selectedItem = folderItem;
+ folderItem.doCommand();
+ },
+
+ _markFolderAsRecentlyUsed:
+ function EIO__markFolderAsRecentlyUsed(aFolderId) {
+ var txns = [];
+
+ // Expire old unused recent folders
+ var anno = this._getLastUsedAnnotationObject(false);
+ while (this._recentFolders.length > MAX_FOLDER_ITEM_IN_MENU_LIST) {
+ var folderId = this._recentFolders.pop().folderId;
+ let annoTxn = new PlacesSetItemAnnotationTransaction(folderId, anno);
+ txns.push(annoTxn);
+ }
+
+ // Mark folder as recently used
+ anno = this._getLastUsedAnnotationObject(true);
+ let annoTxn = new PlacesSetItemAnnotationTransaction(aFolderId, anno);
+ txns.push(annoTxn);
+
+ let aggregate = new PlacesAggregatedTransaction("Update last used folders", txns);
+ PlacesUtils.transactionManager.doTransaction(aggregate);
+ },
+
+ /**
+ * Returns an object which could then be used to set/unset the
+ * LAST_USED_ANNO annotation for a folder.
+ *
+ * @param aLastUsed
+ * Whether to set or unset the LAST_USED_ANNO annotation.
+ * @returns an object representing the annotation which could then be used
+ * with the transaction manager.
+ */
+ _getLastUsedAnnotationObject:
+ function EIO__getLastUsedAnnotationObject(aLastUsed) {
+ var anno = { name: LAST_USED_ANNO,
+ type: Ci.nsIAnnotationService.TYPE_INT32,
+ flags: 0,
+ value: aLastUsed ? new Date().getTime() : null,
+ expires: Ci.nsIAnnotationService.EXPIRE_NEVER };
+
+ return anno;
+ },
+
+ _rebuildTagsSelectorList: function EIO__rebuildTagsSelectorList() {
+ var tagsSelector = this._element("tagsSelector");
+ var tagsSelectorRow = this._element("tagsSelectorRow");
+ if (tagsSelectorRow.collapsed)
+ return;
+
+ // Save the current scroll position and restore it after the rebuild.
+ let firstIndex = tagsSelector.getIndexOfFirstVisibleRow();
+ let selectedIndex = tagsSelector.selectedIndex;
+ let selectedTag = selectedIndex >= 0 ? tagsSelector.selectedItem.label
+ : null;
+
+ while (tagsSelector.hasChildNodes())
+ tagsSelector.removeChild(tagsSelector.lastChild);
+
+ var tagsInField = this._getTagsArrayFromTagField();
+ var allTags = PlacesUtils.tagging.allTags;
+ for (var i = 0; i < allTags.length; i++) {
+ var tag = allTags[i];
+ var elt = document.createElement("listitem");
+ elt.setAttribute("type", "checkbox");
+ elt.setAttribute("label", tag);
+ if (tagsInField.indexOf(tag) != -1)
+ elt.setAttribute("checked", "true");
+ tagsSelector.appendChild(elt);
+ if (selectedTag === tag)
+ selectedIndex = tagsSelector.getIndexOfItem(elt);
+ }
+
+ // Restore position.
+ // The listbox allows to scroll only if the required offset doesn't
+ // overflow its capacity, thus need to adjust the index for removals.
+ firstIndex =
+ Math.min(firstIndex,
+ tagsSelector.itemCount - tagsSelector.getNumberOfVisibleRows());
+ tagsSelector.scrollToIndex(firstIndex);
+ if (selectedIndex >= 0 && tagsSelector.itemCount > 0) {
+ selectedIndex = Math.min(selectedIndex, tagsSelector.itemCount - 1);
+ tagsSelector.selectedIndex = selectedIndex;
+ tagsSelector.ensureIndexIsVisible(selectedIndex);
+ }
+ },
+
+ toggleTagsSelector: function EIO_toggleTagsSelector() {
+ var tagsSelector = this._element("tagsSelector");
+ var tagsSelectorRow = this._element("tagsSelectorRow");
+ var expander = this._element("tagsSelectorExpander");
+ if (tagsSelectorRow.collapsed) {
+ expander.className = "expander-up";
+ expander.setAttribute("tooltiptext",
+ expander.getAttribute("tooltiptextup"));
+ tagsSelectorRow.collapsed = false;
+ this._rebuildTagsSelectorList();
+
+ // This is a no-op if we've added the listener.
+ tagsSelector.addEventListener("CheckboxStateChange", this, false);
+ }
+ else {
+ expander.className = "expander-down";
+ expander.setAttribute("tooltiptext",
+ expander.getAttribute("tooltiptextdown"));
+ tagsSelectorRow.collapsed = true;
+ }
+ },
+
+ /**
+ * Splits "tagsField" element value, returning an array of valid tag strings.
+ *
+ * @return Array of tag strings found in the field value.
+ */
+ _getTagsArrayFromTagField: function EIO__getTagsArrayFromTagField() {
+ let tags = this._element("tagsField").value;
+ return tags.trim()
+ .split(/\s*,\s*/) // Split on commas and remove spaces.
+ .filter(function (tag) tag.length > 0); // Kill empty tags.
+ },
+
+ newFolder: function EIO_newFolder() {
+ var ip = this._folderTree.insertionPoint;
+
+ // default to the bookmarks menu folder
+ if (!ip || ip.itemId == PlacesUIUtils.allBookmarksFolderId) {
+ ip = new InsertionPoint(PlacesUtils.bookmarksMenuFolderId,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ Ci.nsITreeView.DROP_ON);
+ }
+
+ // XXXmano: add a separate "New Folder" string at some point...
+ var defaultLabel = this._element("newFolderButton").label;
+ var txn = new PlacesCreateFolderTransaction(defaultLabel, ip.itemId, ip.index);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ this._folderTree.focus();
+ this._folderTree.selectItems([this._lastNewItem]);
+ this._folderTree.startEditing(this._folderTree.view.selection.currentIndex,
+ this._folderTree.columns.getFirstColumn());
+ },
+
+ // nsIDOMEventListener
+ handleEvent: function EIO_nsIDOMEventListener(aEvent) {
+ switch (aEvent.type) {
+ case "CheckboxStateChange":
+ // Update the tags field when items are checked/unchecked in the listbox
+ var tags = this._getTagsArrayFromTagField();
+
+ if (aEvent.target.checked) {
+ if (tags.indexOf(aEvent.target.label) == -1)
+ tags.push(aEvent.target.label);
+ }
+ else {
+ var indexOfItem = tags.indexOf(aEvent.target.label);
+ if (indexOfItem != -1)
+ tags.splice(indexOfItem, 1);
+ }
+ this._element("tagsField").value = tags.join(", ");
+ this._updateTags();
+ break;
+ case "blur":
+ let replaceFn = (str, firstLetter) => firstLetter.toUpperCase();
+ let nodeName = aEvent.target.id.replace(/editBMPanel_(\w)/, replaceFn);
+ this["on" + nodeName + "Blur"]();
+ break;
+ case "unload":
+ this.uninitPanel(false);
+ break;
+ }
+ },
+
+ // nsINavBookmarkObserver
+ onItemChanged: function EIO_onItemChanged(aItemId, aProperty,
+ aIsAnnotationProperty, aValue,
+ aLastModified, aItemType) {
+ if (aProperty == "tags") {
+ // Tags case is special, since they should be updated if either:
+ // - the notification is for the edited bookmark
+ // - the notification is for the edited history entry
+ // - the notification is for one of edited uris
+ let shouldUpdateTagsField = this._itemId == aItemId;
+ if (this._itemId == -1 || this._multiEdit) {
+ // Check if the changed uri is part of the modified ones.
+ let changedURI = PlacesUtils.bookmarks.getBookmarkURI(aItemId);
+ let uris = this._multiEdit ? this._uris : [this._uri];
+ uris.forEach(function (aURI, aIndex) {
+ if (aURI.equals(changedURI)) {
+ shouldUpdateTagsField = true;
+ if (this._multiEdit) {
+ this._tags[aIndex] = PlacesUtils.tagging.getTagsForURI(this._uris[aIndex]);
+ }
+ }
+ }, this);
+ }
+
+ if (shouldUpdateTagsField) {
+ if (this._multiEdit) {
+ this._allTags = this._getCommonTags();
+ this._initTextField("tagsField", this._allTags.join(", "), false);
+ }
+ else {
+ let tags = PlacesUtils.tagging.getTagsForURI(this._uri).join(", ");
+ this._initTextField("tagsField", tags, false);
+ }
+ }
+
+ // Any tags change should be reflected in the tags selector.
+ this._rebuildTagsSelectorList();
+ return;
+ }
+
+ if (this._itemId != aItemId) {
+ if (aProperty == "title") {
+ // If the title of a folder which is listed within the folders
+ // menulist has been changed, we need to update the label of its
+ // representing element.
+ var menupopup = this._folderMenuList.menupopup;
+ for (let i = 0; i < menupopup.childNodes.length; i++) {
+ if ("folderId" in menupopup.childNodes[i] &&
+ menupopup.childNodes[i].folderId == aItemId) {
+ menupopup.childNodes[i].label = aValue;
+ break;
+ }
+ }
+ }
+
+ return;
+ }
+
+ switch (aProperty) {
+ case "title":
+ var namePicker = this._element("namePicker");
+ if (namePicker.value != aValue) {
+ namePicker.value = aValue;
+ this._editorTransactionManagerClear(namePicker);
+ }
+ break;
+ case "uri":
+ var locationField = this._element("locationField");
+ if (locationField.value != aValue) {
+ this._uri = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService).
+ newURI(aValue, null, null);
+ this._initTextField("locationField", this._uri.spec);
+ this._initNamePicker();
+ this._initTextField("tagsField",
+ PlacesUtils.tagging
+ .getTagsForURI(this._uri).join(", "),
+ false);
+ this._rebuildTagsSelectorList();
+ }
+ break;
+ case "keyword":
+ this._keyword = PlacesUtils.bookmarks.getKeywordForBookmark(this._itemId);
+ this._initTextField("keywordField", this._keyword);
+ break;
+ case PlacesUIUtils.DESCRIPTION_ANNO:
+ this._initTextField("descriptionField",
+ PlacesUIUtils.getItemDescription(this._itemId));
+ break;
+ case PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO:
+ this._element("loadInSidebarCheckbox").checked =
+ PlacesUtils.annotations.itemHasAnnotation(this._itemId,
+ PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO);
+ break;
+ case PlacesUtils.LMANNO_FEEDURI:
+ let feedURISpec =
+ PlacesUtils.annotations.getItemAnnotation(this._itemId,
+ PlacesUtils.LMANNO_FEEDURI);
+ this._initTextField("feedLocationField", feedURISpec, true);
+ break;
+ case PlacesUtils.LMANNO_SITEURI:
+ let siteURISpec = "";
+ try {
+ siteURISpec =
+ PlacesUtils.annotations.getItemAnnotation(this._itemId,
+ PlacesUtils.LMANNO_SITEURI);
+ } catch (ex) {}
+ this._initTextField("siteLocationField", siteURISpec, true);
+ break;
+ }
+ },
+
+ onItemMoved: function EIO_onItemMoved(aItemId, aOldParent, aOldIndex,
+ aNewParent, aNewIndex, aItemType) {
+ if (aItemId != this._itemId ||
+ aNewParent == this._getFolderIdFromMenuList())
+ return;
+
+ var folderItem = this._getFolderMenuItem(aNewParent);
+
+ // just setting selectItem _does not_ trigger oncommand, so we don't
+ // recurse
+ this._folderMenuList.selectedItem = folderItem;
+ },
+
+ onItemAdded: function EIO_onItemAdded(aItemId, aParentId, aIndex, aItemType,
+ aURI) {
+ this._lastNewItem = aItemId;
+ },
+
+ onItemRemoved: function() { },
+ onBeginUpdateBatch: function() { },
+ onEndUpdateBatch: function() { },
+ onItemVisited: function() { },
+};
diff --git a/components/places/content/editBookmarkOverlay.xul b/components/places/content/editBookmarkOverlay.xul
new file mode 100644
index 0000000..196369d
--- /dev/null
+++ b/components/places/content/editBookmarkOverlay.xul
@@ -0,0 +1,228 @@
+<!-- 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 % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd">
+%editBookmarkOverlayDTD;
+]>
+
+<?xml-stylesheet href="chrome://browser/skin/places/editBookmarkOverlay.css"?>
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+
+<overlay id="editBookmarkOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <vbox id="editBookmarkPanelContent" flex="1">
+ <broadcaster id="paneElementsBroadcaster"/>
+
+ <hbox id="editBMPanel_selectionCount" hidden="true" pack="center">
+ <label id="editBMPanel_itemsCountText"/>
+ </hbox>
+
+ <grid id="editBookmarkPanelGrid" flex="1">
+ <columns id="editBMPanel_columns">
+ <column id="editBMPanel_labelColumn" />
+ <column flex="1" id="editBMPanel_editColumn" />
+ </columns>
+ <rows id="editBMPanel_rows">
+ <row id="editBMPanel_nameRow"
+ align="center"
+ collapsed="true">
+ <label value="&editBookmarkOverlay.name.label;"
+ class="editBMPanel_rowLabel"
+ accesskey="&editBookmarkOverlay.name.accesskey;"
+ control="editBMPanel_namePicker"
+ observes="paneElementsBroadcaster"/>
+ <textbox id="editBMPanel_namePicker"
+ observes="paneElementsBroadcaster"/>
+ </row>
+
+ <row id="editBMPanel_locationRow"
+ align="center"
+ collapsed="true">
+ <label value="&editBookmarkOverlay.location.label;"
+ class="editBMPanel_rowLabel"
+ accesskey="&editBookmarkOverlay.location.accesskey;"
+ control="editBMPanel_locationField"
+ observes="paneElementsBroadcaster"/>
+ <textbox id="editBMPanel_locationField"
+ class="uri-element"
+ observes="paneElementsBroadcaster"/>
+ </row>
+
+ <row id="editBMPanel_feedLocationRow"
+ align="center"
+ collapsed="true">
+ <label value="&editBookmarkOverlay.feedLocation.label;"
+ class="editBMPanel_rowLabel"
+ accesskey="&editBookmarkOverlay.feedLocation.accesskey;"
+ control="editBMPanel_feedLocationField"
+ observes="paneElementsBroadcaster"/>
+ <textbox id="editBMPanel_feedLocationField"
+ class="uri-element"
+ observes="paneElementsBroadcaster"/>
+ </row>
+
+ <row id="editBMPanel_siteLocationRow"
+ align="center"
+ collapsed="true">
+ <label value="&editBookmarkOverlay.siteLocation.label;"
+ class="editBMPanel_rowLabel"
+ accesskey="&editBookmarkOverlay.siteLocation.accesskey;"
+ control="editBMPanel_siteLocationField"
+ observes="paneElementsBroadcaster"/>
+ <textbox id="editBMPanel_siteLocationField"
+ class="uri-element"
+ observes="paneElementsBroadcaster"/>
+ </row>
+
+ <row id="editBMPanel_folderRow"
+ align="center"
+ collapsed="true">
+ <label value="&editBookmarkOverlay.folder.label;"
+ class="editBMPanel_rowLabel"
+ control="editBMPanel_folderMenuList"
+ observes="paneElementsBroadcaster"/>
+ <hbox flex="1" align="center">
+ <menulist id="editBMPanel_folderMenuList"
+ class="folder-icon"
+ flex="1"
+ oncommand="gEditItemOverlay.onFolderMenuListCommand(event);"
+ observes="paneElementsBroadcaster">
+ <menupopup>
+ <!-- Static item for special folders -->
+ <menuitem id="editBMPanel_toolbarFolderItem"
+ class="menuitem-iconic folder-icon"/>
+ <menuitem id="editBMPanel_bmRootItem"
+ class="menuitem-iconic folder-icon"/>
+ <menuitem id="editBMPanel_unfiledRootItem"
+ class="menuitem-iconic folder-icon"/>
+ <menuseparator id="editBMPanel_chooseFolderSeparator"/>
+ <menuitem id="editBMPanel_chooseFolderMenuItem"
+ label="&editBookmarkOverlay.choose.label;"
+ class="menuitem-iconic folder-icon"/>
+ <menuseparator id="editBMPanel_foldersSeparator" hidden="true"/>
+ </menupopup>
+ </menulist>
+ <button id="editBMPanel_foldersExpander"
+ class="expander-down"
+ tooltiptext="&editBookmarkOverlay.foldersExpanderDown.tooltip;"
+ tooltiptextdown="&editBookmarkOverlay.foldersExpanderDown.tooltip;"
+ tooltiptextup="&editBookmarkOverlay.expanderUp.tooltip;"
+ oncommand="gEditItemOverlay.toggleFolderTreeVisibility();"
+ observes="paneElementsBroadcaster"/>
+ </hbox>
+ </row>
+
+ <row id="editBMPanel_folderTreeRow"
+ collapsed="true"
+ flex="1">
+ <spacer/>
+ <vbox flex="1">
+ <tree id="editBMPanel_folderTree"
+ flex="1"
+ class="placesTree"
+ type="places"
+ height="150"
+ minheight="150"
+ editable="true"
+ onselect="gEditItemOverlay.onFolderTreeSelect();"
+ hidecolumnpicker="true"
+ observes="paneElementsBroadcaster">
+ <treecols>
+ <treecol anonid="title" flex="1" primary="true" hideheader="true"/>
+ </treecols>
+ <treechildren flex="1"/>
+ </tree>
+
+ <hbox id="editBMPanel_newFolderBox">
+ <button label="&editBookmarkOverlay.newFolderButton.label;"
+ id="editBMPanel_newFolderButton"
+ accesskey="&editBookmarkOverlay.newFolderButton.accesskey;"
+ oncommand="gEditItemOverlay.newFolder();"/>
+ </hbox>
+ </vbox>
+ </row>
+
+ <row id="editBMPanel_tagsRow"
+ align="center"
+ collapsed="true">
+ <label value="&editBookmarkOverlay.tags.label;"
+ class="editBMPanel_rowLabel"
+ accesskey="&editBookmarkOverlay.tags.accesskey;"
+ control="editBMPanel_tagsField"
+ observes="paneElementsBroadcaster"/>
+ <hbox flex="1" align="center">
+ <textbox id="editBMPanel_tagsField"
+ type="autocomplete"
+ class="padded"
+ flex="1"
+ autocompletesearch="places-tag-autocomplete"
+ completedefaultindex="true"
+ tabscrolling="true"
+ showcommentcolumn="true"
+ observes="paneElementsBroadcaster"
+ placeholder="&editBookmarkOverlay.tagsEmptyDesc.label;"/>
+ <button id="editBMPanel_tagsSelectorExpander"
+ class="expander-down"
+ tooltiptext="&editBookmarkOverlay.tagsExpanderDown.tooltip;"
+ tooltiptextdown="&editBookmarkOverlay.tagsExpanderDown.tooltip;"
+ tooltiptextup="&editBookmarkOverlay.expanderUp.tooltip;"
+ oncommand="gEditItemOverlay.toggleTagsSelector();"
+ observes="paneElementsBroadcaster"/>
+ </hbox>
+ </row>
+
+ <row id="editBMPanel_tagsSelectorRow"
+ align="center"
+ collapsed="true">
+ <spacer/>
+ <listbox id="editBMPanel_tagsSelector"
+ height="150"
+ observes="paneElementsBroadcaster"/>
+ </row>
+
+ <row id="editBMPanel_keywordRow"
+ align="center"
+ collapsed="true">
+ <observes element="additionalInfoBroadcaster" attribute="hidden"/>
+ <label value="&editBookmarkOverlay.keyword.label;"
+ class="editBMPanel_rowLabel"
+ accesskey="&editBookmarkOverlay.keyword.accesskey;"
+ control="editBMPanel_keywordField"
+ observes="paneElementsBroadcaster"/>
+ <textbox id="editBMPanel_keywordField"
+ observes="paneElementsBroadcaster"/>
+ </row>
+
+ <row id="editBMPanel_descriptionRow"
+ collapsed="true">
+ <observes element="additionalInfoBroadcaster" attribute="hidden"/>
+ <label value="&editBookmarkOverlay.description.label;"
+ class="editBMPanel_rowLabel"
+ accesskey="&editBookmarkOverlay.description.accesskey;"
+ control="editBMPanel_descriptionField"
+ observes="paneElementsBroadcaster"/>
+ <textbox id="editBMPanel_descriptionField"
+ multiline="true"
+ observes="paneElementsBroadcaster"/>
+ </row>
+ </rows>
+ </grid>
+
+ <checkbox id="editBMPanel_loadInSidebarCheckbox"
+ collapsed="true"
+ label="&editBookmarkOverlay.loadInSidebar.label;"
+ accesskey="&editBookmarkOverlay.loadInSidebar.accesskey;"
+ oncommand="gEditItemOverlay.onLoadInSidebarCheckboxCommand();"
+ observes="paneElementsBroadcaster">
+ <observes element="additionalInfoBroadcaster" attribute="hidden"/>
+ </checkbox>
+
+ <!-- If the ids are changing or additional fields are being added, be sure
+ to sync the values in places.js -->
+ <broadcaster id="additionalInfoBroadcaster"/>
+
+ </vbox>
+</overlay>
diff --git a/components/places/content/history-panel.js b/components/places/content/history-panel.js
new file mode 100644
index 0000000..cda39dd
--- /dev/null
+++ b/components/places/content/history-panel.js
@@ -0,0 +1,91 @@
+/* -*- 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/. */
+
+var gHistoryTree;
+var gSearchBox;
+var gHistoryGrouping = "";
+var gSearching = false;
+
+function HistorySidebarInit()
+{
+ gHistoryTree = document.getElementById("historyTree");
+ gSearchBox = document.getElementById("search-box");
+
+ gHistoryGrouping = document.getElementById("viewButton").
+ getAttribute("selectedsort");
+
+ if (gHistoryGrouping == "site")
+ document.getElementById("bysite").setAttribute("checked", "true");
+ else if (gHistoryGrouping == "visited")
+ document.getElementById("byvisited").setAttribute("checked", "true");
+ else if (gHistoryGrouping == "lastvisited")
+ document.getElementById("bylastvisited").setAttribute("checked", "true");
+ else if (gHistoryGrouping == "dayandsite")
+ document.getElementById("bydayandsite").setAttribute("checked", "true");
+ else
+ document.getElementById("byday").setAttribute("checked", "true");
+
+ searchHistory("");
+}
+
+function GroupBy(groupingType)
+{
+ gHistoryGrouping = groupingType;
+ searchHistory(gSearchBox.value);
+}
+
+function searchHistory(aInput)
+{
+ var query = PlacesUtils.history.getNewQuery();
+ var options = PlacesUtils.history.getNewQueryOptions();
+
+ const NHQO = Ci.nsINavHistoryQueryOptions;
+ var sortingMode;
+ var resultType;
+
+ switch (gHistoryGrouping) {
+ case "visited":
+ resultType = NHQO.RESULTS_AS_URI;
+ sortingMode = NHQO.SORT_BY_VISITCOUNT_DESCENDING;
+ break;
+ case "lastvisited":
+ resultType = NHQO.RESULTS_AS_URI;
+ sortingMode = NHQO.SORT_BY_DATE_DESCENDING;
+ break;
+ case "dayandsite":
+ resultType = NHQO.RESULTS_AS_DATE_SITE_QUERY;
+ break;
+ case "site":
+ resultType = NHQO.RESULTS_AS_SITE_QUERY;
+ sortingMode = NHQO.SORT_BY_TITLE_ASCENDING;
+ break;
+ case "day":
+ default:
+ resultType = NHQO.RESULTS_AS_DATE_QUERY;
+ break;
+ }
+
+ if (aInput) {
+ query.searchTerms = aInput;
+ if (gHistoryGrouping != "visited" && gHistoryGrouping != "lastvisited") {
+ sortingMode = NHQO.SORT_BY_FRECENCY_DESCENDING;
+ resultType = NHQO.RESULTS_AS_URI;
+ }
+ }
+
+ options.sortingMode = sortingMode;
+ options.resultType = resultType;
+ options.includeHidden = !!aInput;
+
+ // call load() on the tree manually
+ // instead of setting the place attribute in history-panel.xul
+ // otherwise, we will end up calling load() twice
+ gHistoryTree.load([query], options);
+}
+
+window.addEventListener("SidebarFocused",
+ function()
+ gSearchBox.focus(),
+ false);
diff --git a/components/places/content/history-panel.xul b/components/places/content/history-panel.xul
new file mode 100644
index 0000000..d1c875a
--- /dev/null
+++ b/components/places/content/history-panel.xul
@@ -0,0 +1,95 @@
+<?xml version="1.0"?> <!-- -*- Mode: xml; indent-tabs-mode: nil; -*- -->
+
+# 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/places/places.css"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+
+<!DOCTYPE page [
+<!ENTITY % placesDTD SYSTEM "chrome://browser/locale/places/places.dtd">
+%placesDTD;
+]>
+
+<!-- we need to keep id="history-panel" for upgrade and switching
+ between versions of the browser -->
+
+<page id="history-panel" orient="vertical"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="HistorySidebarInit();"
+ onunload="SidebarUtils.setMouseoverURL('');">
+
+ <script type="application/javascript"
+ src="chrome://browser/content/bookmarks/sidebarUtils.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/places/history-panel.js"/>
+
+ <commandset id="editMenuCommands"/>
+ <commandset id="placesCommands"/>
+
+ <keyset id="editMenuKeys">
+#ifdef XP_MACOSX
+ <key id="key_delete2" keycode="VK_BACK" command="cmd_delete"/>
+#endif
+ </keyset>
+
+ <!-- required to overlay the context menu -->
+ <menupopup id="placesContext"/>
+
+ <!-- Bookmarks and history tooltip -->
+ <tooltip id="bhTooltip"/>
+
+ <hbox id="sidebar-search-container" align="center">
+ <label id="sidebar-search-label"
+ value="&find.label;" accesskey="&find.accesskey;"
+ control="search-box"/>
+ <textbox id="search-box" flex="1" type="search" class="compact"
+ aria-controls="historyTree"
+ oncommand="searchHistory(this.value);"/>
+ <button id="viewButton" style="min-width:0px !important;" type="menu"
+ label="&view.label;" accesskey="&view.accesskey;" selectedsort="day"
+ persist="selectedsort">
+ <menupopup>
+ <menuitem id="bydayandsite" label="&byDayAndSite.label;"
+ accesskey="&byDayAndSite.accesskey;" type="radio"
+ oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'dayandsite'); GroupBy('dayandsite');"/>
+ <menuitem id="bysite" label="&bySite.label;"
+ accesskey="&bySite.accesskey;" type="radio"
+ oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'site'); GroupBy('site');"/>
+ <menuitem id="byday" label="&byDate.label;"
+ accesskey="&byDate.accesskey;"
+ type="radio"
+ oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'day'); GroupBy('day');"/>
+ <menuitem id="byvisited" label="&byMostVisited.label;"
+ accesskey="&byMostVisited.accesskey;"
+ type="radio"
+ oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'visited'); GroupBy('visited');"/>
+ <menuitem id="bylastvisited" label="&byLastVisited.label;"
+ accesskey="&byLastVisited.accesskey;"
+ type="radio"
+ oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'lastvisited'); GroupBy('lastvisited');"/>
+ </menupopup>
+ </button>
+ </hbox>
+
+ <tree id="historyTree"
+ class="sidebar-placesTree"
+ flex="1"
+ type="places"
+ context="placesContext"
+ hidecolumnpicker="true"
+ onkeypress="SidebarUtils.handleTreeKeyPress(event);"
+ onclick="SidebarUtils.handleTreeClick(this, event, true);"
+ onmousemove="SidebarUtils.handleTreeMouseMove(event);"
+ onmouseout="SidebarUtils.setMouseoverURL('');">
+ <treecols>
+ <treecol id="title" flex="1" primary="true" hideheader="true"/>
+ </treecols>
+ <treechildren class="sidebar-placesTreechildren" flex="1" tooltip="bhTooltip"/>
+ </tree>
+</page>
diff --git a/components/places/content/menu.xml b/components/places/content/menu.xml
new file mode 100644
index 0000000..d4041ec
--- /dev/null
+++ b/components/places/content/menu.xml
@@ -0,0 +1,488 @@
+<?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="placesMenuBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xbl="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">
+
+ <binding id="places-popup-base"
+ extends="chrome://global/content/bindings/popup.xml#popup">
+ <content>
+ <xul:hbox flex="1">
+ <xul:vbox class="menupopup-drop-indicator-bar" hidden="true">
+ <xul:image class="menupopup-drop-indicator" mousethrough="always"/>
+ </xul:vbox>
+ <xul:arrowscrollbox class="popup-internal-box" flex="1" orient="vertical"
+ smoothscroll="false">
+ <children/>
+ </xul:arrowscrollbox>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+
+ <field name="_indicatorBar">
+ document.getAnonymousElementByAttribute(this, "class",
+ "menupopup-drop-indicator-bar");
+ </field>
+
+ <field name="_scrollBox">
+ document.getAnonymousElementByAttribute(this, "class",
+ "popup-internal-box");
+ </field>
+
+ <!-- This is the view that manage the popup -->
+ <field name="_rootView">PlacesUIUtils.getViewForNode(this);</field>
+
+ <!-- Check if we should hide the drop indicator for the target -->
+ <method name="_hideDropIndicator">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ let target = aEvent.target;
+
+ // Don't draw the drop indicator outside of markers.
+ // The markers are hidden, since otherwise sometimes popups acquire
+ // scrollboxes on OS X, so we can't use them directly.
+ let firstChildTop = this._startMarker.nextSibling.boxObject.y;
+ let lastChildBottom = this._endMarker.previousSibling.boxObject.y +
+ this._endMarker.previousSibling.boxObject.height;
+ let betweenMarkers = target.boxObject.y >= firstChildTop ||
+ target.boxObject.y <= lastChildBottom;
+
+ // Hide the dropmarker if current node is not a Places node.
+ return !(target && target._placesNode && betweenMarkers);
+ ]]></body>
+ </method>
+
+ <!-- This function returns information about where to drop when
+ dragging over this popup insertion point -->
+ <method name="_getDropPoint">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ // Can't drop if the menu isn't a folder
+ let resultNode = this._placesNode;
+
+ if (!PlacesUtils.nodeIsFolder(resultNode) ||
+ PlacesControllerDragHelper.disallowInsertion(resultNode)) {
+ return null;
+ }
+
+ var dropPoint = { ip: null, folderElt: null };
+
+ // The element we are dragging over
+ let elt = aEvent.target;
+ if (elt.localName == "menupopup")
+ elt = elt.parentNode;
+
+ // Calculate positions taking care of arrowscrollbox
+ let eventY = aEvent.layerY;
+ let scrollbox = this._scrollBox;
+ let scrollboxOffset = scrollbox.scrollBoxObject.y -
+ (scrollbox.boxObject.y - this.boxObject.y);
+ let eltY = elt.boxObject.y - scrollboxOffset;
+ let eltHeight = elt.boxObject.height;
+
+ if (!elt._placesNode) {
+ // If we are dragging over a non places node drop at the end.
+ dropPoint.ip = new InsertionPoint(
+ PlacesUtils.getConcreteItemId(resultNode),
+ -1,
+ Ci.nsITreeView.DROP_ON);
+ // We can set folderElt if we are dropping over a static menu that
+ // has an internal placespopup.
+ let isMenu = elt.localName == "menu" ||
+ (elt.localName == "toolbarbutton" &&
+ elt.getAttribute("type") == "menu");
+ if (isMenu && elt.lastChild &&
+ elt.lastChild.hasAttribute("placespopup"))
+ dropPoint.folderElt = elt;
+ return dropPoint;
+ }
+ if ((PlacesUtils.nodeIsFolder(elt._placesNode) &&
+ !PlacesUIUtils.isContentsReadOnly(elt._placesNode)) ||
+ PlacesUtils.nodeIsTagQuery(elt._placesNode)) {
+ // This is a folder or a tag container.
+ if (eventY - eltY < eltHeight * 0.20) {
+ // If mouse is in the top part of the element, drop above folder.
+ dropPoint.ip = new InsertionPoint(
+ PlacesUtils.getConcreteItemId(resultNode),
+ -1,
+ Ci.nsITreeView.DROP_BEFORE,
+ PlacesUtils.nodeIsTagQuery(elt._placesNode),
+ elt._placesNode.itemId);
+ return dropPoint;
+ }
+ else if (eventY - eltY < eltHeight * 0.80) {
+ // If mouse is in the middle of the element, drop inside folder.
+ dropPoint.ip = new InsertionPoint(
+ PlacesUtils.getConcreteItemId(elt._placesNode),
+ -1,
+ Ci.nsITreeView.DROP_ON,
+ PlacesUtils.nodeIsTagQuery(elt._placesNode));
+ dropPoint.folderElt = elt;
+ return dropPoint;
+ }
+ }
+ else if (eventY - eltY <= eltHeight / 2) {
+ // This is a non-folder node or a readonly folder.
+ // If the mouse is above the middle, drop above this item.
+ dropPoint.ip = new InsertionPoint(
+ PlacesUtils.getConcreteItemId(resultNode),
+ -1,
+ Ci.nsITreeView.DROP_BEFORE,
+ PlacesUtils.nodeIsTagQuery(elt._placesNode),
+ elt._placesNode.itemId);
+ return dropPoint;
+ }
+
+ // Drop below the item.
+ dropPoint.ip = new InsertionPoint(
+ PlacesUtils.getConcreteItemId(resultNode),
+ -1,
+ Ci.nsITreeView.DROP_AFTER,
+ PlacesUtils.nodeIsTagQuery(elt._placesNode),
+ elt._placesNode.itemId);
+ return dropPoint;
+ ]]></body>
+ </method>
+
+ <!-- Sub-menus should be opened when the mouse drags over them, and closed
+ when the mouse drags off. The overFolder object manages opening and
+ closing of folders when the mouse hovers. -->
+ <field name="_overFolder"><![CDATA[({
+ _self: this,
+ _folder: {elt: null,
+ openTimer: null,
+ hoverTime: 350,
+ closeTimer: null},
+ _closeMenuTimer: null,
+
+ get elt() {
+ return this._folder.elt;
+ },
+ set elt(val) {
+ return this._folder.elt = val;
+ },
+
+ get openTimer() {
+ return this._folder.openTimer;
+ },
+ set openTimer(val) {
+ return this._folder.openTimer = val;
+ },
+
+ get hoverTime() {
+ return this._folder.hoverTime;
+ },
+ set hoverTime(val) {
+ return this._folder.hoverTime = val;
+ },
+
+ get closeTimer() {
+ return this._folder.closeTimer;
+ },
+ set closeTimer(val) {
+ return this._folder.closeTimer = val;
+ },
+
+ get closeMenuTimer() {
+ return this._closeMenuTimer;
+ },
+ set closeMenuTimer(val) {
+ return this._closeMenuTimer = val;
+ },
+
+ setTimer: function OF__setTimer(aTime) {
+ var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(this, aTime, timer.TYPE_ONE_SHOT);
+ return timer;
+ },
+
+ notify: function OF__notify(aTimer) {
+ // Function to process all timer notifications.
+
+ if (aTimer == this._folder.openTimer) {
+ // Timer to open a submenu that's being dragged over.
+ this._folder.elt.lastChild.setAttribute("autoopened", "true");
+ this._folder.elt.lastChild.showPopup(this._folder.elt);
+ this._folder.openTimer = null;
+ }
+
+ else if (aTimer == this._folder.closeTimer) {
+ // Timer to close a submenu that's been dragged off of.
+ // Only close the submenu if the mouse isn't being dragged over any
+ // of its child menus.
+ var draggingOverChild = PlacesControllerDragHelper
+ .draggingOverChildNode(this._folder.elt);
+ if (draggingOverChild)
+ this._folder.elt = null;
+ this.clear();
+
+ // Close any parent folders which aren't being dragged over.
+ // (This is necessary because of the above code that keeps a folder
+ // open while its children are being dragged over.)
+ if (!draggingOverChild)
+ this.closeParentMenus();
+ }
+
+ else if (aTimer == this.closeMenuTimer) {
+ // Timer to close this menu after the drag exit.
+ var popup = this._self;
+ // if we are no more dragging we can leave the menu open to allow
+ // for better D&D bookmark organization
+ if (PlacesControllerDragHelper.getSession() &&
+ !PlacesControllerDragHelper.draggingOverChildNode(popup.parentNode)) {
+ popup.hidePopup();
+ // Close any parent menus that aren't being dragged over;
+ // otherwise they'll stay open because they couldn't close
+ // while this menu was being dragged over.
+ this.closeParentMenus();
+ }
+ this._closeMenuTimer = null;
+ }
+ },
+
+ // Helper function to close all parent menus of this menu,
+ // as long as none of the parent's children are currently being
+ // dragged over.
+ closeParentMenus: function OF__closeParentMenus() {
+ var popup = this._self;
+ var parent = popup.parentNode;
+ while (parent) {
+ if (parent.localName == "menupopup" && parent._placesNode) {
+ if (PlacesControllerDragHelper.draggingOverChildNode(parent.parentNode))
+ break;
+ parent.hidePopup();
+ }
+ parent = parent.parentNode;
+ }
+ },
+
+ // The mouse is no longer dragging over the stored menubutton.
+ // Close the menubutton, clear out drag styles, and clear all
+ // timers for opening/closing it.
+ clear: function OF__clear() {
+ if (this._folder.elt && this._folder.elt.lastChild) {
+ if (!this._folder.elt.lastChild.hasAttribute("dragover"))
+ this._folder.elt.lastChild.hidePopup();
+ // remove menuactive style
+ this._folder.elt.removeAttribute("_moz-menuactive");
+ this._folder.elt = null;
+ }
+ if (this._folder.openTimer) {
+ this._folder.openTimer.cancel();
+ this._folder.openTimer = null;
+ }
+ if (this._folder.closeTimer) {
+ this._folder.closeTimer.cancel();
+ this._folder.closeTimer = null;
+ }
+ }
+ })]]></field>
+
+ <method name="_cleanupDragDetails">
+ <body><![CDATA[
+ // Called on dragend and drop.
+ PlacesControllerDragHelper.currentDropTarget = null;
+ this._rootView._draggedElt = null;
+ this.removeAttribute("dragover");
+ this.removeAttribute("dragstart");
+ this._indicatorBar.hidden = true;
+ ]]></body>
+ </method>
+
+ </implementation>
+
+ <handlers>
+ <handler event="DOMMenuItemActive"><![CDATA[
+ let elt = event.target;
+ if (elt.parentNode != this)
+ return;
+
+#ifdef XP_MACOSX
+ // XXX: The following check is a temporary hack until bug 420033 is
+ // resolved.
+ let parentElt = elt.parent;
+ while (parentElt) {
+ if (parentElt.id == "bookmarksMenuPopup" ||
+ parentElt.id == "goPopup")
+ return;
+
+ parentElt = parentElt.parentNode;
+ }
+#endif
+
+ if (window.XULBrowserWindow) {
+ let elt = event.target;
+ let placesNode = elt._placesNode;
+
+ var linkURI;
+ if (placesNode && PlacesUtils.nodeIsURI(placesNode))
+ linkURI = placesNode.uri;
+ else if (elt.hasAttribute("targetURI"))
+ linkURI = elt.getAttribute("targetURI");
+
+ if (linkURI)
+ window.XULBrowserWindow.setOverLink(linkURI, null);
+ }
+ ]]></handler>
+
+ <handler event="DOMMenuItemInactive"><![CDATA[
+ let elt = event.target;
+ if (elt.parentNode != this)
+ return;
+
+ if (window.XULBrowserWindow)
+ window.XULBrowserWindow.setOverLink("", null);
+ ]]></handler>
+
+ <handler event="dragstart"><![CDATA[
+ if (!event.target._placesNode)
+ return;
+
+ let draggedElt = event.target._placesNode;
+
+ // Force a copy action if parent node is a query or we are dragging a
+ // not-removable node.
+ if (!PlacesControllerDragHelper.canMoveNode(draggedElt))
+ event.dataTransfer.effectAllowed = "copyLink";
+
+ // Activate the view and cache the dragged element.
+ this._rootView._draggedElt = draggedElt;
+ this._rootView.controller.setDataTransfer(event);
+ this.setAttribute("dragstart", "true");
+ event.stopPropagation();
+ ]]></handler>
+
+ <handler event="drop"><![CDATA[
+ PlacesControllerDragHelper.currentDropTarget = event.target;
+
+ let dropPoint = this._getDropPoint(event);
+ if (dropPoint && dropPoint.ip) {
+ PlacesControllerDragHelper.onDrop(dropPoint.ip, event.dataTransfer);
+ event.preventDefault();
+ }
+
+ this._cleanupDragDetails();
+ event.stopPropagation();
+ ]]></handler>
+
+ <handler event="dragover"><![CDATA[
+ PlacesControllerDragHelper.currentDropTarget = event.target;
+ let dt = event.dataTransfer;
+
+ let dropPoint = this._getDropPoint(event);
+ if (!dropPoint || !dropPoint.ip ||
+ !PlacesControllerDragHelper.canDrop(dropPoint.ip, dt)) {
+ this._indicatorBar.hidden = true;
+ event.stopPropagation();
+ return;
+ }
+
+ // Mark this popup as being dragged over.
+ this.setAttribute("dragover", "true");
+
+ if (dropPoint.folderElt) {
+ // We are dragging over a folder.
+ // _overFolder should take the care of opening it on a timer.
+ if (this._overFolder.elt &&
+ this._overFolder.elt != dropPoint.folderElt) {
+ // We are dragging over a new folder, let's clear old values
+ this._overFolder.clear();
+ }
+ if (!this._overFolder.elt) {
+ this._overFolder.elt = dropPoint.folderElt;
+ // Create the timer to open this folder.
+ this._overFolder.openTimer = this._overFolder
+ .setTimer(this._overFolder.hoverTime);
+ }
+ // Since we are dropping into a folder set the corresponding style.
+ dropPoint.folderElt.setAttribute("_moz-menuactive", true);
+ }
+ else {
+ // We are not dragging over a folder.
+ // Clear out old _overFolder information.
+ this._overFolder.clear();
+ }
+
+ // Autoscroll the popup strip if we drag over the scroll buttons.
+ let anonid = event.originalTarget.getAttribute('anonid');
+ let scrollDir = anonid == "scrollbutton-up" ? -1 :
+ anonid == "scrollbutton-down" ? 1 : 0;
+ if (scrollDir != 0) {
+ this._scrollBox.scrollByIndex(scrollDir, false);
+ }
+
+ // Check if we should hide the drop indicator for this target.
+ if (dropPoint.folderElt || this._hideDropIndicator(event)) {
+ this._indicatorBar.hidden = true;
+ event.preventDefault();
+ event.stopPropagation();
+ return;
+ }
+
+ // We should display the drop indicator relative to the arrowscrollbox.
+ let sbo = this._scrollBox.scrollBoxObject;
+ let newMarginTop = 0;
+ if (scrollDir == 0) {
+ let elt = this.firstChild;
+ while (elt && event.screenY > elt.boxObject.screenY +
+ elt.boxObject.height / 2)
+ elt = elt.nextSibling;
+ newMarginTop = elt ? elt.boxObject.screenY - sbo.screenY :
+ sbo.height;
+ }
+ else if (scrollDir == 1)
+ newMarginTop = sbo.height;
+
+ // Set the new marginTop based on arrowscrollbox.
+ newMarginTop += sbo.y - this._scrollBox.boxObject.y;
+ this._indicatorBar.firstChild.style.marginTop = newMarginTop + "px";
+ this._indicatorBar.hidden = false;
+
+ event.preventDefault();
+ event.stopPropagation();
+ ]]></handler>
+
+ <handler event="dragexit"><![CDATA[
+ PlacesControllerDragHelper.currentDropTarget = null;
+ this.removeAttribute("dragover");
+
+ // If we have not moved to a valid new target clear the drop indicator
+ // this happens when moving out of the popup.
+ let target = event.relatedTarget;
+ if (!target)
+ this._indicatorBar.hidden = true;
+
+ // Close any folder being hovered over
+ if (this._overFolder.elt) {
+ this._overFolder.closeTimer = this._overFolder
+ .setTimer(this._overFolder.hoverTime);
+ }
+
+ // The autoopened attribute is set when this folder was automatically
+ // opened after the user dragged over it. If this attribute is set,
+ // auto-close the folder on drag exit.
+ // We should also try to close this popup if the drag has started
+ // from here, the timer will check if we are dragging over a child.
+ if (this.hasAttribute("autoopened") ||
+ this.hasAttribute("dragstart")) {
+ this._overFolder.closeMenuTimer = this._overFolder
+ .setTimer(this._overFolder.hoverTime);
+ }
+
+ event.stopPropagation();
+ ]]></handler>
+
+ <handler event="dragend"><![CDATA[
+ this._cleanupDragDetails();
+ ]]></handler>
+
+ </handlers>
+ </binding>
+</bindings>
diff --git a/components/places/content/moveBookmarks.js b/components/places/content/moveBookmarks.js
new file mode 100644
index 0000000..964604f
--- /dev/null
+++ b/components/places/content/moveBookmarks.js
@@ -0,0 +1,54 @@
+/* -*- 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/. */
+
+var gMoveBookmarksDialog = {
+ _nodes: null,
+
+ _foldersTree: null,
+ get foldersTree() {
+ if (!this._foldersTree)
+ this._foldersTree = document.getElementById("foldersTree");
+
+ return this._foldersTree;
+ },
+
+ init: function() {
+ this._nodes = window.arguments[0];
+
+ this.foldersTree.place =
+ "place:excludeItems=1&excludeQueries=1&excludeReadOnlyFolders=1&folder=" +
+ PlacesUIUtils.allBookmarksFolderId;
+ },
+
+ onOK: function MBD_onOK(aEvent) {
+ var selectedNode = this.foldersTree.selectedNode;
+ NS_ASSERT(selectedNode,
+ "selectedNode must be set in a single-selection tree with initial selection set");
+ var selectedFolderID = PlacesUtils.getConcreteItemId(selectedNode);
+
+ var transactions = [];
+ for (var i=0; i < this._nodes.length; i++) {
+ // Nothing to do if the node is already under the selected folder
+ if (this._nodes[i].parent.itemId == selectedFolderID)
+ continue;
+
+ let txn = new PlacesMoveItemTransaction(this._nodes[i].itemId,
+ selectedFolderID,
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ transactions.push(txn);
+ }
+
+ if (transactions.length != 0) {
+ let txn = new PlacesAggregatedTransaction("Move Items", transactions);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ }
+ },
+
+ newFolder: function MBD_newFolder() {
+ // The command is disabled when the tree is not focused
+ this.foldersTree.focus();
+ goDoCommand("placesCmd_new:folder");
+ }
+};
diff --git a/components/places/content/moveBookmarks.xul b/components/places/content/moveBookmarks.xul
new file mode 100644
index 0000000..b6e75f3
--- /dev/null
+++ b/components/places/content/moveBookmarks.xul
@@ -0,0 +1,53 @@
+<?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/"?>
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+<?xml-stylesheet href="chrome://browser/content/places/places.css"?>
+
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+
+<!DOCTYPE window [
+ <!ENTITY % moveBookmarksDTD SYSTEM "chrome://browser/locale/places/moveBookmarks.dtd">
+ %moveBookmarksDTD;
+]>
+
+<dialog id="moveBookmarkDialog"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ ondialogaccept="return gMoveBookmarksDialog.onOK(event);"
+ title="&window.title;"
+ onload="gMoveBookmarksDialog.init();"
+ style="&window.style;"
+ screenX="24"
+ screenY="24"
+ persist="screenX screenY width height">
+
+ <script type="application/javascript"
+ src="chrome://browser/content/places/moveBookmarks.js"/>
+
+ <hbox flex="1">
+ <label id="movetolabel" value="&moveTo.label;" control="foldersTree"/>
+ <hbox flex="1">
+ <tree id="foldersTree"
+ class="placesTree"
+ flex="1"
+ type="places"
+ seltype="single"
+ hidecolumnpicker="true">
+ <treecols>
+ <treecol id="title" flex="1" primary="true" hideheader="true"/>
+ </treecols>
+ <treechildren id="placesListChildren" view="placesList" flex="1"/>
+ </tree>
+ <vbox>
+ <button id="newFolderButton"
+ label="&newFolderButton.label;"
+ accesskey="&newFolderButton.accesskey;"
+ oncommand="gMoveBookmarksDialog.newFolder();"/>
+ </vbox>
+ </hbox>
+ </hbox>
+</dialog>
diff --git a/components/places/content/organizer.css b/components/places/content/organizer.css
new file mode 100644
index 0000000..47b1832
--- /dev/null
+++ b/components/places/content/organizer.css
@@ -0,0 +1,7 @@
+/* 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/. */
+
+#searchFilter {
+ width: 23em;
+}
diff --git a/components/places/content/places.css b/components/places/content/places.css
new file mode 100644
index 0000000..5151cca
--- /dev/null
+++ b/components/places/content/places.css
@@ -0,0 +1,16 @@
+/* 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/. */
+
+tree[type="places"] {
+ -moz-binding: url("chrome://browser/content/places/tree.xml#places-tree");
+}
+
+.toolbar-drop-indicator {
+ position: relative;
+ z-index: 1;
+}
+
+menupopup[placespopup="true"] {
+ -moz-binding: url("chrome://browser/content/places/menu.xml#places-popup-base");
+}
diff --git a/components/places/content/places.js b/components/places/content/places.js
new file mode 100644
index 0000000..40dbcb9
--- /dev/null
+++ b/components/places/content/places.js
@@ -0,0 +1,1553 @@
+/* -*- 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/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "BookmarkJSONUtils",
+ "resource://gre/modules/BookmarkJSONUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups",
+ "resource://gre/modules/PlacesBackups.jsm");
+
+const RESTORE_FILEPICKER_FILTER_EXT = "*.json;*.jsonlz4";
+
+var PlacesOrganizer = {
+ _places: null,
+
+ // IDs of fields from editBookmarkOverlay that should be hidden when infoBox
+ // is minimal. IDs should be kept in sync with the IDs of the elements
+ // observing additionalInfoBroadcaster.
+ _additionalInfoFields: [
+ "editBMPanel_descriptionRow",
+ "editBMPanel_loadInSidebarCheckbox",
+ "editBMPanel_keywordRow",
+ ],
+
+ _initFolderTree: function() {
+ var leftPaneRoot = PlacesUIUtils.leftPaneFolderId;
+ this._places.place = "place:excludeItems=1&expandQueries=0&folder=" + leftPaneRoot;
+ },
+
+ selectLeftPaneQuery: function PO_selectLeftPaneQuery(aQueryName) {
+ var itemId = PlacesUIUtils.leftPaneQueries[aQueryName];
+ this._places.selectItems([itemId]);
+ // Forcefully expand all-bookmarks
+ if (aQueryName == "AllBookmarks" || aQueryName == "History")
+ PlacesUtils.asContainer(this._places.selectedNode).containerOpen = true;
+ },
+
+ init: function PO_init() {
+ ContentArea.init();
+
+ this._places = document.getElementById("placesList");
+ this._initFolderTree();
+
+ var leftPaneSelection = "AllBookmarks"; // default to all-bookmarks
+ if (window.arguments && window.arguments[0])
+ leftPaneSelection = window.arguments[0];
+
+ this.selectLeftPaneQuery(leftPaneSelection);
+ if (leftPaneSelection == "History") {
+ let historyNode = this._places.selectedNode;
+ if (historyNode.childCount > 0)
+ this._places.selectNode(historyNode.getChild(0));
+ }
+ // clear the back-stack
+ this._backHistory.splice(0, this._backHistory.length);
+ document.getElementById("OrganizerCommand:Back").setAttribute("disabled", true);
+
+ // Set up the search UI.
+ PlacesSearchBox.init();
+
+ window.addEventListener("AppCommand", this, true);
+#ifdef XP_MACOSX
+ // 1. Map Edit->Find command to OrganizerCommand_find:all. Need to map
+ // both the menuitem and the Find key.
+ var findMenuItem = document.getElementById("menu_find");
+ findMenuItem.setAttribute("command", "OrganizerCommand_find:all");
+ var findKey = document.getElementById("key_find");
+ findKey.setAttribute("command", "OrganizerCommand_find:all");
+
+ // 2. Disable some keybindings from browser.xul
+ var elements = ["cmd_handleBackspace", "cmd_handleShiftBackspace"];
+ for (var i=0; i < elements.length; i++) {
+ document.getElementById(elements[i]).setAttribute("disabled", "true");
+ }
+
+ // 3. Disable the keyboard shortcut for the History menu back/forward
+ // in order to support those in the Library
+ var historyMenuBack = document.getElementById("historyMenuBack");
+ historyMenuBack.removeAttribute("key");
+ var historyMenuForward = document.getElementById("historyMenuForward");
+ historyMenuForward.removeAttribute("key");
+#endif
+
+ // remove the "Properties" context-menu item, we've our own details pane
+ document.getElementById("placesContext")
+ .removeChild(document.getElementById("placesContext_show:info"));
+
+ ContentArea.focus();
+ },
+
+ QueryInterface: function PO_QueryInterface(aIID) {
+ if (aIID.equals(Components.interfaces.nsIDOMEventListener) ||
+ aIID.equals(Components.interfaces.nsISupports))
+ return this;
+
+ throw new Components.Exception("", Components.results.NS_NOINTERFACE);
+ },
+
+ handleEvent: function PO_handleEvent(aEvent) {
+ if (aEvent.type != "AppCommand")
+ return;
+
+ aEvent.stopPropagation();
+ switch (aEvent.command) {
+ case "Back":
+ if (this._backHistory.length > 0)
+ this.back();
+ break;
+ case "Forward":
+ if (this._forwardHistory.length > 0)
+ this.forward();
+ break;
+ case "Search":
+ PlacesSearchBox.findAll();
+ break;
+ }
+ },
+
+ destroy: function PO_destroy() {
+ },
+
+ _location: null,
+ get location() {
+ return this._location;
+ },
+
+ set location(aLocation) {
+ if (!aLocation || this._location == aLocation)
+ return aLocation;
+
+ if (this.location) {
+ this._backHistory.unshift(this.location);
+ this._forwardHistory.splice(0, this._forwardHistory.length);
+ }
+
+ this._location = aLocation;
+ this._places.selectPlaceURI(aLocation);
+
+ if (!this._places.hasSelection) {
+ // If no node was found for the given place: uri, just load it directly
+ ContentArea.currentPlace = aLocation;
+ }
+ this.updateDetailsPane();
+
+ // update navigation commands
+ if (this._backHistory.length == 0)
+ document.getElementById("OrganizerCommand:Back").setAttribute("disabled", true);
+ else
+ document.getElementById("OrganizerCommand:Back").removeAttribute("disabled");
+ if (this._forwardHistory.length == 0)
+ document.getElementById("OrganizerCommand:Forward").setAttribute("disabled", true);
+ else
+ document.getElementById("OrganizerCommand:Forward").removeAttribute("disabled");
+
+ return aLocation;
+ },
+
+ _backHistory: [],
+ _forwardHistory: [],
+
+ back: function PO_back() {
+ this._forwardHistory.unshift(this.location);
+ var historyEntry = this._backHistory.shift();
+ this._location = null;
+ this.location = historyEntry;
+ },
+ forward: function PO_forward() {
+ this._backHistory.unshift(this.location);
+ var historyEntry = this._forwardHistory.shift();
+ this._location = null;
+ this.location = historyEntry;
+ },
+
+ /**
+ * Called when a place folder is selected in the left pane.
+ * @param resetSearchBox
+ * true if the search box should also be reset, false otherwise.
+ * The search box should be reset when a new folder in the left
+ * pane is selected; the search scope and text need to be cleared in
+ * preparation for the new folder. Note that if the user manually
+ * resets the search box, either by clicking its reset button or by
+ * deleting its text, this will be false.
+ */
+ _cachedLeftPaneSelectedURI: null,
+ onPlaceSelected: function PO_onPlaceSelected(resetSearchBox) {
+ // Don't change the right-hand pane contents when there's no selection.
+ if (!this._places.hasSelection)
+ return;
+
+ var node = this._places.selectedNode;
+ var queries = PlacesUtils.asQuery(node).getQueries();
+
+ // Items are only excluded on the left pane.
+ var options = node.queryOptions.clone();
+ options.excludeItems = false;
+ var placeURI = PlacesUtils.history.queriesToQueryString(queries,
+ queries.length,
+ options);
+
+ // If either the place of the content tree in the right pane has changed or
+ // the user cleared the search box, update the place, hide the search UI,
+ // and update the back/forward buttons by setting location.
+ if (ContentArea.currentPlace != placeURI || !resetSearchBox) {
+ ContentArea.currentPlace = placeURI;
+ PlacesSearchBox.hideSearchUI();
+ this.location = node.uri;
+ }
+
+ // Update the selected folder title where it appears in the UI: the folder
+ // scope button, and the search box emptytext.
+ // They must be updated even if the selection hasn't changed --
+ // specifically when node's title changes. In that case a selection event
+ // is generated, this method is called, but the selection does not change.
+ var folderButton = document.getElementById("scopeBarFolder");
+ var folderTitle = node.title || folderButton.getAttribute("emptytitle");
+ folderButton.setAttribute("label", folderTitle);
+ if (PlacesSearchBox.filterCollection == "collection")
+ PlacesSearchBox.updateCollectionTitle(folderTitle);
+
+ // When we invalidate a container we use suppressSelectionEvent, when it is
+ // unset a select event is fired, in many cases the selection did not really
+ // change, so we should check for it, and return early in such a case. Note
+ // that we cannot return any earlier than this point, because when
+ // !resetSearchBox, we need to update location and hide the UI as above,
+ // even though the selection has not changed.
+ if (node.uri == this._cachedLeftPaneSelectedURI)
+ return;
+ this._cachedLeftPaneSelectedURI = node.uri;
+
+ // At this point, resetSearchBox is true, because the left pane selection
+ // has changed; otherwise we would have returned earlier.
+
+ PlacesSearchBox.searchFilter.reset();
+ this._setSearchScopeForNode(node);
+ this.updateDetailsPane();
+ },
+
+ /**
+ * Sets the search scope based on aNode's properties.
+ * @param aNode
+ * the node to set up scope from
+ */
+ _setSearchScopeForNode: function PO__setScopeForNode(aNode) {
+ let itemId = aNode.itemId;
+
+ // Set default buttons status.
+ let bookmarksButton = document.getElementById("scopeBarAll");
+ bookmarksButton.hidden = false;
+ let downloadsButton = document.getElementById("scopeBarDownloads");
+ downloadsButton.hidden = true;
+
+ if (PlacesUtils.nodeIsHistoryContainer(aNode) ||
+ itemId == PlacesUIUtils.leftPaneQueries["History"]) {
+ PlacesQueryBuilder.setScope("history");
+ }
+ else if (itemId == PlacesUIUtils.leftPaneQueries["Downloads"]) {
+ downloadsButton.hidden = false;
+ bookmarksButton.hidden = true;
+ PlacesQueryBuilder.setScope("downloads");
+ }
+ else {
+ // Default to All Bookmarks for all other nodes, per bug 469437.
+ PlacesQueryBuilder.setScope("bookmarks");
+ }
+
+ // Enable or disable the folder scope button.
+ let folderButton = document.getElementById("scopeBarFolder");
+ folderButton.hidden = !PlacesUtils.nodeIsFolder(aNode) ||
+ itemId == PlacesUIUtils.allBookmarksFolderId;
+ },
+
+ /**
+ * Handle clicks on the places list.
+ * Single Left click, right click or modified click do not result in any
+ * special action, since they're related to selection.
+ * @param aEvent
+ * The mouse event.
+ */
+ onPlacesListClick: function PO_onPlacesListClick(aEvent) {
+ // Only handle clicks on tree children.
+ if (aEvent.target.localName != "treechildren")
+ return;
+
+ let node = this._places.selectedNode;
+ if (node) {
+ let middleClick = aEvent.button == 1 && aEvent.detail == 1;
+ if (middleClick && PlacesUtils.nodeIsContainer(node)) {
+ // The command execution function will take care of seeing if the
+ // selection is a folder or a different container type, and will
+ // load its contents in tabs.
+ PlacesUIUtils.openContainerNodeInTabs(selectedNode, aEvent, this._places);
+ }
+ }
+ },
+
+ /**
+ * Handle focus changes on the places list and the current content view.
+ */
+ updateDetailsPane: function PO_updateDetailsPane() {
+ if (!ContentArea.currentViewOptions.showDetailsPane)
+ return;
+ let view = PlacesUIUtils.getViewForNode(document.activeElement);
+ if (view) {
+ let selectedNodes = view.selectedNode ?
+ [view.selectedNode] : view.selectedNodes;
+ this._fillDetailsPane(selectedNodes);
+ }
+ },
+
+ openFlatContainer: function PO_openFlatContainerFlatContainer(aContainer) {
+ if (aContainer.itemId != -1)
+ this._places.selectItems([aContainer.itemId]);
+ else if (PlacesUtils.nodeIsQuery(aContainer))
+ this._places.selectPlaceURI(aContainer.uri);
+ },
+
+ /**
+ * Returns the options associated with the query currently loaded in the
+ * main places pane.
+ */
+ getCurrentOptions: function PO_getCurrentOptions() {
+ return PlacesUtils.asQuery(ContentArea.currentView.result.root).queryOptions;
+ },
+
+ /**
+ * Returns the queries associated with the query currently loaded in the
+ * main places pane.
+ */
+ getCurrentQueries: function PO_getCurrentQueries() {
+ return PlacesUtils.asQuery(ContentArea.currentView.result.root).getQueries();
+ },
+
+ /**
+ * Open a file-picker and import the selected file into the bookmarks store
+ */
+ importFromFile: function PO_importFromFile() {
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ let fpCallback = function fpCallback_done(aResult) {
+ if (aResult != Ci.nsIFilePicker.returnCancel && fp.fileURL) {
+ Components.utils.import("resource://gre/modules/BookmarkHTMLUtils.jsm");
+ BookmarkHTMLUtils.importFromURL(fp.fileURL.spec, false)
+ .then(null, Components.utils.reportError);
+ }
+ };
+
+ fp.init(window, PlacesUIUtils.getString("SelectImport"),
+ Ci.nsIFilePicker.modeOpen);
+ fp.appendFilters(Ci.nsIFilePicker.filterHTML);
+ fp.open(fpCallback);
+ },
+
+ /**
+ * Allows simple exporting of bookmarks.
+ */
+ exportBookmarks: function PO_exportBookmarks() {
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ let fpCallback = function fpCallback_done(aResult) {
+ if (aResult != Ci.nsIFilePicker.returnCancel) {
+ Components.utils.import("resource://gre/modules/BookmarkHTMLUtils.jsm");
+ BookmarkHTMLUtils.exportToFile(fp.file.path)
+ .then(null, Components.utils.reportError);
+ }
+ };
+
+ fp.init(window, PlacesUIUtils.getString("EnterExport"),
+ Ci.nsIFilePicker.modeSave);
+ fp.appendFilters(Ci.nsIFilePicker.filterHTML);
+ fp.defaultString = "bookmarks.html";
+ fp.open(fpCallback);
+ },
+
+ /**
+ * Populates the restore menu with the dates of the backups available.
+ */
+ populateRestoreMenu: function PO_populateRestoreMenu() {
+ let restorePopup = document.getElementById("fileRestorePopup");
+
+ let dateSvc = Cc["@mozilla.org/intl/scriptabledateformat;1"].
+ getService(Ci.nsIScriptableDateFormat);
+
+ // Remove existing menu items. Last item is the restoreFromFile item.
+ while (restorePopup.childNodes.length > 1)
+ restorePopup.removeChild(restorePopup.firstChild);
+
+ Task.spawn(function() {
+ let backupFiles = yield PlacesBackups.getBackupFiles();
+ if (backupFiles.length == 0)
+ return;
+
+ // Populate menu with backups.
+ for (let i = 0; i < backupFiles.length; i++) {
+ let fileSize = (yield OS.File.stat(backupFiles[i])).size;
+ let [size, unit] = DownloadUtils.convertByteUnits(fileSize);
+ let sizeString = PlacesUtils.getFormattedString("backupFileSizeText",
+ [size, unit]);
+ let sizeInfo;
+ let bookmarkCount = PlacesBackups.getBookmarkCountForFile(backupFiles[i]);
+ if (bookmarkCount != null) {
+ sizeInfo = " (" + sizeString + " - " +
+ PlacesUIUtils.getPluralString("detailsPane.itemsCountLabel",
+ bookmarkCount,
+ [bookmarkCount]) +
+ ")";
+ } else {
+ sizeInfo = " (" + sizeString + ")";
+ }
+
+ let backupDate = PlacesBackups.getDateForFile(backupFiles[i]);
+ let m = restorePopup.insertBefore(document.createElement("menuitem"),
+ document.getElementById("restoreFromFile"));
+ m.setAttribute("label",
+ dateSvc.FormatDate("",
+ Ci.nsIScriptableDateFormat.dateFormatLong,
+ backupDate.getFullYear(),
+ backupDate.getMonth() + 1,
+ backupDate.getDate()) +
+ sizeInfo);
+ m.setAttribute("value", OS.Path.basename(backupFiles[i]));
+ m.setAttribute("oncommand",
+ "PlacesOrganizer.onRestoreMenuItemClick(this);");
+ }
+
+ // Add the restoreFromFile item.
+ restorePopup.insertBefore(document.createElement("menuseparator"),
+ document.getElementById("restoreFromFile"));
+ });
+ },
+
+ /**
+ * Called when a menuitem is selected from the restore menu.
+ */
+ onRestoreMenuItemClick: function PO_onRestoreMenuItemClick(aMenuItem) {
+ Task.spawn(function() {
+ let backupName = aMenuItem.getAttribute("value");
+ let backupFilePaths = yield PlacesBackups.getBackupFiles();
+ for (let backupFilePath of backupFilePaths) {
+ if (OS.Path.basename(backupFilePath) == backupName) {
+ PlacesOrganizer.restoreBookmarksFromFile(new FileUtils.File(backupFilePath));
+ break;
+ }
+ }
+ });
+ },
+
+ /**
+ * Called when 'Choose File...' is selected from the restore menu.
+ * Prompts for a file and restores bookmarks to those in the file.
+ */
+ onRestoreBookmarksFromFile: function PO_onRestoreBookmarksFromFile() {
+ let dirSvc = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties);
+ let backupsDir = dirSvc.get("Desk", Ci.nsILocalFile);
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ let fpCallback = function fpCallback_done(aResult) {
+ if (aResult != Ci.nsIFilePicker.returnCancel) {
+ this.restoreBookmarksFromFile(fp.file);
+ }
+ }.bind(this);
+
+ fp.init(window, PlacesUIUtils.getString("bookmarksRestoreTitle"),
+ Ci.nsIFilePicker.modeOpen);
+ fp.appendFilter(PlacesUIUtils.getString("bookmarksRestoreFilterName"),
+ RESTORE_FILEPICKER_FILTER_EXT);
+ fp.appendFilters(Ci.nsIFilePicker.filterAll);
+ fp.displayDirectory = backupsDir;
+ fp.open(fpCallback);
+ },
+
+ /**
+ * Restores bookmarks from a JSON file.
+ */
+ restoreBookmarksFromFile: function PO_restoreBookmarksFromFile(aFile) {
+ // check file extension
+ let filePath = aFile.path;
+ if (!filePath.toLowerCase().endsWith("json") &&
+ !filePath.toLowerCase().endsWith("jsonlz4")) {
+ this._showErrorAlert(PlacesUIUtils.getString("bookmarksRestoreFormatError"));
+ return;
+ }
+
+ // confirm ok to delete existing bookmarks
+ var prompts = Cc["@mozilla.org/embedcomp/prompt-service;1"].
+ getService(Ci.nsIPromptService);
+ if (!prompts.confirm(null,
+ PlacesUIUtils.getString("bookmarksRestoreAlertTitle"),
+ PlacesUIUtils.getString("bookmarksRestoreAlert")))
+ return;
+
+ Task.spawn(function() {
+ try {
+ yield BookmarkJSONUtils.importFromFile(aFile.path, true);
+ } catch(ex) {
+ PlacesOrganizer._showErrorAlert(PlacesUIUtils.getString("bookmarksRestoreParseError"));
+ }
+ });
+ },
+
+ _showErrorAlert: function PO__showErrorAlert(aMsg) {
+ var brandShortName = document.getElementById("brandStrings").
+ getString("brandShortName");
+
+ Cc["@mozilla.org/embedcomp/prompt-service;1"].
+ getService(Ci.nsIPromptService).
+ alert(window, brandShortName, aMsg);
+ },
+
+ /**
+ * Backup bookmarks to desktop, auto-generate a filename with a date.
+ * The file is a JSON serialization of bookmarks, tags and any annotations
+ * of those items.
+ */
+ backupBookmarks: function PO_backupBookmarks() {
+ let dirSvc = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties);
+ let backupsDir = dirSvc.get("Desk", Ci.nsILocalFile);
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ let fpCallback = function fpCallback_done(aResult) {
+ if (aResult != Ci.nsIFilePicker.returnCancel) {
+ BookmarkJSONUtils.exportToFile(fp.file.path);
+ }
+ };
+
+ fp.init(window, PlacesUIUtils.getString("bookmarksBackupTitle"),
+ Ci.nsIFilePicker.modeSave);
+ fp.appendFilter(PlacesUIUtils.getString("bookmarksRestoreFilterName"),
+ RESTORE_FILEPICKER_FILTER_EXT);
+ fp.defaultString = PlacesBackups.getFilenameForDate();
+ fp.displayDirectory = backupsDir;
+ fp.open(fpCallback);
+ },
+
+ _paneDisabled: false,
+ _setDetailsFieldsDisabledState:
+ function PO__setDetailsFieldsDisabledState(aDisabled) {
+ if (aDisabled) {
+ document.getElementById("paneElementsBroadcaster")
+ .setAttribute("disabled", "true");
+ }
+ else {
+ document.getElementById("paneElementsBroadcaster")
+ .removeAttribute("disabled");
+ }
+ },
+
+ _detectAndSetDetailsPaneMinimalState:
+ function PO__detectAndSetDetailsPaneMinimalState(aNode) {
+ /**
+ * The details of simple folder-items (as opposed to livemarks) or the
+ * of livemark-children are not likely to fill the infoBox anyway,
+ * thus we remove the "More/Less" button and show all details.
+ *
+ * the wasminimal attribute here is used to persist the "more/less"
+ * state in a bookmark->folder->bookmark scenario.
+ */
+ var infoBox = document.getElementById("infoBox");
+ var infoBoxExpander = document.getElementById("infoBoxExpander");
+ var infoBoxExpanderWrapper = document.getElementById("infoBoxExpanderWrapper");
+ var additionalInfoBroadcaster = document.getElementById("additionalInfoBroadcaster");
+
+ if (!aNode) {
+ infoBoxExpanderWrapper.hidden = true;
+ return;
+ }
+ if (aNode.itemId != -1 &&
+ PlacesUtils.nodeIsFolder(aNode) && !aNode._feedURI) {
+ if (infoBox.getAttribute("minimal") == "true")
+ infoBox.setAttribute("wasminimal", "true");
+ infoBox.removeAttribute("minimal");
+ infoBoxExpanderWrapper.hidden = true;
+ }
+ else {
+ if (infoBox.getAttribute("wasminimal") == "true")
+ infoBox.setAttribute("minimal", "true");
+ infoBox.removeAttribute("wasminimal");
+ infoBoxExpanderWrapper.hidden =
+ this._additionalInfoFields.every(function (id)
+ document.getElementById(id).collapsed);
+ }
+ additionalInfoBroadcaster.hidden = infoBox.getAttribute("minimal") == "true";
+ },
+
+ // NOT YET USED
+ updateThumbnailProportions: function PO_updateThumbnailProportions() {
+ var previewBox = document.getElementById("previewBox");
+ var canvas = document.getElementById("itemThumbnail");
+ var height = previewBox.boxObject.height;
+ var width = height * (screen.width / screen.height);
+ canvas.width = width;
+ canvas.height = height;
+ },
+
+ _fillDetailsPane: function PO__fillDetailsPane(aNodeList) {
+ var infoBox = document.getElementById("infoBox");
+ var detailsDeck = document.getElementById("detailsDeck");
+
+ // Make sure the infoBox UI is visible if we need to use it, we hide it
+ // below when we don't.
+ infoBox.hidden = false;
+ var aSelectedNode = aNodeList.length == 1 ? aNodeList[0] : null;
+ // If a textbox within a panel is focused, force-blur it so its contents
+ // are saved
+ if (gEditItemOverlay.itemId != -1) {
+ var focusedElement = document.commandDispatcher.focusedElement;
+ if ((focusedElement instanceof HTMLInputElement ||
+ focusedElement instanceof HTMLTextAreaElement) &&
+ /^editBMPanel.*/.test(focusedElement.parentNode.parentNode.id))
+ focusedElement.blur();
+
+ // don't update the panel if we are already editing this node unless we're
+ // in multi-edit mode
+ if (aSelectedNode) {
+ var concreteId = PlacesUtils.getConcreteItemId(aSelectedNode);
+ var nodeIsSame = gEditItemOverlay.itemId == aSelectedNode.itemId ||
+ gEditItemOverlay.itemId == concreteId ||
+ (aSelectedNode.itemId == -1 && gEditItemOverlay.uri &&
+ gEditItemOverlay.uri == aSelectedNode.uri);
+ if (nodeIsSame && detailsDeck.selectedIndex == 1 &&
+ !gEditItemOverlay.multiEdit)
+ return;
+ }
+ }
+
+ // Clean up the panel before initing it again.
+ gEditItemOverlay.uninitPanel(false);
+
+ if (aSelectedNode && !PlacesUtils.nodeIsSeparator(aSelectedNode)) {
+ detailsDeck.selectedIndex = 1;
+ // Using the concrete itemId is arguably wrong. The bookmarks API
+ // does allow setting properties for folder shortcuts as well, but since
+ // the UI does not distinct between the couple, we better just show
+ // the concrete item properties for shortcuts to root nodes.
+ var concreteId = PlacesUtils.getConcreteItemId(aSelectedNode);
+ var isRootItem = concreteId != -1 && PlacesUtils.isRootItem(concreteId);
+ var readOnly = isRootItem ||
+ aSelectedNode.parent.itemId == PlacesUIUtils.leftPaneFolderId;
+ var useConcreteId = isRootItem ||
+ PlacesUtils.nodeIsTagQuery(aSelectedNode);
+ var itemId = -1;
+ if (concreteId != -1 && useConcreteId)
+ itemId = concreteId;
+ else if (aSelectedNode.itemId != -1)
+ itemId = aSelectedNode.itemId;
+ else
+ itemId = PlacesUtils._uri(aSelectedNode.uri);
+
+ gEditItemOverlay.initPanel(itemId, { hiddenRows: ["folderPicker"]
+ , forceReadOnly: readOnly
+ , titleOverride: aSelectedNode.title
+ });
+
+ // Dynamically generated queries, like history date containers, have
+ // itemId !=0 and do not exist in history. For them the panel is
+ // read-only, but empty, since it can't get a valid title for the object.
+ // In such a case we force the title using the selectedNode one, for UI
+ // polishness.
+ if (aSelectedNode.itemId == -1 &&
+ (PlacesUtils.nodeIsDay(aSelectedNode) ||
+ PlacesUtils.nodeIsHost(aSelectedNode)))
+ gEditItemOverlay._element("namePicker").value = aSelectedNode.title;
+
+ this._detectAndSetDetailsPaneMinimalState(aSelectedNode);
+ }
+ else if (!aSelectedNode && aNodeList[0]) {
+ var itemIds = [];
+ for (var i = 0; i < aNodeList.length; i++) {
+ if (!PlacesUtils.nodeIsBookmark(aNodeList[i]) &&
+ !PlacesUtils.nodeIsURI(aNodeList[i])) {
+ detailsDeck.selectedIndex = 0;
+ var selectItemDesc = document.getElementById("selectItemDescription");
+ var itemsCountLabel = document.getElementById("itemsCountText");
+ selectItemDesc.hidden = false;
+ itemsCountLabel.value =
+ PlacesUIUtils.getPluralString("detailsPane.itemsCountLabel",
+ aNodeList.length, [aNodeList.length]);
+ infoBox.hidden = true;
+ return;
+ }
+ itemIds[i] = aNodeList[i].itemId != -1 ? aNodeList[i].itemId :
+ PlacesUtils._uri(aNodeList[i].uri);
+ }
+ detailsDeck.selectedIndex = 1;
+ gEditItemOverlay.initPanel(itemIds,
+ { hiddenRows: ["folderPicker",
+ "loadInSidebar",
+ "location",
+ "keyword",
+ "description",
+ "name"]});
+ this._detectAndSetDetailsPaneMinimalState(aSelectedNode);
+ }
+ else {
+ detailsDeck.selectedIndex = 0;
+ infoBox.hidden = true;
+ let selectItemDesc = document.getElementById("selectItemDescription");
+ let itemsCountLabel = document.getElementById("itemsCountText");
+ let itemsCount = 0;
+ if (ContentArea.currentView.result) {
+ let rootNode = ContentArea.currentView.result.root;
+ if (rootNode.containerOpen)
+ itemsCount = rootNode.childCount;
+ }
+ if (itemsCount == 0) {
+ selectItemDesc.hidden = true;
+ itemsCountLabel.value = PlacesUIUtils.getString("detailsPane.noItems");
+ }
+ else {
+ selectItemDesc.hidden = false;
+ itemsCountLabel.value =
+ PlacesUIUtils.getPluralString("detailsPane.itemsCountLabel",
+ itemsCount, [itemsCount]);
+ }
+ }
+ },
+
+ // NOT YET USED
+ _updateThumbnail: function PO__updateThumbnail() {
+ var bo = document.getElementById("previewBox").boxObject;
+ var width = bo.width;
+ var height = bo.height;
+
+ var canvas = document.getElementById("itemThumbnail");
+ var ctx = canvas.getContext('2d');
+ var notAvailableText = canvas.getAttribute("notavailabletext");
+ ctx.save();
+ ctx.fillStyle = "-moz-Dialog";
+ ctx.fillRect(0, 0, width, height);
+ ctx.translate(width/2, height/2);
+
+ ctx.fillStyle = "GrayText";
+ ctx.mozTextStyle = "12pt sans serif";
+ var len = ctx.mozMeasureText(notAvailableText);
+ ctx.translate(-len/2,0);
+ ctx.mozDrawText(notAvailableText);
+ ctx.restore();
+ },
+
+ toggleAdditionalInfoFields: function PO_toggleAdditionalInfoFields() {
+ var infoBox = document.getElementById("infoBox");
+ var infoBoxExpander = document.getElementById("infoBoxExpander");
+ var infoBoxExpanderLabel = document.getElementById("infoBoxExpanderLabel");
+ var additionalInfoBroadcaster = document.getElementById("additionalInfoBroadcaster");
+
+ if (infoBox.getAttribute("minimal") == "true") {
+ infoBox.removeAttribute("minimal");
+ infoBoxExpanderLabel.value = infoBoxExpanderLabel.getAttribute("lesslabel");
+ infoBoxExpanderLabel.accessKey = infoBoxExpanderLabel.getAttribute("lessaccesskey");
+ infoBoxExpander.className = "expander-up";
+ additionalInfoBroadcaster.removeAttribute("hidden");
+ }
+ else {
+ infoBox.setAttribute("minimal", "true");
+ infoBoxExpanderLabel.value = infoBoxExpanderLabel.getAttribute("morelabel");
+ infoBoxExpanderLabel.accessKey = infoBoxExpanderLabel.getAttribute("moreaccesskey");
+ infoBoxExpander.className = "expander-down";
+ additionalInfoBroadcaster.setAttribute("hidden", "true");
+ }
+ },
+
+ /**
+ * Save the current search (or advanced query) to the bookmarks root.
+ */
+ saveSearch: function PO_saveSearch() {
+ // Get the place: uri for the query.
+ // If the advanced query builder is showing, use that.
+ var options = this.getCurrentOptions();
+ var queries = this.getCurrentQueries();
+
+ var placeSpec = PlacesUtils.history.queriesToQueryString(queries,
+ queries.length,
+ options);
+ var placeURI = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService).
+ newURI(placeSpec, null, null);
+
+ // Prompt the user for a name for the query.
+ // XXX - using prompt service for now; will need to make
+ // a real dialog and localize when we're sure this is the UI we want.
+ var title = PlacesUIUtils.getString("saveSearch.title");
+ var inputLabel = PlacesUIUtils.getString("saveSearch.inputLabel");
+ var defaultText = PlacesUIUtils.getString("saveSearch.inputDefaultText");
+
+ var prompts = Cc["@mozilla.org/embedcomp/prompt-service;1"].
+ getService(Ci.nsIPromptService);
+ var check = {value: false};
+ var input = {value: defaultText};
+ var save = prompts.prompt(null, title, inputLabel, input, null, check);
+
+ // Don't add the query if the user cancels or clears the seach name.
+ if (!save || input.value == "")
+ return;
+
+ // Add the place: uri as a bookmark under the bookmarks root.
+ var txn = new PlacesCreateBookmarkTransaction(placeURI,
+ PlacesUtils.bookmarksMenuFolderId,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ input.value);
+ PlacesUtils.transactionManager.doTransaction(txn);
+
+ // select and load the new query
+ this._places.selectPlaceURI(placeSpec);
+ }
+};
+
+/**
+ * A set of utilities relating to search within Bookmarks and History.
+ */
+var PlacesSearchBox = {
+
+ /**
+ * The Search text field
+ */
+ get searchFilter() {
+ return document.getElementById("searchFilter");
+ },
+
+ /**
+ * Folders to include when searching.
+ */
+ _folders: [],
+ get folders() {
+ if (this._folders.length == 0) {
+ this._folders.push(PlacesUtils.bookmarksMenuFolderId,
+ PlacesUtils.unfiledBookmarksFolderId,
+ PlacesUtils.toolbarFolderId);
+ }
+ return this._folders;
+ },
+ set folders(aFolders) {
+ this._folders = aFolders;
+ return aFolders;
+ },
+
+ /**
+ * Run a search for the specified text, over the collection specified by
+ * the dropdown arrow. The default is all bookmarks, but can be
+ * localized to the active collection.
+ * @param filterString
+ * The text to search for.
+ */
+ search: function PSB_search(filterString) {
+ var PO = PlacesOrganizer;
+ // If the user empties the search box manually, reset it and load all
+ // contents of the current scope.
+ // XXX this might be to jumpy, maybe should search for "", so results
+ // are ungrouped, and search box not reset
+ if (filterString == "") {
+ PO.onPlaceSelected(false);
+ return;
+ }
+
+ let currentView = ContentArea.currentView;
+ let currentOptions = PO.getCurrentOptions();
+
+ // Search according to the current scope and folders, which were set by
+ // PQB_setScope()
+ switch (PlacesSearchBox.filterCollection) {
+ case "collection":
+ currentView.applyFilter(filterString, this.folders);
+ break;
+ case "bookmarks":
+ currentView.applyFilter(filterString, this.folders);
+ break;
+ case "history":
+ if (currentOptions.queryType != Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) {
+ var query = PlacesUtils.history.getNewQuery();
+ query.searchTerms = filterString;
+ var options = currentOptions.clone();
+ // Make sure we're getting uri results.
+ options.resultType = currentOptions.RESULTS_AS_URI;
+ options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY;
+ options.includeHidden = true;
+ currentView.load([query], options);
+ }
+ else {
+ currentView.applyFilter(filterString, null, true);
+ }
+ break;
+ case "downloads":
+ if (currentView == ContentTree.view) {
+ let query = PlacesUtils.history.getNewQuery();
+ query.searchTerms = filterString;
+ query.setTransitions([Ci.nsINavHistoryService.TRANSITION_DOWNLOAD], 1);
+ let options = currentOptions.clone();
+ // Make sure we're getting uri results.
+ options.resultType = currentOptions.RESULTS_AS_URI;
+ options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY;
+ options.includeHidden = true;
+ currentView.load([query], options);
+ }
+ else {
+ // The new downloads view doesn't use places for searching downloads.
+ currentView.searchTerm = filterString;
+ }
+ break;
+ default:
+ throw new Components.Exception("Invalid filterCollection on search",
+ Components.results.NS_ERROR_INVALID_ARG);
+ }
+
+ PlacesSearchBox.showSearchUI();
+
+ // Update the details panel
+ PlacesOrganizer.updateDetailsPane();
+ },
+
+ /**
+ * Finds across all history, downloads or all bookmarks.
+ */
+ findAll: function PSB_findAll() {
+ switch (this.filterCollection) {
+ case "history":
+ PlacesQueryBuilder.setScope("history");
+ break;
+ case "downloads":
+ PlacesQueryBuilder.setScope("downloads");
+ break;
+ default:
+ PlacesQueryBuilder.setScope("bookmarks");
+ break;
+ }
+ this.focus();
+ },
+
+ /**
+ * Updates the display with the title of the current collection.
+ * @param aTitle
+ * The title of the current collection.
+ */
+ updateCollectionTitle: function PSB_updateCollectionTitle(aTitle) {
+ let title = "";
+ // This is needed when a user performs a folder-specific search
+ // using the scope bar, removes the search-string, and unfocuses
+ // the search box, at least until the removal of the scope bar.
+ if (aTitle) {
+ title = PlacesUIUtils.getFormattedString("searchCurrentDefault",
+ [aTitle]);
+ }
+ else {
+ switch (this.filterCollection) {
+ case "history":
+ title = PlacesUIUtils.getString("searchHistory");
+ break;
+ case "downloads":
+ title = PlacesUIUtils.getString("searchDownloads");
+ break;
+ default:
+ title = PlacesUIUtils.getString("searchBookmarks");
+ }
+ }
+ this.searchFilter.placeholder = title;
+ },
+
+ /**
+ * Gets/sets the active collection from the dropdown menu.
+ */
+ get filterCollection() {
+ return this.searchFilter.getAttribute("collection");
+ },
+ set filterCollection(collectionName) {
+ if (collectionName == this.filterCollection)
+ return collectionName;
+
+ this.searchFilter.setAttribute("collection", collectionName);
+
+ var newGrayText = null;
+ if (collectionName == "collection") {
+ newGrayText = PlacesOrganizer._places.selectedNode.title ||
+ document.getElementById("scopeBarFolder").
+ getAttribute("emptytitle");
+ }
+ this.updateCollectionTitle(newGrayText);
+ return collectionName;
+ },
+
+ /**
+ * Focus the search box
+ */
+ focus: function PSB_focus() {
+ this.searchFilter.focus();
+ },
+
+ /**
+ * Set up the gray text in the search bar as the Places View loads.
+ */
+ init: function PSB_init() {
+ this.updateCollectionTitle();
+ },
+
+ /**
+ * Gets or sets the text shown in the Places Search Box
+ */
+ get value() {
+ return this.searchFilter.value;
+ },
+ set value(value) {
+ return this.searchFilter.value = value;
+ },
+
+ showSearchUI: function PSB_showSearchUI() {
+ // Hide the advanced search controls when the user hasn't searched
+ var searchModifiers = document.getElementById("searchModifiers");
+ searchModifiers.hidden = false;
+ },
+
+ hideSearchUI: function PSB_hideSearchUI() {
+ var searchModifiers = document.getElementById("searchModifiers");
+ searchModifiers.hidden = true;
+ }
+};
+
+/**
+ * Functions and data for advanced query builder
+ */
+var PlacesQueryBuilder = {
+
+ queries: [],
+ queryOptions: null,
+
+ /**
+ * Called when a scope button in the scope bar is clicked.
+ * @param aButton
+ * the scope button that was selected
+ */
+ onScopeSelected: function PQB_onScopeSelected(aButton) {
+ switch (aButton.id) {
+ case "scopeBarHistory":
+ this.setScope("history");
+ break;
+ case "scopeBarFolder":
+ this.setScope("collection");
+ break;
+ case "scopeBarDownloads":
+ this.setScope("downloads");
+ break;
+ case "scopeBarAll":
+ this.setScope("bookmarks");
+ break;
+ default:
+ throw new Components.Exception("Invalid search scope button ID",
+ Components.results.NS_ERROR_INVALID_ARG);
+ break;
+ }
+ },
+
+ /**
+ * Sets the search scope. This can be called when no search is active, and
+ * in that case, when the user does begin a search aScope will be used (see
+ * PSB_search()). If there is an active search, it's performed again to
+ * update the content tree.
+ * @param aScope
+ * The search scope: "bookmarks", "collection", "downloads" or
+ * "history".
+ */
+ setScope: function PQB_setScope(aScope) {
+ // Determine filterCollection, folders, and scopeButtonId based on aScope.
+ var filterCollection;
+ var folders = [];
+ var scopeButtonId;
+ switch (aScope) {
+ case "history":
+ filterCollection = "history";
+ scopeButtonId = "scopeBarHistory";
+ break;
+ case "collection":
+ // The folder scope button can only become hidden upon selecting a new
+ // folder in the left pane, and the disabled state will remain unchanged
+ // until a new folder is selected. See PO__setScopeForNode().
+ if (!document.getElementById("scopeBarFolder").hidden) {
+ filterCollection = "collection";
+ scopeButtonId = "scopeBarFolder";
+ folders.push(PlacesUtils.getConcreteItemId(
+ PlacesOrganizer._places.selectedNode));
+ break;
+ }
+ // Fall through. If collection scope doesn't make sense for the
+ // selected node, choose bookmarks scope.
+ case "bookmarks":
+ filterCollection = "bookmarks";
+ scopeButtonId = "scopeBarAll";
+ folders.push(PlacesUtils.bookmarksMenuFolderId,
+ PlacesUtils.toolbarFolderId,
+ PlacesUtils.unfiledBookmarksFolderId);
+ break;
+ case "downloads":
+ filterCollection = "downloads";
+ scopeButtonId = "scopeBarDownloads";
+ break;
+ default:
+ throw new Components.Exception("Invalid search scope",
+ Components.results.NS_ERROR_INVALID_ARG);
+ break;
+ }
+
+ // Check the appropriate scope button in the scope bar.
+ document.getElementById(scopeButtonId).checked = true;
+
+ // Update the search box. Re-search if there's an active search.
+ PlacesSearchBox.filterCollection = filterCollection;
+ PlacesSearchBox.folders = folders;
+ var searchStr = PlacesSearchBox.searchFilter.value;
+ if (searchStr)
+ PlacesSearchBox.search(searchStr);
+ }
+};
+
+/**
+ * Population and commands for the View Menu.
+ */
+var ViewMenu = {
+ /**
+ * Removes content generated previously from a menupopup.
+ * @param popup
+ * The popup that contains the previously generated content.
+ * @param startID
+ * The id attribute of an element that is the start of the
+ * dynamically generated region - remove elements after this
+ * item only.
+ * Must be contained by popup. Can be null (in which case the
+ * contents of popup are removed).
+ * @param endID
+ * The id attribute of an element that is the end of the
+ * dynamically generated region - remove elements up to this
+ * item only.
+ * Must be contained by popup. Can be null (in which case all
+ * items until the end of the popup will be removed). Ignored
+ * if startID is null.
+ * @returns The element for the caller to insert new items before,
+ * null if the caller should just append to the popup.
+ */
+ _clean: function VM__clean(popup, startID, endID) {
+ if (endID)
+ NS_ASSERT(startID, "meaningless to have valid endID and null startID");
+ if (startID) {
+ var startElement = document.getElementById(startID);
+ NS_ASSERT(startElement.parentNode ==
+ popup, "startElement is not in popup");
+ NS_ASSERT(startElement,
+ "startID does not correspond to an existing element");
+ var endElement = null;
+ if (endID) {
+ endElement = document.getElementById(endID);
+ NS_ASSERT(endElement.parentNode == popup,
+ "endElement is not in popup");
+ NS_ASSERT(endElement,
+ "endID does not correspond to an existing element");
+ }
+ while (startElement.nextSibling != endElement)
+ popup.removeChild(startElement.nextSibling);
+ return endElement;
+ }
+ else {
+ while(popup.hasChildNodes())
+ popup.removeChild(popup.firstChild);
+ }
+ return null;
+ },
+
+ /**
+ * Fills a menupopup with a list of columns
+ * @param event
+ * The popupshowing event that invoked this function.
+ * @param startID
+ * see _clean
+ * @param endID
+ * see _clean
+ * @param type
+ * the type of the menuitem, e.g. "radio" or "checkbox".
+ * Can be null (no-type).
+ * Checkboxes are checked if the column is visible.
+ * @param propertyPrefix
+ * If propertyPrefix is non-null:
+ * propertyPrefix + column ID + ".label" will be used to get the
+ * localized label string.
+ * propertyPrefix + column ID + ".accesskey" will be used to get the
+ * localized accesskey.
+ * If propertyPrefix is null, the column label is used as label and
+ * no accesskey is assigned.
+ */
+ fillWithColumns: function VM_fillWithColumns(event, startID, endID, type, propertyPrefix) {
+ var popup = event.target;
+ var pivot = this._clean(popup, startID, endID);
+
+ // If no column is "sort-active", the "Unsorted" item needs to be checked,
+ // so track whether or not we find a column that is sort-active.
+ var isSorted = false;
+ var content = document.getElementById("placeContent");
+ var columns = content.columns;
+ for (var i = 0; i < columns.count; ++i) {
+ var column = columns.getColumnAt(i).element;
+ if (popup.parentNode && (popup.parentNode.id == "viewSort")) {
+ switch (column.id) {
+ case "placesContentParentFolder":
+ continue;
+ case "placesContentParentFolderPath":
+ continue;
+ }
+ }
+ var menuitem = document.createElement("menuitem");
+ menuitem.id = "menucol_" + column.id;
+ menuitem.column = column;
+ var label = column.getAttribute("label");
+ if (propertyPrefix) {
+ var menuitemPrefix = propertyPrefix;
+ // for string properties, use "name" as the id, instead of "title"
+ // see bug #386287 for details
+ var columnId = column.getAttribute("anonid");
+ menuitemPrefix += columnId == "title" ? "name" : columnId;
+ label = PlacesUIUtils.getString(menuitemPrefix + ".label");
+ var accesskey = PlacesUIUtils.getString(menuitemPrefix + ".accesskey");
+ menuitem.setAttribute("accesskey", accesskey);
+ }
+ menuitem.setAttribute("label", label);
+ if (type == "radio") {
+ menuitem.setAttribute("type", "radio");
+ menuitem.setAttribute("name", "columns");
+ // This column is the sort key. Its item is checked.
+ if (column.getAttribute("sortDirection") != "") {
+ menuitem.setAttribute("checked", "true");
+ isSorted = true;
+ }
+ }
+ else if (type == "checkbox") {
+ menuitem.setAttribute("type", "checkbox");
+ // Cannot uncheck the primary column.
+ if (column.getAttribute("primary") == "true")
+ menuitem.setAttribute("disabled", "true");
+ // Items for visible columns are checked.
+ if (!column.hidden)
+ menuitem.setAttribute("checked", "true");
+ }
+ if (pivot)
+ popup.insertBefore(menuitem, pivot);
+ else
+ popup.appendChild(menuitem);
+ }
+ event.stopPropagation();
+ },
+
+ /**
+ * Set up the content of the view menu.
+ */
+ populateSortMenu: function VM_populateSortMenu(event) {
+ this.fillWithColumns(event, "viewUnsorted", "directionSeparator", "radio", "view.sortBy.");
+
+ var sortColumn = this._getSortColumn();
+ var viewSortAscending = document.getElementById("viewSortAscending");
+ var viewSortDescending = document.getElementById("viewSortDescending");
+ // We need to remove an existing checked attribute because the unsorted
+ // menu item is not rebuilt every time we open the menu like the others.
+ var viewUnsorted = document.getElementById("viewUnsorted");
+ if (!sortColumn) {
+ viewSortAscending.removeAttribute("checked");
+ viewSortDescending.removeAttribute("checked");
+ viewUnsorted.setAttribute("checked", "true");
+ }
+ else if (sortColumn.getAttribute("sortDirection") == "ascending") {
+ viewSortAscending.setAttribute("checked", "true");
+ viewSortDescending.removeAttribute("checked");
+ viewUnsorted.removeAttribute("checked");
+ }
+ else if (sortColumn.getAttribute("sortDirection") == "descending") {
+ viewSortDescending.setAttribute("checked", "true");
+ viewSortAscending.removeAttribute("checked");
+ viewUnsorted.removeAttribute("checked");
+ }
+ },
+
+ /**
+ * Shows/Hides a tree column.
+ * @param element
+ * The menuitem element for the column
+ */
+ showHideColumn: function VM_showHideColumn(element) {
+ var column = element.column;
+
+ var splitter = column.nextSibling;
+ if (splitter && splitter.localName != "splitter")
+ splitter = null;
+
+ if (element.getAttribute("checked") == "true") {
+ column.setAttribute("hidden", "false");
+ if (splitter)
+ splitter.removeAttribute("hidden");
+ }
+ else {
+ column.setAttribute("hidden", "true");
+ if (splitter)
+ splitter.setAttribute("hidden", "true");
+ }
+ },
+
+ /**
+ * Gets the last column that was sorted.
+ * @returns the currently sorted column, null if there is no sorted column.
+ */
+ _getSortColumn: function VM__getSortColumn() {
+ var content = document.getElementById("placeContent");
+ var cols = content.columns;
+ for (var i = 0; i < cols.count; ++i) {
+ var column = cols.getColumnAt(i).element;
+ var sortDirection = column.getAttribute("sortDirection");
+ if (sortDirection == "ascending" || sortDirection == "descending")
+ return column;
+ }
+ return null;
+ },
+
+ /**
+ * Sorts the view by the specified column.
+ * @param aColumn
+ * The colum that is the sort key. Can be null - the
+ * current sort column or the title column will be used.
+ * @param aDirection
+ * The direction to sort - "ascending" or "descending".
+ * Can be null - the last direction or descending will be used.
+ *
+ * If both aColumnID and aDirection are null, the view will be unsorted.
+ */
+ setSortColumn: function VM_setSortColumn(aColumn, aDirection) {
+ var result = document.getElementById("placeContent").result;
+ if (!aColumn && !aDirection) {
+ result.sortingMode = Ci.nsINavHistoryQueryOptions.SORT_BY_NONE;
+ return;
+ }
+
+ var columnId;
+ if (aColumn) {
+ columnId = aColumn.getAttribute("anonid");
+ if (!aDirection) {
+ var sortColumn = this._getSortColumn();
+ if (sortColumn)
+ aDirection = sortColumn.getAttribute("sortDirection");
+ }
+ }
+ else {
+ var sortColumn = this._getSortColumn();
+ columnId = sortColumn ? sortColumn.getAttribute("anonid") : "title";
+ }
+
+ // This maps the possible values of columnId (i.e., anonid's of treecols in
+ // placeContent) to the default sortingMode and sortingAnnotation values for
+ // each column.
+ // key: Sort key in the name of one of the
+ // nsINavHistoryQueryOptions.SORT_BY_* constants
+ // dir: Default sort direction to use if none has been specified
+ // anno: The annotation to sort by, if key is "ANNOTATION"
+ var colLookupTable = {
+ title: { key: "TITLE", dir: "ascending" },
+ tags: { key: "TAGS", dir: "ascending" },
+ url: { key: "URI", dir: "ascending" },
+ date: { key: "DATE", dir: "descending" },
+ visitCount: { key: "VISITCOUNT", dir: "descending" },
+ keyword: { key: "KEYWORD", dir: "ascending" },
+ dateAdded: { key: "DATEADDED", dir: "descending" },
+ lastModified: { key: "LASTMODIFIED", dir: "descending" },
+ description: { key: "ANNOTATION",
+ dir: "ascending",
+ anno: PlacesUIUtils.DESCRIPTION_ANNO }
+ };
+
+ // Make sure we have a valid column.
+ if (!colLookupTable.hasOwnProperty(columnId))
+ throw new Components.Exception("Invalid column",
+ Components.results.NS_ERROR_INVALID_ARG);
+
+ // Use a default sort direction if none has been specified. If aDirection
+ // is invalid, result.sortingMode will be undefined, which has the effect
+ // of unsorting the tree.
+ aDirection = (aDirection || colLookupTable[columnId].dir).toUpperCase();
+
+ var sortConst = "SORT_BY_" + colLookupTable[columnId].key + "_" + aDirection;
+ result.sortingAnnotation = colLookupTable[columnId].anno || "";
+ result.sortingMode = Ci.nsINavHistoryQueryOptions[sortConst];
+ }
+}
+
+var ContentArea = {
+ _specialViews: new Map(),
+
+ init: function CA_init() {
+ this._deck = document.getElementById("placesViewsDeck");
+ this._toolbar = document.getElementById("placesToolbar");
+ ContentTree.init();
+ this._setupView();
+ },
+
+ /**
+ * Gets the content view to be used for loading the given query.
+ * If a custom view was set by setContentViewForQueryString, that
+ * view would be returned, else the default tree view is returned
+ *
+ * @param aQueryString
+ * a query string
+ * @return the view to be used for loading aQueryString.
+ */
+ getContentViewForQueryString:
+ function CA_getContentViewForQueryString(aQueryString) {
+ try {
+ if (this._specialViews.has(aQueryString)) {
+ let { view, options } = this._specialViews.get(aQueryString);
+ if (typeof view == "function") {
+ view = view();
+ this._specialViews.set(aQueryString, { view: view, options: options });
+ }
+ return view;
+ }
+ }
+ catch(ex) {
+ Components.utils.reportError(ex);
+ }
+ return ContentTree.view;
+ },
+
+ /**
+ * Sets a custom view to be used rather than the default places tree
+ * whenever the given query is selected in the left pane.
+ * @param aQueryString
+ * a query string
+ * @param aView
+ * Either the custom view or a function that will return the view
+ * the first (and only) time it's called.
+ * @param [optional] aOptions
+ * Object defining special options for the view.
+ * @see ContentTree.viewOptions for supported options and default values.
+ */
+ setContentViewForQueryString:
+ function CA_setContentViewForQueryString(aQueryString, aView, aOptions) {
+ if (!aQueryString ||
+ typeof aView != "object" && typeof aView != "function")
+ throw new Components.Exception("Invalid arguments",
+ Components.results.NS_ERROR_INVALID_ARG);
+
+ this._specialViews.set(aQueryString, { view: aView,
+ options: aOptions || new Object() });
+ },
+
+ get currentView() PlacesUIUtils.getViewForNode(this._deck.selectedPanel),
+ set currentView(aNewView) {
+ let oldView = this.currentView;
+ if (oldView != aNewView) {
+ this._deck.selectedPanel = aNewView.associatedElement;
+
+ // If the content area inactivated view was focused, move focus
+ // to the new view.
+ if (document.activeElement == oldView.associatedElement)
+ aNewView.associatedElement.focus();
+ }
+ return aNewView;
+ },
+
+ get currentPlace() this.currentView.place,
+ set currentPlace(aQueryString) {
+ let oldView = this.currentView;
+ let newView = this.getContentViewForQueryString(aQueryString);
+ newView.place = aQueryString;
+ if (oldView != newView) {
+ oldView.active = false;
+ this.currentView = newView;
+ this._setupView();
+ newView.active = true;
+ }
+ return aQueryString;
+ },
+
+ /**
+ * Applies view options.
+ */
+ _setupView: function CA__setupView() {
+ let options = this.currentViewOptions;
+
+ // showDetailsPane.
+ let detailsDeck = document.getElementById("detailsDeck");
+ detailsDeck.hidden = !options.showDetailsPane;
+
+ // toolbarSet.
+ for (let elt of this._toolbar.childNodes) {
+ // On Windows and Linux the menu buttons are menus wrapped in a menubar.
+ if (elt.id == "placesMenu") {
+ for (let menuElt of elt.childNodes) {
+ menuElt.hidden = options.toolbarSet.indexOf(menuElt.id) == -1;
+ }
+ }
+ else {
+ elt.hidden = options.toolbarSet.indexOf(elt.id) == -1;
+ }
+ }
+ },
+
+ /**
+ * Options for the current view.
+ *
+ * @see ContentTree.viewOptions for supported options and default values.
+ */
+ get currentViewOptions() {
+ // Use ContentTree options as default.
+ let viewOptions = ContentTree.viewOptions;
+ if (this._specialViews.has(this.currentPlace)) {
+ let { view, options } = this._specialViews.get(this.currentPlace);
+ for (let option in options) {
+ viewOptions[option] = options[option];
+ }
+ }
+ return viewOptions;
+ },
+
+ focus: function() {
+ this._deck.selectedPanel.focus();
+ }
+};
+
+var ContentTree = {
+ init: function CT_init() {
+ this._view = document.getElementById("placeContent");
+ },
+
+ get view() this._view,
+
+ get viewOptions() Object.seal({
+ showDetailsPane: true,
+ toolbarSet: "back-button, forward-button, organizeButton, viewMenu, maintenanceButton, libraryToolbarSpacer, searchFilter"
+ }),
+
+ openSelectedNode: function CT_openSelectedNode(aEvent) {
+ let view = this.view;
+ PlacesUIUtils.openNodeWithEvent(view.selectedNode, aEvent, view);
+ },
+
+ onClick: function CT_onClick(aEvent) {
+ let node = this.view.selectedNode;
+ if (node) {
+ let doubleClick = aEvent.button == 0 && aEvent.detail == 2;
+ let middleClick = aEvent.button == 1 && aEvent.detail == 1;
+ if (PlacesUtils.nodeIsURI(node) && (doubleClick || middleClick)) {
+ // Open associated uri in the browser.
+ this.openSelectedNode(aEvent);
+ }
+ else if (middleClick && PlacesUtils.nodeIsContainer(node)) {
+ // The command execution function will take care of seeing if the
+ // selection is a folder or a different container type, and will
+ // load its contents in tabs.
+ PlacesUIUtils.openContainerNodeInTabs(node, aEvent, this.view);
+ }
+ }
+ },
+
+ onKeyPress: function CT_onKeyPress(aEvent) {
+ if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN)
+ this.openSelectedNode(aEvent);
+ }
+};
diff --git a/components/places/content/places.xul b/components/places/content/places.xul
new file mode 100644
index 0000000..92e8a70
--- /dev/null
+++ b/components/places/content/places.xul
@@ -0,0 +1,471 @@
+<?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/places/places.css"?>
+<?xml-stylesheet href="chrome://browser/content/places/organizer.css"?>
+
+<?xml-stylesheet href="chrome://global/skin/"?>
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+<?xml-stylesheet href="chrome://browser/skin/places/organizer.css"?>
+
+<?xul-overlay href="chrome://browser/content/places/editBookmarkOverlay.xul"?>
+
+#ifdef XP_MACOSX
+<?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?>
+#else
+<?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"?>
+#endif
+
+<!DOCTYPE window [
+<!ENTITY % placesDTD SYSTEM "chrome://browser/locale/places/places.dtd">
+%placesDTD;
+<!ENTITY % editMenuOverlayDTD SYSTEM "chrome://global/locale/editMenuOverlay.dtd">
+%editMenuOverlayDTD;
+<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
+%browserDTD;
+]>
+
+<window id="places"
+ title="&places.library.title;"
+ windowtype="Places:Organizer"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="PlacesOrganizer.init();"
+ onunload="PlacesOrganizer.destroy();"
+ width="&places.library.width;" height="&places.library.height;"
+ screenX="10" screenY="10"
+ toggletoolbar="true"
+ persist="width height screenX screenY sizemode">
+
+ <script type="application/javascript"
+ src="chrome://browser/content/places/places.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/utilityOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/places/editBookmarkOverlay.js"/>
+
+ <stringbundleset id="placesStringSet">
+ <stringbundle id="brandStrings" src="chrome://branding/locale/brand.properties"/>
+ </stringbundleset>
+
+#ifdef XP_MACOSX
+#include ../../../base/content/browserMountPoints.inc
+#else
+ <commandset id="editMenuCommands"/>
+ <commandset id="placesCommands"/>
+#endif
+ <keyset id="placesCommandKeys"/>
+
+ <commandset id="organizerCommandSet">
+ <command id="OrganizerCommand_find:all"
+ oncommand="PlacesSearchBox.findAll();"/>
+ <command id="OrganizerCommand_export"
+ oncommand="PlacesOrganizer.exportBookmarks();"/>
+ <command id="OrganizerCommand_import"
+ oncommand="PlacesOrganizer.importFromFile();"/>
+ <command id="OrganizerCommand_backup"
+ oncommand="PlacesOrganizer.backupBookmarks();"/>
+ <command id="OrganizerCommand_restoreFromFile"
+ oncommand="PlacesOrganizer.onRestoreBookmarksFromFile();"/>
+ <command id="OrganizerCommand_search:save"
+ oncommand="PlacesOrganizer.saveSearch();"/>
+ <command id="OrganizerCommand_search:moreCriteria"
+ oncommand="PlacesQueryBuilder.addRow();"/>
+ <command id="OrganizerCommand:Back"
+ oncommand="PlacesOrganizer.back();"/>
+ <command id="OrganizerCommand:Forward"
+ oncommand="PlacesOrganizer.forward();"/>
+ </commandset>
+
+ <keyset id="placesOrganizerKeyset">
+ <!-- Instantiation Keys -->
+ <key id="placesKey_close" key="&cmd.close.key;" modifiers="accel"
+ oncommand="close();"/>
+
+ <!-- Command Keys -->
+ <key id="placesKey_find:all"
+ command="OrganizerCommand_find:all"
+ key="&cmd.find.key;"
+ modifiers="accel"/>
+
+ <!-- Back/Forward Keys Support -->
+#ifndef XP_MACOSX
+ <key id="placesKey_goBackKb"
+ keycode="VK_LEFT"
+ command="OrganizerCommand:Back"
+ modifiers="alt"/>
+ <key id="placesKey_goForwardKb"
+ keycode="VK_RIGHT"
+ command="OrganizerCommand:Forward"
+ modifiers="alt"/>
+#else
+ <key id="placesKey_goBackKb"
+ keycode="VK_LEFT"
+ command="OrganizerCommand:Back"
+ modifiers="accel"/>
+ <key id="placesKey_goForwardKb"
+ keycode="VK_RIGHT"
+ command="OrganizerCommand:Forward"
+ modifiers="accel"/>
+#endif
+#ifdef XP_UNIX
+ <key id="placesKey_goBackKb2"
+ key="&goBackCmd.commandKey;"
+ command="OrganizerCommand:Back"
+ modifiers="accel"/>
+ <key id="placesKey_goForwardKb2"
+ key="&goForwardCmd.commandKey;"
+ command="OrganizerCommand:Forward"
+ modifiers="accel"/>
+#endif
+ </keyset>
+
+ <keyset id="editMenuKeys">
+#ifdef XP_MACOSX
+ <key id="key_delete2" keycode="VK_BACK" command="cmd_delete"/>
+#endif
+ </keyset>
+
+ <popupset id="placesPopupset">
+ <menupopup id="placesContext"/>
+ <menupopup id="placesColumnsContext"
+ onpopupshowing="ViewMenu.fillWithColumns(event, null, null, 'checkbox', null);"
+ oncommand="ViewMenu.showHideColumn(event.target); event.stopPropagation();"/>
+ </popupset>
+
+ <toolbox id="placesToolbox">
+ <toolbar class="chromeclass-toolbar" id="placesToolbar" align="center">
+ <toolbarbutton id="back-button"
+ command="OrganizerCommand:Back"
+ tooltiptext="&backButton.tooltip;"
+ disabled="true"/>
+
+ <toolbarbutton id="forward-button"
+ command="OrganizerCommand:Forward"
+ tooltiptext="&forwardButton.tooltip;"
+ disabled="true"/>
+
+#ifdef XP_MACOSX
+ <toolbarbutton type="menu" class="tabbable"
+ onpopupshowing="document.getElementById('placeContent').focus()"
+#else
+ <menubar id="placesMenu">
+ <menu accesskey="&organize.accesskey;" class="menu-iconic"
+#endif
+ id="organizeButton" label="&organize.label;"
+ tooltiptext="&organize.tooltip;">
+ <menupopup id="organizeButtonPopup">
+ <menuitem id="newbookmark"
+ command="placesCmd_new:bookmark"
+ label="&cmd.new_bookmark.label;"
+ accesskey="&cmd.new_bookmark.accesskey;"/>
+ <menuitem id="newfolder"
+ command="placesCmd_new:folder"
+ label="&cmd.new_folder.label;"
+ accesskey="&cmd.new_folder.accesskey;"/>
+ <menuitem id="newseparator"
+ command="placesCmd_new:separator"
+ label="&cmd.new_separator.label;"
+ accesskey="&cmd.new_separator.accesskey;"/>
+
+#ifndef XP_MACOSX
+ <menuseparator id="orgUndoSeparator"/>
+
+ <menuitem id="orgUndo"
+ command="cmd_undo"
+ label="&undoCmd.label;"
+ key="key_undo"
+ accesskey="&undoCmd.accesskey;"/>
+ <menuitem id="orgRedo"
+ command="cmd_redo"
+ label="&redoCmd.label;"
+ key="key_redo"
+ accesskey="&redoCmd.accesskey;"/>
+
+ <menuseparator id="orgCutSeparator"/>
+
+ <menuitem id="orgCut"
+ command="cmd_cut"
+ label="&cutCmd.label;"
+ key="key_cut"
+ accesskey="&cutCmd.accesskey;"
+ selection="separator|link|folder|mixed"/>
+ <menuitem id="orgCopy"
+ command="cmd_copy"
+ label="&copyCmd.label;"
+ key="key_copy"
+ accesskey="&copyCmd.accesskey;"
+ selection="separator|link|folder|mixed"/>
+ <menuitem id="orgPaste"
+ command="cmd_paste"
+ label="&pasteCmd.label;"
+ key="key_paste"
+ accesskey="&pasteCmd.accesskey;"
+ selection="mutable"/>
+ <menuitem id="orgDelete"
+ command="cmd_delete"
+ label="&deleteCmd.label;"
+ key="key_delete"
+ accesskey="&deleteCmd.accesskey;"/>
+
+ <menuseparator id="selectAllSeparator"/>
+
+ <menuitem id="orgSelectAll"
+ command="cmd_selectAll"
+ label="&selectAllCmd.label;"
+ key="key_selectAll"
+ accesskey="&selectAllCmd.accesskey;"/>
+
+#endif
+ <menuseparator id="orgMoveSeparator"/>
+
+ <menuitem id="orgMoveBookmarks"
+ command="placesCmd_moveBookmarks"
+ label="&cmd.moveBookmarks.label;"
+ accesskey="&cmd.moveBookmarks.accesskey;"/>
+#ifdef XP_MACOSX
+ <menuitem id="orgDelete"
+ command="cmd_delete"
+ label="&deleteCmd.label;"
+ key="key_delete"
+ accesskey="&deleteCmd.accesskey;"/>
+#else
+ <menuseparator id="orgCloseSeparator"/>
+
+ <menuitem id="orgClose"
+ key="placesKey_close"
+ label="&file.close.label;"
+ accesskey="&file.close.accesskey;"
+ oncommand="close();"/>
+#endif
+ </menupopup>
+#ifdef XP_MACOSX
+ </toolbarbutton>
+ <toolbarbutton type="menu" class="tabbable"
+#else
+ </menu>
+ <menu accesskey="&views.accesskey;" class="menu-iconic"
+#endif
+ id="viewMenu" label="&views.label;"
+ tooltiptext="&views.tooltip;">
+ <menupopup id="viewMenuPopup">
+
+ <menu id="viewColumns"
+ label="&view.columns.label;" accesskey="&view.columns.accesskey;">
+ <menupopup onpopupshowing="ViewMenu.fillWithColumns(event, null, null, 'checkbox', null);"
+ oncommand="ViewMenu.showHideColumn(event.target); event.stopPropagation();"/>
+ </menu>
+
+ <menu id="viewSort" label="&view.sort.label;"
+ accesskey="&view.sort.accesskey;">
+ <menupopup onpopupshowing="ViewMenu.populateSortMenu(event);"
+ oncommand="ViewMenu.setSortColumn(event.target.column, null);">
+ <menuitem id="viewUnsorted" type="radio" name="columns"
+ label="&view.unsorted.label;" accesskey="&view.unsorted.accesskey;"
+ oncommand="ViewMenu.setSortColumn(null, null);"/>
+ <menuseparator id="directionSeparator"/>
+ <menuitem id="viewSortAscending" type="radio" name="direction"
+ label="&view.sortAscending.label;" accesskey="&view.sortAscending.accesskey;"
+ oncommand="ViewMenu.setSortColumn(null, 'ascending'); event.stopPropagation();"/>
+ <menuitem id="viewSortDescending" type="radio" name="direction"
+ label="&view.sortDescending.label;" accesskey="&view.sortDescending.accesskey;"
+ oncommand="ViewMenu.setSortColumn(null, 'descending'); event.stopPropagation();"/>
+ </menupopup>
+ </menu>
+ </menupopup>
+#ifdef XP_MACOSX
+ </toolbarbutton>
+ <toolbarbutton type="menu" class="tabbable"
+#else
+ </menu>
+ <menu accesskey="&maintenance.accesskey;" class="menu-iconic"
+#endif
+ id="maintenanceButton" label="&maintenance.label;"
+ tooltiptext="&maintenance.tooltip;">
+ <menupopup id="maintenanceButtonPopup">
+ <menuitem id="backupBookmarks"
+ command="OrganizerCommand_backup"
+ label="&cmd.backup.label;"
+ accesskey="&cmd.backup.accesskey;"/>
+ <menu id="fileRestoreMenu" label="&cmd.restore2.label;"
+ accesskey="&cmd.restore2.accesskey;">
+ <menupopup id="fileRestorePopup" onpopupshowing="PlacesOrganizer.populateRestoreMenu();">
+ <menuitem id="restoreFromFile"
+ command="OrganizerCommand_restoreFromFile"
+ label="&cmd.restoreFromFile.label;"
+ accesskey="&cmd.restoreFromFile.accesskey;"/>
+ </menupopup>
+ </menu>
+ <menuseparator/>
+ <menuitem id="fileImport"
+ command="OrganizerCommand_import"
+ label="&importBookmarksFromHTML.label;"
+ accesskey="&importBookmarksFromHTML.accesskey;"/>
+ <menuitem id="fileExport"
+ command="OrganizerCommand_export"
+ label="&exportBookmarksToHTML.label;"
+ accesskey="&exportBookmarksToHTML.accesskey;"/>
+ </menupopup>
+#ifdef XP_MACOSX
+ </toolbarbutton>
+#else
+ </menu>
+ </menubar>
+#endif
+
+ <spacer id="libraryToolbarSpacer" flex="1"/>
+
+ <textbox id="searchFilter"
+ clickSelectsAll="true"
+ type="search"
+ aria-controls="placeContent"
+ oncommand="PlacesSearchBox.search(this.value);"
+ collection="bookmarks">
+ </textbox>
+ </toolbar>
+ </toolbox>
+
+ <hbox flex="1" id="placesView">
+ <tree id="placesList"
+ class="plain placesTree"
+ type="places"
+ hidecolumnpicker="true" context="placesContext"
+ onselect="PlacesOrganizer.onPlaceSelected(true);"
+ onclick="PlacesOrganizer.onPlacesListClick(event);"
+ onfocus="PlacesOrganizer.updateDetailsPane(event);"
+ seltype="single"
+ persist="width"
+ width="200"
+ minwidth="100"
+ maxwidth="400">
+ <treecols>
+ <treecol anonid="title" flex="1" primary="true" hideheader="true"/>
+ </treecols>
+ <treechildren flex="1"/>
+ </tree>
+ <splitter collapse="none" persist="state"></splitter>
+ <vbox id="contentView" flex="4">
+ <toolbox id="searchModifiers" hidden="true">
+ <toolbar id="organizerScopeBar" class="chromeclass-toolbar" align="center">
+ <label id="scopeBarTitle" value="&search.in.label;"/>
+ <toolbarbutton id="scopeBarAll" class="small-margin"
+ type="radio" group="scopeBar"
+ oncommand="PlacesQueryBuilder.onScopeSelected(this);"
+ label="&search.scopeBookmarks.label;"
+ accesskey="&search.scopeBookmarks.accesskey;"/>
+ <toolbarbutton id="scopeBarHistory" class="small-margin"
+ type="radio" group="scopeBar"
+ oncommand="PlacesQueryBuilder.onScopeSelected(this);"
+ label="&search.scopeHistory.label;"
+ accesskey="&search.scopeHistory.accesskey;"/>
+ <toolbarbutton id="scopeBarDownloads" class="small-margin"
+ type="radio" group="scopeBar"
+ oncommand="PlacesQueryBuilder.onScopeSelected(this);"
+ label="&search.scopeDownloads.label;"
+ accesskey="&search.scopeDownloads.accesskey;"/>
+ <toolbarbutton id="scopeBarFolder" class="small-margin"
+ type="radio" group="scopeBar"
+ oncommand="PlacesQueryBuilder.onScopeSelected(this);"
+ accesskey="&search.scopeFolder.accesskey;"
+ emptytitle="&search.scopeFolder.label;" flex="1"/>
+ <!-- The folder scope button should flex but not take up more room
+ than its label needs. The only simple way to do that is to
+ set a really big flex on the spacer, e.g., 2^31 - 1. -->
+ <spacer flex="2147483647"/>
+ <button id="saveSearch" class="small-margin"
+ label="&saveSearch.label;" accesskey="&saveSearch.accesskey;"
+ command="OrganizerCommand_search:save"/>
+ </toolbar>
+ </toolbox>
+ <deck id="placesViewsDeck"
+ selectedIndex="0"
+ flex="1">
+ <tree id="placeContent"
+ class="plain placesTree"
+ context="placesContext"
+ hidecolumnpicker="true"
+ flex="1"
+ type="places"
+ flatList="true"
+ selectfirstnode="true"
+ enableColumnDrag="true"
+ onfocus="PlacesOrganizer.updateDetailsPane(event)"
+ onselect="PlacesOrganizer.updateDetailsPane(event)"
+ onkeypress="ContentTree.onKeyPress(event);"
+ onopenflatcontainer="PlacesOrganizer.openFlatContainer(aContainer);">
+ <treecols id="placeContentColumns" context="placesColumnsContext">
+ <treecol label="&col.name.label;" id="placesContentTitle" anonid="title" flex="5" primary="true" ordinal="1"
+ persist="width hidden ordinal sortActive sortDirection"/>
+ <splitter class="tree-splitter"/>
+ <treecol label="&col.tags.label;" id="placesContentTags" anonid="tags" flex="2"
+ persist="width hidden ordinal sortActive sortDirection"/>
+ <splitter class="tree-splitter"/>
+ <treecol label="&col.url.label;" id="placesContentUrl" anonid="url" flex="5"
+ persist="width hidden ordinal sortActive sortDirection"/>
+ <splitter class="tree-splitter"/>
+ <treecol label="&col.lastvisit.label;" id="placesContentDate" anonid="date" flex="1" hidden="true"
+ persist="width hidden ordinal sortActive sortDirection"/>
+ <splitter class="tree-splitter"/>
+ <treecol label="&col.visitcount.label;" id="placesContentVisitCount" anonid="visitCount" flex="1" hidden="true"
+ persist="width hidden ordinal sortActive sortDirection"/>
+ <splitter class="tree-splitter"/>
+ <treecol label="&col.keyword.label;" id="placesContentKeyword" anonid="keyword" flex="1" hidden="true"
+ persist="width hidden ordinal sortActive sortDirection"/>
+ <splitter class="tree-splitter"/>
+ <treecol label="&col.description.label;" id="placesContentDescription" anonid="description" flex="1" hidden="true"
+ persist="width hidden ordinal sortActive sortDirection"/>
+ <splitter class="tree-splitter"/>
+ <treecol label="&col.dateadded.label;" id="placesContentDateAdded" anonid="dateAdded" flex="1" hidden="true"
+ persist="width hidden ordinal sortActive sortDirection"/>
+ <splitter class="tree-splitter"/>
+ <treecol label="&col.lastmodified.label;" id="placesContentLastModified" anonid="lastModified" flex="1" hidden="true"
+ persist="width hidden ordinal sortActive sortDirection"/>
+ <splitter class="tree-splitter"/>
+ <treecol label="&col.parentfolder.label;" id="placesContentParentFolder" anonid="parentFolder" flex="1" hidden="true"
+ persist="width hidden ordinal"/>
+ <splitter class="tree-splitter"/>
+ <treecol label="&col.parentfolderpath.label;" id="placesContentParentFolderPath" anonid="parentFolderPath" flex="1" hidden="true"
+ persist="width hidden ordinal"/>
+ </treecols>
+ <treechildren flex="1" onclick="ContentTree.onClick(event);"/>
+ </tree>
+ </deck>
+ <deck id="detailsDeck" style="height: 11em;">
+ <vbox id="itemsCountBox" align="center">
+ <spacer flex="3"/>
+ <label id="itemsCountText"/>
+ <spacer flex="1"/>
+ <description id="selectItemDescription">
+ &detailsPane.selectAnItemText.description;
+ </description>
+ <spacer flex="3"/>
+ </vbox>
+ <vbox id="infoBox" minimal="true">
+ <vbox id="editBookmarkPanelContent" flex="1"/>
+ <hbox id="infoBoxExpanderWrapper" align="center">
+
+ <button type="image" id="infoBoxExpander"
+ class="expander-down"
+ oncommand="PlacesOrganizer.toggleAdditionalInfoFields();"
+ observes="paneElementsBroadcaster"/>
+
+ <label id="infoBoxExpanderLabel"
+ lesslabel="&detailsPane.less.label;"
+ lessaccesskey="&detailsPane.less.accesskey;"
+ morelabel="&detailsPane.more.label;"
+ moreaccesskey="&detailsPane.more.accesskey;"
+ value="&detailsPane.more.label;"
+ accesskey="&detailsPane.more.accesskey;"
+ control="infoBoxExpander"/>
+
+ </hbox>
+ </vbox>
+ </deck>
+ </vbox>
+ </hbox>
+</window>
diff --git a/components/places/content/placesOverlay.xul b/components/places/content/placesOverlay.xul
new file mode 100644
index 0000000..59115a5
--- /dev/null
+++ b/components/places/content/placesOverlay.xul
@@ -0,0 +1,247 @@
+<!-- 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 % placesDTD SYSTEM "chrome://browser/locale/places/places.dtd">
+%placesDTD;
+<!ENTITY % editMenuOverlayDTD SYSTEM "chrome://global/locale/editMenuOverlay.dtd">
+%editMenuOverlayDTD;
+]>
+
+<overlay id="placesOverlay"
+ 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://global/content/globalOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/utilityOverlay.js"/>
+ <script type="application/javascript"><![CDATA[
+ // TODO: Bug 406371.
+ // A bunch of browser code depends on us defining these, sad but true :(
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+ var Cr = Components.results;
+
+ Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
+ Components.utils.import("resource:///modules/PlacesUIUtils.jsm");
+ ]]></script>
+ <script type="application/javascript"
+ src="chrome://browser/content/places/controller.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/places/treeView.js"/>
+
+ <!-- Bookmarks and history tooltip -->
+ <tooltip id="bhTooltip" noautohide="true"
+ onpopupshowing="return window.top.BookmarksEventHandler.fillInBHTooltip(document, event)">
+ <vbox id="bhTooltipTextBox" flex="1">
+ <label id="bhtTitleText" class="tooltip-label" />
+ <label id="bhtUrlText" crop="center" class="tooltip-label" />
+ </vbox>
+ </tooltip>
+
+ <commandset id="placesCommands"
+ commandupdater="true"
+ events="focus,sort,places"
+ oncommandupdate="goUpdatePlacesCommands();">
+ <command id="placesCmd_open"
+ oncommand="goDoPlacesCommand('placesCmd_open');"/>
+ <command id="placesCmd_open:window"
+ oncommand="goDoPlacesCommand('placesCmd_open:window');"/>
+ <command id="placesCmd_open:privatewindow"
+ oncommand="goDoPlacesCommand('placesCmd_open:privatewindow');"/>
+ <command id="placesCmd_open:tab"
+ oncommand="goDoPlacesCommand('placesCmd_open:tab');"/>
+
+ <command id="placesCmd_new:bookmark"
+ oncommand="goDoPlacesCommand('placesCmd_new:bookmark');"/>
+ <command id="placesCmd_new:livemark"
+ oncommand="goDoPlacesCommand('placesCmd_new:livemark');"/>
+ <command id="placesCmd_new:folder"
+ oncommand="goDoPlacesCommand('placesCmd_new:folder');"/>
+ <command id="placesCmd_new:separator"
+ oncommand="goDoPlacesCommand('placesCmd_new:separator');"/>
+ <command id="placesCmd_show:info"
+ oncommand="goDoPlacesCommand('placesCmd_show:info');"/>
+ <command id="placesCmd_rename"
+ oncommand="goDoPlacesCommand('placesCmd_show:info');"
+ observes="placesCmd_show:info"/>
+ <command id="placesCmd_reload"
+ oncommand="goDoPlacesCommand('placesCmd_reload');"/>
+ <command id="placesCmd_sortBy:name"
+ oncommand="goDoPlacesCommand('placesCmd_sortBy:name');"/>
+ <command id="placesCmd_moveBookmarks"
+ oncommand="goDoPlacesCommand('placesCmd_moveBookmarks');"/>
+ <command id="placesCmd_deleteDataHost"
+ oncommand="goDoPlacesCommand('placesCmd_deleteDataHost');"/>
+ <command id="placesCmd_createBookmark"
+ oncommand="goDoPlacesCommand('placesCmd_createBookmark');"/>
+ <command id="placesCmd_openParentFolder"
+ oncommand="goDoPlacesCommand('placesCmd_openParentFolder');"/>
+
+ <!-- Special versions of cut/copy/paste/delete which check for an open context menu. -->
+ <command id="placesCmd_cut"
+ oncommand="goDoPlacesCommand('placesCmd_cut');"/>
+ <command id="placesCmd_copy"
+ oncommand="goDoPlacesCommand('placesCmd_copy');"/>
+ <command id="placesCmd_paste"
+ oncommand="goDoPlacesCommand('placesCmd_paste');"/>
+ <command id="placesCmd_delete"
+ oncommand="goDoPlacesCommand('placesCmd_delete');"/>
+ </commandset>
+
+ <keyset id="placesCommandKeys">
+ <key id="key_placesCmd_openParentFolder"
+ keycode="VK_F1"
+ command="placesCmd_openParentFolder"
+ modifiers="accel,shift"/>
+ </keyset>
+
+ <menupopup id="placesContext"
+ onpopupshowing="this._view = PlacesUIUtils.getViewForNode(document.popupNode);
+ return this._view.buildContextMenu(this);"
+ onpopuphiding="this._view.destroyContextMenu();">
+ <menuitem id="placesContext_open"
+ command="placesCmd_open"
+ label="&cmd.open.label;"
+ accesskey="&cmd.open.accesskey;"
+ default="true"
+ selectiontype="single"
+ selection="link"/>
+ <menuitem id="placesContext_open:newtab"
+ command="placesCmd_open:tab"
+ label="&cmd.open_tab.label;"
+ accesskey="&cmd.open_tab.accesskey;"
+ selectiontype="single"
+ selection="link"/>
+ <menuitem id="placesContext_openContainer:tabs"
+ oncommand="var view = PlacesUIUtils.getViewForNode(document.popupNode);
+ view.controller.openSelectionInTabs(event);"
+ onclick="checkForMiddleClick(this, event);"
+ label="&cmd.open_all_in_tabs.label;"
+ accesskey="&cmd.open_all_in_tabs.accesskey;"
+ selectiontype="single"
+ selection="folder|host|query"/>
+ <menuitem id="placesContext_openLinks:tabs"
+ oncommand="var view = PlacesUIUtils.getViewForNode(document.popupNode);
+ view.controller.openSelectionInTabs(event);"
+ onclick="checkForMiddleClick(this, event);"
+ label="&cmd.open_all_in_tabs.label;"
+ accesskey="&cmd.open_all_in_tabs.accesskey;"
+ selectiontype="multiple"
+ selection="link"/>
+ <menuitem id="placesContext_open:newwindow"
+ command="placesCmd_open:window"
+ label="&cmd.open_window.label;"
+ accesskey="&cmd.open_window.accesskey;"
+ selectiontype="single"
+ selection="link"/>
+ <menuitem id="placesContext_open:newprivatewindow"
+ command="placesCmd_open:privatewindow"
+ label="&cmd.open_private_window.label;"
+ accesskey="&cmd.open_private_window.accesskey;"
+ selectiontype="single"
+ selection="link"
+ hideifprivatebrowsing="true"/>
+ <menuseparator id="placesContext_openSeparator"/>
+ <menuitem id="placesContext_new:bookmark"
+ command="placesCmd_new:bookmark"
+ label="&cmd.new_bookmark.label;"
+ accesskey="&cmd.new_bookmark.accesskey;"
+ selectiontype="any"
+ hideifnoinsertionpoint="true"/>
+ <menuitem id="placesContext_new:folder"
+ command="placesCmd_new:folder"
+ label="&cmd.new_folder.label;"
+ accesskey="&cmd.context_new_folder.accesskey;"
+ selectiontype="any"
+ hideifnoinsertionpoint="true"/>
+ <menuitem id="placesContext_new:separator"
+ command="placesCmd_new:separator"
+ label="&cmd.new_separator.label;"
+ accesskey="&cmd.new_separator.accesskey;"
+ closemenu="single"
+ selectiontype="any"
+ hideifnoinsertionpoint="true"/>
+ <menuseparator id="placesContext_newSeparator"/>
+ <menuitem id="placesContext_createBookmark"
+ command="placesCmd_createBookmark"
+ label="&cmd.bookmarkLink.label;"
+ accesskey="&cmd.bookmarkLink.accesskey;"
+ selection="link"
+ forcehideselection="bookmark|tagChild"/>
+ <menuitem id="placesContext_cut"
+ command="placesCmd_cut"
+ label="&cutCmd.label;"
+ accesskey="&cutCmd.accesskey;"
+ closemenu="single"
+ selection="bookmark|folder|separator|query"
+ forcehideselection="tagChild|livemarkChild"/>
+ <menuitem id="placesContext_copy"
+ command="placesCmd_copy"
+ label="&copyCmd.label;"
+ closemenu="single"
+ accesskey="&copyCmd.accesskey;"/>
+ <menuitem id="placesContext_paste"
+ command="placesCmd_paste"
+ label="&pasteCmd.label;"
+ closemenu="single"
+ accesskey="&pasteCmd.accesskey;"
+ selectiontype="any"
+ hideifnoinsertionpoint="true"/>
+ <menuseparator id="placesContext_editSeparator"/>
+ <menuitem id="placesContext_delete"
+ command="placesCmd_delete"
+ label="&deleteCmd.label;"
+ accesskey="&deleteCmd.accesskey;"
+ closemenu="single"
+ selection="bookmark|tagChild|folder|query|dynamiccontainer|separator|host"/>
+ <menuitem id="placesContext_delete_history"
+ command="placesCmd_delete"
+ label="&cmd.delete.label;"
+ accesskey="&cmd.delete.accesskey;"
+ closemenu="single"
+ selection="link"
+ forcehideselection="bookmark|livemarkChild"/>
+ <menuitem id="placesContext_deleteHost"
+ command="placesCmd_deleteDataHost"
+ label="&cmd.deleteDomainData.label;"
+ accesskey="&cmd.deleteDomainData.accesskey;"
+ closemenu="single"
+ selection="link|host"
+ selectiontype="single"
+ hideifprivatebrowsing="true"
+ forcehideselection="bookmark|livemarkChild"/>
+ <menuseparator id="placesContext_deleteSeparator"/>
+ <menuitem id="placesContext_reload"
+ command="placesCmd_reload"
+ label="&cmd.reloadLivebookmark.label;"
+ accesskey="&cmd.reloadLivebookmark.accesskey;"
+ closemenu="single"
+ selection="livemark/feedURI"/>
+ <menuitem id="placesContext_sortBy:name"
+ command="placesCmd_sortBy:name"
+ label="&cmd.sortby_name.label;"
+ accesskey="&cmd.context_sortby_name.accesskey;"
+ closemenu="single"
+ selection="folder"/>
+ <menuseparator id="placesContext_sortSeparator"/>
+ <menuitem id="placesContext_openParentFolder"
+ command="placesCmd_openParentFolder"
+ label="&cmd.openParentFolder.label;"
+ key="key_placesCmd_openParentFolder"
+ accesskey="&cmd.openParentFolder.accesskey;"
+ selectiontype="single"
+ selection="bookmark"
+ forcehideselection="livemarkChild|livemark/feedURI|PlacesOrganizer/OrganizerQuery"/>
+ <menuseparator id="placesContext_parentFolderSeparator"/>
+ <menuitem id="placesContext_show:info"
+ command="placesCmd_show:info"
+ label="&cmd.properties.label;"
+ accesskey="&cmd.properties.accesskey;"
+ selection="bookmark|folder|query"
+ forcehideselection="livemarkChild"/>
+ </menupopup>
+
+</overlay>
diff --git a/components/places/content/sidebarUtils.js b/components/places/content/sidebarUtils.js
new file mode 100644
index 0000000..06ed537
--- /dev/null
+++ b/components/places/content/sidebarUtils.js
@@ -0,0 +1,108 @@
+# -*- 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 SidebarUtils = {
+ handleTreeClick: function SU_handleTreeClick(aTree, aEvent, aGutterSelect) {
+ // right-clicks are not handled here
+ if (aEvent.button == 2)
+ return;
+
+ var tbo = aTree.treeBoxObject;
+ var cell = tbo.getCellAt(aEvent.clientX, aEvent.clientY);
+
+ if (cell.row == -1 || cell.childElt == "twisty")
+ return;
+
+ var mouseInGutter = false;
+ if (aGutterSelect) {
+ var rect = tbo.getCoordsForCellItem(cell.row, cell.col, "image");
+ // getCoordsForCellItem returns the x coordinate in logical coordinates
+ // (i.e., starting from the left and right sides in LTR and RTL modes,
+ // respectively.) Therefore, we make sure to exclude the blank area
+ // before the tree item icon (that is, to the left or right of it in
+ // LTR and RTL modes, respectively) from the click target area.
+ var isRTL = window.getComputedStyle(aTree, null).direction == "rtl";
+ if (isRTL)
+ mouseInGutter = aEvent.clientX > rect.x;
+ else
+ mouseInGutter = aEvent.clientX < rect.x;
+ }
+
+#ifdef XP_MACOSX
+ var modifKey = aEvent.metaKey || aEvent.shiftKey;
+#else
+ var modifKey = aEvent.ctrlKey || aEvent.shiftKey;
+#endif
+
+ var isContainer = tbo.view.isContainer(cell.row);
+ var openInTabs = isContainer &&
+ (aEvent.button == 1 ||
+ (aEvent.button == 0 && modifKey)) &&
+ PlacesUtils.hasChildURIs(tbo.view.nodeForTreeIndex(cell.row), true);
+
+ if (aEvent.button == 0 && isContainer && !openInTabs) {
+ tbo.view.toggleOpenState(cell.row);
+ return;
+ }
+ else if (!mouseInGutter && openInTabs &&
+ aEvent.originalTarget.localName == "treechildren") {
+ tbo.view.selection.select(cell.row);
+ PlacesUIUtils.openContainerNodeInTabs(aTree.selectedNode, aEvent, aTree);
+ }
+ else if (!mouseInGutter && !isContainer &&
+ aEvent.originalTarget.localName == "treechildren") {
+ // Clear all other selection since we're loading a link now. We must
+ // do this *before* attempting to load the link since openURL uses
+ // selection as an indication of which link to load.
+ tbo.view.selection.select(cell.row);
+ PlacesUIUtils.openNodeWithEvent(aTree.selectedNode, aEvent, aTree);
+ }
+ },
+
+ handleTreeKeyPress: function SU_handleTreeKeyPress(aEvent) {
+ // XXX Bug 627901: Post Fx4, this method should take a tree parameter.
+ let tree = aEvent.target;
+ let node = tree.selectedNode;
+ if (node) {
+ if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN)
+ PlacesUIUtils.openNodeWithEvent(node, aEvent, tree);
+ }
+ },
+
+ /**
+ * The following function displays the URL of a node that is being
+ * hovered over.
+ */
+ handleTreeMouseMove: function SU_handleTreeMouseMove(aEvent) {
+ if (aEvent.target.localName != "treechildren")
+ return;
+
+ var tree = aEvent.target.parentNode;
+ var tbo = tree.treeBoxObject;
+ var cell = tbo.getCellAt(aEvent.clientX, aEvent.clientY);
+
+ // cell.row is -1 when the mouse is hovering an empty area within the tree.
+ // To avoid showing a URL from a previously hovered node for a currently
+ // hovered non-url node, we must clear the moused-over URL in these cases.
+ if (cell.row != -1) {
+ var node = tree.view.nodeForTreeIndex(cell.row);
+ if (PlacesUtils.nodeIsURI(node))
+ this.setMouseoverURL(node.uri);
+ else
+ this.setMouseoverURL("");
+ }
+ else
+ this.setMouseoverURL("");
+ },
+
+ setMouseoverURL: function SU_setMouseoverURL(aURL) {
+ // When the browser window is closed with an open sidebar, the sidebar
+ // unload event happens after the browser's one. In this case
+ // top.XULBrowserWindow has been nullified already.
+ if (top.XULBrowserWindow) {
+ top.XULBrowserWindow.setOverLink(aURL, null);
+ }
+ }
+};
diff --git a/components/places/content/tree.xml b/components/places/content/tree.xml
new file mode 100644
index 0000000..05b0169
--- /dev/null
+++ b/components/places/content/tree.xml
@@ -0,0 +1,789 @@
+<?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="placesTreeBindings"
+ xmlns="http://www.mozilla.org/xbl"
+ xmlns:xbl="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">
+
+ <binding id="places-tree" extends="chrome://global/content/bindings/tree.xml#tree">
+ <implementation>
+ <constructor><![CDATA[
+ // Force an initial build.
+ if (this.place)
+ this.place = this.place;
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ // Break the treeviewer->result->treeviewer cycle.
+ // Note: unsetting the result's viewer also unsets
+ // the viewer's reference to our treeBoxObject.
+ var result = this.result;
+ if (result) {
+ result.root.containerOpen = false;
+ }
+
+ // Unregister the controllber before unlinking the view, otherwise it
+ // may still try to update commands on a view with a null result.
+ if (this._controller) {
+ this._controller.terminate();
+ this.controllers.removeController(this._controller);
+ }
+
+ this.view = null;
+ ]]></destructor>
+
+ <property name="controller"
+ readonly="true"
+ onget="return this._controller"/>
+
+ <!-- overriding -->
+ <property name="view">
+ <getter><![CDATA[
+ try {
+ return this.treeBoxObject.view.wrappedJSObject;
+ }
+ catch(e) {
+ return null;
+ }
+ ]]></getter>
+ <setter><![CDATA[
+ return this.treeBoxObject.view = val;
+ ]]></setter>
+ </property>
+
+ <property name="associatedElement"
+ readonly="true"
+ onget="return this"/>
+
+ <method name="applyFilter">
+ <parameter name="filterString"/>
+ <parameter name="folderRestrict"/>
+ <parameter name="includeHidden"/>
+ <body><![CDATA[
+ // preserve grouping
+ var queryNode = PlacesUtils.asQuery(this.result.root);
+ var options = queryNode.queryOptions.clone();
+
+ // Make sure we're getting uri results.
+ // We do not yet support searching into grouped queries or into
+ // tag containers, so we must fall to the default case.
+ if (PlacesUtils.nodeIsHistoryContainer(queryNode) ||
+ options.resultType == options.RESULTS_AS_TAG_QUERY ||
+ options.resultType == options.RESULTS_AS_TAG_CONTENTS)
+ options.resultType = options.RESULTS_AS_URI;
+
+ var query = PlacesUtils.history.getNewQuery();
+ query.searchTerms = filterString;
+
+ if (folderRestrict) {
+ query.setFolders(folderRestrict, folderRestrict.length);
+ options.queryType = options.QUERY_TYPE_BOOKMARKS;
+ }
+
+ options.includeHidden = !!includeHidden;
+
+ this.load([query], options);
+ ]]></body>
+ </method>
+
+ <method name="load">
+ <parameter name="queries"/>
+ <parameter name="options"/>
+ <body><![CDATA[
+ let result = PlacesUtils.history
+ .executeQueries(queries, queries.length,
+ options);
+ let callback;
+ if (this.flatList) {
+ let onOpenFlatContainer = this.onOpenFlatContainer;
+ if (onOpenFlatContainer)
+ callback = new Function("aContainer", onOpenFlatContainer);
+ }
+
+ if (!this._controller) {
+ this._controller = new PlacesController(this);
+ this.controllers.appendController(this._controller);
+ }
+
+ let treeView = new PlacesTreeView(this.flatList, callback, this._controller);
+
+ // Observer removal is done within the view itself. When the tree
+ // goes away, treeboxobject calls view.setTree(null), which then
+ // calls removeObserver.
+ result.addObserver(treeView, false);
+ this.view = treeView;
+
+ if (this.getAttribute("selectfirstnode") == "true" && treeView.rowCount > 0) {
+ treeView.selection.select(0);
+ }
+
+ this._cachedInsertionPoint = undefined;
+ ]]></body>
+ </method>
+
+ <property name="flatList">
+ <getter><![CDATA[
+ return this.getAttribute("flatList") == "true";
+ ]]></getter>
+ <setter><![CDATA[
+ if (this.flatList != val) {
+ this.setAttribute("flatList", val);
+ // reload with the last place set
+ if (this.place)
+ this.place = this.place;
+ }
+ return val;
+ ]]></setter>
+ </property>
+
+ <property name="onOpenFlatContainer">
+ <getter><![CDATA[
+ return this.getAttribute("onopenflatcontainer");
+ ]]></getter>
+ <setter><![CDATA[
+ if (this.onOpenFlatContainer != val) {
+ this.setAttribute("onopenflatcontainer", val);
+ // reload with the last place set
+ if (this.place)
+ this.place = this.place;
+ }
+ return val;
+ ]]></setter>
+ </property>
+
+ <!--
+ Causes a particular node represented by the specified placeURI to be
+ selected in the tree. All containers above the node in the hierarchy
+ will be opened, so that the node is visible.
+ -->
+ <method name="selectPlaceURI">
+ <parameter name="placeURI"/>
+ <body><![CDATA[
+ // Do nothing if a node matching the given uri is already selected
+ if (this.hasSelection && this.selectedNode.uri == placeURI)
+ return;
+
+ function findNode(container, placeURI, nodesURIChecked) {
+ var containerURI = container.uri;
+ if (containerURI == placeURI)
+ return container;
+ if (nodesURIChecked.indexOf(containerURI) != -1)
+ return null;
+
+ // never check the contents of the same query
+ nodesURIChecked.push(containerURI);
+
+ var wasOpen = container.containerOpen;
+ if (!wasOpen)
+ container.containerOpen = true;
+ for (var i = 0; i < container.childCount; ++i) {
+ var child = container.getChild(i);
+ var childURI = child.uri;
+ if (childURI == placeURI)
+ return child;
+ else if (PlacesUtils.nodeIsContainer(child)) {
+ var nested = findNode(PlacesUtils.asContainer(child), placeURI, nodesURIChecked);
+ if (nested)
+ return nested;
+ }
+ }
+
+ if (!wasOpen)
+ container.containerOpen = false;
+
+ return null;
+ }
+
+ var container = this.result.root;
+ NS_ASSERT(container, "No result, cannot select place URI!");
+ if (!container)
+ return;
+
+ var child = findNode(container, placeURI, []);
+ if (child)
+ this.selectNode(child);
+ else {
+ // If the specified child could not be located, clear the selection
+ var selection = this.view.selection;
+ selection.clearSelection();
+ }
+ ]]></body>
+ </method>
+
+ <!--
+ Causes a particular node to be selected in the tree, resulting in all
+ containers above the node in the hierarchy to be opened, so that the
+ node is visible.
+ -->
+ <method name="selectNode">
+ <parameter name="node"/>
+ <body><![CDATA[
+ var view = this.view;
+
+ var parent = node.parent;
+ if (parent && !parent.containerOpen) {
+ // Build a list of all of the nodes that are the parent of this one
+ // in the result.
+ var parents = [];
+ var root = this.result.root;
+ while (parent && parent != root) {
+ parents.push(parent);
+ parent = parent.parent;
+ }
+
+ // Walk the list backwards (opening from the root of the hierarchy)
+ // opening each folder as we go.
+ for (var i = parents.length - 1; i >= 0; --i) {
+ var index = view.treeIndexForNode(parents[i]);
+ if (index != Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE &&
+ view.isContainer(index) && !view.isContainerOpen(index))
+ view.toggleOpenState(index);
+ }
+ // Select the specified node...
+ }
+
+ var index = view.treeIndexForNode(node);
+ if (index == Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE)
+ return;
+
+ view.selection.select(index);
+ // ... and ensure it's visible, not scrolled off somewhere.
+ this.treeBoxObject.ensureRowIsVisible(index);
+ ]]></body>
+ </method>
+
+ <!-- nsIPlacesView -->
+ <property name="result">
+ <getter><![CDATA[
+ try {
+ return this.view.QueryInterface(Ci.nsINavHistoryResultObserver).result;
+ }
+ catch (e) {
+ return null;
+ }
+ ]]></getter>
+ </property>
+
+ <!-- nsIPlacesView -->
+ <property name="place">
+ <getter><![CDATA[
+ return this.getAttribute("place");
+ ]]></getter>
+ <setter><![CDATA[
+ this.setAttribute("place", val);
+
+ var queriesRef = { };
+ var queryCountRef = { };
+ var optionsRef = { };
+ PlacesUtils.history.queryStringToQueries(val, queriesRef, queryCountRef, optionsRef);
+ if (queryCountRef.value == 0)
+ queriesRef.value = [PlacesUtils.history.getNewQuery()];
+ if (!optionsRef.value)
+ optionsRef.value = PlacesUtils.history.getNewQueryOptions();
+
+ this.load(queriesRef.value, optionsRef.value);
+
+ return val;
+ ]]></setter>
+ </property>
+
+ <!-- nsIPlacesView -->
+ <property name="hasSelection">
+ <getter><![CDATA[
+ return this.view && this.view.selection.count >= 1;
+ ]]></getter>
+ </property>
+
+ <!-- nsIPlacesView -->
+ <property name="selectedNodes">
+ <getter><![CDATA[
+ let nodes = [];
+ if (!this.hasSelection)
+ return nodes;
+
+ let selection = this.view.selection;
+ let rc = selection.getRangeCount();
+ let resultview = this.view;
+ for (let i = 0; i < rc; ++i) {
+ let min = { }, max = { };
+ selection.getRangeAt(i, min, max);
+
+ for (let j = min.value; j <= max.value; ++j)
+ nodes.push(resultview.nodeForTreeIndex(j));
+ }
+ return nodes;
+ ]]></getter>
+ </property>
+
+ <method name="toggleCutNode">
+ <parameter name="aNode"/>
+ <parameter name="aValue"/>
+ <body><![CDATA[
+ this.view.toggleCutNode(aNode, aValue);
+ ]]></body>
+ </method>
+
+ <!-- nsIPlacesView -->
+ <property name="removableSelectionRanges">
+ <getter><![CDATA[
+ // This property exists in addition to selectedNodes because it
+ // encodes selection ranges (which only occur in list views) into
+ // the return value. For each removed range, the index at which items
+ // will be re-inserted upon the remove transaction being performed is
+ // the first index of the range, so that the view updates correctly.
+ //
+ // For example, if we remove rows 2,3,4 and 7,8 from a list, when we
+ // undo that operation, if we insert what was at row 3 at row 3 again,
+ // it will show up _after_ the item that was at row 5. So we need to
+ // insert all items at row 2, and the tree view will update correctly.
+ //
+ // Also, this function collapses the selection to remove redundant
+ // data, e.g. when deleting this selection:
+ //
+ // http://www.foo.com/
+ // (-) Some Folder
+ // http://www.bar.com/
+ //
+ // ... returning http://www.bar.com/ as part of the selection is
+ // redundant because it is implied by removing "Some Folder". We
+ // filter out all such redundancies since some partial amount of
+ // the folder's children may be selected.
+ //
+ let nodes = [];
+ if (!this.hasSelection)
+ return nodes;
+
+ var selection = this.view.selection;
+ var rc = selection.getRangeCount();
+ var resultview = this.view;
+ // This list is kept independently of the range selected (i.e. OUTSIDE
+ // the for loop) since the row index of a container is unique for the
+ // entire view, and we could have some really wacky selection and we
+ // don't want to blow up.
+ var containers = { };
+ for (var i = 0; i < rc; ++i) {
+ var range = [];
+ var min = { }, max = { };
+ selection.getRangeAt(i, min, max);
+
+ for (var j = min.value; j <= max.value; ++j) {
+ if (this.view.isContainer(j))
+ containers[j] = true;
+ if (!(this.view.getParentIndex(j) in containers))
+ range.push(resultview.nodeForTreeIndex(j));
+ }
+ nodes.push(range);
+ }
+ return nodes;
+ ]]></getter>
+ </property>
+
+ <!-- nsIPlacesView -->
+ <property name="draggableSelection"
+ onget="return this.selectedNodes"/>
+
+ <!-- nsIPlacesView -->
+ <property name="selectedNode">
+ <getter><![CDATA[
+ var view = this.view;
+ if (!view || view.selection.count != 1)
+ return null;
+
+ var selection = view.selection;
+ var min = { }, max = { };
+ selection.getRangeAt(0, min, max);
+
+ return this.view.nodeForTreeIndex(min.value);
+ ]]></getter>
+ </property>
+
+ <!-- nsIPlacesView -->
+ <property name="insertionPoint">
+ <getter><![CDATA[
+ // invalidated on selection and focus changes
+ if (this._cachedInsertionPoint !== undefined)
+ return this._cachedInsertionPoint;
+
+ // there is no insertion point for history queries
+ // so bail out now and save a lot of work when updating commands
+ var resultNode = this.result.root;
+ if (PlacesUtils.nodeIsQuery(resultNode) &&
+ PlacesUtils.asQuery(resultNode).queryOptions.queryType ==
+ Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY)
+ return this._cachedInsertionPoint = null;
+
+ var orientation = Ci.nsITreeView.DROP_BEFORE;
+ // If there is no selection, insert at the end of the container.
+ if (!this.hasSelection) {
+ var index = this.view.rowCount - 1;
+ this._cachedInsertionPoint =
+ this._getInsertionPoint(index, orientation);
+ return this._cachedInsertionPoint;
+ }
+
+ // This is a two-part process. The first part is determining the drop
+ // orientation.
+ // * The default orientation is to drop _before_ the selected item.
+ // * If the selected item is a container, the default orientation
+ // is to drop _into_ that container.
+ //
+ // Warning: It may be tempting to use tree indexes in this code, but
+ // you must not, since the tree is nested and as your tree
+ // index may change when folders before you are opened and
+ // closed. You must convert your tree index to a node, and
+ // then use getChildIndex to find your absolute index in
+ // the parent container instead.
+ //
+ var resultView = this.view;
+ var selection = resultView.selection;
+ var rc = selection.getRangeCount();
+ var min = { }, max = { };
+ selection.getRangeAt(rc - 1, min, max);
+
+ // If the sole selection is a container, and we are not in
+ // a flatlist, insert into it.
+ // Note that this only applies to _single_ selections,
+ // if the last element within a multi-selection is a
+ // container, insert _adjacent_ to the selection.
+ //
+ // If the sole selection is the bookmarks toolbar folder, we insert
+ // into it even if it is not opened
+ var itemId =
+ PlacesUtils.getConcreteItemId(resultView.nodeForTreeIndex(max.value));
+ if (selection.count == 1 && resultView.isContainer(max.value) &&
+ !this.flatList)
+ orientation = Ci.nsITreeView.DROP_ON;
+
+ this._cachedInsertionPoint =
+ this._getInsertionPoint(max.value, orientation);
+ return this._cachedInsertionPoint;
+ ]]></getter>
+ </property>
+
+ <method name="_getInsertionPoint">
+ <parameter name="index"/>
+ <parameter name="orientation"/>
+ <body><![CDATA[
+ var result = this.result;
+ var resultview = this.view;
+ var container = result.root;
+ var dropNearItemId = -1;
+ NS_ASSERT(container, "null container");
+ // When there's no selection, assume the container is the container
+ // the view is populated from (i.e. the result's itemId).
+ if (index != -1) {
+ var lastSelected = resultview.nodeForTreeIndex(index);
+ if (resultview.isContainer(index) && orientation == Ci.nsITreeView.DROP_ON) {
+ // If the last selected item is an open container, append _into_
+ // it, rather than insert adjacent to it.
+ container = lastSelected;
+ index = -1;
+ }
+ else if (lastSelected.containerOpen &&
+ orientation == Ci.nsITreeView.DROP_AFTER &&
+ lastSelected.hasChildren) {
+ // If the last selected item is an open container and the user is
+ // trying to drag into it as a first item, really insert into it.
+ container = lastSelected;
+ orientation = Ci.nsITreeView.DROP_ON;
+ index = 0;
+ }
+ else {
+ // Use the last-selected node's container.
+ container = lastSelected.parent;
+
+ // See comment in the treeView.js's copy of this method
+ if (!container || !container.containerOpen)
+ return null;
+
+ // Avoid the potentially expensive call to getChildIndex
+ // if we know this container doesn't allow insertion
+ if (PlacesControllerDragHelper.disallowInsertion(container))
+ return null;
+
+ var queryOptions = PlacesUtils.asQuery(result.root).queryOptions;
+ if (queryOptions.sortingMode !=
+ Ci.nsINavHistoryQueryOptions.SORT_BY_NONE) {
+ // If we are within a sorted view, insert at the end
+ index = -1;
+ }
+ else if (queryOptions.excludeItems ||
+ queryOptions.excludeQueries ||
+ queryOptions.excludeReadOnlyFolders) {
+ // Some item may be invisible, insert near last selected one.
+ // We don't replace index here to avoid requests to the db,
+ // instead it will be calculated later by the controller.
+ index = -1;
+ dropNearItemId = lastSelected.itemId;
+ }
+ else {
+ var lsi = container.getChildIndex(lastSelected);
+ index = orientation == Ci.nsITreeView.DROP_BEFORE ? lsi : lsi + 1;
+ }
+ }
+ }
+
+ if (PlacesControllerDragHelper.disallowInsertion(container))
+ return null;
+
+ return new InsertionPoint(PlacesUtils.getConcreteItemId(container),
+ index, orientation,
+ PlacesUtils.nodeIsTagQuery(container),
+ dropNearItemId);
+ ]]></body>
+ </method>
+
+ <!-- nsIPlacesView -->
+ <method name="selectAll">
+ <body><![CDATA[
+ this.view.selection.selectAll();
+ ]]></body>
+ </method>
+
+ <!-- This method will select the first node in the tree that matches
+ each given item id. It will open any parent nodes that it needs
+ to in order to show the selected items.
+ -->
+ <method name="selectItems">
+ <parameter name="aIDs"/>
+ <parameter name="aOpenContainers"/>
+ <body><![CDATA[
+ // By default, we do search and select within containers which were
+ // closed (note that containers in which nodes were not found are
+ // closed).
+ if (aOpenContainers === undefined)
+ aOpenContainers = true;
+
+ var ids = aIDs; // don't manipulate the caller's array
+
+ // Array of nodes found by findNodes which are to be selected
+ var nodes = [];
+
+ // Array of nodes found by findNodes which should be opened
+ var nodesToOpen = [];
+
+ // A set of URIs of container-nodes that were previously searched,
+ // and thus shouldn't be searched again. This is empty at the initial
+ // start of the recursion and gets filled in as the recursion
+ // progresses.
+ var nodesURIChecked = [];
+
+ /**
+ * Recursively search through a node's children for items
+ * with the given IDs. When a matching item is found, remove its ID
+ * from the IDs array, and add the found node to the nodes dictionary.
+ *
+ * NOTE: This method will leave open any node that had matching items
+ * in its subtree.
+ */
+ function findNodes(node) {
+ var foundOne = false;
+ // See if node matches an ID we wanted; add to results.
+ // For simple folder queries, check both itemId and the concrete
+ // item id.
+ var index = ids.indexOf(node.itemId);
+ if (index == -1 &&
+ node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT)
+ index = ids.indexOf(PlacesUtils.asQuery(node).folderItemId);
+
+ if (index != -1) {
+ nodes.push(node);
+ foundOne = true;
+ ids.splice(index, 1);
+ }
+
+ if (ids.length == 0 || !PlacesUtils.nodeIsContainer(node) ||
+ nodesURIChecked.indexOf(node.uri) != -1)
+ return foundOne;
+
+ PlacesUtils.asContainer(node);
+ if (!aOpenContainers && !node.containerOpen)
+ return foundOne;
+
+ nodesURIChecked.push(node.uri);
+
+ // Remember the beginning state so that we can re-close
+ // this node if we don't find any additional results here.
+ var previousOpenness = node.containerOpen;
+ node.containerOpen = true;
+ for (var child = 0; child < node.childCount && ids.length > 0;
+ child++) {
+ var childNode = node.getChild(child);
+ var found = findNodes(childNode);
+ if (!foundOne)
+ foundOne = found;
+ }
+
+ // If we didn't find any additional matches in this node's
+ // subtree, revert the node to its previous openness.
+ if (foundOne)
+ nodesToOpen.unshift(node);
+ node.containerOpen = previousOpenness;
+ return foundOne;
+ }
+
+ // Disable notifications while looking for nodes.
+ let result = this.result;
+ let didSuppressNotifications = result.suppressNotifications;
+ if (!didSuppressNotifications)
+ result.suppressNotifications = true
+ try {
+ findNodes(this.result.root);
+ }
+ finally {
+ if (!didSuppressNotifications)
+ result.suppressNotifications = false;
+ }
+
+ // For all the nodes we've found, highlight the corresponding
+ // index in the tree.
+ var resultview = this.view;
+ var selection = this.view.selection;
+ selection.selectEventsSuppressed = true;
+ selection.clearSelection();
+ // Open nodes containing found items
+ for (var i = 0; i < nodesToOpen.length; i++) {
+ nodesToOpen[i].containerOpen = true;
+ }
+ for (var i = 0; i < nodes.length; i++) {
+ var index = resultview.treeIndexForNode(nodes[i]);
+ if (index == Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE)
+ continue;
+ selection.rangedSelect(index, index, true);
+ }
+ selection.selectEventsSuppressed = false;
+ ]]></body>
+ </method>
+
+ <field name="_contextMenuShown">false</field>
+
+ <method name="buildContextMenu">
+ <parameter name="aPopup"/>
+ <body><![CDATA[
+ this._contextMenuShown = true;
+ return this.controller.buildContextMenu(aPopup);
+ ]]></body>
+ </method>
+
+ <method name="destroyContextMenu">
+ <parameter name="aPopup"/>
+ this._contextMenuShown = false;
+ <body/>
+ </method>
+
+ <property name="ownerWindow"
+ readonly="true"
+ onget="return window;"/>
+
+ <field name="_active">true</field>
+ <property name="active"
+ onget="return this._active"
+ onset="return this._active = val"/>
+
+ </implementation>
+ <handlers>
+ <handler event="focus"><![CDATA[
+ this._cachedInsertionPoint = undefined;
+
+ // See select handler. We need the sidebar's places commandset to be
+ // updated as well
+ document.commandDispatcher.updateCommands("focus");
+ ]]></handler>
+ <handler event="select"><![CDATA[
+ this._cachedInsertionPoint = undefined;
+
+ // This additional complexity is here for the sidebars
+ var win = window;
+ while (true) {
+ win.document.commandDispatcher.updateCommands("focus");
+ if (win == window.top)
+ break;
+
+ win = win.parent;
+ }
+ ]]></handler>
+
+ <handler event="dragstart"><![CDATA[
+ if (event.target.localName != "treechildren")
+ return;
+
+ let nodes = this.selectedNodes;
+ for (let i = 0; i < nodes.length; i++) {
+ let node = nodes[i];
+
+ // Disallow dragging the root node of a tree.
+ if (!node.parent) {
+ event.preventDefault();
+ event.stopPropagation();
+ return;
+ }
+
+ // If this node is child of a readonly container (e.g. a livemark)
+ // or cannot be moved, we must force a copy.
+ if (!PlacesControllerDragHelper.canMoveNode(node)) {
+ event.dataTransfer.effectAllowed = "copyLink";
+ break;
+ }
+ }
+
+ this._controller.setDataTransfer(event);
+ event.stopPropagation();
+ ]]></handler>
+
+ <handler event="dragover"><![CDATA[
+ if (event.target.localName != "treechildren")
+ return;
+
+ let cell = this.treeBoxObject.getCellAt(event.clientX, event.clientY);
+ let node = cell.row != -1 ?
+ this.view.nodeForTreeIndex(cell.row) :
+ this.result.root;
+ // cache the dropTarget for the view
+ PlacesControllerDragHelper.currentDropTarget = node;
+
+ // We have to calculate the orientation since view.canDrop will use
+ // it and we want to be consistent with the dropfeedback.
+ let tbo = this.treeBoxObject;
+ let rowHeight = tbo.rowHeight;
+ let eventY = event.clientY - tbo.treeBody.boxObject.y -
+ rowHeight * (cell.row - tbo.getFirstVisibleRow());
+
+ let orientation = Ci.nsITreeView.DROP_BEFORE;
+
+ if (cell.row == -1) {
+ // If the row is not valid we try to insert inside the resultNode.
+ orientation = Ci.nsITreeView.DROP_ON;
+ }
+ else if (PlacesUtils.nodeIsContainer(node) &&
+ eventY > rowHeight * 0.75) {
+ // If we are below the 75% of a container the treeview we try
+ // to drop after the node.
+ orientation = Ci.nsITreeView.DROP_AFTER;
+ }
+ else if (PlacesUtils.nodeIsContainer(node) &&
+ eventY > rowHeight * 0.25) {
+ // If we are below the 25% of a container the treeview we try
+ // to drop inside the node.
+ orientation = Ci.nsITreeView.DROP_ON;
+ }
+
+ if (!this.view.canDrop(cell.row, orientation, event.dataTransfer))
+ return;
+
+ event.preventDefault();
+ event.stopPropagation();
+ ]]></handler>
+
+ <handler event="dragend"><![CDATA[
+ PlacesControllerDragHelper.currentDropTarget = null;
+ ]]></handler>
+
+ </handlers>
+ </binding>
+
+</bindings>
diff --git a/components/places/content/treeView.js b/components/places/content/treeView.js
new file mode 100644
index 0000000..aba7314
--- /dev/null
+++ b/components/places/content/treeView.js
@@ -0,0 +1,1770 @@
+/* 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');
+
+const PTV_interfaces = [Ci.nsITreeView,
+ Ci.nsINavHistoryResultObserver,
+ Ci.nsINavHistoryResultTreeViewer,
+ Ci.nsISupportsWeakReference];
+
+function PlacesTreeView(aFlatList, aOnOpenFlatContainer, aController) {
+ this._tree = null;
+ this._result = null;
+ this._selection = null;
+ this._rootNode = null;
+ this._rows = [];
+ this._flatList = aFlatList;
+ this._openContainerCallback = aOnOpenFlatContainer;
+ this._controller = aController;
+}
+
+PlacesTreeView.prototype = {
+ get wrappedJSObject() this,
+
+ __dateService: null,
+ get _dateService() {
+ if (!this.__dateService) {
+ this.__dateService = Cc["@mozilla.org/intl/scriptabledateformat;1"].
+ getService(Ci.nsIScriptableDateFormat);
+ }
+ return this.__dateService;
+ },
+
+ QueryInterface: XPCOMUtils.generateQI(PTV_interfaces),
+
+ // Bug 761494:
+ // ----------
+ // Some addons use methods from nsINavHistoryResultObserver and
+ // nsINavHistoryResultTreeViewer, without QIing to these interfaces first.
+ // That's not a problem when the view is retrieved through the
+ // <tree>.view getter (which returns the wrappedJSObject of this object),
+ // it raises an issue when the view retrieved through the treeBoxObject.view
+ // getter. Thus, to avoid breaking addons, the interfaces are prefetched.
+ classInfo: XPCOMUtils.generateCI({ interfaces: PTV_interfaces }),
+
+ /**
+ * This is called once both the result and the tree are set.
+ */
+ _finishInit: function PTV__finishInit() {
+ let selection = this.selection;
+ if (selection)
+ selection.selectEventsSuppressed = true;
+
+ if (!this._rootNode.containerOpen) {
+ // This triggers containerStateChanged which then builds the visible
+ // section.
+ this._rootNode.containerOpen = true;
+ }
+ else
+ this.invalidateContainer(this._rootNode);
+
+ // "Activate" the sorting column and update commands.
+ this.sortingChanged(this._result.sortingMode);
+
+ if (selection)
+ selection.selectEventsSuppressed = false;
+ },
+
+ /**
+ * Plain Container: container result nodes which may never include sub
+ * hierarchies.
+ *
+ * When the rows array is constructed, we don't set the children of plain
+ * containers. Instead, we keep placeholders for these children. We then
+ * build these children lazily as the tree asks us for information about each
+ * row. Luckily, the tree doesn't ask about rows outside the visible area.
+ *
+ * @see _getNodeForRow and _getRowForNode for the actual magic.
+ *
+ * @note It's guaranteed that all containers are listed in the rows
+ * elements array. It's also guaranteed that separators (if they're not
+ * filtered, see below) are listed in the visible elements array, because
+ * bookmark folders are never built lazily, as described above.
+ *
+ * @param aContainer
+ * A container result node.
+ *
+ * @return true if aContainer is a plain container, false otherwise.
+ */
+ _isPlainContainer: function PTV__isPlainContainer(aContainer) {
+ // Livemarks are always plain containers.
+ if (this._controller.hasCachedLivemarkInfo(aContainer))
+ return true;
+
+ // We don't know enough about non-query containers.
+ if (!(aContainer instanceof Ci.nsINavHistoryQueryResultNode))
+ return false;
+
+ switch (aContainer.queryOptions.resultType) {
+ case Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY:
+ case Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY:
+ case Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY:
+ case Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY:
+ return false;
+ }
+
+ // If it's a folder, it's not a plain container.
+ let nodeType = aContainer.type;
+ return nodeType != Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER &&
+ nodeType != Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT;
+ },
+
+ /**
+ * Gets the row number for a given node. Assumes that the given node is
+ * visible (i.e. it's not an obsolete node).
+ *
+ * @param aNode
+ * A result node. Do not pass an obsolete node, or any
+ * node which isn't supposed to be in the tree (e.g. separators in
+ * sorted trees).
+ * @param [optional] aForceBuild
+ * @see _isPlainContainer.
+ * If true, the row will be computed even if the node still isn't set
+ * in our rows array.
+ * @param [optional] aParentRow
+ * The row of aNode's parent. Ignored for the root node.
+ * @param [optional] aNodeIndex
+ * The index of aNode in its parent. Only used if aParentRow is
+ * set too.
+ *
+ * @throws if aNode is invisible.
+ * @note If aParentRow and aNodeIndex are passed and parent is a plain
+ * container, this method will just return a calculated row value, without
+ * making assumptions on existence of the node at that position.
+ * @return aNode's row if it's in the rows list or if aForceBuild is set, -1
+ * otherwise.
+ */
+ _getRowForNode:
+ function PTV__getRowForNode(aNode, aForceBuild, aParentRow, aNodeIndex) {
+ if (aNode == this._rootNode)
+ throw new Error("The root node is never visible");
+
+ // A node is removed form the view either if it has no parent or if its
+ // root-ancestor is not the root node (in which case that's the node
+ // for which nodeRemoved was called).
+ // Tycho: let ancestors = [x for (x of PlacesUtils.nodeAncestors(aNode))];
+ let ancestors = [];
+ for (let x of PlacesUtils.nodeAncestors(aNode)) {
+ ancestors.push(x);
+ }
+
+ if (ancestors.length == 0 ||
+ ancestors[ancestors.length - 1] != this._rootNode) {
+ throw new Error("Removed node passed to _getRowForNode");
+ }
+
+ // Ensure that the entire chain is open, otherwise that node is invisible.
+ for (let ancestor of ancestors) {
+ if (!ancestor.containerOpen)
+ throw new Error("Invisible node passed to _getRowForNode");
+ }
+
+ // Non-plain containers are initially built with their contents.
+ let parent = aNode.parent;
+ let parentIsPlain = this._isPlainContainer(parent);
+ if (!parentIsPlain) {
+ if (parent == this._rootNode)
+ return this._rows.indexOf(aNode);
+
+ return this._rows.indexOf(aNode, aParentRow);
+ }
+
+ let row = -1;
+ let useNodeIndex = typeof(aNodeIndex) == "number";
+ if (parent == this._rootNode) {
+ if (aNode instanceof Ci.nsINavHistoryResultNode) {
+ row = useNodeIndex ? aNodeIndex : this._rootNode.getChildIndex(aNode);
+ }
+ } else if (useNodeIndex && typeof(aParentRow) == "number") {
+ // If we have both the row of the parent node, and the node's index, we
+ // can avoid searching the rows array if the parent is a plain container.
+ row = aParentRow + aNodeIndex + 1;
+ } else {
+ // Look for the node in the nodes array. Start the search at the parent
+ // row. If the parent row isn't passed, we'll pass undefined to indexOf,
+ // which is fine.
+ row = this._rows.indexOf(aNode, aParentRow);
+ if (row == -1 && aForceBuild) {
+ let parentRow = typeof(aParentRow) == "number" ? aParentRow
+ : this._getRowForNode(parent);
+ row = parentRow + parent.getChildIndex(aNode) + 1;
+ }
+ }
+
+ if (row != -1)
+ this._rows[row] = aNode;
+
+ return row;
+ },
+
+ /**
+ * Given a row, finds and returns the parent details of the associated node.
+ *
+ * @param aChildRow
+ * Row number.
+ * @return [parentNode, parentRow]
+ */
+ _getParentByChildRow: function PTV__getParentByChildRow(aChildRow) {
+ let node = this._getNodeForRow(aChildRow);
+ let parent = (node === null) ? this._rootNode : node.parent;
+
+ // The root node is never visible
+ if (parent == this._rootNode)
+ return [this._rootNode, -1];
+
+ let parentRow = this._rows.lastIndexOf(parent, aChildRow - 1);
+ return [parent, parentRow];
+ },
+
+ /**
+ * Gets the node at a given row.
+ */
+ _getNodeForRow: function PTV__getNodeForRow(aRow) {
+ if (aRow < 0) {
+ return null;
+ }
+
+ let node = this._rows[aRow];
+ if (node !== undefined)
+ return node;
+
+ // Find the nearest node.
+ let rowNode, row;
+ for (let i = aRow - 1; i >= 0 && rowNode === undefined; i--) {
+ rowNode = this._rows[i];
+ row = i;
+ }
+
+ // If there's no container prior to the given row, it's a child of
+ // the root node (remember: all containers are listed in the rows array).
+ if (!rowNode)
+ return this._rows[aRow] = this._rootNode.getChild(aRow);
+
+ // Unset elements may exist only in plain containers. Thus, if the nearest
+ // node is a container, it's the row's parent, otherwise, it's a sibling.
+ if (rowNode instanceof Ci.nsINavHistoryContainerResultNode)
+ return this._rows[aRow] = rowNode.getChild(aRow - row - 1);
+
+ let [parent, parentRow] = this._getParentByChildRow(row);
+ return this._rows[aRow] = parent.getChild(aRow - parentRow - 1);
+ },
+
+ /**
+ * This takes a container and recursively appends our rows array per its
+ * contents. Assumes that the rows arrays has no rows for the given
+ * container.
+ *
+ * @param [in] aContainer
+ * A container result node.
+ * @param [in] aFirstChildRow
+ * The first row at which nodes may be inserted to the row array.
+ * In other words, that's aContainer's row + 1.
+ * @param [out] aToOpen
+ * An array of containers to open once the build is done.
+ *
+ * @return the number of rows which were inserted.
+ */
+ _buildVisibleSection:
+ function PTV__buildVisibleSection(aContainer, aFirstChildRow, aToOpen)
+ {
+ // There's nothing to do if the container is closed.
+ if (!aContainer.containerOpen)
+ return 0;
+
+ // Inserting the new elements into the rows array in one shot (by
+ // Array.concat) is faster than resizing the array (by splice) on each loop
+ // iteration.
+ let cc = aContainer.childCount;
+ let newElements = new Array(cc);
+ this._rows = this._rows.splice(0, aFirstChildRow)
+ .concat(newElements, this._rows);
+
+ if (this._isPlainContainer(aContainer))
+ return cc;
+
+ const openLiteral = PlacesUIUtils.RDF.GetResource("http://home.netscape.com/NC-rdf#open");
+ const trueLiteral = PlacesUIUtils.RDF.GetLiteral("true");
+ let sortingMode = this._result.sortingMode;
+
+ let rowsInserted = 0;
+ for (let i = 0; i < cc; i++) {
+ let curChild = aContainer.getChild(i);
+ let curChildType = curChild.type;
+
+ let row = aFirstChildRow + rowsInserted;
+
+ // Don't display separators when sorted.
+ if (curChildType == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
+ if (sortingMode != Ci.nsINavHistoryQueryOptions.SORT_BY_NONE) {
+ // Remove the element for the filtered separator.
+ // Notice that the rows array was initially resized to include all
+ // children.
+ this._rows.splice(row, 1);
+ continue;
+ }
+ }
+
+ this._rows[row] = curChild;
+ rowsInserted++;
+
+ // Recursively do containers.
+ if (!this._flatList &&
+ curChild instanceof Ci.nsINavHistoryContainerResultNode &&
+ !this._controller.hasCachedLivemarkInfo(curChild)) {
+ let resource = this._getResourceForNode(curChild);
+ let isopen = resource != null &&
+ PlacesUIUtils.localStore.HasAssertion(resource,
+ openLiteral,
+ trueLiteral, true);
+ if (isopen != curChild.containerOpen)
+ aToOpen.push(curChild);
+ else if (curChild.containerOpen && curChild.childCount > 0)
+ rowsInserted += this._buildVisibleSection(curChild, row + 1, aToOpen);
+ }
+ }
+
+ return rowsInserted;
+ },
+
+ /**
+ * This counts how many rows a node takes in the tree. For containers it
+ * will count the node itself plus any child node following it.
+ */
+ _countVisibleRowsForNodeAtRow:
+ function PTV__countVisibleRowsForNodeAtRow(aNodeRow) {
+ let node = this._rows[aNodeRow];
+
+ // If it's not listed yet, we know that it's a leaf node (instanceof also
+ // null-checks).
+ if (!(node instanceof Ci.nsINavHistoryContainerResultNode))
+ return 1;
+
+ let outerLevel = node.indentLevel;
+ for (let i = aNodeRow + 1; i < this._rows.length; i++) {
+ let rowNode = this._rows[i];
+ if (rowNode && rowNode.indentLevel <= outerLevel)
+ return i - aNodeRow;
+ }
+
+ // This node plus its children take up the bottom of the list.
+ return this._rows.length - aNodeRow;
+ },
+
+ _getSelectedNodesInRange:
+ function PTV__getSelectedNodesInRange(aFirstRow, aLastRow) {
+ let selection = this.selection;
+ let rc = selection.getRangeCount();
+ if (rc == 0)
+ return [];
+
+ // The visible-area borders are needed for checking whether a
+ // selected row is also visible.
+ let firstVisibleRow = this._tree.getFirstVisibleRow();
+ let lastVisibleRow = this._tree.getLastVisibleRow();
+
+ let nodesInfo = [];
+ for (let rangeIndex = 0; rangeIndex < rc; rangeIndex++) {
+ let min = { }, max = { };
+ selection.getRangeAt(rangeIndex, min, max);
+
+ // If this range does not overlap the replaced chunk, we don't need to
+ // persist the selection.
+ if (max.value < aFirstRow || min.value > aLastRow)
+ continue;
+
+ let firstRow = Math.max(min.value, aFirstRow);
+ let lastRow = Math.min(max.value, aLastRow);
+ for (let i = firstRow; i <= lastRow; i++) {
+ nodesInfo.push({
+ node: this._rows[i],
+ oldRow: i,
+ wasVisible: i >= firstVisibleRow && i <= lastVisibleRow
+ });
+ }
+ }
+
+ return nodesInfo;
+ },
+
+ /**
+ * Tries to find an equivalent node for a node which was removed. We first
+ * look for the original node, in case it was just relocated. Then, if we
+ * that node was not found, we look for a node that has the same itemId, uri
+ * and time values.
+ *
+ * @param aUpdatedContainer
+ * An ancestor of the node which was removed. It does not have to be
+ * its direct parent.
+ * @param aOldNode
+ * The node which was removed.
+ *
+ * @return the row number of an equivalent node for aOldOne, if one was
+ * found, -1 otherwise.
+ */
+ _getNewRowForRemovedNode:
+ function PTV__getNewRowForRemovedNode(aUpdatedContainer, aOldNode) {
+ if (aOldNode == undefined) {
+ return -1;
+ }
+ let parent = aOldNode.parent;
+ if (parent) {
+ // If the node's parent is still set, the node is not obsolete
+ // and we should just find out its new position.
+ // However, if any of the node's ancestor is closed, the node is
+ // invisible.
+ let ancestors = PlacesUtils.nodeAncestors(aOldNode);
+ for (let ancestor of ancestors) {
+ if (!ancestor.containerOpen)
+ return -1;
+ }
+
+ return this._getRowForNode(aOldNode, true);
+ }
+
+ // There's a broken edge case here.
+ // If a visit appears in two queries, and the second one was
+ // the old node, we'll select the first one after refresh. There's
+ // nothing we could do about that, because aOldNode.parent is
+ // gone by the time invalidateContainer is called.
+ let newNode = aUpdatedContainer.findNodeByDetails(aOldNode.uri,
+ aOldNode.time,
+ aOldNode.itemId,
+ true);
+ if (!newNode)
+ return -1;
+
+ return this._getRowForNode(newNode, true);
+ },
+
+ /**
+ * Restores a given selection state as near as possible to the original
+ * selection state.
+ *
+ * @param aNodesInfo
+ * The persisted selection state as returned by
+ * _getSelectedNodesInRange.
+ * @param aUpdatedContainer
+ * The container which was updated.
+ */
+ _restoreSelection:
+ function PTV__restoreSelection(aNodesInfo, aUpdatedContainer) {
+ if (aNodesInfo.length == 0)
+ return;
+
+ let selection = this.selection;
+
+ // Attempt to ensure that previously-visible selection will be visible
+ // if it's re-selected. However, we can only ensure that for one row.
+ let scrollToRow = -1;
+ for (let i = 0; i < aNodesInfo.length; i++) {
+ let nodeInfo = aNodesInfo[i];
+ let row = this._getNewRowForRemovedNode(aUpdatedContainer,
+ nodeInfo.node);
+ // Select the found node, if any.
+ if (row != -1) {
+ selection.rangedSelect(row, row, true);
+ if (nodeInfo.wasVisible && scrollToRow == -1)
+ scrollToRow = row;
+ }
+ }
+
+ // If only one node was previously selected and there's no selection now,
+ // select the node at its old row, if any.
+ if (aNodesInfo.length == 1 && selection.count == 0) {
+ let row = Math.min(aNodesInfo[0].oldRow, this._rows.length - 1);
+ if (row != -1) {
+ selection.rangedSelect(row, row, true);
+ if (aNodesInfo[0].wasVisible && scrollToRow == -1)
+ scrollToRow = aNodesInfo[0].oldRow;
+ }
+ }
+
+ if (scrollToRow != -1)
+ this._tree.ensureRowIsVisible(scrollToRow);
+ },
+
+ _convertPRTimeToString: function PTV__convertPRTimeToString(aTime) {
+ const MS_PER_MINUTE = 60000;
+ const MS_PER_DAY = 86400000;
+ let timeMs = aTime / 1000; // PRTime is in microseconds
+
+ // Date is calculated starting from midnight, so the modulo with a day are
+ // milliseconds from today's midnight.
+ // getTimezoneOffset corrects that based on local time, notice midnight
+ // can have a different offset during DST-change days.
+ let dateObj = new Date();
+ let now = dateObj.getTime() - dateObj.getTimezoneOffset() * MS_PER_MINUTE;
+ let midnight = now - (now % MS_PER_DAY);
+ midnight += new Date(midnight).getTimezoneOffset() * MS_PER_MINUTE;
+
+ let dateFormat = timeMs >= midnight ?
+ Ci.nsIScriptableDateFormat.dateFormatNone :
+ Ci.nsIScriptableDateFormat.dateFormatShort;
+
+ let timeObj = new Date(timeMs);
+ return (this._dateService.FormatDateTime("", dateFormat,
+ Ci.nsIScriptableDateFormat.timeFormatNoSeconds,
+ timeObj.getFullYear(), timeObj.getMonth() + 1,
+ timeObj.getDate(), timeObj.getHours(),
+ timeObj.getMinutes(), timeObj.getSeconds()));
+ },
+
+ COLUMN_TYPE_UNKNOWN: 0,
+ COLUMN_TYPE_TITLE: 1,
+ COLUMN_TYPE_URI: 2,
+ COLUMN_TYPE_DATE: 3,
+ COLUMN_TYPE_VISITCOUNT: 4,
+ COLUMN_TYPE_KEYWORD: 5,
+ COLUMN_TYPE_DESCRIPTION: 6,
+ COLUMN_TYPE_DATEADDED: 7,
+ COLUMN_TYPE_LASTMODIFIED: 8,
+ COLUMN_TYPE_TAGS: 9,
+ COLUMN_TYPE_PARENTFOLDER: 10,
+ COLUMN_TYPE_PARENTFOLDERPATH: 11,
+
+ _getColumnType: function PTV__getColumnType(aColumn) {
+ let columnType = aColumn.element.getAttribute("anonid") || aColumn.id;
+
+ switch (columnType) {
+ case "title":
+ return this.COLUMN_TYPE_TITLE;
+ case "url":
+ return this.COLUMN_TYPE_URI;
+ case "date":
+ return this.COLUMN_TYPE_DATE;
+ case "visitCount":
+ return this.COLUMN_TYPE_VISITCOUNT;
+ case "keyword":
+ return this.COLUMN_TYPE_KEYWORD;
+ case "description":
+ return this.COLUMN_TYPE_DESCRIPTION;
+ case "dateAdded":
+ return this.COLUMN_TYPE_DATEADDED;
+ case "lastModified":
+ return this.COLUMN_TYPE_LASTMODIFIED;
+ case "tags":
+ return this.COLUMN_TYPE_TAGS;
+ case "parentFolder":
+ return this.COLUMN_TYPE_PARENTFOLDER;
+ case "parentFolderPath":
+ return this.COLUMN_TYPE_PARENTFOLDERPATH;
+ }
+ return this.COLUMN_TYPE_UNKNOWN;
+ },
+
+ _sortTypeToColumnType: function PTV__sortTypeToColumnType(aSortType) {
+ switch (aSortType) {
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_ASCENDING:
+ return [this.COLUMN_TYPE_TITLE, false];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_DESCENDING:
+ return [this.COLUMN_TYPE_TITLE, true];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_ASCENDING:
+ return [this.COLUMN_TYPE_DATE, false];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING:
+ return [this.COLUMN_TYPE_DATE, true];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_URI_ASCENDING:
+ return [this.COLUMN_TYPE_URI, false];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_URI_DESCENDING:
+ return [this.COLUMN_TYPE_URI, true];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_ASCENDING:
+ return [this.COLUMN_TYPE_VISITCOUNT, false];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_DESCENDING:
+ return [this.COLUMN_TYPE_VISITCOUNT, true];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_KEYWORD_ASCENDING:
+ return [this.COLUMN_TYPE_KEYWORD, false];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_KEYWORD_DESCENDING:
+ return [this.COLUMN_TYPE_KEYWORD, true];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_ANNOTATION_ASCENDING:
+ if (this._result.sortingAnnotation == PlacesUIUtils.DESCRIPTION_ANNO)
+ return [this.COLUMN_TYPE_DESCRIPTION, false];
+ break;
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_ANNOTATION_DESCENDING:
+ if (this._result.sortingAnnotation == PlacesUIUtils.DESCRIPTION_ANNO)
+ return [this.COLUMN_TYPE_DESCRIPTION, true];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_ASCENDING:
+ return [this.COLUMN_TYPE_DATEADDED, false];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING:
+ return [this.COLUMN_TYPE_DATEADDED, true];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_LASTMODIFIED_ASCENDING:
+ return [this.COLUMN_TYPE_LASTMODIFIED, false];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_LASTMODIFIED_DESCENDING:
+ return [this.COLUMN_TYPE_LASTMODIFIED, true];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_TAGS_ASCENDING:
+ return [this.COLUMN_TYPE_TAGS, false];
+ case Ci.nsINavHistoryQueryOptions.SORT_BY_TAGS_DESCENDING:
+ return [this.COLUMN_TYPE_TAGS, true];
+ }
+ return [this.COLUMN_TYPE_UNKNOWN, false];
+ },
+
+ // nsINavHistoryResultObserver
+ nodeInserted: function PTV_nodeInserted(aParentNode, aNode, aNewIndex) {
+ NS_ASSERT(this._result, "Got a notification but have no result!");
+ if (!this._tree || !this._result)
+ return;
+
+ // Bail out for hidden separators.
+ if (PlacesUtils.nodeIsSeparator(aNode) && this.isSorted())
+ return;
+
+ let parentRow;
+ if (aParentNode != this._rootNode) {
+ parentRow = this._getRowForNode(aParentNode);
+
+ // Update parent when inserting the first item, since twisty has changed.
+ if (aParentNode.childCount == 1)
+ this._tree.invalidateRow(parentRow);
+ }
+
+ // Compute the new row number of the node.
+ let row = -1;
+ let cc = aParentNode.childCount;
+ if (aNewIndex == 0 || this._isPlainContainer(aParentNode) || cc == 0) {
+ // We don't need to worry about sub hierarchies of the parent node
+ // if it's a plain container, or if the new node is its first child.
+ if (aParentNode == this._rootNode)
+ row = aNewIndex;
+ else
+ row = parentRow + aNewIndex + 1;
+ }
+ else {
+ // Here, we try to find the next visible element in the child list so we
+ // can set the new visible index to be right before that. Note that we
+ // have to search down instead of up, because some siblings could have
+ // children themselves that would be in the way.
+ let separatorsAreHidden = PlacesUtils.nodeIsSeparator(aNode) &&
+ this.isSorted();
+ for (let i = aNewIndex + 1; i < cc; i++) {
+ let node = aParentNode.getChild(i);
+ if (!separatorsAreHidden || PlacesUtils.nodeIsSeparator(node)) {
+ // The children have not been shifted so the next item will have what
+ // should be our index.
+ row = this._getRowForNode(node, false, parentRow, i);
+ break;
+ }
+ }
+ if (row < 0) {
+ // At the end of the child list without finding a visible sibling. This
+ // is a little harder because we don't know how many rows the last item
+ // in our list takes up (it could be a container with many children).
+ let prevChild = aParentNode.getChild(aNewIndex - 1);
+ let prevIndex = this._getRowForNode(prevChild, false, parentRow,
+ aNewIndex - 1);
+ row = prevIndex + this._countVisibleRowsForNodeAtRow(prevIndex);
+ }
+ }
+
+ this._rows.splice(row, 0, aNode);
+ this._tree.rowCountChanged(row, 1);
+
+ if (PlacesUtils.nodeIsContainer(aNode) &&
+ PlacesUtils.asContainer(aNode).containerOpen) {
+ this.invalidateContainer(aNode);
+ }
+ },
+
+ /**
+ * THIS FUNCTION DOES NOT HANDLE cases where a collapsed node is being
+ * removed but the node it is collapsed with is not being removed (this then
+ * just swap out the removee with its collapsing partner). The only time
+ * when we really remove things is when deleting URIs, which will apply to
+ * all collapsees. This function is called sometimes when resorting items.
+ * However, we won't do this when sorted by date because dates will never
+ * change for visits, and date sorting is the only time things are collapsed.
+ */
+ nodeRemoved: function PTV_nodeRemoved(aParentNode, aNode, aOldIndex) {
+ NS_ASSERT(this._result, "Got a notification but have no result!");
+ if (!this._tree || !this._result)
+ return;
+
+ // XXX bug 517701: We don't know what to do when the root node is removed.
+ if (aNode == this._rootNode)
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+
+ // Bail out for hidden separators.
+ if (PlacesUtils.nodeIsSeparator(aNode) && this.isSorted())
+ return;
+
+ let parentRow = aParentNode == this._rootNode ?
+ undefined : this._getRowForNode(aParentNode, true);
+ let oldRow = this._getRowForNode(aNode, true, parentRow, aOldIndex);
+ if (oldRow < 0)
+ throw Cr.NS_ERROR_UNEXPECTED;
+
+ // If the node was exclusively selected, the node next to it will be
+ // selected.
+ let selectNext = false;
+ let selection = this.selection;
+ if (selection.getRangeCount() == 1) {
+ let min = { }, max = { };
+ selection.getRangeAt(0, min, max);
+ if (min.value == max.value &&
+ this.nodeForTreeIndex(min.value) == aNode)
+ selectNext = true;
+ }
+
+ // Remove the node and its children, if any.
+ let count = this._countVisibleRowsForNodeAtRow(oldRow);
+ this._rows.splice(oldRow, count);
+ this._tree.rowCountChanged(oldRow, -count);
+
+ // Redraw the parent if its twisty state has changed.
+ if (aParentNode != this._rootNode && !aParentNode.hasChildren) {
+ let parentRow = oldRow - 1;
+ this._tree.invalidateRow(parentRow);
+ }
+
+ // Restore selection if the node was exclusively selected.
+ if (!selectNext)
+ return;
+
+ // Restore selection.
+ let rowToSelect = Math.min(oldRow, this._rows.length - 1);
+ if (rowToSelect != -1)
+ this.selection.rangedSelect(rowToSelect, rowToSelect, true);
+ },
+
+ nodeMoved:
+ function PTV_nodeMoved(aNode, aOldParent, aOldIndex, aNewParent, aNewIndex) {
+ NS_ASSERT(this._result, "Got a notification but have no result!");
+ if (!this._tree || !this._result)
+ return;
+
+ // Bail out for hidden separators.
+ if (PlacesUtils.nodeIsSeparator(aNode) && this.isSorted())
+ return;
+
+ // Note that at this point the node has already been moved by the backend,
+ // so we must give hints to _getRowForNode to get the old row position.
+ let oldParentRow = aOldParent == this._rootNode ?
+ undefined : this._getRowForNode(aOldParent, true);
+ let oldRow = this._getRowForNode(aNode, true, oldParentRow, aOldIndex);
+ if (oldRow < 0)
+ throw Cr.NS_ERROR_UNEXPECTED;
+
+ // If this node is a container it could take up more than one row.
+ let count = this._countVisibleRowsForNodeAtRow(oldRow);
+
+ // Persist selection state.
+ let nodesToReselect =
+ this._getSelectedNodesInRange(oldRow, oldRow + count);
+ if (nodesToReselect.length > 0)
+ this.selection.selectEventsSuppressed = true;
+
+ // Redraw the parent if its twisty state has changed.
+ if (aOldParent != this._rootNode && !aOldParent.hasChildren) {
+ let parentRow = oldRow - 1;
+ this._tree.invalidateRow(parentRow);
+ }
+
+ // Remove node and its children, if any, from the old position.
+ this._rows.splice(oldRow, count);
+ this._tree.rowCountChanged(oldRow, -count);
+
+ // Insert the node into the new position.
+ this.nodeInserted(aNewParent, aNode, aNewIndex);
+
+ // Restore selection.
+ if (nodesToReselect.length > 0) {
+ this._restoreSelection(nodesToReselect, aNewParent);
+ this.selection.selectEventsSuppressed = false;
+ }
+ },
+
+ _invalidateCellValue: function PTV__invalidateCellValue(aNode,
+ aColumnType) {
+ NS_ASSERT(this._result, "Got a notification but have no result!");
+ if (!this._tree || !this._result)
+ return;
+
+ // Nothing to do for the root node.
+ if (aNode == this._rootNode)
+ return;
+
+ let row = this._getRowForNode(aNode);
+ if (row == -1)
+ return;
+
+ let column = this._findColumnByType(aColumnType);
+ if (column && !column.element.hidden)
+ this._tree.invalidateCell(row, column);
+
+ // Last modified time is altered for almost all node changes.
+ if (aColumnType != this.COLUMN_TYPE_LASTMODIFIED) {
+ let lastModifiedColumn =
+ this._findColumnByType(this.COLUMN_TYPE_LASTMODIFIED);
+ if (lastModifiedColumn && !lastModifiedColumn.hidden)
+ this._tree.invalidateCell(row, lastModifiedColumn);
+ }
+ },
+
+ _populateLivemarkContainer: function PTV__populateLivemarkContainer(aNode) {
+ PlacesUtils.livemarks.getLivemark({ id: aNode.itemId })
+ .then(aLivemark => {
+ let placesNode = aNode;
+ // Need to check containerOpen since getLivemark is async.
+ if (!placesNode.containerOpen)
+ return;
+
+ let children = aLivemark.getNodesForContainer(placesNode);
+ for (let i = 0; i < children.length; i++) {
+ let child = children[i];
+ this.nodeInserted(placesNode, child, i);
+ }
+ }, Components.utils.reportError);
+ },
+
+ nodeTitleChanged: function PTV_nodeTitleChanged(aNode, aNewTitle) {
+ this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE);
+ },
+
+ nodeURIChanged: function PTV_nodeURIChanged(aNode, aNewURI) {
+ this._invalidateCellValue(aNode, this.COLUMN_TYPE_URI);
+ },
+
+ nodeIconChanged: function PTV_nodeIconChanged(aNode) {
+ this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE);
+ },
+
+ nodeHistoryDetailsChanged:
+ function PTV_nodeHistoryDetailsChanged(aNode, aUpdatedVisitDate,
+ aUpdatedVisitCount) {
+ if (aNode.parent && this._controller.hasCachedLivemarkInfo(aNode.parent)) {
+ // Find the node in the parent.
+ let parentRow = this._flatList ? 0 : this._getRowForNode(aNode.parent);
+ for (let i = parentRow; i < this._rows.length; i++) {
+ let child = this.nodeForTreeIndex(i);
+ if (child.uri == aNode.uri) {
+ this._cellProperties.delete(child);
+ this._invalidateCellValue(child, this.COLUMN_TYPE_TITLE);
+ break;
+ }
+ }
+ return;
+ }
+
+ this._invalidateCellValue(aNode, this.COLUMN_TYPE_DATE);
+ this._invalidateCellValue(aNode, this.COLUMN_TYPE_VISITCOUNT);
+ },
+
+ nodeTagsChanged: function PTV_nodeTagsChanged(aNode) {
+ this._invalidateCellValue(aNode, this.COLUMN_TYPE_TAGS);
+ },
+
+ nodeKeywordChanged: function PTV_nodeKeywordChanged(aNode, aNewKeyword) {
+ this._invalidateCellValue(aNode, this.COLUMN_TYPE_KEYWORD);
+ },
+
+ nodeAnnotationChanged: function PTV_nodeAnnotationChanged(aNode, aAnno) {
+ if (aAnno == PlacesUIUtils.DESCRIPTION_ANNO) {
+ this._invalidateCellValue(aNode, this.COLUMN_TYPE_DESCRIPTION);
+ }
+ else if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
+ PlacesUtils.livemarks.getLivemark({ id: aNode.itemId })
+ .then(aLivemark => {
+ this._controller.cacheLivemarkInfo(aNode, aLivemark);
+ let properties = this._cellProperties.get(aNode);
+ this._cellProperties.set(aNode, properties += " livemark");
+ // The livemark attribute is set as a cell property on the title cell.
+ this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE);
+ }, Components.utils.reportError);
+ }
+ },
+
+ nodeDateAddedChanged: function PTV_nodeDateAddedChanged(aNode, aNewValue) {
+ this._invalidateCellValue(aNode, this.COLUMN_TYPE_DATEADDED);
+ },
+
+ nodeLastModifiedChanged:
+ function PTV_nodeLastModifiedChanged(aNode, aNewValue) {
+ this._invalidateCellValue(aNode, this.COLUMN_TYPE_LASTMODIFIED);
+ },
+
+ containerStateChanged:
+ function PTV_containerStateChanged(aNode, aOldState, aNewState) {
+ this.invalidateContainer(aNode);
+
+ if (PlacesUtils.nodeIsFolder(aNode) ||
+ (this._flatList && aNode == this._rootNode)) {
+ let queryOptions = PlacesUtils.asQuery(this._rootNode).queryOptions;
+ if (queryOptions.excludeItems) {
+ return;
+ }
+ if (aNode.itemId != -1) { // run when there's a valid node id
+ PlacesUtils.livemarks.getLivemark({ id: aNode.itemId })
+ .then(aLivemark => {
+ let shouldInvalidate =
+ !this._controller.hasCachedLivemarkInfo(aNode);
+ this._controller.cacheLivemarkInfo(aNode, aLivemark);
+ if (aNewState == Components.interfaces.nsINavHistoryContainerResultNode.STATE_OPENED) {
+ aLivemark.registerForUpdates(aNode, this);
+ // Prioritize the current livemark.
+ aLivemark.reload();
+ PlacesUtils.livemarks.reloadLivemarks();
+ if (shouldInvalidate)
+ this.invalidateContainer(aNode);
+ }
+ else {
+ aLivemark.unregisterForUpdates(aNode);
+ }
+ }, () => undefined);
+ }
+ }
+ },
+
+ invalidateContainer: function PTV_invalidateContainer(aContainer) {
+ NS_ASSERT(this._result, "Need to have a result to update");
+ if (!this._tree)
+ return;
+
+ let startReplacement, replaceCount;
+ if (aContainer == this._rootNode) {
+ startReplacement = 0;
+ replaceCount = this._rows.length;
+
+ // If the root node is now closed, the tree is empty.
+ if (!this._rootNode.containerOpen) {
+ this._rows = [];
+ if (replaceCount)
+ this._tree.rowCountChanged(startReplacement, -replaceCount);
+
+ return;
+ }
+ }
+ else {
+ // Update the twisty state.
+ let row = this._getRowForNode(aContainer);
+ this._tree.invalidateRow(row);
+
+ // We don't replace the container node itself, so we should decrease the
+ // replaceCount by 1.
+ startReplacement = row + 1;
+ replaceCount = this._countVisibleRowsForNodeAtRow(row) - 1;
+ }
+
+ // Persist selection state.
+ let nodesToReselect =
+ this._getSelectedNodesInRange(startReplacement,
+ startReplacement + replaceCount);
+
+ // Now update the number of elements.
+ this.selection.selectEventsSuppressed = true;
+
+ // First remove the old elements
+ this._rows.splice(startReplacement, replaceCount);
+
+ // If the container is now closed, we're done.
+ if (!aContainer.containerOpen) {
+ let oldSelectionCount = this.selection.count;
+ if (replaceCount)
+ this._tree.rowCountChanged(startReplacement, -replaceCount);
+
+ // Select the row next to the closed container if any of its
+ // children were selected, and nothing else is selected.
+ if (nodesToReselect.length > 0 &&
+ nodesToReselect.length == oldSelectionCount) {
+ this.selection.rangedSelect(startReplacement, startReplacement, true);
+ this._tree.ensureRowIsVisible(startReplacement);
+ }
+
+ this.selection.selectEventsSuppressed = false;
+ return;
+ }
+
+ // Otherwise, start a batch first.
+ this._tree.beginUpdateBatch();
+ if (replaceCount)
+ this._tree.rowCountChanged(startReplacement, -replaceCount);
+
+ let toOpenElements = [];
+ let elementsAddedCount = this._buildVisibleSection(aContainer,
+ startReplacement,
+ toOpenElements);
+ if (elementsAddedCount)
+ this._tree.rowCountChanged(startReplacement, elementsAddedCount);
+
+ if (!this._flatList) {
+ // Now, open any containers that were persisted.
+ for (let i = 0; i < toOpenElements.length; i++) {
+ let item = toOpenElements[i];
+ let parent = item.parent;
+
+ // Avoid recursively opening containers.
+ while (parent) {
+ if (parent.uri == item.uri)
+ break;
+ parent = parent.parent;
+ }
+
+ // If we don't have a parent, we made it all the way to the root
+ // and didn't find a match, so we can open our item.
+ if (!parent && !item.containerOpen)
+ item.containerOpen = true;
+ }
+ }
+
+ if (this._controller.hasCachedLivemarkInfo(aContainer)) {
+ let queryOptions = PlacesUtils.asQuery(this._result.root).queryOptions;
+ if (!queryOptions.excludeItems) {
+ this._populateLivemarkContainer(aContainer);
+ }
+ }
+
+ this._tree.endUpdateBatch();
+
+ // Restore selection.
+ this._restoreSelection(nodesToReselect, aContainer);
+ this.selection.selectEventsSuppressed = false;
+ },
+
+ _columns: [],
+ _findColumnByType: function PTV__findColumnByType(aColumnType) {
+ if (this._columns[aColumnType])
+ return this._columns[aColumnType];
+
+ let columns = this._tree.columns;
+ let colCount = columns.count;
+ for (let i = 0; i < colCount; i++) {
+ let column = columns.getColumnAt(i);
+ let columnType = this._getColumnType(column);
+ this._columns[columnType] = column;
+ if (columnType == aColumnType)
+ return column;
+ }
+
+ // That's completely valid. Most of our trees actually include just the
+ // title column.
+ return null;
+ },
+
+ sortingChanged: function PTV__sortingChanged(aSortingMode) {
+ if (!this._tree || !this._result)
+ return;
+
+ // Depending on the sort mode, certain commands may be disabled.
+ window.updateCommands("sort");
+
+ let columns = this._tree.columns;
+
+ // Clear old sorting indicator.
+ let sortedColumn = columns.getSortedColumn();
+ if (sortedColumn)
+ sortedColumn.element.removeAttribute("sortDirection");
+
+ // Set new sorting indicator by looking through all columns for ours.
+ if (aSortingMode == Ci.nsINavHistoryQueryOptions.SORT_BY_NONE)
+ return;
+
+ let [desiredColumn, desiredIsDescending] =
+ this._sortTypeToColumnType(aSortingMode);
+ let colCount = columns.count;
+ let column = this._findColumnByType(desiredColumn);
+ if (column) {
+ let sortDir = desiredIsDescending ? "descending" : "ascending";
+ column.element.setAttribute("sortDirection", sortDir);
+ }
+ },
+
+ _inBatchMode: false,
+ batching: function PTV__batching(aToggleMode) {
+ if (this._inBatchMode != aToggleMode) {
+ this._inBatchMode = this.selection.selectEventsSuppressed = aToggleMode;
+ if (this._inBatchMode) {
+ this._tree.beginUpdateBatch();
+ }
+ else {
+ this._tree.endUpdateBatch();
+ }
+ }
+ },
+
+ get result() this._result,
+ set result(val) {
+ if (this._result) {
+ this._result.removeObserver(this);
+ this._rootNode.containerOpen = false;
+ }
+
+ if (val) {
+ this._result = val;
+ this._rootNode = this._result.root;
+ this._cellProperties = new Map();
+ this._cuttingNodes = new Set();
+ }
+ else if (this._result) {
+ delete this._result;
+ delete this._rootNode;
+ delete this._cellProperties;
+ delete this._cuttingNodes;
+ }
+
+ // If the tree is not set yet, setTree will call finishInit.
+ if (this._tree && val)
+ this._finishInit();
+
+ return val;
+ },
+
+ nodeForTreeIndex: function PTV_nodeForTreeIndex(aIndex) {
+ if (aIndex > this._rows.length)
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ return this._getNodeForRow(aIndex);
+ },
+
+ treeIndexForNode: function PTV_treeNodeForIndex(aNode) {
+ // The API allows passing invisible nodes.
+ try {
+ return this._getRowForNode(aNode, true);
+ }
+ catch(ex) { }
+
+ return Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE;
+ },
+
+ _getResourceForNode: function PTV_getResourceForNode(aNode)
+ {
+ let uri = aNode.uri;
+ NS_ASSERT(uri, "if there is no uri, we can't persist the open state");
+ return uri ? PlacesUIUtils.RDF.GetResource(uri) : null;
+ },
+
+ // nsITreeView
+ get rowCount() this._rows.length,
+ get selection() this._selection,
+ set selection(val) this._selection = val,
+
+ getRowProperties: function() { return ""; },
+
+ getCellProperties:
+ function PTV_getCellProperties(aRow, aColumn) {
+ // for anonid-trees, we need to add the column-type manually
+ var props = "";
+ let columnType = aColumn.element.getAttribute("anonid");
+ if (columnType)
+ props += columnType;
+ else
+ columnType = aColumn.id;
+
+ // Set the "ltr" property on url cells
+ if (columnType == "url")
+ props += " ltr";
+
+ if (columnType != "title")
+ return props;
+
+ let node = this._getNodeForRow(aRow);
+
+ if (this._cuttingNodes.has(node)) {
+ props += " cutting";
+ }
+
+ let properties = this._cellProperties.get(node);
+ if (properties === undefined) {
+ properties = "";
+ let itemId = node.itemId;
+ let nodeType = node.type;
+ if (PlacesUtils.containerTypes.indexOf(nodeType) != -1) {
+ if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY) {
+ properties += " query";
+ if (PlacesUtils.nodeIsTagQuery(node))
+ properties += " tagContainer";
+ else if (PlacesUtils.nodeIsDay(node))
+ properties += " dayContainer";
+ else if (PlacesUtils.nodeIsHost(node))
+ properties += " hostContainer";
+ }
+ else if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER ||
+ nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT) {
+ if (this._controller.hasCachedLivemarkInfo(node)) {
+ properties += " livemark";
+ }
+ else {
+ PlacesUtils.livemarks.getLivemark({ id: node.itemId })
+ .then(aLivemark => {
+ this._controller.cacheLivemarkInfo(node, aLivemark);
+ let props = this._cellProperties.get(node);
+ this._cellProperties.set(node, props += " livemark");
+ // The livemark attribute is set as a cell property on the title cell.
+ this._invalidateCellValue(node, this.COLUMN_TYPE_TITLE);
+ }, () => undefined);
+ }
+ }
+
+ if (itemId != -1) {
+ let queryName = PlacesUIUtils.getLeftPaneQueryNameFromId(itemId);
+ if (queryName)
+ properties += " OrganizerQuery_" + queryName;
+ }
+ }
+ else if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR)
+ properties += " separator";
+ else if (PlacesUtils.nodeIsURI(node)) {
+ properties += " " + PlacesUIUtils.guessUrlSchemeForUI(node.uri);
+
+ if (this._controller.hasCachedLivemarkInfo(node.parent)) {
+ properties += " livemarkItem";
+ if (node.accessCount) {
+ properties += " visited";
+ }
+ }
+ }
+
+ this._cellProperties.set(node, properties);
+ }
+
+ return props + " " + properties;
+ },
+
+ getColumnProperties: function(aColumn) { return ""; },
+
+ isContainer: function PTV_isContainer(aRow) {
+ // Only leaf nodes aren't listed in the rows array.
+ let node = this._rows[aRow];
+ if (node === undefined)
+ return false;
+
+ if (PlacesUtils.nodeIsContainer(node)) {
+ // Flat-lists may ignore expandQueries and other query options when
+ // they are asked to open a container.
+ if (this._flatList)
+ return true;
+
+ // treat non-expandable childless queries as non-containers
+ if (PlacesUtils.nodeIsQuery(node)) {
+ let parent = node.parent;
+ if ((PlacesUtils.nodeIsQuery(parent) ||
+ PlacesUtils.nodeIsFolder(parent)) &&
+ !PlacesUtils.asQuery(node).hasChildren)
+ return PlacesUtils.asQuery(parent).queryOptions.expandQueries;
+ }
+ return true;
+ }
+ return false;
+ },
+
+ isContainerOpen: function PTV_isContainerOpen(aRow) {
+ if (this._flatList)
+ return false;
+
+ // All containers are listed in the rows array.
+ return this._rows[aRow].containerOpen;
+ },
+
+ isContainerEmpty: function PTV_isContainerEmpty(aRow) {
+ if (this._flatList)
+ return true;
+
+ let node = this._rows[aRow];
+ if (this._controller.hasCachedLivemarkInfo(node)) {
+ let queryOptions = PlacesUtils.asQuery(this._result.root).queryOptions;
+ return queryOptions.excludeItems;
+ }
+
+ // All containers are listed in the rows array.
+ return !node.hasChildren;
+ },
+
+ isSeparator: function PTV_isSeparator(aRow) {
+ // All separators are listed in the rows array.
+ let node = this._rows[aRow];
+ return node && PlacesUtils.nodeIsSeparator(node);
+ },
+
+ isSorted: function PTV_isSorted() {
+ return this._result.sortingMode !=
+ Ci.nsINavHistoryQueryOptions.SORT_BY_NONE;
+ },
+
+ canDrop: function PTV_canDrop(aRow, aOrientation, aDataTransfer) {
+ if (!this._result)
+ throw Cr.NS_ERROR_UNEXPECTED;
+
+ // Drop position into a sorted treeview would be wrong.
+ if (this.isSorted())
+ return false;
+
+ let ip = this._getInsertionPoint(aRow, aOrientation);
+ return ip && PlacesControllerDragHelper.canDrop(ip, aDataTransfer);
+ },
+
+ _getInsertionPoint: function PTV__getInsertionPoint(index, orientation) {
+ let container = this._result.root;
+ let dropNearItemId = -1;
+ // When there's no selection, assume the container is the container
+ // the view is populated from (i.e. the result's itemId).
+ if (index != -1) {
+ let lastSelected = this.nodeForTreeIndex(index);
+ if (this.isContainer(index) && orientation == Ci.nsITreeView.DROP_ON) {
+ // If the last selected item is an open container, append _into_
+ // it, rather than insert adjacent to it.
+ container = lastSelected;
+ index = -1;
+ }
+ else if (lastSelected.containerOpen &&
+ orientation == Ci.nsITreeView.DROP_AFTER &&
+ lastSelected.hasChildren) {
+ // If the last selected node is an open container and the user is
+ // trying to drag into it as a first node, really insert into it.
+ container = lastSelected;
+ orientation = Ci.nsITreeView.DROP_ON;
+ index = 0;
+ }
+ else {
+ // Use the last-selected node's container.
+ container = lastSelected.parent;
+
+ // During its Drag & Drop operation, the tree code closes-and-opens
+ // containers very often (part of the XUL "spring-loaded folders"
+ // implementation). And in certain cases, we may reach a closed
+ // container here. However, we can simply bail out when this happens,
+ // because we would then be back here in less than a millisecond, when
+ // the container had been reopened.
+ if (!container || !container.containerOpen)
+ return null;
+
+ // Avoid the potentially expensive call to getChildIndex
+ // if we know this container doesn't allow insertion.
+ if (PlacesControllerDragHelper.disallowInsertion(container))
+ return null;
+
+ let queryOptions = PlacesUtils.asQuery(this._result.root).queryOptions;
+ if (queryOptions.sortingMode !=
+ Ci.nsINavHistoryQueryOptions.SORT_BY_NONE) {
+ // If we are within a sorted view, insert at the end.
+ index = -1;
+ }
+ else if (queryOptions.excludeItems ||
+ queryOptions.excludeQueries ||
+ queryOptions.excludeReadOnlyFolders) {
+ // Some item may be invisible, insert near last selected one.
+ // We don't replace index here to avoid requests to the db,
+ // instead it will be calculated later by the controller.
+ index = -1;
+ dropNearItemId = lastSelected.itemId;
+ }
+ else {
+ let lsi = container.getChildIndex(lastSelected);
+ index = orientation == Ci.nsITreeView.DROP_BEFORE ? lsi : lsi + 1;
+ }
+ }
+ }
+
+ if (PlacesControllerDragHelper.disallowInsertion(container))
+ return null;
+
+ return new InsertionPoint(PlacesUtils.getConcreteItemId(container),
+ index, orientation,
+ PlacesUtils.nodeIsTagQuery(container),
+ dropNearItemId);
+ },
+
+ drop: function PTV_drop(aRow, aOrientation, aDataTransfer) {
+ // We are responsible for translating the |index| and |orientation|
+ // parameters into a container id and index within the container,
+ // since this information is specific to the tree view.
+ let ip = this._getInsertionPoint(aRow, aOrientation);
+ if (ip)
+ PlacesControllerDragHelper.onDrop(ip, aDataTransfer);
+
+ PlacesControllerDragHelper.currentDropTarget = null;
+ },
+
+ getParentIndex: function PTV_getParentIndex(aRow) {
+ let [parentNode, parentRow] = this._getParentByChildRow(aRow);
+ return parentRow;
+ },
+
+ hasNextSibling: function PTV_hasNextSibling(aRow, aAfterIndex) {
+ if (aRow == this._rows.length - 1) {
+ // The last row has no sibling.
+ return false;
+ }
+
+ let node = this._rows[aRow];
+ if (node === undefined || this._isPlainContainer(node.parent)) {
+ // The node is a child of a plain container.
+ // If the next row is either unset or has the same parent,
+ // it's a sibling.
+ let nextNode = this._rows[aRow + 1];
+ return (nextNode == undefined || nextNode.parent == node.parent);
+ }
+
+ let thisLevel = node.indentLevel;
+ for (let i = aAfterIndex + 1; i < this._rows.length; ++i) {
+ let rowNode = this._getNodeForRow(i);
+ let nextLevel = rowNode.indentLevel;
+ if (nextLevel == thisLevel)
+ return true;
+ if (nextLevel < thisLevel)
+ break;
+ }
+
+ return false;
+ },
+
+ getLevel: function(aRow) this._getNodeForRow(aRow).indentLevel,
+
+ getImageSrc: function PTV_getImageSrc(aRow, aColumn) {
+ // Only the title column has an image.
+ if (this._getColumnType(aColumn) != this.COLUMN_TYPE_TITLE)
+ return "";
+
+ return this._getNodeForRow(aRow).icon;
+ },
+
+ getProgressMode: function(aRow, aColumn) { },
+ getCellValue: function(aRow, aColumn) { },
+
+ getCellText: function PTV_getCellText(aRow, aColumn) {
+ let node = this._getNodeForRow(aRow);
+ switch (this._getColumnType(aColumn)) {
+ case this.COLUMN_TYPE_TITLE:
+ // normally, this is just the title, but we don't want empty items in
+ // the tree view so return a special string if the title is empty.
+ // Do it here so that callers can still get at the 0 length title
+ // if they go through the "result" API.
+ if (PlacesUtils.nodeIsSeparator(node))
+ return "";
+ return PlacesUIUtils.getBestTitle(node, true);
+ case this.COLUMN_TYPE_TAGS:
+ return node.tags;
+ case this.COLUMN_TYPE_URI:
+ if (PlacesUtils.nodeIsURI(node))
+ return node.uri;
+ return "";
+ case this.COLUMN_TYPE_DATE:
+ let nodeTime = node.time;
+ if (nodeTime == 0 || !PlacesUtils.nodeIsURI(node)) {
+ // hosts and days shouldn't have a value for the date column.
+ // Actually, you could argue this point, but looking at the
+ // results, seeing the most recently visited date is not what
+ // I expect, and gives me no information I know how to use.
+ // Only show this for URI-based items.
+ return "";
+ }
+
+ return this._convertPRTimeToString(nodeTime);
+ case this.COLUMN_TYPE_VISITCOUNT:
+ return node.accessCount;
+ case this.COLUMN_TYPE_KEYWORD:
+ if (PlacesUtils.nodeIsBookmark(node))
+ return PlacesUtils.bookmarks.getKeywordForBookmark(node.itemId);
+ return "";
+ case this.COLUMN_TYPE_DESCRIPTION:
+ if (node.itemId != -1) {
+ try {
+ return PlacesUtils.annotations.
+ getItemAnnotation(node.itemId, PlacesUIUtils.DESCRIPTION_ANNO);
+ }
+ catch (ex) { /* has no description */ }
+ }
+ return "";
+ case this.COLUMN_TYPE_DATEADDED:
+ if (node.dateAdded)
+ return this._convertPRTimeToString(node.dateAdded);
+ return "";
+ case this.COLUMN_TYPE_LASTMODIFIED:
+ if (node.lastModified)
+ return this._convertPRTimeToString(node.lastModified);
+ return "";
+ case this.COLUMN_TYPE_PARENTFOLDER:
+ if (PlacesUtils.nodeIsQuery(node.parent) &&
+ PlacesUtils.asQuery(node.parent).queryOptions.queryType ==
+ Components.interfaces.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY && node.uri)
+ return "";
+ var bmsvc = Components.classes["@mozilla.org/browser/nav-bookmarks-service;1"].
+ getService(Components.interfaces.nsINavBookmarksService);
+ var rowId = node.itemId;
+ try {
+ var parentFolderId = bmsvc.getFolderIdForItem(rowId);
+ var folderTitle = bmsvc.getItemTitle(parentFolderId);
+ } catch(ex) {
+ var folderTitle = "";
+ }
+ return folderTitle;
+ case this.COLUMN_TYPE_PARENTFOLDERPATH:
+ if (PlacesUtils.nodeIsQuery(node.parent) &&
+ PlacesUtils.asQuery(node.parent).queryOptions.queryType ==
+ Components.interfaces.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY && node.uri)
+ return "";
+ var bmsvc = Components.classes["@mozilla.org/browser/nav-bookmarks-service;1"].
+ getService(Components.interfaces.nsINavBookmarksService);
+ var rowId = node.itemId;
+ try {
+ var FolderId;
+ var parentFolderId = bmsvc.getFolderIdForItem(rowId);
+ var folderTitle = bmsvc.getItemTitle(parentFolderId);
+ while ((FolderId = bmsvc.getFolderIdForItem(parentFolderId))) {
+ if (FolderId == parentFolderId)
+ break;
+ parentFolderId = FolderId;
+ var text = bmsvc.getItemTitle(parentFolderId);
+ if (!text)
+ break;
+ folderTitle = text + " /"+ folderTitle;
+ }
+ folderTitle = folderTitle.replace(/^\s/,"");
+ } catch(ex) {
+ var folderTitle = "";
+ }
+ return folderTitle;
+ }
+ return "";
+ },
+
+ setTree: function PTV_setTree(aTree) {
+ // If we are replacing the tree during a batch, there is a concrete risk
+ // that the treeView goes out of sync, thus it's safer to end the batch now.
+ // This is a no-op if we are not batching.
+ this.batching(false);
+
+ let hasOldTree = this._tree != null;
+ this._tree = aTree;
+
+ if (this._result) {
+ if (hasOldTree) {
+ // detach from result when we are detaching from the tree.
+ // This breaks the reference cycle between us and the result.
+ if (!aTree) {
+ this._result.removeObserver(this);
+ this._rootNode.containerOpen = false;
+ }
+ }
+ if (aTree)
+ this._finishInit();
+ }
+ },
+
+ toggleOpenState: function PTV_toggleOpenState(aRow) {
+ if (!this._result)
+ throw Cr.NS_ERROR_UNEXPECTED;
+
+ let node = this._rows[aRow];
+ if (this._flatList && this._openContainerCallback) {
+ this._openContainerCallback(node);
+ return;
+ }
+
+ // Persist containers open status, but never persist livemarks.
+ if (!this._controller.hasCachedLivemarkInfo(node)) {
+ let resource = this._getResourceForNode(node);
+ if (resource) {
+ const openLiteral = PlacesUIUtils.RDF.GetResource("http://home.netscape.com/NC-rdf#open");
+ const trueLiteral = PlacesUIUtils.RDF.GetLiteral("true");
+
+ if (node.containerOpen)
+ PlacesUIUtils.localStore.Unassert(resource, openLiteral, trueLiteral);
+ else
+ PlacesUIUtils.localStore.Assert(resource, openLiteral, trueLiteral, true);
+ }
+ }
+
+ node.containerOpen = !node.containerOpen;
+ },
+
+ cycleHeader: function PTV_cycleHeader(aColumn) {
+ if (!this._result)
+ throw Cr.NS_ERROR_UNEXPECTED;
+
+ // Sometimes you want a tri-state sorting, and sometimes you don't. This
+ // rule allows tri-state sorting when the root node is a folder. This will
+ // catch the most common cases. When you are looking at folders, you want
+ // the third state to reset the sorting to the natural bookmark order. When
+ // you are looking at history, that third state has no meaning so we try
+ // to disallow it.
+ //
+ // The problem occurs when you have a query that results in bookmark
+ // folders. One example of this is the subscriptions view. In these cases,
+ // this rule doesn't allow you to sort those sub-folders by their natural
+ // order.
+ let allowTriState = PlacesUtils.nodeIsFolder(this._result.root);
+
+ let oldSort = this._result.sortingMode;
+ let oldSortingAnnotation = this._result.sortingAnnotation;
+ let newSort;
+ let newSortingAnnotation = "";
+ const NHQO = Ci.nsINavHistoryQueryOptions;
+ switch (this._getColumnType(aColumn)) {
+ case this.COLUMN_TYPE_TITLE:
+ if (oldSort == NHQO.SORT_BY_TITLE_ASCENDING)
+ newSort = NHQO.SORT_BY_TITLE_DESCENDING;
+ else if (allowTriState && oldSort == NHQO.SORT_BY_TITLE_DESCENDING)
+ newSort = NHQO.SORT_BY_NONE;
+ else
+ newSort = NHQO.SORT_BY_TITLE_ASCENDING;
+
+ break;
+ case this.COLUMN_TYPE_URI:
+ if (oldSort == NHQO.SORT_BY_URI_ASCENDING)
+ newSort = NHQO.SORT_BY_URI_DESCENDING;
+ else if (allowTriState && oldSort == NHQO.SORT_BY_URI_DESCENDING)
+ newSort = NHQO.SORT_BY_NONE;
+ else
+ newSort = NHQO.SORT_BY_URI_ASCENDING;
+
+ break;
+ case this.COLUMN_TYPE_DATE:
+ if (oldSort == NHQO.SORT_BY_DATE_ASCENDING)
+ newSort = NHQO.SORT_BY_DATE_DESCENDING;
+ else if (allowTriState &&
+ oldSort == NHQO.SORT_BY_DATE_DESCENDING)
+ newSort = NHQO.SORT_BY_NONE;
+ else
+ newSort = NHQO.SORT_BY_DATE_ASCENDING;
+
+ break;
+ case this.COLUMN_TYPE_VISITCOUNT:
+ // visit count default is unusual because we sort by descending
+ // by default because you are most likely to be looking for
+ // highly visited sites when you click it
+ if (oldSort == NHQO.SORT_BY_VISITCOUNT_DESCENDING)
+ newSort = NHQO.SORT_BY_VISITCOUNT_ASCENDING;
+ else if (allowTriState && oldSort == NHQO.SORT_BY_VISITCOUNT_ASCENDING)
+ newSort = NHQO.SORT_BY_NONE;
+ else
+ newSort = NHQO.SORT_BY_VISITCOUNT_DESCENDING;
+
+ break;
+ case this.COLUMN_TYPE_KEYWORD:
+ if (oldSort == NHQO.SORT_BY_KEYWORD_ASCENDING)
+ newSort = NHQO.SORT_BY_KEYWORD_DESCENDING;
+ else if (allowTriState && oldSort == NHQO.SORT_BY_KEYWORD_DESCENDING)
+ newSort = NHQO.SORT_BY_NONE;
+ else
+ newSort = NHQO.SORT_BY_KEYWORD_ASCENDING;
+
+ break;
+ case this.COLUMN_TYPE_DESCRIPTION:
+ if (oldSort == NHQO.SORT_BY_ANNOTATION_ASCENDING &&
+ oldSortingAnnotation == PlacesUIUtils.DESCRIPTION_ANNO) {
+ newSort = NHQO.SORT_BY_ANNOTATION_DESCENDING;
+ newSortingAnnotation = PlacesUIUtils.DESCRIPTION_ANNO;
+ }
+ else if (allowTriState &&
+ oldSort == NHQO.SORT_BY_ANNOTATION_DESCENDING &&
+ oldSortingAnnotation == PlacesUIUtils.DESCRIPTION_ANNO)
+ newSort = NHQO.SORT_BY_NONE;
+ else {
+ newSort = NHQO.SORT_BY_ANNOTATION_ASCENDING;
+ newSortingAnnotation = PlacesUIUtils.DESCRIPTION_ANNO;
+ }
+
+ break;
+ case this.COLUMN_TYPE_DATEADDED:
+ if (oldSort == NHQO.SORT_BY_DATEADDED_ASCENDING)
+ newSort = NHQO.SORT_BY_DATEADDED_DESCENDING;
+ else if (allowTriState &&
+ oldSort == NHQO.SORT_BY_DATEADDED_DESCENDING)
+ newSort = NHQO.SORT_BY_NONE;
+ else
+ newSort = NHQO.SORT_BY_DATEADDED_ASCENDING;
+
+ break;
+ case this.COLUMN_TYPE_LASTMODIFIED:
+ if (oldSort == NHQO.SORT_BY_LASTMODIFIED_ASCENDING)
+ newSort = NHQO.SORT_BY_LASTMODIFIED_DESCENDING;
+ else if (allowTriState &&
+ oldSort == NHQO.SORT_BY_LASTMODIFIED_DESCENDING)
+ newSort = NHQO.SORT_BY_NONE;
+ else
+ newSort = NHQO.SORT_BY_LASTMODIFIED_ASCENDING;
+
+ break;
+ case this.COLUMN_TYPE_TAGS:
+ if (oldSort == NHQO.SORT_BY_TAGS_ASCENDING)
+ newSort = NHQO.SORT_BY_TAGS_DESCENDING;
+ else if (allowTriState && oldSort == NHQO.SORT_BY_TAGS_DESCENDING)
+ newSort = NHQO.SORT_BY_NONE;
+ else
+ newSort = NHQO.SORT_BY_TAGS_ASCENDING;
+
+ break;
+ case this.COLUMN_TYPE_PARENTFOLDER:
+ return;
+
+ break;
+ case this.COLUMN_TYPE_PARENTFOLDERPATH:
+ return;
+
+ break;
+ default:
+ throw Cr.NS_ERROR_INVALID_ARG;
+ }
+ this._result.sortingAnnotation = newSortingAnnotation;
+ this._result.sortingMode = newSort;
+ },
+
+ isEditable: function PTV_isEditable(aRow, aColumn) {
+ // At this point we only support editing the title field.
+ if (aColumn.index != 0)
+ return false;
+
+ let node = this._rows[aRow];
+ if (!node) {
+ Cu.reportError("isEditable called for an unbuilt row.");
+ return false;
+ }
+ let itemId = node.itemId;
+
+ // Only bookmark-nodes are editable. Fortunately, this check also takes
+ // care of livemark children.
+ if (itemId == -1)
+ return false;
+
+ // The following items are also not editable, even though they are bookmark
+ // items.
+ // * places-roots
+ // * the left pane special folders and queries (those are place: uri
+ // bookmarks)
+ // * separators
+ //
+ // Note that concrete itemIds aren't used intentionally. For example, we
+ // have no reason to disallow renaming a shortcut to the Bookmarks Toolbar,
+ // except for the one under All Bookmarks.
+ if (PlacesUtils.nodeIsSeparator(node) || PlacesUtils.isRootItem(itemId))
+ return false;
+
+ let parentId = PlacesUtils.getConcreteItemId(node.parent);
+ if (parentId == PlacesUIUtils.leftPaneFolderId ||
+ parentId == PlacesUIUtils.allBookmarksFolderId) {
+ // Note that the for the time being this is the check that actually
+ // blocks renaming places "roots", and not the isRootItem check above.
+ // That's because places root are only exposed through folder shortcuts
+ // descendants of the left pane folder.
+ return false;
+ }
+
+ return true;
+ },
+
+ setCellText: function PTV_setCellText(aRow, aColumn, aText) {
+ // We may only get here if the cell is editable.
+ let node = this._rows[aRow];
+ if (node.title != aText) {
+ let txn = new PlacesEditItemTitleTransaction(node.itemId, aText);
+ PlacesUtils.transactionManager.doTransaction(txn);
+ }
+ },
+
+ toggleCutNode: function PTV_toggleCutNode(aNode, aValue) {
+ let currentVal = this._cuttingNodes.has(aNode);
+ if (currentVal != aValue) {
+ if (aValue)
+ this._cuttingNodes.add(aNode);
+ else
+ this._cuttingNodes.delete(aNode);
+
+ this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE);
+ }
+ },
+
+ selectionChanged: function() { },
+ cycleCell: function(aRow, aColumn) { },
+ isSelectable: function(aRow, aColumn) { return false; },
+ performAction: function(aAction) { },
+ performActionOnRow: function(aAction, aRow) { },
+ performActionOnCell: function(aAction, aRow, aColumn) { }
+};
diff --git a/components/places/jar.mn b/components/places/jar.mn
new file mode 100644
index 0000000..41222e1
--- /dev/null
+++ b/components/places/jar.mn
@@ -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/.
+
+browser.jar:
+% overlay chrome://browser/content/places/places.xul chrome://browser/content/places/downloadsViewOverlay.xul
+# Provide another URI for the bookmarkProperties dialog so we can persist the
+# attributes separately
+ content/browser/places/bookmarkProperties2.xul (content/bookmarkProperties.xul)
+* content/browser/places/places.xul (content/places.xul)
+* content/browser/places/places.js (content/places.js)
+ content/browser/places/places.css (content/places.css)
+ content/browser/places/organizer.css (content/organizer.css)
+ content/browser/places/bookmarkProperties.xul (content/bookmarkProperties.xul)
+ content/browser/places/bookmarkProperties.js (content/bookmarkProperties.js)
+ content/browser/places/placesOverlay.xul (content/placesOverlay.xul)
+* content/browser/places/menu.xml (content/menu.xml)
+ content/browser/places/tree.xml (content/tree.xml)
+ content/browser/places/controller.js (content/controller.js)
+ content/browser/places/treeView.js (content/treeView.js)
+* content/browser/places/browserPlacesViews.js (content/browserPlacesViews.js)
+# keep the Places version of the history sidebar at history/history-panel.xul
+# to prevent having to worry about between versions of the browser
+* content/browser/history/history-panel.xul (content/history-panel.xul)
+ content/browser/places/history-panel.js (content/history-panel.js)
+# ditto for the bookmarks sidebar
+ content/browser/bookmarks/bookmarksPanel.xul (content/bookmarksPanel.xul)
+ content/browser/bookmarks/bookmarksPanel.js (content/bookmarksPanel.js)
+* content/browser/bookmarks/sidebarUtils.js (content/sidebarUtils.js)
+ content/browser/places/moveBookmarks.xul (content/moveBookmarks.xul)
+ content/browser/places/moveBookmarks.js (content/moveBookmarks.js)
+ content/browser/places/editBookmarkOverlay.xul (content/editBookmarkOverlay.xul)
+ content/browser/places/editBookmarkOverlay.js (content/editBookmarkOverlay.js)
+* content/browser/places/downloadsViewOverlay.xul (content/downloadsViewOverlay.xul)
diff --git a/components/places/moz.build b/components/places/moz.build
new file mode 100644
index 0000000..f8b0d12
--- /dev/null
+++ b/components/places/moz.build
@@ -0,0 +1,9 @@
+# -*- 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/.
+
+JAR_MANIFESTS += ['jar.mn']
+
+EXTRA_JS_MODULES += [ 'PlacesUIUtils.jsm' ]
diff --git a/components/preferences/advanced.js b/components/preferences/advanced.js
new file mode 100644
index 0000000..aab58b3
--- /dev/null
+++ b/components/preferences/advanced.js
@@ -0,0 +1,755 @@
+# -*- 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/.
+
+// Load DownloadUtils module for convertByteUnits
+Components.utils.import("resource://gre/modules/DownloadUtils.jsm");
+Components.utils.import("resource://gre/modules/ctypes.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/LoadContextInfo.jsm");
+Components.utils.import("resource://gre/modules/BrowserUtils.jsm");
+
+var gAdvancedPane = {
+ _inited: false,
+
+ /**
+ * Brings the appropriate tab to the front and initializes various bits of UI.
+ */
+ init: function ()
+ {
+ this._inited = true;
+ var advancedPrefs = document.getElementById("advancedPrefs");
+
+ var extraArgs = window.arguments[1];
+ if (extraArgs && extraArgs["advancedTab"]){
+ advancedPrefs.selectedTab = document.getElementById(extraArgs["advancedTab"]);
+ } else {
+ var preference = document.getElementById("browser.preferences.advanced.selectedTabIndex");
+ if (preference.value !== null)
+ advancedPrefs.selectedIndex = preference.value;
+ }
+
+#ifdef HAVE_SHELL_SERVICE
+ this.updateSetDefaultBrowser();
+#ifdef XP_WIN
+ // In Windows 8 we launch the control panel since it's the only
+ // way to get all file type association prefs. So we don't know
+ // when the user will select the default. We refresh here periodically
+ // in case the default changes. On other Windows OS's defaults can also
+ // be set while the prefs are open.
+ window.setInterval(this.updateSetDefaultBrowser, 1000);
+#endif
+#endif
+
+#ifdef MOZ_UPDATER
+ this.updateReadPrefs();
+#endif
+ this.updateOfflineAppsPermissions();
+ this.updateOfflineApps();
+
+ this.updateActualCacheSize();
+ this.updateActualAppCacheSize();
+
+ // Notify observers that the UI is now ready
+ Services.obs.notifyObservers(window, "advanced-pane-loaded", null);
+ },
+
+ /**
+ * Stores the identity of the current tab in preferences so that the selected
+ * tab can be persisted between openings of the preferences window.
+ */
+ tabSelectionChanged: function ()
+ {
+ if (!this._inited)
+ return;
+ var advancedPrefs = document.getElementById("advancedPrefs");
+ var preference = document.getElementById("browser.preferences.advanced.selectedTabIndex");
+ preference.valueFromPreferences = advancedPrefs.selectedIndex;
+ },
+
+ // GENERAL TAB
+
+ /*
+ * Preferences:
+ *
+ * accessibility.browsewithcaret
+ * - true enables keyboard navigation and selection within web pages using a
+ * visible caret, false uses normal keyboard navigation with no caret
+ * accessibility.typeaheadfind
+ * - when set to true, typing outside text areas and input boxes will
+ * automatically start searching for what's typed within the current
+ * document; when set to false, no search action happens
+ * general.autoScroll
+ * - when set to true, clicking the scroll wheel on the mouse activates a
+ * mouse mode where moving the mouse down scrolls the document downward with
+ * speed correlated with the distance of the cursor from the original
+ * position at which the click occurred (and likewise with movement upward);
+ * if false, this behavior is disabled
+ * general.smoothScroll
+ * - set to true to enable finer page scrolling than line-by-line on page-up,
+ * page-down, and other such page movements
+ * layout.spellcheckDefault
+ * - an integer:
+ * 0 disables spellchecking
+ * 1 enables spellchecking, but only for multiline text fields
+ * 2 enables spellchecking for all text fields
+ */
+
+ /**
+ * Stores the original value of the spellchecking preference to enable proper
+ * restoration if unchanged (since we're mapping a tristate onto a checkbox).
+ */
+ _storedSpellCheck: 0,
+
+ /**
+ * Returns true if any spellchecking is enabled and false otherwise, caching
+ * the current value to enable proper pref restoration if the checkbox is
+ * never changed.
+ */
+ readCheckSpelling: function ()
+ {
+ var pref = document.getElementById("layout.spellcheckDefault");
+ this._storedSpellCheck = pref.value;
+
+ return (pref.value != 0);
+ },
+
+ /**
+ * Returns the value of the spellchecking preference represented by UI,
+ * preserving the preference's "hidden" value if the preference is
+ * unchanged and represents a value not strictly allowed in UI.
+ */
+ writeCheckSpelling: function ()
+ {
+ var checkbox = document.getElementById("checkSpelling");
+ return checkbox.checked ? (this._storedSpellCheck == 2 ? 2 : 1) : 0;
+ },
+
+ /**
+ * security.OCSP.enabled is an integer value for legacy reasons.
+ * A value of 1 means OCSP is enabled. Any other value means it is disabled.
+ */
+ readEnableOCSP: function ()
+ {
+ var preference = document.getElementById("security.OCSP.enabled");
+ // This is the case if the preference is the default value.
+ if (preference.value === undefined) {
+ return true;
+ }
+ return preference.value == 1;
+ },
+
+ /**
+ * See documentation for readEnableOCSP.
+ */
+ writeEnableOCSP: function ()
+ {
+ var checkbox = document.getElementById("enableOCSP");
+ return checkbox.checked ? 1 : 0;
+ },
+
+ /**
+ * When the user toggles the layers.acceleration.disabled pref,
+ * sync its new value to the gfx.direct2d.disabled pref too.
+ */
+ updateHardwareAcceleration: function()
+ {
+#ifdef XP_WIN
+ var fromPref = document.getElementById("layers.acceleration.disabled");
+ var toPref = document.getElementById("gfx.direct2d.disabled");
+ toPref.value = fromPref.value;
+#endif
+ },
+
+ // DATA CHOICES TAB
+
+ /**
+ * opening links behind a modal dialog is poor form. Work around flawed text-link handling here.
+ */
+ openTextLink: function (evt) {
+ let where = Services.prefs.getBoolPref("browser.preferences.instantApply") ? "tab" : "window";
+ openUILinkIn(evt.target.getAttribute("href"), where);
+ evt.preventDefault();
+ },
+
+ /**
+ * Set up or hide the Learn More links for various data collection options
+ */
+ _setupLearnMoreLink: function (pref, element) {
+ // set up the Learn More link with the correct URL
+ let url = Services.prefs.getCharPref(pref);
+ let el = document.getElementById(element);
+
+ if (url) {
+ el.setAttribute("href", url);
+ } else {
+ el.setAttribute("hidden", "true");
+ }
+ },
+
+ // NETWORK TAB
+
+ /*
+ * Preferences:
+ *
+ * browser.cache.disk.capacity
+ * - the size of the browser cache in KB
+ * - Only used if browser.cache.disk.smart_size.enabled is disabled
+ */
+
+ /**
+ * Displays a dialog in which proxy settings may be changed.
+ */
+ showConnections: function ()
+ {
+ document.documentElement.openSubDialog("chrome://browser/content/preferences/connection.xul",
+ "", null);
+ },
+
+ // Retrieves the amount of space currently used by disk cache
+ updateActualCacheSize: function ()
+ {
+ var sum = 0;
+ function updateUI(consumption) {
+ var actualSizeLabel = document.getElementById("actualDiskCacheSize");
+ var sizeStrings = DownloadUtils.convertByteUnits(consumption);
+ var prefStrBundle = document.getElementById("bundlePreferences");
+ var sizeStr = prefStrBundle.getFormattedString("actualDiskCacheSize", sizeStrings);
+ actualSizeLabel.value = sizeStr;
+ }
+
+ Visitor.prototype = {
+ expected: 0,
+ sum: 0,
+ QueryInterface: function listener_qi(iid) {
+ if (iid.equals(Components.interfaces.nsISupports) ||
+ iid.equals(Components.interfaces.nsICacheStorageVisitor)) {
+ return this;
+ }
+ throw Components.results.NS_ERROR_NO_INTERFACE;
+ },
+ onCacheStorageInfo: function(num, consumption)
+ {
+ this.sum += consumption;
+ if (!--this.expected)
+ updateUI(this.sum);
+ }
+ };
+ function Visitor(callbacksExpected) {
+ this.expected = callbacksExpected;
+ }
+
+ var cacheService =
+ Components.classes["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Components.interfaces.nsICacheStorageService);
+ // non-anonymous
+ var storage1 = cacheService.diskCacheStorage(LoadContextInfo.default, false);
+ // anonymous
+ var storage2 = cacheService.diskCacheStorage(LoadContextInfo.anonymous, false);
+
+ // expect 2 callbacks
+ var visitor = new Visitor(2);
+ storage1.asyncVisitStorage(visitor, false /* Do not walk entries */);
+ storage2.asyncVisitStorage(visitor, false /* Do not walk entries */);
+ },
+
+ // Retrieves the amount of space currently used by offline cache
+ updateActualAppCacheSize: function ()
+ {
+ var visitor = {
+ onCacheStorageInfo: function (aEntryCount, aConsumption, aCapacity, aDiskDirectory)
+ {
+ var actualSizeLabel = document.getElementById("actualAppCacheSize");
+ var sizeStrings = DownloadUtils.convertByteUnits(aConsumption);
+ var prefStrBundle = document.getElementById("bundlePreferences");
+ var sizeStr = prefStrBundle.getFormattedString("actualAppCacheSize", sizeStrings);
+ actualSizeLabel.value = sizeStr;
+ }
+ };
+
+ var cacheService =
+ Components.classes["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Components.interfaces.nsICacheStorageService);
+ var storage = cacheService.appCacheStorage(LoadContextInfo.default, null);
+ storage.asyncVisitStorage(visitor, false);
+ },
+
+ updateCacheSizeUI: function (smartSizeEnabled)
+ {
+ document.getElementById("useCacheBefore").disabled = smartSizeEnabled;
+ document.getElementById("cacheSize").disabled = smartSizeEnabled;
+ document.getElementById("useCacheAfter").disabled = smartSizeEnabled;
+ },
+
+ readSmartSizeEnabled: function ()
+ {
+ // The smart_size.enabled preference element is inverted="true", so its
+ // value is the opposite of the actual pref value
+ var disabled = document.getElementById("browser.cache.disk.smart_size.enabled").value;
+ this.updateCacheSizeUI(!disabled);
+ },
+
+ /**
+ * Converts the cache size from units of KB to units of MB and returns that
+ * value.
+ */
+ readCacheSize: function ()
+ {
+ var preference = document.getElementById("browser.cache.disk.capacity");
+ return preference.value / 1024;
+ },
+
+ /**
+ * Converts the cache size as specified in UI (in MB) to KB and returns that
+ * value.
+ */
+ writeCacheSize: function ()
+ {
+ var cacheSize = document.getElementById("cacheSize");
+ var intValue = parseInt(cacheSize.value, 10);
+ return isNaN(intValue) ? 0 : intValue * 1024;
+ },
+
+ /**
+ * Clears the cache.
+ */
+ clearCache: function ()
+ {
+ var cache = Components.classes["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Components.interfaces.nsICacheStorageService);
+ try {
+ cache.clear();
+ } catch(ex) {}
+ this.updateActualCacheSize();
+ },
+
+ /**
+ * Clears the application cache.
+ */
+ clearOfflineAppCache: function ()
+ {
+ Components.utils.import("resource:///modules/offlineAppCache.jsm");
+ OfflineAppCacheHelper.clear();
+
+ this.updateActualAppCacheSize();
+ this.updateOfflineApps();
+ },
+
+ updateOfflineAppsPermissions: function()
+ {
+ var permPref = document.getElementById("offline-apps.permissions");
+ var allowPref = document.getElementById("offline-apps.allow_by_default");
+ var notifyPref = document.getElementById("browser.offline-apps.notify");
+ switch (permPref.value) {
+ case 0: allowPref.value = false;
+ notifyPref.value = false;
+ break;
+ case 1: allowPref.value = false;
+ notifyPref.value = true;
+ break;
+ case 2: allowPref.value = true;
+ notifyPref.value = true;
+ break;
+ default: console.error("Preference error: Invalid value ",permPref.value," for offline app permissions - resetting to default.");
+ permPref.value = 2;
+ allowPref.value = true;
+ notifyPref.value = true;
+ }
+ // Set state of "Exceptions" button accordingly.
+ var button = document.getElementById("offlineNotifyExceptions");
+ button.disabled = !allowPref.value && !notifyPref.value;
+ },
+
+ showOfflineExceptions: function()
+ {
+ var bundlePreferences = document.getElementById("bundlePreferences");
+ var params = { blockVisible : false,
+ sessionVisible : false,
+ allowVisible : false,
+ prefilledHost : "",
+ permissionType : "offline-app",
+ manageCapability : Components.interfaces.nsIPermissionManager.DENY_ACTION,
+ windowTitle : bundlePreferences.getString("offlinepermissionstitle"),
+ introText : bundlePreferences.getString("offlinepermissionstext") };
+ document.documentElement.openWindow("Browser:Permissions",
+ "chrome://browser/content/preferences/permissions.xul",
+ "", params);
+ },
+
+ // XXX: duplicated in browser.js
+ _getOfflineAppUsage: function (perm, groups)
+ {
+ var cacheService = Components.classes["@mozilla.org/network/application-cache-service;1"].
+ getService(Components.interfaces.nsIApplicationCacheService);
+ if (!groups)
+ groups = cacheService.getGroups();
+
+ var ios = Components.classes["@mozilla.org/network/io-service;1"].
+ getService(Components.interfaces.nsIIOService);
+
+ var usage = 0;
+ for (var i = 0; i < groups.length; i++) {
+ var uri = ios.newURI(groups[i], null, null);
+ if (perm.matchesURI(uri, true)) {
+ var cache = cacheService.getActiveCache(groups[i]);
+ usage += cache.usage;
+ }
+ }
+
+ return usage;
+ },
+
+ /**
+ * Updates the list of offline applications
+ */
+ updateOfflineApps: function ()
+ {
+ var pm = Components.classes["@mozilla.org/permissionmanager;1"]
+ .getService(Components.interfaces.nsIPermissionManager);
+
+ var list = document.getElementById("offlineAppsList");
+ while (list.firstChild) {
+ list.removeChild(list.firstChild);
+ }
+
+ var cacheService = Components.classes["@mozilla.org/network/application-cache-service;1"].
+ getService(Components.interfaces.nsIApplicationCacheService);
+ var groups = cacheService.getGroups();
+
+ var bundle = document.getElementById("bundlePreferences");
+
+ var enumerator = pm.enumerator;
+ while (enumerator.hasMoreElements()) {
+ var perm = enumerator.getNext().QueryInterface(Components.interfaces.nsIPermission);
+ if (perm.type == "offline-app" &&
+ perm.capability != Components.interfaces.nsIPermissionManager.DEFAULT_ACTION &&
+ perm.capability != Components.interfaces.nsIPermissionManager.DENY_ACTION) {
+ var row = document.createElement("listitem");
+ row.id = "";
+ row.className = "offlineapp";
+ row.setAttribute("origin", perm.principal.origin);
+ var converted = DownloadUtils.
+ convertByteUnits(this._getOfflineAppUsage(perm, groups));
+ row.setAttribute("usage",
+ bundle.getFormattedString("offlineAppUsage",
+ converted));
+ list.appendChild(row);
+ }
+ }
+ },
+
+ offlineAppSelected: function()
+ {
+ var removeButton = document.getElementById("offlineAppsListRemove");
+ var list = document.getElementById("offlineAppsList");
+ if (list.selectedItem) {
+ removeButton.setAttribute("disabled", "false");
+ } else {
+ removeButton.setAttribute("disabled", "true");
+ }
+ },
+
+ removeOfflineApp: function()
+ {
+ var list = document.getElementById("offlineAppsList");
+ var item = list.selectedItem;
+ var origin = item.getAttribute("origin");
+ var principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin(origin);
+
+ var prompts = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
+ .getService(Components.interfaces.nsIPromptService);
+ var flags = prompts.BUTTON_TITLE_IS_STRING * prompts.BUTTON_POS_0 +
+ prompts.BUTTON_TITLE_CANCEL * prompts.BUTTON_POS_1;
+
+ var bundle = document.getElementById("bundlePreferences");
+ var title = bundle.getString("offlineAppRemoveTitle");
+ var prompt = bundle.getFormattedString("offlineAppRemovePrompt", [principal.URI.prePath]);
+ var confirm = bundle.getString("offlineAppRemoveConfirm");
+ var result = prompts.confirmEx(window, title, prompt, flags, confirm,
+ null, null, null, {});
+ if (result != 0)
+ return;
+
+ // get the permission
+ var pm = Components.classes["@mozilla.org/permissionmanager;1"]
+ .getService(Components.interfaces.nsIPermissionManager);
+ var perm = pm.getPermissionObject(principal, "offline-app", true);
+ if (perm) {
+ // clear offline cache entries
+ try {
+ var cacheService = Components.classes["@mozilla.org/network/application-cache-service;1"].
+ getService(Components.interfaces.nsIApplicationCacheService);
+ var groups = cacheService.getGroups();
+ for (var i = 0; i < groups.length; i++) {
+ var uri = Services.io.newURI(groups[i], null, null);
+ if (perm.matchesURI(uri, true)) {
+ var cache = cacheService.getActiveCache(groups[i]);
+ cache.discard();
+ }
+ }
+ } catch (e) {}
+
+ pm.removePermission(perm);
+ }
+ list.removeChild(item);
+ gAdvancedPane.offlineAppSelected();
+ this.updateActualAppCacheSize();
+ },
+
+ // UPDATE TAB
+
+ /*
+ * Preferences:
+ *
+ * app.update.enabled
+ * - true if updates to the application are enabled, false otherwise
+ * extensions.update.enabled
+ * - true if updates to extensions and themes are enabled, false otherwise
+ * browser.search.update
+ * - true if updates to search engines are enabled, false otherwise
+ * app.update.auto
+ * - true if updates should be automatically downloaded and installed,
+ * possibly with a warning if incompatible extensions are installed (see
+ * app.update.mode); false if the user should be asked what he wants to do
+ * when an update is available
+ * app.update.mode
+ * - an integer:
+ * 0 do not warn if an update will disable extensions or themes
+ * 1 warn if an update will disable extensions or themes
+ * 2 warn if an update will disable extensions or themes *or* if the
+ * update is a major update
+ */
+
+#ifdef MOZ_UPDATER
+ /**
+ * Selects the item of the radiogroup, and sets the warnIncompatible checkbox
+ * based on the pref values and locked states.
+ *
+ * UI state matrix for update preference conditions
+ *
+ * UI Components: Preferences
+ * Radiogroup i = app.update.enabled
+ * Warn before disabling extensions checkbox ii = app.update.auto
+ * iii = app.update.mode
+ *
+ * Disabled states:
+ * Element pref value locked disabled
+ * radiogroup i t/f f false
+ * i t/f *t* *true*
+ * ii t/f f false
+ * ii t/f *t* *true*
+ * iii 0/1/2 t/f false
+ * warnIncompatible i t f false
+ * i t *t* *true*
+ * i *f* t/f *true*
+ * ii t f false
+ * ii t *t* *true*
+ * ii *f* t/f *true*
+ * iii 0/1/2 f false
+ * iii 0/1/2 *t* *true*
+ */
+ updateReadPrefs: function ()
+ {
+ var enabledPref = document.getElementById("app.update.enabled");
+ var autoPref = document.getElementById("app.update.auto");
+ var radiogroup = document.getElementById("updateRadioGroup");
+
+ if (!enabledPref.value) // Don't care for autoPref.value in this case.
+ radiogroup.value="manual"; // 3. Never check for updates.
+ else if (autoPref.value) // enabledPref.value && autoPref.value
+ radiogroup.value="auto"; // 1. Automatically install updates for Desktop only
+ else // enabledPref.value && !autoPref.value
+ radiogroup.value="checkOnly"; // 2. Check, but let me choose
+
+ var canCheck = Components.classes["@mozilla.org/updates/update-service;1"].
+ getService(Components.interfaces.nsIApplicationUpdateService).
+ canCheckForUpdates;
+ // canCheck is false if the enabledPref is false and locked,
+ // or the binary platform or OS version is not known.
+ // A locked pref is sufficient to disable the radiogroup.
+ radiogroup.disabled = !canCheck || enabledPref.locked || autoPref.locked;
+
+ var modePref = document.getElementById("app.update.mode");
+ var warnIncompatible = document.getElementById("warnIncompatible");
+ // the warnIncompatible checkbox value is set by readAddonWarn
+ warnIncompatible.disabled = radiogroup.disabled || modePref.locked ||
+ !enabledPref.value || !autoPref.value;
+ },
+
+ /**
+ * Sets the pref values based on the selected item of the radiogroup,
+ * and sets the disabled state of the warnIncompatible checkbox accordingly.
+ */
+ updateWritePrefs: function ()
+ {
+ var enabledPref = document.getElementById("app.update.enabled");
+ var autoPref = document.getElementById("app.update.auto");
+ var radiogroup = document.getElementById("updateRadioGroup");
+ switch (radiogroup.value) {
+ case "auto": // 1. Automatically install updates for Desktop only
+ enabledPref.value = true;
+ autoPref.value = true;
+ break;
+ case "checkOnly": // 2. Check, but let me choose
+ enabledPref.value = true;
+ autoPref.value = false;
+ break;
+ case "manual": // 3. Never check for updates.
+ enabledPref.value = false;
+ autoPref.value = false;
+ }
+
+ var warnIncompatible = document.getElementById("warnIncompatible");
+ var modePref = document.getElementById("app.update.mode");
+ warnIncompatible.disabled = enabledPref.locked || !enabledPref.value ||
+ autoPref.locked || !autoPref.value ||
+ modePref.locked;
+
+ },
+
+ /**
+ * Stores the value of the app.update.mode preference, which is a tristate
+ * integer preference. We store the value here so that we can properly
+ * restore the preference value if the UI reflecting the preference value
+ * is in a state which can represent either of two integer values (as
+ * opposed to only one possible value in the other UI state).
+ */
+ _modePreference: -1,
+
+ /**
+ * Reads the app.update.mode preference and converts its value into a
+ * true/false value for use in determining whether the "Warn me if this will
+ * disable extensions or themes" checkbox is checked. We also save the value
+ * of the preference so that the preference value can be properly restored if
+ * the user's preferences cannot adequately be expressed by a single checkbox.
+ *
+ * app.update.mode Checkbox State Meaning
+ * 0 Unchecked Do not warn
+ * 1 Checked Warn if there are incompatibilities
+ * 2 Checked Warn if there are incompatibilities,
+ * or the update is major.
+ */
+ readAddonWarn: function ()
+ {
+ var preference = document.getElementById("app.update.mode");
+ var warn = preference.value != 0;
+ gAdvancedPane._modePreference = warn ? preference.value : 1;
+ return warn;
+ },
+
+ /**
+ * Converts the state of the "Warn me if this will disable extensions or
+ * themes" checkbox into the integer preference which represents it,
+ * returning that value.
+ */
+ writeAddonWarn: function ()
+ {
+ var warnIncompatible = document.getElementById("warnIncompatible");
+ return !warnIncompatible.checked ? 0 : gAdvancedPane._modePreference;
+ },
+
+ /**
+ * Displays the history of installed updates.
+ */
+ showUpdates: function ()
+ {
+ var prompter = Components.classes["@mozilla.org/updates/update-prompt;1"]
+ .createInstance(Components.interfaces.nsIUpdatePrompt);
+ prompter.showUpdateHistory(window);
+ },
+#endif
+
+ // CERTIFICATES TAB
+
+ /*
+ * Preferences:
+ *
+ * security.default_personal_cert
+ * - a string:
+ * "Select Automatically" select a certificate automatically when a site
+ * requests one
+ * "Ask Every Time" present a dialog to the user so he can select
+ * the certificate to use on a site which
+ * requests one
+ */
+
+ /**
+ * Displays the user's certificates and associated options.
+ */
+ showCertificates: function ()
+ {
+ document.documentElement.openWindow("mozilla:certmanager",
+ "chrome://pippki/content/certManager.xul",
+ "", null);
+ },
+
+ /**
+ * Displays a dialog from which the user can manage his security devices.
+ */
+ showSecurityDevices: function ()
+ {
+ document.documentElement.openWindow("mozilla:devicemanager",
+ "chrome://pippki/content/device_manager.xul",
+ "", null);
+ }
+#ifdef HAVE_SHELL_SERVICE
+ ,
+
+ // SYSTEM DEFAULTS
+
+ /*
+ * Preferences:
+ *
+ * browser.shell.checkDefault
+ * - true if a default-browser check (and prompt to make it so if necessary)
+ * occurs at startup, false otherwise
+ */
+
+ /**
+ * Show button for setting browser as default browser or information that
+ * browser is already the default browser.
+ */
+ updateSetDefaultBrowser: function()
+ {
+ let shellSvc = getShellService();
+ let setDefaultPane = document.getElementById("setDefaultPane");
+ if (!shellSvc) {
+ setDefaultPane.hidden = true;
+ document.getElementById("alwaysCheckDefault").disabled = true;
+ return;
+ }
+ let selectedIndex =
+ shellSvc.isDefaultBrowser(false, true) ? 1 : 0;
+ setDefaultPane.selectedIndex = selectedIndex;
+ },
+
+ /**
+ * Set browser as the operating system default browser.
+ */
+ setDefaultBrowser: function()
+ {
+ let shellSvc = getShellService();
+ if (!shellSvc)
+ return;
+ try {
+ let claimAllTypes = true;
+#ifdef XP_WIN
+ // In Windows 8+, the UI for selecting default protocol is much
+ // nicer than the UI for setting file type associations. So we
+ // only show the protocol association screen on Windows 8+.
+ // Windows 8 is version 6.2.
+ let version = Services.sysinfo.getProperty("version");
+ claimAllTypes = (parseFloat(version) < 6.2);
+#endif
+ shellSvc.setDefaultBrowser(claimAllTypes, false);
+ } catch (ex) {
+ Cu.reportError(ex);
+ return;
+ }
+ let selectedIndex =
+ shellSvc.isDefaultBrowser(false, true) ? 1 : 0;
+ document.getElementById("setDefaultPane").selectedIndex = selectedIndex;
+ }
+#endif
+};
diff --git a/components/preferences/advanced.xul b/components/preferences/advanced.xul
new file mode 100644
index 0000000..e5f3bb1
--- /dev/null
+++ b/components/preferences/advanced.xul
@@ -0,0 +1,465 @@
+<?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/.
+
+<!DOCTYPE overlay [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % advancedDTD SYSTEM "chrome://browser/locale/preferences/advanced.dtd">
+%advancedDTD;
+<!ENTITY % privacyDTD SYSTEM "chrome://browser/locale/preferences/privacy.dtd">
+%privacyDTD;
+]>
+
+<overlay id="AdvancedPaneOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <prefpane id="paneAdvanced" onpaneload="gAdvancedPane.init();">
+
+ <preferences id="advancedPreferences">
+ <preference id="browser.preferences.advanced.selectedTabIndex"
+ name="browser.preferences.advanced.selectedTabIndex"
+ type="int"/>
+
+ <!--XXX button prefs -->
+
+ <!-- General tab -->
+ <preference id="accessibility.typeaheadfind" name="accessibility.typeaheadfind" type="bool"/>
+
+ <preference id="general.autoScroll" name="general.autoScroll" type="bool"/>
+ <preference id="general.smoothScroll" name="general.smoothScroll" type="bool"/>
+ <preference id="layers.acceleration.disabled" name="layers.acceleration.disabled" type="bool" inverted="true"
+ onchange="gAdvancedPane.updateHardwareAcceleration()"/>
+#ifdef XP_WIN
+ <preference id="gfx.direct2d.disabled" name="gfx.direct2d.disabled" type="bool" inverted="true"/>
+#endif
+ <preference id="layout.spellcheckDefault" name="layout.spellcheckDefault" type="int"/>
+
+#ifdef HAVE_SHELL_SERVICE
+ <preference id="browser.shell.checkDefaultBrowser"
+ name="browser.shell.checkDefaultBrowser"
+ type="bool"/>
+
+ <preference id="pref.general.disable_button.default_browser"
+ name="pref.general.disable_button.default_browser"
+ type="bool"/>
+#endif
+ <preference id="pref.general.compatmode" name="general.useragent.compatMode" type="int"/>
+
+ <preference id="pref.general.captiveportal" name="network.captive-portal-service.enabled" type="bool"/>
+
+ <!-- Network tab -->
+ <preference id="browser.cache.disk.capacity" name="browser.cache.disk.capacity" type="int"/>
+
+ <preference id="browser.cache.disk.smart_size.enabled"
+ name="browser.cache.disk.smart_size.enabled"
+ inverted="true"
+ type="bool"/>
+
+ <preference id="offline-apps.permissions" name="offline-apps.permissions" type="int"
+ onchange="gAdvancedPane.updateOfflineAppsPermissions()"/>
+ <preference id="browser.offline-apps.notify" name="browser.offline-apps.notify" type="bool"/>
+ <preference id="offline-apps.allow_by_default" name="offline-apps.allow_by_default" type="bool"/>
+
+ <!-- Update tab -->
+#ifdef MOZ_UPDATER
+ <preference id="app.update.enabled" name="app.update.enabled" type="bool"/>
+ <preference id="app.update.auto" name="app.update.auto" type="bool"/>
+ <preference id="app.update.mode" name="app.update.mode" type="int"/>
+
+ <preference id="app.update.disable_button.showUpdateHistory"
+ name="app.update.disable_button.showUpdateHistory"
+ type="bool"/>
+#endif
+
+ <preference id="browser.search.update" name="browser.search.update" type="bool"/>
+
+ <!-- Certificates tab -->
+ <preference id="security.default_personal_cert" name="security.default_personal_cert" type="string"/>
+
+ <preference id="security.disable_button.openCertManager"
+ name="security.disable_button.openCertManager"
+ type="bool"/>
+ <preference id="security.disable_button.openDeviceManager"
+ name="security.disable_button.openDeviceManager"
+ type="bool"/>
+ <preference id="security.OCSP.enabled"
+ name="security.OCSP.enabled"
+ type="int"/>
+ <preference id="security.OCSP.require"
+ name="security.OCSP.require"
+ type="bool"/>
+
+ <!-- Pale Moon: smooth scrolling tab -->
+ <preference id="general.smoothScroll.lines" name="general.smoothScroll.lines" type="bool"/>
+ <preference id="general.smoothScroll.lines.durationMinMS" name="general.smoothScroll.lines.durationMinMS" type="int"/>
+ <preference id="general.smoothScroll.lines.durationMaxMS" name="general.smoothScroll.lines.durationMaxMS" type="int"/>
+ <preference id="general.smoothScroll.pages" name="general.smoothScroll.pages" type="bool"/>
+ <preference id="general.smoothScroll.pages.durationMinMS" name="general.smoothScroll.pages.durationMinMS" type="int"/>
+ <preference id="general.smoothScroll.pages.durationMaxMS" name="general.smoothScroll.pages.durationMaxMS" type="int"/>
+ <preference id="general.smoothScroll.mouseWheel" name="general.smoothScroll.mouseWheel" type="bool"/>
+ <preference id="general.smoothScroll.mouseWheel.durationMinMS" name="general.smoothScroll.mouseWheel.durationMinMS" type="int"/>
+ <preference id="general.smoothScroll.mouseWheel.durationMaxMS" name="general.smoothScroll.mouseWheel.durationMaxMS" type="int"/>
+ <preference id="general.smoothScroll.scrollbars" name="general.smoothScroll.scrollbars" type="bool"/>
+ <preference id="general.smoothScroll.scrollbars.durationMinMS" name="general.smoothScroll.scrollbars.durationMinMS" type="int"/>
+ <preference id="general.smoothScroll.scrollbars.durationMaxMS" name="general.smoothScroll.scrollbars.durationMaxMS" type="int"/>
+
+ <preference id="mousewheel.default.delta_multiplier_y" name="mousewheel.default.delta_multiplier_y" type="int"/>
+ </preferences>
+
+#ifdef HAVE_SHELL_SERVICE
+ <stringbundle id="bundleShell" src="chrome://browser/locale/shellservice.properties"/>
+ <stringbundle id="bundleBrand" src="chrome://branding/locale/brand.properties"/>
+#endif
+ <stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences/preferences.properties"/>
+
+ <script type="application/javascript" src="chrome://browser/content/preferences/advanced.js"/>
+
+ <tabbox id="advancedPrefs" flex="1"
+ onselect="gAdvancedPane.tabSelectionChanged();">
+
+ <tabs id="tabsElement">
+ <tab id="generalTab" label="&generalTab.label;" helpTopic="prefs-advanced-general"/>
+ <tab id="networkTab" label="&networkTab.label;" helpTopic="prefs-advanced-network"/>
+ <tab id="updateTab" label="&updateTab.label;" helpTopic="prefs-advanced-update"/>
+ <tab id="encryptionTab" label="&certificateTab.label;" helpTopic="prefs-advanced-encryption"/>
+ <tab id="scrollparamTab" label="&scrollparamTab.label;" helpTopic="prefs-advanced-scrollparams"/>
+ </tabs>
+
+ <tabpanels flex="1">
+
+ <!-- General -->
+ <tabpanel id="generalPanel" orient="vertical">
+
+ <!-- Accessibility -->
+ <groupbox id="accessibilityGroup" align="start">
+ <caption label="&accessibility.label;"/>
+
+ <checkbox id="searchStartTyping"
+ label="&searchStartTyping.label;"
+ accesskey="&searchStartTyping.accesskey;"
+ preference="accessibility.typeaheadfind"/>
+ </groupbox>
+
+ <!-- Browsing -->
+ <groupbox id="browsingGroup" align="start">
+ <caption label="&browsing.label;"/>
+
+ <checkbox id="useAutoScroll"
+ label="&useAutoScroll.label;"
+ accesskey="&useAutoScroll.accesskey;"
+ preference="general.autoScroll"/>
+ <checkbox id="allowHWAccel"
+ label="&allowHWAccel.label;"
+ accesskey="&allowHWAccel.accesskey;"
+ preference="layers.acceleration.disabled"/>
+ <checkbox id="checkSpelling"
+ label="&checkSpelling.label;"
+ accesskey="&checkSpelling.accesskey;"
+ onsyncfrompreference="return gAdvancedPane.readCheckSpelling();"
+ onsynctopreference="return gAdvancedPane.writeCheckSpelling();"
+ preference="layout.spellcheckDefault"/>
+ </groupbox>
+
+#ifdef HAVE_SHELL_SERVICE
+ <!-- System Defaults -->
+ <groupbox id="systemDefaultsGroup" orient="vertical">
+ <caption label="&systemDefaults.label;"/>
+
+ <checkbox id="alwaysCheckDefault" preference="browser.shell.checkDefaultBrowser"
+ label="&alwaysCheckDefault.label;" accesskey="&alwaysCheckDefault.accesskey;"
+ flex="1"/>
+ <hbox class="indent">
+ <deck id="setDefaultPane">
+ <button id="setDefaultButton"
+ label="&setDefault.label;" accesskey="&setDefault.accesskey;"
+ oncommand="gAdvancedPane.setDefaultBrowser();"
+ preference="pref.general.disable_button.default_browser"/>
+ <description>&isDefault.label;</description>
+ </deck>
+ </hbox>
+ </groupbox>
+#endif
+ <!-- User Agent compatibility -->
+ <groupbox id="UACompatGroup" orient="vertical">
+ <caption label="&UACompatGroup.label;"/>
+ <hbox align="center">
+ <label id="UACompat" control="UACompat-menu">&UACompat.label;</label>
+ <menulist id="UACompat-menu" preference="pref.general.compatmode" sizetopopup="always">
+ <menupopup>
+ <menuitem label="&UACompat.Native;" value="0" />
+ <menuitem label="&UACompat.Gecko;" value="1" />
+ <menuitem label="&UACompat.Firefox;" value="2" />
+ </menupopup>
+ </menulist>
+ </hbox>
+ </groupbox>
+
+ <!-- Captive portal detection -->
+ <groupbox id="captivePortalGroup" orient="vertical">
+ <caption label="&captivePortalGroup.label;"/>
+ <checkbox id="captivePortalDetect"
+ label="&captivePortalDetect.label;"
+ preference="pref.general.captiveportal"/>
+ </groupbox>
+
+ </tabpanel>
+
+ <!-- Network -->
+ <tabpanel id="networkPanel" orient="vertical">
+
+ <!-- Connection -->
+ <groupbox id="connectionGroup">
+ <caption label="&connection.label;"/>
+
+ <hbox align="center">
+ <description flex="1" control="connectionSettings">&connectionDesc.label;</description>
+ <button id="connectionSettings" icon="network" label="&connectionSettings.label;"
+ accesskey="&connectionSettings.accesskey;"
+ oncommand="gAdvancedPane.showConnections();"/>
+ </hbox>
+ </groupbox>
+
+ <!-- Cache -->
+ <groupbox id="cacheGroup">
+ <caption label="&httpCache.label;"/>
+
+ <hbox align="center">
+ <label id="actualDiskCacheSize" flex="1"/>
+ <button id="clearCacheButton" icon="clear"
+ label="&clearCacheNow.label;" accesskey="&clearCacheNow.accesskey;"
+ oncommand="gAdvancedPane.clearCache();"/>
+ </hbox>
+ <checkbox preference="browser.cache.disk.smart_size.enabled"
+ id="allowSmartSize" flex="1"
+ onsyncfrompreference="return gAdvancedPane.readSmartSizeEnabled();"
+ label="&overrideSmartCacheSize.label;"
+ accesskey="&overrideSmartCacheSize.accesskey;"/>
+ <hbox align="center" class="indent">
+ <label id="useCacheBefore" control="cacheSize"
+ accesskey="&limitCacheSizeBefore.accesskey;"
+ value="&limitCacheSizeBefore.label;"/>
+ <textbox id="cacheSize" type="number" size="4" max="1024"
+ preference="browser.cache.disk.capacity"
+ onsyncfrompreference="return gAdvancedPane.readCacheSize();"
+ onsynctopreference="return gAdvancedPane.writeCacheSize();"
+ aria-labelledby="useCacheBefore cacheSize useCacheAfter"/>
+ <label id="useCacheAfter" flex="1">&limitCacheSizeAfter.label;</label>
+ </hbox>
+ </groupbox>
+
+ <!-- Offline apps -->
+ <groupbox id="offlineGroup">
+ <caption label="&offlineStorage2.label;"/>
+
+ <hbox align="center">
+ <label id="actualAppCacheSize" flex="1"/>
+ <button id="clearOfflineAppCacheButton" icon="clear"
+ label="&clearOfflineAppCacheNow.label;" accesskey="&clearOfflineAppCacheNow.accesskey;"
+ oncommand="gAdvancedPane.clearOfflineAppCache();"/>
+ </hbox>
+ <label id="offlineAppsPermsLabel">&offlineAppsPermissions.label;</label>
+ <hbox align="center">
+ <menulist id="offlineAppsPerms-menu" preference="offline-apps.permissions" sizetopopup="always">
+ <menupopup>
+ <menuitem label="&offlineAppsPermissions.Allow;" value="2" />
+ <menuitem label="&offlineAppsPermissions.Ask;" value="1" />
+ <menuitem label="&offlineAppsPermissions.Deny;" value="0" />
+ </menupopup>
+ </menulist>
+ <spacer flex="1"/>
+ <button id="offlineNotifyExceptions"
+ label="&offlineNotifyExceptions.label;"
+ accesskey="&offlineNotifyExceptions.accesskey;"
+ oncommand="gAdvancedPane.showOfflineExceptions();"/>
+ </hbox>
+ <hbox>
+ <vbox flex="1">
+ <label id="offlineAppsListLabel">&offlineAppsList2.label;</label>
+ <listbox id="offlineAppsList"
+ style="height: &offlineAppsList.height;;"
+ flex="1"
+ aria-labelledby="offlineAppsListLabel"
+ onselect="gAdvancedPane.offlineAppSelected(event);">
+ </listbox>
+ </vbox>
+ <vbox pack="end">
+ <button id="offlineAppsListRemove"
+ disabled="true"
+ label="&offlineAppsListRemove.label;"
+ accesskey="&offlineAppsListRemove.accesskey;"
+ oncommand="gAdvancedPane.removeOfflineApp();"/>
+ </vbox>
+ </hbox>
+ </groupbox>
+ </tabpanel>
+
+ <!-- Update -->
+ <tabpanel id="updatePanel" orient="vertical">
+#ifdef MOZ_UPDATER
+ <groupbox id="updateApp">
+ <caption label="&updateApp.label;"/>
+ <radiogroup id="updateRadioGroup"
+ oncommand="gAdvancedPane.updateWritePrefs();">
+ <radio id="autoDesktop"
+ value="auto"
+ label="&updateAuto1.label;"
+ accesskey="&updateAuto1.accesskey;"/>
+ <hbox class="indent">
+ <checkbox id="warnIncompatible"
+ label="&updateAutoAddonWarn.label;"
+ accesskey="&updateAutoAddonWarn.accesskey;"
+ preference="app.update.mode"
+ onsyncfrompreference="return gAdvancedPane.readAddonWarn();"
+ onsynctopreference="return gAdvancedPane.writeAddonWarn();"/>
+ </hbox>
+ <radio value="checkOnly"
+ label="&updateCheck.label;"
+ accesskey="&updateCheck.accesskey;"/>
+ <radio value="manual"
+ label="&updateManual.label;"
+ accesskey="&updateManual.accesskey;"/>
+ </radiogroup>
+
+ <hbox>
+ <button id="showUpdateHistory"
+ label="&updateHistory.label;"
+ accesskey="&updateHistory.accesskey;"
+ preference="app.update.disable_button.showUpdateHistory"
+ oncommand="gAdvancedPane.showUpdates();"/>
+ </hbox>
+ </groupbox>
+#endif
+ <groupbox id="updateOthers">
+ <caption label="&updateOthers.label;"/>
+ <checkbox id="enableSearchUpdate"
+ label="&enableSearchUpdate.label;"
+ accesskey="&enableSearchUpdate.accesskey;"
+ preference="browser.search.update"/>
+ </groupbox>
+ </tabpanel>
+
+ <!-- Certificates -->
+ <tabpanel id="encryptionPanel" orient="vertical">
+
+ <!--
+ The values on these radio buttons may look like l12y issues, but
+ they're not - this preference uses *those strings* as its values.
+ I KID YOU NOT.
+ -->
+
+ <groupbox>
+ <caption label="&certGroup.label;"/>
+ <description id="CertSelectionDesc" control="certSelection">&certSelection.description;</description>
+ <radiogroup id="certSelection" orient="horizontal" preftype="string"
+ preference="security.default_personal_cert"
+ aria-labelledby="CertSelectionDesc">
+ <radio label="&certs.auto;" accesskey="&certs.auto.accesskey;"
+ value="Select Automatically"/>
+ <radio label="&certs.ask;" accesskey="&certs.ask.accesskey;"
+ value="Ask Every Time"/>
+ </radiogroup>
+ </groupbox>
+ <groupbox>
+ <caption label="&ocspGroup.label;"/>
+ <checkbox id="enableOCSP"
+ label="&enableOCSP.label;"
+ accesskey="&enableOCSP.accesskey;"
+ onsyncfrompreference="return gAdvancedPane.readEnableOCSP();"
+ onsynctopreference="return gAdvancedPane.writeEnableOCSP();"
+ preference="security.OCSP.enabled"/>
+ <checkbox id="requireOCSP"
+ label="&requireOCSP.label;"
+ accesskey="&requireOCSP.accesskey;"
+ preference="security.OCSP.require"/>
+ </groupbox>
+
+ <separator/>
+
+ <hbox>
+ <button id="viewCertificatesButton"
+ label="&viewCerts.label;" accesskey="&viewCerts.accesskey;"
+ oncommand="gAdvancedPane.showCertificates();"
+ preference="security.disable_button.openCertManager"/>
+ <button id="viewSecurityDevicesButton"
+ label="&viewSecurityDevices.label;" accesskey="&viewSecurityDevices.accesskey;"
+ oncommand="gAdvancedPane.showSecurityDevices();"
+ preference="security.disable_button.openDeviceManager"/>
+ </hbox>
+ </tabpanel>
+
+ <!-- Pale Moon: Scrolling tab -->
+ <tabpanel id="scrollparamTab" orient="vertical">
+
+ <checkbox id="useSmoothScrolling"
+ label="&useSmoothScrolling.label;"
+ accesskey="&useSmoothScrolling.accesskey;"
+ preference="general.smoothScroll"/>
+
+ <label>&smoothscroll.explain.label;</label>
+
+ <groupbox>
+ <caption label="&smoothscroll.params.label;"/>
+
+ <checkbox label="&smoothscroll.mousewheel.label;" preference="general.smoothScroll.mouseWheel"/>
+ <hbox align="center" class="indent">
+ <label value="&smoothscroll.mousewheel.duration;"/>
+ <textbox type="number" size="3" max="500"
+ preference="general.smoothScroll.mouseWheel.durationMinMS"/>
+ <label>&smoothscroll.to;</label>
+ <textbox type="number" size="4" max="2000"
+ preference="general.smoothScroll.mouseWheel.durationMaxMS"/>
+ <label flex="1">ms.</label>
+ </hbox>
+
+ <checkbox label="&smoothscroll.arrowkeys.label;" preference="general.smoothScroll.lines"/>
+ <hbox align="center" class="indent">
+ <label value="&smoothscroll.arrowkeys.duration;"/>
+ <textbox type="number" size="3" max="500"
+ preference="general.smoothScroll.lines.durationMinMS"/>
+ <label>&smoothscroll.to;</label>
+ <textbox type="number" size="4" max="2000"
+ preference="general.smoothScroll.lines.durationMaxMS"/>
+ <label flex="1">ms.</label>
+ </hbox>
+
+ <checkbox label="&smoothscroll.pagekeys.label;" preference="general.smoothScroll.pages"/>
+ <hbox align="center" class="indent">
+ <label value="&smoothscroll.pagekeys.duration;"/>
+ <textbox type="number" size="3" max="500"
+ preference="general.smoothScroll.pages.durationMinMS"/>
+ <label>&smoothscroll.to;</label>
+ <textbox type="number" size="4" max="2000"
+ preference="general.smoothScroll.pages.durationMaxMS"/>
+ <label flex="1">ms.</label>
+ </hbox>
+
+ <checkbox label="&smoothscroll.scrollbar.label;" preference="general.smoothScroll.scrollbars"/>
+ <hbox align="center" class="indent">
+ <label value="&smoothscroll.scrollbar.duration;"/>
+ <textbox type="number" size="3" max="500"
+ preference="general.smoothScroll.scrollbars.durationMinMS"/>
+ <label>&smoothscroll.to;</label>
+ <textbox type="number" size="4" max="2000"
+ preference="general.smoothScroll.scrollbars.durationMaxMS"/>
+ <label flex="1">ms.</label>
+ </hbox>
+
+ <hbox align="center">
+ <label value="&smoothscroll.overall.yspeed.label;"/>
+ <textbox type="number" size="3" min="1" max="999"
+ preference="mousewheel.default.delta_multiplier_y"/>
+ <label flex="1">%.</label>
+ </hbox>
+ </groupbox>
+ </tabpanel>
+ <!-- end Smooth scrolling tab -->
+
+ </tabpanels>
+ </tabbox>
+ </prefpane>
+
+</overlay>
diff --git a/components/preferences/applicationManager.js b/components/preferences/applicationManager.js
new file mode 100644
index 0000000..5213183
--- /dev/null
+++ b/components/preferences/applicationManager.js
@@ -0,0 +1,102 @@
+# 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
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+#endif
+
+var gAppManagerDialog = {
+ _removed: [],
+
+ init: function appManager_init() {
+ this.handlerInfo = window.arguments[0];
+
+ var bundle = document.getElementById("appManagerBundle");
+ var contentText;
+ if (this.handlerInfo.type == TYPE_MAYBE_FEED)
+ contentText = bundle.getString("handleWebFeeds");
+ else {
+ var description = gApplicationsPane._describeType(this.handlerInfo);
+ var key =
+ (this.handlerInfo.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo) ? "handleFile"
+ : "handleProtocol";
+ contentText = bundle.getFormattedString(key, [description]);
+ }
+ contentText = bundle.getFormattedString("descriptionApplications", [contentText]);
+ document.getElementById("appDescription").textContent = contentText;
+
+ var list = document.getElementById("appList");
+ var apps = this.handlerInfo.possibleApplicationHandlers.enumerate();
+ while (apps.hasMoreElements()) {
+ let app = apps.getNext();
+ if (!gApplicationsPane.isValidHandlerApp(app))
+ continue;
+
+ app.QueryInterface(Ci.nsIHandlerApp);
+ var item = list.appendItem(app.name);
+ item.setAttribute("image", gApplicationsPane._getIconURLForHandlerApp(app));
+ item.className = "listitem-iconic";
+ item.app = app;
+ }
+
+ list.selectedIndex = 0;
+ },
+
+ onOK: function appManager_onOK() {
+ if (!this._removed.length) {
+ // return early to avoid calling the |store| method.
+ return;
+ }
+
+ for (var i = 0; i < this._removed.length; ++i)
+ this.handlerInfo.removePossibleApplicationHandler(this._removed[i]);
+
+ this.handlerInfo.store();
+ },
+
+ onCancel: function appManager_onCancel() {
+ // do nothing
+ },
+
+ remove: function appManager_remove() {
+ var list = document.getElementById("appList");
+ this._removed.push(list.selectedItem.app);
+ var index = list.selectedIndex;
+ list.removeItemAt(index);
+ if (list.getRowCount() == 0) {
+ // The list is now empty, make the bottom part disappear
+ document.getElementById("appDetails").hidden = true;
+ }
+ else {
+ // Select the item at the same index, if we removed the last
+ // item of the list, select the previous item
+ if (index == list.getRowCount())
+ --index;
+ list.selectedIndex = index;
+ }
+ },
+
+ onSelect: function appManager_onSelect() {
+ var list = document.getElementById("appList");
+ if (!list.selectedItem) {
+ document.getElementById("remove").disabled = true;
+ return;
+ }
+ document.getElementById("remove").disabled = false;
+ var app = list.selectedItem.app;
+ var address = "";
+ if (app instanceof Ci.nsILocalHandlerApp)
+ address = app.executable.path;
+ else if (app instanceof Ci.nsIWebHandlerApp)
+ address = app.uriTemplate;
+ else if (app instanceof Ci.nsIWebContentHandlerInfo)
+ address = app.uri;
+ document.getElementById("appLocation").value = address;
+ var bundle = document.getElementById("appManagerBundle");
+ var appType = app instanceof Ci.nsILocalHandlerApp ? "descriptionLocalApp"
+ : "descriptionWebApp";
+ document.getElementById("appType").value = bundle.getString(appType);
+ }
+};
diff --git a/components/preferences/applicationManager.xul b/components/preferences/applicationManager.xul
new file mode 100644
index 0000000..b5605c2
--- /dev/null
+++ b/components/preferences/applicationManager.xul
@@ -0,0 +1,59 @@
+<?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/"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://browser/locale/preferences/applicationManager.dtd">
+
+<dialog id="appManager"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ buttons="accept,cancel"
+ onload="gAppManagerDialog.init();"
+ ondialogaccept="gAppManagerDialog.onOK();"
+ ondialogcancel="gAppManagerDialog.onCancel();"
+ title="&appManager.title;"
+ style="&appManager.style;"
+ persist="screenX screenY">
+
+ <script type="application/javascript"
+ src="chrome://browser/content/utilityOverlay.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/preferences/applicationManager.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/preferences/applications.js"/>
+
+ <commandset id="appManagerCommandSet">
+ <command id="cmd_remove"
+ oncommand="gAppManagerDialog.remove();"
+ disabled="true"/>
+ </commandset>
+
+ <keyset id="appManagerKeyset">
+ <key id="delete" keycode="VK_DELETE" command="cmd_remove"/>
+ </keyset>
+
+ <stringbundleset id="appManagerBundleset">
+ <stringbundle id="appManagerBundle"
+ src="chrome://browser/locale/preferences/applicationManager.properties"/>
+ </stringbundleset>
+
+ <description id="appDescription"/>
+ <separator class="thin"/>
+ <hbox flex="1">
+ <listbox id="appList" onselect="gAppManagerDialog.onSelect();" flex="1"/>
+ <vbox>
+ <button id="remove"
+ label="&remove.label;"
+ accesskey="&remove.accesskey;"
+ command="cmd_remove"/>
+ <spacer flex="1"/>
+ </vbox>
+ </hbox>
+ <vbox id="appDetails">
+ <separator class="thin"/>
+ <label id="appType"/>
+ <textbox id="appLocation" readonly="true" class="plain"/>
+ </vbox>
+</dialog>
diff --git a/components/preferences/applications.js b/components/preferences/applications.js
new file mode 100644
index 0000000..d06f9f9
--- /dev/null
+++ b/components/preferences/applications.js
@@ -0,0 +1,1890 @@
+/*
+# -*- 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/.
+ */
+
+//****************************************************************************//
+// Constants & Enumeration Values
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+
+Components.utils.import('resource://gre/modules/Services.jsm');
+
+const TYPE_MAYBE_FEED = "application/vnd.mozilla.maybe.feed";
+const TYPE_MAYBE_VIDEO_FEED = "application/vnd.mozilla.maybe.video.feed";
+const TYPE_MAYBE_AUDIO_FEED = "application/vnd.mozilla.maybe.audio.feed";
+
+const PREF_DISABLED_PLUGIN_TYPES = "plugin.disable_full_page_plugin_for_types";
+
+// Preferences that affect which entries to show in the list.
+const PREF_SHOW_PLUGINS_IN_LIST = "browser.download.show_plugins_in_list";
+const PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS =
+ "browser.download.hide_plugins_without_extensions";
+
+/*
+ * Preferences where we store handling information about the feed type.
+ *
+ * browser.feeds.handler
+ * - "bookmarks", "reader" (clarified further using the .default preference),
+ * or "ask" -- indicates the default handler being used to process feeds;
+ * "bookmarks" is obsolete; to specify that the handler is bookmarks,
+ * set browser.feeds.handler.default to "bookmarks";
+ *
+ * browser.feeds.handler.default
+ * - "bookmarks", "client" or "web" -- indicates the chosen feed reader used
+ * to display feeds, either transiently (i.e., when the "use as default"
+ * checkbox is unchecked, corresponds to when browser.feeds.handler=="ask")
+ * or more permanently (i.e., the item displayed in the dropdown in Feeds
+ * preferences)
+ *
+ * browser.feeds.handler.webservice
+ * - the URL of the currently selected web service used to read feeds
+ *
+ * browser.feeds.handlers.application
+ * - nsILocalFile, stores the current client-side feed reading app if one has
+ * been chosen
+ */
+const PREF_FEED_SELECTED_APP = "browser.feeds.handlers.application";
+const PREF_FEED_SELECTED_WEB = "browser.feeds.handlers.webservice";
+const PREF_FEED_SELECTED_ACTION = "browser.feeds.handler";
+const PREF_FEED_SELECTED_READER = "browser.feeds.handler.default";
+
+const PREF_VIDEO_FEED_SELECTED_APP = "browser.videoFeeds.handlers.application";
+const PREF_VIDEO_FEED_SELECTED_WEB = "browser.videoFeeds.handlers.webservice";
+const PREF_VIDEO_FEED_SELECTED_ACTION = "browser.videoFeeds.handler";
+const PREF_VIDEO_FEED_SELECTED_READER = "browser.videoFeeds.handler.default";
+
+const PREF_AUDIO_FEED_SELECTED_APP = "browser.audioFeeds.handlers.application";
+const PREF_AUDIO_FEED_SELECTED_WEB = "browser.audioFeeds.handlers.webservice";
+const PREF_AUDIO_FEED_SELECTED_ACTION = "browser.audioFeeds.handler";
+const PREF_AUDIO_FEED_SELECTED_READER = "browser.audioFeeds.handler.default";
+
+// The nsHandlerInfoAction enumeration values in nsIHandlerInfo identify
+// the actions the application can take with content of various types.
+// But since nsIHandlerInfo doesn't support plugins, there's no value
+// identifying the "use plugin" action, so we use this constant instead.
+const kActionUsePlugin = 5;
+
+/*
+#ifdef MOZ_WIDGET_GTK
+*/
+const ICON_URL_APP = "moz-icon://dummy.exe?size=16";
+/*
+#else
+*/
+const ICON_URL_APP = "chrome://browser/skin/preferences/application.png";
+/*
+#endif
+*/
+
+// For CSS. Can be one of "ask", "save", "plugin" or "feed". If absent, the icon URL
+// was set by us to a custom handler icon and CSS should not try to override it.
+const APP_ICON_ATTR_NAME = "appHandlerIcon";
+
+//****************************************************************************//
+// Utilities
+
+function getFileDisplayName(file) {
+#ifdef XP_WIN
+ if (file instanceof Ci.nsILocalFileWin) {
+ try {
+ return file.getVersionInfoField("FileDescription");
+ } catch (e) {}
+ }
+#endif
+#ifdef XP_MACOSX
+ if (file instanceof Ci.nsILocalFileMac) {
+ try {
+ return file.bundleDisplayName;
+ } catch (e) {}
+ }
+#endif
+ return file.leafName;
+}
+
+function getLocalHandlerApp(aFile) {
+ var localHandlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
+ createInstance(Ci.nsILocalHandlerApp);
+ localHandlerApp.name = getFileDisplayName(aFile);
+ localHandlerApp.executable = aFile;
+
+ return localHandlerApp;
+}
+
+/**
+ * An enumeration of items in a JS array.
+ *
+ * FIXME: use ArrayConverter once it lands (bug 380839).
+ *
+ * @constructor
+ */
+function ArrayEnumerator(aItems) {
+ this._index = 0;
+ this._contents = aItems;
+}
+
+ArrayEnumerator.prototype = {
+ _index: 0,
+
+ hasMoreElements: function() {
+ return this._index < this._contents.length;
+ },
+
+ getNext: function() {
+ return this._contents[this._index++];
+ }
+};
+
+function isFeedType(t) {
+ return t == TYPE_MAYBE_FEED || t == TYPE_MAYBE_VIDEO_FEED || t == TYPE_MAYBE_AUDIO_FEED;
+}
+
+//****************************************************************************//
+// HandlerInfoWrapper
+
+/**
+ * This object wraps nsIHandlerInfo with some additional functionality
+ * the Applications prefpane needs to display and allow modification of
+ * the list of handled types.
+ *
+ * We create an instance of this wrapper for each entry we might display
+ * in the prefpane, and we compose the instances from various sources,
+ * including plugins and the handler service.
+ *
+ * We don't implement all the original nsIHandlerInfo functionality,
+ * just the stuff that the prefpane needs.
+ *
+ * In theory, all of the custom functionality in this wrapper should get
+ * pushed down into nsIHandlerInfo eventually.
+ */
+function HandlerInfoWrapper(aType, aHandlerInfo) {
+ this._type = aType;
+ this.wrappedHandlerInfo = aHandlerInfo;
+}
+
+HandlerInfoWrapper.prototype = {
+ // The wrapped nsIHandlerInfo object. In general, this object is private,
+ // but there are a couple cases where callers access it directly for things
+ // we haven't (yet?) implemented, so we make it a public property.
+ wrappedHandlerInfo: null,
+
+
+ //**************************************************************************//
+ // Convenience Utils
+
+ _handlerSvc: Cc["@mozilla.org/uriloader/handler-service;1"].
+ getService(Ci.nsIHandlerService),
+
+ _prefSvc: Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch),
+
+ _categoryMgr: Cc["@mozilla.org/categorymanager;1"].
+ getService(Ci.nsICategoryManager),
+
+ element: function(aID) {
+ return document.getElementById(aID);
+ },
+
+
+ //**************************************************************************//
+ // nsIHandlerInfo
+
+ // The MIME type or protocol scheme.
+ _type: null,
+ get type() {
+ return this._type;
+ },
+
+ get description() {
+ if (this.wrappedHandlerInfo.description)
+ return this.wrappedHandlerInfo.description;
+
+ if (this.primaryExtension) {
+ var extension = this.primaryExtension.toUpperCase();
+ return this.element("bundlePreferences").getFormattedString("fileEnding",
+ [extension]);
+ }
+
+ return this.type;
+ },
+
+ get preferredApplicationHandler() {
+ return this.wrappedHandlerInfo.preferredApplicationHandler;
+ },
+
+ set preferredApplicationHandler(aNewValue) {
+ this.wrappedHandlerInfo.preferredApplicationHandler = aNewValue;
+
+ // Make sure the preferred handler is in the set of possible handlers.
+ if (aNewValue)
+ this.addPossibleApplicationHandler(aNewValue)
+ },
+
+ get possibleApplicationHandlers() {
+ return this.wrappedHandlerInfo.possibleApplicationHandlers;
+ },
+
+ addPossibleApplicationHandler: function(aNewHandler) {
+ var possibleApps = this.possibleApplicationHandlers.enumerate();
+ while (possibleApps.hasMoreElements()) {
+ if (possibleApps.getNext().equals(aNewHandler))
+ return;
+ }
+ this.possibleApplicationHandlers.appendElement(aNewHandler, false);
+ },
+
+ removePossibleApplicationHandler: function(aHandler) {
+ var defaultApp = this.preferredApplicationHandler;
+ if (defaultApp && aHandler.equals(defaultApp)) {
+ // If the app we remove was the default app, we must make sure
+ // it won't be used anymore
+ this.alwaysAskBeforeHandling = true;
+ this.preferredApplicationHandler = null;
+ }
+
+ var handlers = this.possibleApplicationHandlers;
+ for (var i = 0; i < handlers.length; ++i) {
+ var handler = handlers.queryElementAt(i, Ci.nsIHandlerApp);
+ if (handler.equals(aHandler)) {
+ handlers.removeElementAt(i);
+ break;
+ }
+ }
+ },
+
+ get hasDefaultHandler() {
+ return this.wrappedHandlerInfo.hasDefaultHandler;
+ },
+
+ get defaultDescription() {
+ return this.wrappedHandlerInfo.defaultDescription;
+ },
+
+ // What to do with content of this type.
+ get preferredAction() {
+ // If we have an enabled plugin, then the action is to use that plugin.
+ if (this.pluginName && !this.isDisabledPluginType)
+ return kActionUsePlugin;
+
+ // If the action is to use a helper app, but we don't have a preferred
+ // handler app, then switch to using the system default, if any; otherwise
+ // fall back to saving to disk, which is the default action in nsMIMEInfo.
+ // Note: "save to disk" is an invalid value for protocol info objects,
+ // but the alwaysAskBeforeHandling getter will detect that situation
+ // and always return true in that case to override this invalid value.
+ if (this.wrappedHandlerInfo.preferredAction == Ci.nsIHandlerInfo.useHelperApp &&
+ !gApplicationsPane.isValidHandlerApp(this.preferredApplicationHandler)) {
+ if (this.wrappedHandlerInfo.hasDefaultHandler)
+ return Ci.nsIHandlerInfo.useSystemDefault;
+ else
+ return Ci.nsIHandlerInfo.saveToDisk;
+ }
+
+ return this.wrappedHandlerInfo.preferredAction;
+ },
+
+ set preferredAction(aNewValue) {
+ // If the action is to use the plugin,
+ // we must set the preferred action to "save to disk".
+ // But only if it's not currently the preferred action.
+ if ((aNewValue == kActionUsePlugin) &&
+ (this.preferredAction != Ci.nsIHandlerInfo.saveToDisk)) {
+ aNewValue = Ci.nsIHandlerInfo.saveToDisk;
+ }
+
+ // We don't modify the preferred action if the new action is to use a plugin
+ // because handler info objects don't understand our custom "use plugin"
+ // value. Also, leaving it untouched means that we can automatically revert
+ // to the old setting if the user ever removes the plugin.
+
+ if (aNewValue != kActionUsePlugin)
+ this.wrappedHandlerInfo.preferredAction = aNewValue;
+ },
+
+ get alwaysAskBeforeHandling() {
+ // If this type is handled only by a plugin, we can't trust the value
+ // in the handler info object, since it'll be a default based on the absence
+ // of any user configuration, and the default in that case is to always ask,
+ // even though we never ask for content handled by a plugin, so special case
+ // plugin-handled types by returning false here.
+ if (this.pluginName && this.handledOnlyByPlugin)
+ return false;
+
+ // If this is a protocol type and the preferred action is "save to disk",
+ // which is invalid for such types, then return true here to override that
+ // action. This could happen when the preferred action is to use a helper
+ // app, but the preferredApplicationHandler is invalid, and there isn't
+ // a default handler, so the preferredAction getter returns save to disk
+ // instead.
+ if (!(this.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo) &&
+ this.preferredAction == Ci.nsIHandlerInfo.saveToDisk)
+ return true;
+
+ return this.wrappedHandlerInfo.alwaysAskBeforeHandling;
+ },
+
+ set alwaysAskBeforeHandling(aNewValue) {
+ this.wrappedHandlerInfo.alwaysAskBeforeHandling = aNewValue;
+ },
+
+
+ //**************************************************************************//
+ // nsIMIMEInfo
+
+ // The primary file extension associated with this type, if any.
+ //
+ // XXX Plugin objects contain an array of MimeType objects with "suffixes"
+ // properties; if this object has an associated plugin, shouldn't we check
+ // those properties for an extension?
+ get primaryExtension() {
+ try {
+ if (this.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo &&
+ this.wrappedHandlerInfo.primaryExtension)
+ return this.wrappedHandlerInfo.primaryExtension
+ } catch(ex) {}
+
+ return null;
+ },
+
+
+ //**************************************************************************//
+ // Plugin Handling
+
+ // A plugin that can handle this type, if any.
+ //
+ // Note: just because we have one doesn't mean it *will* handle the type.
+ // That depends on whether or not the type is in the list of types for which
+ // plugin handling is disabled.
+ plugin: null,
+
+ // Whether or not this type is only handled by a plugin or is also handled
+ // by some user-configured action as specified in the handler info object.
+ //
+ // Note: we can't just check if there's a handler info object for this type,
+ // because OS and user configuration is mixed up in the handler info object,
+ // so we always need to retrieve it for the OS info and can't tell whether
+ // it represents only OS-default information or user-configured information.
+ //
+ // FIXME: once handler info records are broken up into OS-provided records
+ // and user-configured records, stop using this boolean flag and simply
+ // check for the presence of a user-configured record to determine whether
+ // or not this type is only handled by a plugin. Filed as bug 395142.
+ handledOnlyByPlugin: undefined,
+
+ get isDisabledPluginType() {
+ return this._getDisabledPluginTypes().indexOf(this.type) != -1;
+ },
+
+ _getDisabledPluginTypes: function() {
+ var types = "";
+
+ if (this._prefSvc.prefHasUserValue(PREF_DISABLED_PLUGIN_TYPES))
+ types = this._prefSvc.getCharPref(PREF_DISABLED_PLUGIN_TYPES);
+
+ // Only split if the string isn't empty so we don't end up with an array
+ // containing a single empty string.
+ if (types != "")
+ return types.split(",");
+
+ return [];
+ },
+
+ disablePluginType: function() {
+ var disabledPluginTypes = this._getDisabledPluginTypes();
+
+ if (disabledPluginTypes.indexOf(this.type) == -1)
+ disabledPluginTypes.push(this.type);
+
+ this._prefSvc.setCharPref(PREF_DISABLED_PLUGIN_TYPES,
+ disabledPluginTypes.join(","));
+
+ // Update the category manager so existing browser windows update.
+ this._categoryMgr.deleteCategoryEntry("Goanna-Content-Viewers",
+ this.type,
+ false);
+ },
+
+ enablePluginType: function() {
+ var disabledPluginTypes = this._getDisabledPluginTypes();
+
+ var type = this.type;
+ disabledPluginTypes = disabledPluginTypes.filter(function(v) v != type);
+
+ this._prefSvc.setCharPref(PREF_DISABLED_PLUGIN_TYPES,
+ disabledPluginTypes.join(","));
+
+ // Update the category manager so existing browser windows update.
+ this._categoryMgr.
+ addCategoryEntry("Goanna-Content-Viewers",
+ this.type,
+ "@mozilla.org/content/plugin/document-loader-factory;1",
+ false,
+ true);
+ },
+
+
+ //**************************************************************************//
+ // Storage
+
+ store: function() {
+ this._handlerSvc.store(this.wrappedHandlerInfo);
+ },
+
+
+ //**************************************************************************//
+ // Icons
+
+ get smallIcon() {
+ return this._getIcon(16);
+ },
+
+ _getIcon: function(aSize) {
+ if (this.primaryExtension)
+ return "moz-icon://goat." + this.primaryExtension + "?size=" + aSize;
+
+ if (this.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo)
+ return "moz-icon://goat?size=" + aSize + "&contentType=" + this.type;
+
+ // FIXME: consider returning some generic icon when we can't get a URL for
+ // one (for example in the case of protocol schemes). Filed as bug 395141.
+ return null;
+ }
+
+};
+
+
+//****************************************************************************//
+// Feed Handler Info
+
+/**
+ * This object implements nsIHandlerInfo for the feed types. It's a separate
+ * object because we currently store handling information for the feed type
+ * in a set of preferences rather than the nsIHandlerService-managed datastore.
+ *
+ * This object inherits from HandlerInfoWrapper in order to get functionality
+ * that isn't special to the feed type.
+ *
+ * XXX Should we inherit from HandlerInfoWrapper? After all, we override
+ * most of that wrapper's properties and methods, and we have to dance around
+ * the fact that the wrapper expects to have a wrappedHandlerInfo, which we
+ * don't provide.
+ */
+
+function FeedHandlerInfo(aMIMEType) {
+ HandlerInfoWrapper.call(this, aMIMEType, null);
+}
+
+FeedHandlerInfo.prototype = {
+ __proto__: HandlerInfoWrapper.prototype,
+
+ //**************************************************************************//
+ // Convenience Utils
+
+ _converterSvc:
+ Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
+ getService(Ci.nsIWebContentConverterService),
+
+ _shellSvc:
+#ifdef HAVE_SHELL_SERVICE
+ getShellService(),
+#else
+ null,
+#endif
+
+
+ //**************************************************************************//
+ // nsIHandlerInfo
+
+ get description() {
+ return this.element("bundlePreferences").getString(this._appPrefLabel);
+ },
+
+ get preferredApplicationHandler() {
+ switch (this.element(this._prefSelectedReader).value) {
+ case "client":
+ var file = this.element(this._prefSelectedApp).value;
+ if (file)
+ return getLocalHandlerApp(file);
+
+ return null;
+
+ case "web":
+ var uri = this.element(this._prefSelectedWeb).value;
+ if (!uri)
+ return null;
+ return this._converterSvc.getWebContentHandlerByURI(this.type, uri);
+
+ case "bookmarks":
+ default:
+ // When the pref is set to bookmarks, we handle feeds internally,
+ // we don't forward them to a local or web handler app, so there is
+ // no preferred handler.
+ return null;
+ }
+ },
+
+ set preferredApplicationHandler(aNewValue) {
+ if (aNewValue instanceof Ci.nsILocalHandlerApp) {
+ this.element(this._prefSelectedApp).value = aNewValue.executable;
+ this.element(this._prefSelectedReader).value = "client";
+ }
+ else if (aNewValue instanceof Ci.nsIWebContentHandlerInfo) {
+ this.element(this._prefSelectedWeb).value = aNewValue.uri;
+ this.element(this._prefSelectedReader).value = "web";
+ // Make the web handler be the new "auto handler" for feeds.
+ // Note: we don't have to unregister the auto handler when the user picks
+ // a non-web handler (local app, Live Bookmarks, etc.) because the service
+ // only uses the "auto handler" when the selected reader is a web handler.
+ // We also don't have to unregister it when the user turns on "always ask"
+ // (i.e. preview in browser), since that also overrides the auto handler.
+ this._converterSvc.setAutoHandler(this.type, aNewValue);
+ }
+ },
+
+ _possibleApplicationHandlers: null,
+
+ get possibleApplicationHandlers() {
+ if (this._possibleApplicationHandlers)
+ return this._possibleApplicationHandlers;
+
+ // A minimal implementation of nsIMutableArray. It only supports the two
+ // methods its callers invoke, namely appendElement and nsIArray::enumerate.
+ this._possibleApplicationHandlers = {
+ _inner: [],
+ _removed: [],
+
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIMutableArray) ||
+ aIID.equals(Ci.nsIArray) ||
+ aIID.equals(Ci.nsISupports))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ get length() {
+ return this._inner.length;
+ },
+
+ enumerate: function() {
+ return new ArrayEnumerator(this._inner);
+ },
+
+ appendElement: function(aHandlerApp, aWeak) {
+ this._inner.push(aHandlerApp);
+ },
+
+ removeElementAt: function(aIndex) {
+ this._removed.push(this._inner[aIndex]);
+ this._inner.splice(aIndex, 1);
+ },
+
+ queryElementAt: function(aIndex, aInterface) {
+ return this._inner[aIndex].QueryInterface(aInterface);
+ }
+ };
+
+ // Add the selected local app if it's different from the OS default handler.
+ // Unlike for other types, we can store only one local app at a time for the
+ // feed type, since we store it in a preference that historically stores
+ // only a single path. But we display all the local apps the user chooses
+ // while the prefpane is open, only dropping the list when the user closes
+ // the prefpane, for maximum usability and consistency with other types.
+ var preferredAppFile = this.element(this._prefSelectedApp).value;
+ if (preferredAppFile) {
+ let preferredApp = getLocalHandlerApp(preferredAppFile);
+ let defaultApp = this._defaultApplicationHandler;
+ if (!defaultApp || !defaultApp.equals(preferredApp))
+ this._possibleApplicationHandlers.appendElement(preferredApp, false);
+ }
+
+ // Add the registered web handlers. There can be any number of these.
+ var webHandlers = this._converterSvc.getContentHandlers(this.type);
+ for each (let webHandler in webHandlers)
+ this._possibleApplicationHandlers.appendElement(webHandler, false);
+
+ return this._possibleApplicationHandlers;
+ },
+
+ __defaultApplicationHandler: undefined,
+ get _defaultApplicationHandler() {
+ if (typeof this.__defaultApplicationHandler != "undefined")
+ return this.__defaultApplicationHandler;
+
+ var defaultFeedReader = null;
+#ifdef HAVE_SHELL_SERVICE
+ try {
+ defaultFeedReader = this._shellSvc.defaultFeedReader;
+ }
+ catch(ex) {
+ // no default reader or _shellSvc is null
+ }
+#endif
+
+ if (defaultFeedReader) {
+ let handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
+ createInstance(Ci.nsIHandlerApp);
+ handlerApp.name = getFileDisplayName(defaultFeedReader);
+ handlerApp.QueryInterface(Ci.nsILocalHandlerApp);
+ handlerApp.executable = defaultFeedReader;
+
+ this.__defaultApplicationHandler = handlerApp;
+ }
+ else {
+ this.__defaultApplicationHandler = null;
+ }
+
+ return this.__defaultApplicationHandler;
+ },
+
+ get hasDefaultHandler() {
+#ifdef HAVE_SHELL_SERVICE
+ try {
+ if (this._shellSvc.defaultFeedReader)
+ return true;
+ }
+ catch(ex) {
+ // no default reader or _shellSvc is null
+ }
+#endif
+
+ return false;
+ },
+
+ get defaultDescription() {
+ if (this.hasDefaultHandler)
+ return this._defaultApplicationHandler.name;
+
+ // Should we instead return null?
+ return "";
+ },
+
+ // What to do with content of this type.
+ get preferredAction() {
+ switch (this.element(this._prefSelectedAction).value) {
+
+ case "bookmarks":
+ return Ci.nsIHandlerInfo.handleInternally;
+
+ case "reader": {
+ let preferredApp = this.preferredApplicationHandler;
+ let defaultApp = this._defaultApplicationHandler;
+
+ // If we have a valid preferred app, return useSystemDefault if it's
+ // the default app; otherwise return useHelperApp.
+ if (gApplicationsPane.isValidHandlerApp(preferredApp)) {
+ if (defaultApp && defaultApp.equals(preferredApp))
+ return Ci.nsIHandlerInfo.useSystemDefault;
+
+ return Ci.nsIHandlerInfo.useHelperApp;
+ }
+
+ // The pref is set to "reader", but we don't have a valid preferred app.
+ // What do we do now? Not sure this is the best option (perhaps we
+ // should direct the user to the default app, if any), but for now let's
+ // direct the user to live bookmarks.
+ return Ci.nsIHandlerInfo.handleInternally;
+ }
+
+ // If the action is "ask", then alwaysAskBeforeHandling will override
+ // the action, so it doesn't matter what we say it is, it just has to be
+ // something that doesn't cause the controller to hide the type.
+ case "ask":
+ default:
+ return Ci.nsIHandlerInfo.handleInternally;
+ }
+ },
+
+ set preferredAction(aNewValue) {
+ switch (aNewValue) {
+
+ case Ci.nsIHandlerInfo.handleInternally:
+ this.element(this._prefSelectedReader).value = "bookmarks";
+ break;
+
+ case Ci.nsIHandlerInfo.useHelperApp:
+ this.element(this._prefSelectedAction).value = "reader";
+ // The controller has already set preferredApplicationHandler
+ // to the new helper app.
+ break;
+
+ case Ci.nsIHandlerInfo.useSystemDefault:
+ this.element(this._prefSelectedAction).value = "reader";
+ this.preferredApplicationHandler = this._defaultApplicationHandler;
+ break;
+ }
+ },
+
+ get alwaysAskBeforeHandling() {
+ return this.element(this._prefSelectedAction).value == "ask";
+ },
+
+ set alwaysAskBeforeHandling(aNewValue) {
+ if (aNewValue == true)
+ this.element(this._prefSelectedAction).value = "ask";
+ else
+ this.element(this._prefSelectedAction).value = "reader";
+ },
+
+ // Whether or not we are currently storing the action selected by the user.
+ // We use this to suppress notification-triggered updates to the list when
+ // we make changes that may spawn such updates, specifically when we change
+ // the action for the feed type, which results in feed preference updates,
+ // which spawn "pref changed" notifications that would otherwise cause us
+ // to rebuild the view unnecessarily.
+ _storingAction: false,
+
+
+ //**************************************************************************//
+ // nsIMIMEInfo
+
+ get primaryExtension() {
+ return "xml";
+ },
+
+
+ //**************************************************************************//
+ // Storage
+
+ // Changes to the preferred action and handler take effect immediately
+ // (we write them out to the preferences right as they happen),
+ // so we when the controller calls store() after modifying the handlers,
+ // the only thing we need to store is the removal of possible handlers
+ // XXX Should we hold off on making the changes until this method gets called?
+ store: function() {
+ for each (let app in this._possibleApplicationHandlers._removed) {
+ if (app instanceof Ci.nsILocalHandlerApp) {
+ let pref = this.element(PREF_FEED_SELECTED_APP);
+ var preferredAppFile = pref.value;
+ if (preferredAppFile) {
+ let preferredApp = getLocalHandlerApp(preferredAppFile);
+ if (app.equals(preferredApp))
+ pref.reset();
+ }
+ }
+ else {
+ app.QueryInterface(Ci.nsIWebContentHandlerInfo);
+ this._converterSvc.removeContentHandler(app.contentType, app.uri);
+ }
+ }
+ this._possibleApplicationHandlers._removed = [];
+ },
+
+
+ //**************************************************************************//
+ // Icons
+
+ get smallIcon() {
+ return this._smallIcon;
+ }
+
+};
+
+var feedHandlerInfo = {
+ __proto__: new FeedHandlerInfo(TYPE_MAYBE_FEED),
+ _prefSelectedApp: PREF_FEED_SELECTED_APP,
+ _prefSelectedWeb: PREF_FEED_SELECTED_WEB,
+ _prefSelectedAction: PREF_FEED_SELECTED_ACTION,
+ _prefSelectedReader: PREF_FEED_SELECTED_READER,
+ _smallIcon: "chrome://browser/skin/feeds/feedIcon16.png",
+ _appPrefLabel: "webFeed"
+}
+
+var videoFeedHandlerInfo = {
+ __proto__: new FeedHandlerInfo(TYPE_MAYBE_VIDEO_FEED),
+ _prefSelectedApp: PREF_VIDEO_FEED_SELECTED_APP,
+ _prefSelectedWeb: PREF_VIDEO_FEED_SELECTED_WEB,
+ _prefSelectedAction: PREF_VIDEO_FEED_SELECTED_ACTION,
+ _prefSelectedReader: PREF_VIDEO_FEED_SELECTED_READER,
+ _smallIcon: "chrome://browser/skin/feeds/videoFeedIcon16.png",
+ _appPrefLabel: "videoPodcastFeed"
+}
+
+var audioFeedHandlerInfo = {
+ __proto__: new FeedHandlerInfo(TYPE_MAYBE_AUDIO_FEED),
+ _prefSelectedApp: PREF_AUDIO_FEED_SELECTED_APP,
+ _prefSelectedWeb: PREF_AUDIO_FEED_SELECTED_WEB,
+ _prefSelectedAction: PREF_AUDIO_FEED_SELECTED_ACTION,
+ _prefSelectedReader: PREF_AUDIO_FEED_SELECTED_READER,
+ _smallIcon: "chrome://browser/skin/feeds/audioFeedIcon16.png",
+ _appPrefLabel: "audioPodcastFeed"
+}
+
+/**
+ * InternalHandlerInfoWrapper provides a basic mechanism to create an internal
+ * mime type handler that can be enabled/disabled in the applications preference
+ * menu.
+ */
+function InternalHandlerInfoWrapper(aMIMEType) {
+ var mimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
+ var handlerInfo = mimeSvc.getFromTypeAndExtension(aMIMEType, null);
+
+ HandlerInfoWrapper.call(this, aMIMEType, handlerInfo);
+}
+
+InternalHandlerInfoWrapper.prototype = {
+ __proto__: HandlerInfoWrapper.prototype,
+
+ // Override store so we so we can notify any code listening for registration
+ // or unregistration of this handler.
+ store: function() {
+ HandlerInfoWrapper.prototype.store.call(this);
+ Services.obs.notifyObservers(null, this._handlerChanged, null);
+ },
+
+ get enabled() {
+ throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+ },
+
+ get description() {
+ return this.element("bundlePreferences").getString(this._appPrefLabel);
+ }
+};
+
+//****************************************************************************//
+// Prefpane Controller
+
+var gApplicationsPane = {
+ // The set of types the app knows how to handle. A hash of HandlerInfoWrapper
+ // objects, indexed by type.
+ _handledTypes: {},
+
+ // The list of types we can show, sorted by the sort column/direction.
+ // An array of HandlerInfoWrapper objects. We build this list when we first
+ // load the data and then rebuild it when users change a pref that affects
+ // what types we can show or change the sort column/direction.
+ // Note: this isn't necessarily the list of types we *will* show; if the user
+ // provides a filter string, we'll only show the subset of types in this list
+ // that match that string.
+ _visibleTypes: [],
+
+ // A count of the number of times each visible type description appears.
+ // We use these counts to determine whether or not to annotate descriptions
+ // with their types to distinguish duplicate descriptions from each other.
+ // A hash of integer counts, indexed by string description.
+ _visibleTypeDescriptionCount: {},
+
+
+ //**************************************************************************//
+ // Convenience & Performance Shortcuts
+
+ // These get defined by init().
+ _brandShortName : null,
+ _prefsBundle : null,
+ _list : null,
+ _filter : null,
+
+ _prefSvc : Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch),
+
+ _mimeSvc : Cc["@mozilla.org/mime;1"].
+ getService(Ci.nsIMIMEService),
+
+ _helperAppSvc : Cc["@mozilla.org/uriloader/external-helper-app-service;1"].
+ getService(Ci.nsIExternalHelperAppService),
+
+ _handlerSvc : Cc["@mozilla.org/uriloader/handler-service;1"].
+ getService(Ci.nsIHandlerService),
+
+ _ioSvc : Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService),
+
+
+ //**************************************************************************//
+ // Initialization & Destruction
+
+ init: function() {
+ // Initialize shortcuts to some commonly accessed elements & values.
+ this._brandShortName =
+ document.getElementById("bundleBrand").getString("brandShortName");
+ this._prefsBundle = document.getElementById("bundlePreferences");
+ this._list = document.getElementById("handlersView");
+ this._filter = document.getElementById("filter");
+
+ // Observe preferences that influence what we display so we can rebuild
+ // the view when they change.
+ this._prefSvc.addObserver(PREF_SHOW_PLUGINS_IN_LIST, this, false);
+ this._prefSvc.addObserver(PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS, this, false);
+ this._prefSvc.addObserver(PREF_FEED_SELECTED_APP, this, false);
+ this._prefSvc.addObserver(PREF_FEED_SELECTED_WEB, this, false);
+ this._prefSvc.addObserver(PREF_FEED_SELECTED_ACTION, this, false);
+ this._prefSvc.addObserver(PREF_FEED_SELECTED_READER, this, false);
+
+ this._prefSvc.addObserver(PREF_VIDEO_FEED_SELECTED_APP, this, false);
+ this._prefSvc.addObserver(PREF_VIDEO_FEED_SELECTED_WEB, this, false);
+ this._prefSvc.addObserver(PREF_VIDEO_FEED_SELECTED_ACTION, this, false);
+ this._prefSvc.addObserver(PREF_VIDEO_FEED_SELECTED_READER, this, false);
+
+ this._prefSvc.addObserver(PREF_AUDIO_FEED_SELECTED_APP, this, false);
+ this._prefSvc.addObserver(PREF_AUDIO_FEED_SELECTED_WEB, this, false);
+ this._prefSvc.addObserver(PREF_AUDIO_FEED_SELECTED_ACTION, this, false);
+ this._prefSvc.addObserver(PREF_AUDIO_FEED_SELECTED_READER, this, false);
+
+
+ // Listen for window unload so we can remove our preference observers.
+ window.addEventListener("unload", this, false);
+
+ // Figure out how we should be sorting the list. We persist sort settings
+ // across sessions, so we can't assume the default sort column/direction.
+ // XXX should we be using the XUL sort service instead?
+ if (document.getElementById("actionColumn").hasAttribute("sortDirection")) {
+ this._sortColumn = document.getElementById("actionColumn");
+ // The typeColumn element always has a sortDirection attribute,
+ // either because it was persisted or because the default value
+ // from the xul file was used. If we are sorting on the other
+ // column, we should remove it.
+ document.getElementById("typeColumn").removeAttribute("sortDirection");
+ }
+ else
+ this._sortColumn = document.getElementById("typeColumn");
+
+ // Load the data and build the list of handlers.
+ // By doing this in a timeout, we let the preferences dialog resize itself
+ // to an appropriate size before we add a bunch of items to the list.
+ // Otherwise, if there are many items, and the Applications prefpane
+ // is the one that gets displayed when the user first opens the dialog,
+ // the dialog might stretch too much in an attempt to fit them all in.
+ // XXX Shouldn't we perhaps just set a max-height on the richlistbox?
+ var _delayedPaneLoad = function(self) {
+ self._loadData();
+ self._rebuildVisibleTypes();
+ self._sortVisibleTypes();
+ self._rebuildView();
+
+ // Notify observers that the UI is now ready
+ Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService).
+ notifyObservers(window, "app-handler-pane-loaded", null);
+ }
+ setTimeout(_delayedPaneLoad, 0, this);
+ },
+
+ destroy: function() {
+ window.removeEventListener("unload", this, false);
+ this._prefSvc.removeObserver(PREF_SHOW_PLUGINS_IN_LIST, this);
+ this._prefSvc.removeObserver(PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS, this);
+ this._prefSvc.removeObserver(PREF_FEED_SELECTED_APP, this);
+ this._prefSvc.removeObserver(PREF_FEED_SELECTED_WEB, this);
+ this._prefSvc.removeObserver(PREF_FEED_SELECTED_ACTION, this);
+ this._prefSvc.removeObserver(PREF_FEED_SELECTED_READER, this);
+
+ this._prefSvc.removeObserver(PREF_VIDEO_FEED_SELECTED_APP, this);
+ this._prefSvc.removeObserver(PREF_VIDEO_FEED_SELECTED_WEB, this);
+ this._prefSvc.removeObserver(PREF_VIDEO_FEED_SELECTED_ACTION, this);
+ this._prefSvc.removeObserver(PREF_VIDEO_FEED_SELECTED_READER, this);
+
+ this._prefSvc.removeObserver(PREF_AUDIO_FEED_SELECTED_APP, this);
+ this._prefSvc.removeObserver(PREF_AUDIO_FEED_SELECTED_WEB, this);
+ this._prefSvc.removeObserver(PREF_AUDIO_FEED_SELECTED_ACTION, this);
+ this._prefSvc.removeObserver(PREF_AUDIO_FEED_SELECTED_READER, this);
+ },
+
+
+ //**************************************************************************//
+ // nsISupports
+
+ QueryInterface: function(aIID) {
+ if (aIID.equals(Ci.nsIObserver) ||
+ aIID.equals(Ci.nsIDOMEventListener ||
+ aIID.equals(Ci.nsISupports)))
+ return this;
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+
+ //**************************************************************************//
+ // nsIObserver
+
+ observe: function (aSubject, aTopic, aData) {
+ // Rebuild the list when there are changes to preferences that influence
+ // whether or not to show certain entries in the list.
+ if (aTopic == "nsPref:changed" && !this._storingAction) {
+ // These two prefs alter the list of visible types, so we have to rebuild
+ // that list when they change.
+ if (aData == PREF_SHOW_PLUGINS_IN_LIST ||
+ aData == PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS) {
+ this._rebuildVisibleTypes();
+ this._sortVisibleTypes();
+ }
+
+ // All the prefs we observe can affect what we display, so we rebuild
+ // the view when any of them changes.
+ this._rebuildView();
+ }
+ },
+
+
+ //**************************************************************************//
+ // nsIDOMEventListener
+
+ handleEvent: function(aEvent) {
+ if (aEvent.type == "unload") {
+ this.destroy();
+ }
+ },
+
+
+ //**************************************************************************//
+ // Composed Model Construction
+
+ _loadData: function() {
+ this._loadFeedHandler();
+ this._loadPluginHandlers();
+ this._loadApplicationHandlers();
+ },
+
+ _loadFeedHandler: function() {
+ this._handledTypes[TYPE_MAYBE_FEED] = feedHandlerInfo;
+ feedHandlerInfo.handledOnlyByPlugin = false;
+
+ this._handledTypes[TYPE_MAYBE_VIDEO_FEED] = videoFeedHandlerInfo;
+ videoFeedHandlerInfo.handledOnlyByPlugin = false;
+
+ this._handledTypes[TYPE_MAYBE_AUDIO_FEED] = audioFeedHandlerInfo;
+ audioFeedHandlerInfo.handledOnlyByPlugin = false;
+ },
+
+ /**
+ * Load the set of handlers defined by plugins.
+ *
+ * Note: if there's more than one plugin for a given MIME type, we assume
+ * the last one is the one that the application will use. That may not be
+ * correct, but it's how we've been doing it for years.
+ *
+ * Perhaps we should instead query navigator.mimeTypes for the set of types
+ * supported by the application and then get the plugin from each MIME type's
+ * enabledPlugin property. But if there's a plugin for a type, we need
+ * to know about it even if it isn't enabled, since we're going to give
+ * the user an option to enable it.
+ *
+ * Also note that enabledPlugin does not get updated when
+ * plugin.disable_full_page_plugin_for_types changes, so even if we could use
+ * enabledPlugin to get the plugin that would be used, we'd still need to
+ * check the pref ourselves to find out if it's enabled.
+ */
+ _loadPluginHandlers: function() {
+ "use strict";
+
+ let mimeTypes = navigator.mimeTypes;
+
+ for (let mimeType of mimeTypes) {
+ let handlerInfoWrapper;
+ if (mimeType.type in this._handledTypes) {
+ handlerInfoWrapper = this._handledTypes[mimeType.type];
+ } else {
+ let wrappedHandlerInfo =
+ this._mimeSvc.getFromTypeAndExtension(mimeType.type, null);
+ handlerInfoWrapper = new HandlerInfoWrapper(mimeType.type, wrappedHandlerInfo);
+ handlerInfoWrapper.handledOnlyByPlugin = true;
+ this._handledTypes[mimeType.type] = handlerInfoWrapper;
+ }
+ handlerInfoWrapper.pluginName = mimeType.enabledPlugin.name;
+ }
+ },
+
+ /**
+ * Load the set of handlers defined by the application datastore.
+ */
+ _loadApplicationHandlers: function() {
+ var wrappedHandlerInfos = this._handlerSvc.enumerate();
+ while (wrappedHandlerInfos.hasMoreElements()) {
+ let wrappedHandlerInfo =
+ wrappedHandlerInfos.getNext().QueryInterface(Ci.nsIHandlerInfo);
+ let type = wrappedHandlerInfo.type;
+
+ let handlerInfoWrapper;
+ if (type in this._handledTypes)
+ handlerInfoWrapper = this._handledTypes[type];
+ else {
+ handlerInfoWrapper = new HandlerInfoWrapper(type, wrappedHandlerInfo);
+ this._handledTypes[type] = handlerInfoWrapper;
+ }
+
+ handlerInfoWrapper.handledOnlyByPlugin = false;
+ }
+ },
+
+
+ //**************************************************************************//
+ // View Construction
+
+ _rebuildVisibleTypes: function() {
+ // Reset the list of visible types and the visible type description counts.
+ this._visibleTypes = [];
+ this._visibleTypeDescriptionCount = {};
+
+ // Get the preferences that help determine what types to show.
+ var showPlugins = this._prefSvc.getBoolPref(PREF_SHOW_PLUGINS_IN_LIST);
+ var hidePluginsWithoutExtensions =
+ this._prefSvc.getBoolPref(PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS);
+
+ for (let type in this._handledTypes) {
+ let handlerInfo = this._handledTypes[type];
+
+ // Hide plugins without associated extensions if so prefed so we don't
+ // show a whole bunch of obscure types handled by plugins on Mac.
+ // Note: though protocol types don't have extensions, we still show them;
+ // the pref is only meant to be applied to MIME types, since plugins are
+ // only associated with MIME types.
+ // FIXME: should we also check the "suffixes" property of the plugin?
+ // Filed as bug 395135.
+ if (hidePluginsWithoutExtensions && handlerInfo.handledOnlyByPlugin &&
+ handlerInfo.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo &&
+ !handlerInfo.primaryExtension)
+ continue;
+
+ // Hide types handled only by plugins if so prefed.
+ if (handlerInfo.handledOnlyByPlugin && !showPlugins)
+ continue;
+
+ // We couldn't find any reason to exclude the type, so include it.
+ this._visibleTypes.push(handlerInfo);
+
+ if (handlerInfo.description in this._visibleTypeDescriptionCount)
+ this._visibleTypeDescriptionCount[handlerInfo.description]++;
+ else
+ this._visibleTypeDescriptionCount[handlerInfo.description] = 1;
+ }
+ },
+
+ _rebuildView: function() {
+ // Clear the list of entries.
+ while (this._list.childNodes.length > 1)
+ this._list.removeChild(this._list.lastChild);
+
+ var visibleTypes = this._visibleTypes;
+
+ // If the user is filtering the list, then only show matching types.
+ if (this._filter.value)
+ visibleTypes = visibleTypes.filter(this._matchesFilter, this);
+
+ for each (let visibleType in visibleTypes) {
+ let item = document.createElement("richlistitem");
+ item.setAttribute("type", visibleType.type);
+ item.setAttribute("typeDescription", this._describeType(visibleType));
+ if (visibleType.smallIcon)
+ item.setAttribute("typeIcon", visibleType.smallIcon);
+ item.setAttribute("actionDescription",
+ this._describePreferredAction(visibleType));
+
+ if (!this._setIconClassForPreferredAction(visibleType, item)) {
+ item.setAttribute("actionIcon",
+ this._getIconURLForPreferredAction(visibleType));
+ }
+
+ this._list.appendChild(item);
+ }
+
+ this._selectLastSelectedType();
+ },
+
+ _matchesFilter: function(aType) {
+ var filterValue = this._filter.value.toLowerCase();
+ return this._describeType(aType).toLowerCase().indexOf(filterValue) != -1 ||
+ this._describePreferredAction(aType).toLowerCase().indexOf(filterValue) != -1;
+ },
+
+ /**
+ * Describe, in a human-readable fashion, the type represented by the given
+ * handler info object. Normally this is just the description provided by
+ * the info object, but if more than one object presents the same description,
+ * then we annotate the duplicate descriptions with the type itself to help
+ * users distinguish between those types.
+ *
+ * @param aHandlerInfo {nsIHandlerInfo} the type being described
+ * @returns {string} a description of the type
+ */
+ _describeType: function(aHandlerInfo) {
+ if (this._visibleTypeDescriptionCount[aHandlerInfo.description] > 1)
+ return this._prefsBundle.getFormattedString("typeDescriptionWithType",
+ [aHandlerInfo.description,
+ aHandlerInfo.type]);
+
+ return aHandlerInfo.description;
+ },
+
+ /**
+ * Describe, in a human-readable fashion, the preferred action to take on
+ * the type represented by the given handler info object.
+ *
+ * XXX Should this be part of the HandlerInfoWrapper interface? It would
+ * violate the separation of model and view, but it might make more sense
+ * nonetheless (f.e. it would make sortTypes easier).
+ *
+ * @param aHandlerInfo {nsIHandlerInfo} the type whose preferred action
+ * is being described
+ * @returns {string} a description of the action
+ */
+ _describePreferredAction: function(aHandlerInfo) {
+ // alwaysAskBeforeHandling overrides the preferred action, so if that flag
+ // is set, then describe that behavior instead. For most types, this is
+ // the "alwaysAsk" string, but for the feed type we show something special.
+ if (aHandlerInfo.alwaysAskBeforeHandling) {
+ if (isFeedType(aHandlerInfo.type))
+ return this._prefsBundle.getFormattedString("previewInApp",
+ [this._brandShortName]);
+ else
+ return this._prefsBundle.getString("alwaysAsk");
+ }
+
+ switch (aHandlerInfo.preferredAction) {
+ case Ci.nsIHandlerInfo.saveToDisk:
+ return this._prefsBundle.getString("saveFile");
+
+ case Ci.nsIHandlerInfo.useHelperApp:
+ var preferredApp = aHandlerInfo.preferredApplicationHandler;
+ var name;
+ if (preferredApp instanceof Ci.nsILocalHandlerApp)
+ name = getFileDisplayName(preferredApp.executable);
+ else
+ name = preferredApp.name;
+ return this._prefsBundle.getFormattedString("useApp", [name]);
+
+ case Ci.nsIHandlerInfo.handleInternally:
+ // For the feed type, handleInternally means live bookmarks.
+ if (isFeedType(aHandlerInfo.type)) {
+ return this._prefsBundle.getFormattedString("addLiveBookmarksInApp",
+ [this._brandShortName]);
+ }
+
+ if (aHandlerInfo instanceof InternalHandlerInfoWrapper) {
+ return this._prefsBundle.getFormattedString("previewInApp",
+ [this._brandShortName]);
+ }
+
+ // For other types, handleInternally looks like either useHelperApp
+ // or useSystemDefault depending on whether or not there's a preferred
+ // handler app.
+ if (this.isValidHandlerApp(aHandlerInfo.preferredApplicationHandler))
+ return aHandlerInfo.preferredApplicationHandler.name;
+
+ return aHandlerInfo.defaultDescription;
+
+ // XXX Why don't we say the app will handle the type internally?
+ // Is it because the app can't actually do that? But if that's true,
+ // then why would a preferredAction ever get set to this value
+ // in the first place?
+
+ case Ci.nsIHandlerInfo.useSystemDefault:
+ return this._prefsBundle.getFormattedString("useDefault",
+ [aHandlerInfo.defaultDescription]);
+
+ case kActionUsePlugin:
+ return this._prefsBundle.getFormattedString("usePluginIn",
+ [aHandlerInfo.pluginName,
+ this._brandShortName]);
+ }
+ },
+
+ _selectLastSelectedType: function() {
+ // If the list is disabled by the pref.downloads.disable_button.edit_actions
+ // preference being locked, then don't select the type, as that would cause
+ // it to appear selected, with a different background and an actions menu
+ // that makes it seem like you can choose an action for the type.
+ if (this._list.disabled)
+ return;
+
+ var lastSelectedType = this._list.getAttribute("lastSelectedType");
+ if (!lastSelectedType)
+ return;
+
+ var item = this._list.getElementsByAttribute("type", lastSelectedType)[0];
+ if (!item)
+ return;
+
+ this._list.selectedItem = item;
+ },
+
+ /**
+ * Whether or not the given handler app is valid.
+ *
+ * @param aHandlerApp {nsIHandlerApp} the handler app in question
+ *
+ * @returns {boolean} whether or not it's valid
+ */
+ isValidHandlerApp: function(aHandlerApp) {
+ if (!aHandlerApp)
+ return false;
+
+ if (aHandlerApp instanceof Ci.nsILocalHandlerApp)
+ return this._isValidHandlerExecutable(aHandlerApp.executable);
+
+ if (aHandlerApp instanceof Ci.nsIWebHandlerApp)
+ return aHandlerApp.uriTemplate;
+
+ if (aHandlerApp instanceof Ci.nsIWebContentHandlerInfo)
+ return aHandlerApp.uri;
+
+ return false;
+ },
+
+ _isValidHandlerExecutable: function(aExecutable) {
+ return aExecutable &&
+ aExecutable.exists() &&
+ aExecutable.isExecutable() &&
+// XXXben - we need to compare this with the running instance executable
+// just don't know how to do that via script...
+// XXXmano TBD: can probably add this to nsIShellService
+#ifdef XP_WIN
+#expand aExecutable.leafName != "__MOZ_APP_NAME__.exe";
+#else
+#ifdef XP_MACOSX
+#expand aExecutable.leafName != "__MOZ_MACBUNDLE_NAME__";
+#else
+#expand aExecutable.leafName != "__MOZ_APP_NAME__-bin";
+#endif
+#endif
+ },
+
+ /**
+ * Rebuild the actions menu for the selected entry. Gets called by
+ * the richlistitem constructor when an entry in the list gets selected.
+ */
+ rebuildActionsMenu: function() {
+ var typeItem = this._list.selectedItem;
+ var handlerInfo = this._handledTypes[typeItem.type];
+ var menu =
+ document.getAnonymousElementByAttribute(typeItem, "class", "actionsMenu");
+ var menuPopup = menu.menupopup;
+
+ // Clear out existing items.
+ while (menuPopup.hasChildNodes())
+ menuPopup.removeChild(menuPopup.lastChild);
+
+ // Add the "Preview in Firefox" option for optional internal handlers.
+ if (handlerInfo instanceof InternalHandlerInfoWrapper) {
+ var internalMenuItem = document.createElement("menuitem");
+ internalMenuItem.setAttribute("action", Ci.nsIHandlerInfo.handleInternally);
+ let label = this._prefsBundle.getFormattedString("previewInApp",
+ [this._brandShortName]);
+ internalMenuItem.setAttribute("label", label);
+ internalMenuItem.setAttribute("tooltiptext", label);
+ internalMenuItem.setAttribute(APP_ICON_ATTR_NAME, "ask");
+ menuPopup.appendChild(internalMenuItem);
+ }
+
+ {
+ var askMenuItem = document.createElement("menuitem");
+ askMenuItem.setAttribute("action", Ci.nsIHandlerInfo.alwaysAsk);
+ let label;
+ if (isFeedType(handlerInfo.type))
+ label = this._prefsBundle.getFormattedString("previewInApp",
+ [this._brandShortName]);
+ else
+ label = this._prefsBundle.getString("alwaysAsk");
+ askMenuItem.setAttribute("label", label);
+ askMenuItem.setAttribute("tooltiptext", label);
+ askMenuItem.setAttribute(APP_ICON_ATTR_NAME, "ask");
+ menuPopup.appendChild(askMenuItem);
+ }
+
+ // Create a menu item for saving to disk.
+ // Note: this option isn't available to protocol types, since we don't know
+ // what it means to save a URL having a certain scheme to disk, nor is it
+ // available to feeds, since the feed code doesn't implement the capability.
+ if ((handlerInfo.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo) &&
+ !isFeedType(handlerInfo.type)) {
+ var saveMenuItem = document.createElement("menuitem");
+ saveMenuItem.setAttribute("action", Ci.nsIHandlerInfo.saveToDisk);
+ let label = this._prefsBundle.getString("saveFile");
+ saveMenuItem.setAttribute("label", label);
+ saveMenuItem.setAttribute("tooltiptext", label);
+ saveMenuItem.setAttribute(APP_ICON_ATTR_NAME, "save");
+ menuPopup.appendChild(saveMenuItem);
+ }
+
+ // If this is the feed type, add a Live Bookmarks item.
+ if (isFeedType(handlerInfo.type)) {
+ var internalMenuItem = document.createElement("menuitem");
+ internalMenuItem.setAttribute("action", Ci.nsIHandlerInfo.handleInternally);
+ let label = this._prefsBundle.getFormattedString("addLiveBookmarksInApp",
+ [this._brandShortName]);
+ internalMenuItem.setAttribute("label", label);
+ internalMenuItem.setAttribute("tooltiptext", label);
+ internalMenuItem.setAttribute(APP_ICON_ATTR_NAME, "feed");
+ menuPopup.appendChild(internalMenuItem);
+ }
+
+ // Add a separator to distinguish these items from the helper app items
+ // that follow them.
+ let menuItem = document.createElement("menuseparator");
+ menuPopup.appendChild(menuItem);
+
+ // Create a menu item for the OS default application, if any.
+ if (handlerInfo.hasDefaultHandler) {
+ var defaultMenuItem = document.createElement("menuitem");
+ defaultMenuItem.setAttribute("action", Ci.nsIHandlerInfo.useSystemDefault);
+ let label = this._prefsBundle.getFormattedString("useDefault",
+ [handlerInfo.defaultDescription]);
+ defaultMenuItem.setAttribute("label", label);
+ defaultMenuItem.setAttribute("tooltiptext", handlerInfo.defaultDescription);
+ defaultMenuItem.setAttribute("image", this._getIconURLForSystemDefault(handlerInfo));
+
+ menuPopup.appendChild(defaultMenuItem);
+ }
+
+ // Create menu items for possible handlers.
+ let preferredApp = handlerInfo.preferredApplicationHandler;
+ let possibleApps = handlerInfo.possibleApplicationHandlers.enumerate();
+ var possibleAppMenuItems = [];
+ while (possibleApps.hasMoreElements()) {
+ let possibleApp = possibleApps.getNext();
+ if (!this.isValidHandlerApp(possibleApp))
+ continue;
+
+ let menuItem = document.createElement("menuitem");
+ menuItem.setAttribute("action", Ci.nsIHandlerInfo.useHelperApp);
+ let label;
+ if (possibleApp instanceof Ci.nsILocalHandlerApp)
+ label = getFileDisplayName(possibleApp.executable);
+ else
+ label = possibleApp.name;
+ label = this._prefsBundle.getFormattedString("useApp", [label]);
+ menuItem.setAttribute("label", label);
+ menuItem.setAttribute("tooltiptext", label);
+ menuItem.setAttribute("image", this._getIconURLForHandlerApp(possibleApp));
+
+ // Attach the handler app object to the menu item so we can use it
+ // to make changes to the datastore when the user selects the item.
+ menuItem.handlerApp = possibleApp;
+
+ menuPopup.appendChild(menuItem);
+ possibleAppMenuItems.push(menuItem);
+ }
+
+ // Create a menu item for the plugin.
+ if (handlerInfo.pluginName) {
+ var pluginMenuItem = document.createElement("menuitem");
+ pluginMenuItem.setAttribute("action", kActionUsePlugin);
+ let label = this._prefsBundle.getFormattedString("usePluginIn",
+ [handlerInfo.pluginName,
+ this._brandShortName]);
+ pluginMenuItem.setAttribute("label", label);
+ pluginMenuItem.setAttribute("tooltiptext", label);
+ pluginMenuItem.setAttribute(APP_ICON_ATTR_NAME, "plugin");
+ menuPopup.appendChild(pluginMenuItem);
+ }
+
+ // Create a menu item for selecting a local application.
+#ifdef XP_WIN
+ // On Windows, selecting an application to open another application
+ // would be meaningless so we special case executables.
+ var executableType = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService)
+ .getTypeFromExtension("exe");
+ if (handlerInfo.type != executableType)
+#endif
+ {
+ let menuItem = document.createElement("menuitem");
+ menuItem.setAttribute("oncommand", "gApplicationsPane.chooseApp(event)");
+ let label = this._prefsBundle.getString("useOtherApp");
+ menuItem.setAttribute("label", label);
+ menuItem.setAttribute("tooltiptext", label);
+ menuPopup.appendChild(menuItem);
+ }
+
+ // Create a menu item for managing applications.
+ if (possibleAppMenuItems.length) {
+ let menuItem = document.createElement("menuseparator");
+ menuPopup.appendChild(menuItem);
+ menuItem = document.createElement("menuitem");
+ menuItem.setAttribute("oncommand", "gApplicationsPane.manageApp(event)");
+ menuItem.setAttribute("label", this._prefsBundle.getString("manageApp"));
+ menuPopup.appendChild(menuItem);
+ }
+
+ // Select the item corresponding to the preferred action. If the always
+ // ask flag is set, it overrides the preferred action. Otherwise we pick
+ // the item identified by the preferred action (when the preferred action
+ // is to use a helper app, we have to pick the specific helper app item).
+ if (handlerInfo.alwaysAskBeforeHandling)
+ menu.selectedItem = askMenuItem;
+ else switch (handlerInfo.preferredAction) {
+ case Ci.nsIHandlerInfo.handleInternally:
+ menu.selectedItem = internalMenuItem;
+ break;
+ case Ci.nsIHandlerInfo.useSystemDefault:
+ menu.selectedItem = defaultMenuItem;
+ break;
+ case Ci.nsIHandlerInfo.useHelperApp:
+ if (preferredApp)
+ menu.selectedItem =
+ possibleAppMenuItems.filter(function(v) v.handlerApp.equals(preferredApp))[0];
+ break;
+ case kActionUsePlugin:
+ menu.selectedItem = pluginMenuItem;
+ break;
+ case Ci.nsIHandlerInfo.saveToDisk:
+ menu.selectedItem = saveMenuItem;
+ break;
+ }
+ },
+
+
+ //**************************************************************************//
+ // Sorting & Filtering
+
+ _sortColumn: null,
+
+ /**
+ * Sort the list when the user clicks on a column header.
+ */
+ sort: function (event) {
+ var column = event.target;
+
+ // If the user clicked on a new sort column, remove the direction indicator
+ // from the old column.
+ if (this._sortColumn && this._sortColumn != column)
+ this._sortColumn.removeAttribute("sortDirection");
+
+ this._sortColumn = column;
+
+ // Set (or switch) the sort direction indicator.
+ if (column.getAttribute("sortDirection") == "ascending")
+ column.setAttribute("sortDirection", "descending");
+ else
+ column.setAttribute("sortDirection", "ascending");
+
+ this._sortVisibleTypes();
+ this._rebuildView();
+ },
+
+ /**
+ * Sort the list of visible types by the current sort column/direction.
+ */
+ _sortVisibleTypes: function() {
+ if (!this._sortColumn)
+ return;
+
+ var t = this;
+
+ function sortByType(a, b) {
+ return t._describeType(a).toLowerCase().
+ localeCompare(t._describeType(b).toLowerCase());
+ }
+
+ function sortByAction(a, b) {
+ return t._describePreferredAction(a).toLowerCase().
+ localeCompare(t._describePreferredAction(b).toLowerCase());
+ }
+
+ switch (this._sortColumn.getAttribute("value")) {
+ case "type":
+ this._visibleTypes.sort(sortByType);
+ break;
+ case "action":
+ this._visibleTypes.sort(sortByAction);
+ break;
+ }
+
+ if (this._sortColumn.getAttribute("sortDirection") == "descending")
+ this._visibleTypes.reverse();
+ },
+
+ /**
+ * Filter the list when the user enters a filter term into the filter field.
+ */
+ filter: function() {
+ this._rebuildView();
+ },
+
+ focusFilterBox: function() {
+ this._filter.focus();
+ this._filter.select();
+ },
+
+
+ //**************************************************************************//
+ // Changes
+
+ onSelectAction: function(aActionItem) {
+ this._storingAction = true;
+
+ try {
+ this._storeAction(aActionItem);
+ }
+ finally {
+ this._storingAction = false;
+ }
+ },
+
+ _storeAction: function(aActionItem) {
+ var typeItem = this._list.selectedItem;
+ var handlerInfo = this._handledTypes[typeItem.type];
+
+ let action = parseInt(aActionItem.getAttribute("action"));
+
+ // Set the plugin state if we're enabling or disabling a plugin.
+ if (action == kActionUsePlugin)
+ handlerInfo.enablePluginType();
+ else if (handlerInfo.pluginName && !handlerInfo.isDisabledPluginType)
+ handlerInfo.disablePluginType();
+
+ // Set the preferred application handler.
+ // We leave the existing preferred app in the list when we set
+ // the preferred action to something other than useHelperApp so that
+ // legacy datastores that don't have the preferred app in the list
+ // of possible apps still include the preferred app in the list of apps
+ // the user can choose to handle the type.
+ if (action == Ci.nsIHandlerInfo.useHelperApp)
+ handlerInfo.preferredApplicationHandler = aActionItem.handlerApp;
+
+ // Set the "always ask" flag.
+ if (action == Ci.nsIHandlerInfo.alwaysAsk)
+ handlerInfo.alwaysAskBeforeHandling = true;
+ else
+ handlerInfo.alwaysAskBeforeHandling = false;
+
+ // Set the preferred action.
+ handlerInfo.preferredAction = action;
+
+ handlerInfo.store();
+
+ // Make sure the handler info object is flagged to indicate that there is
+ // now some user configuration for the type.
+ handlerInfo.handledOnlyByPlugin = false;
+
+ // Update the action label and image to reflect the new preferred action.
+ typeItem.setAttribute("actionDescription",
+ this._describePreferredAction(handlerInfo));
+ if (!this._setIconClassForPreferredAction(handlerInfo, typeItem)) {
+ typeItem.setAttribute("actionIcon",
+ this._getIconURLForPreferredAction(handlerInfo));
+ }
+ },
+
+ manageApp: function(aEvent) {
+ // Don't let the normal "on select action" handler get this event,
+ // as we handle it specially ourselves.
+ aEvent.stopPropagation();
+
+ var typeItem = this._list.selectedItem;
+ var handlerInfo = this._handledTypes[typeItem.type];
+
+ document.documentElement.openSubDialog("chrome://browser/content/preferences/applicationManager.xul",
+ "", handlerInfo);
+
+ // Rebuild the actions menu so that we revert to the previous selection,
+ // or "Always ask" if the previous default application has been removed
+ this.rebuildActionsMenu();
+
+ // update the richlistitem too. Will be visible when selecting another row
+ typeItem.setAttribute("actionDescription",
+ this._describePreferredAction(handlerInfo));
+ if (!this._setIconClassForPreferredAction(handlerInfo, typeItem)) {
+ typeItem.setAttribute("actionIcon",
+ this._getIconURLForPreferredAction(handlerInfo));
+ }
+ },
+
+ chooseApp: function(aEvent) {
+ // Don't let the normal "on select action" handler get this event,
+ // as we handle it specially ourselves.
+ aEvent.stopPropagation();
+
+ var handlerApp;
+ let chooseAppCallback = function(aHandlerApp) {
+ // Rebuild the actions menu whether the user picked an app or canceled.
+ // If they picked an app, we want to add the app to the menu and select it.
+ // If they canceled, we want to go back to their previous selection.
+ this.rebuildActionsMenu();
+
+ // If the user picked a new app from the menu, select it.
+ if (aHandlerApp) {
+ let typeItem = this._list.selectedItem;
+ let actionsMenu =
+ document.getAnonymousElementByAttribute(typeItem, "class", "actionsMenu");
+ let menuItems = actionsMenu.menupopup.childNodes;
+ for (let i = 0; i < menuItems.length; i++) {
+ let menuItem = menuItems[i];
+ if (menuItem.handlerApp && menuItem.handlerApp.equals(aHandlerApp)) {
+ actionsMenu.selectedIndex = i;
+ this.onSelectAction(menuItem);
+ break;
+ }
+ }
+ }
+ }.bind(this);
+
+#ifdef XP_WIN
+ var params = {};
+ var handlerInfo = this._handledTypes[this._list.selectedItem.type];
+
+ if (isFeedType(handlerInfo.type)) {
+ // MIME info will be null, create a temp object.
+ params.mimeInfo = this._mimeSvc.getFromTypeAndExtension(handlerInfo.type,
+ handlerInfo.primaryExtension);
+ } else {
+ params.mimeInfo = handlerInfo.wrappedHandlerInfo;
+ }
+
+ params.title = this._prefsBundle.getString("fpTitleChooseApp");
+ params.description = handlerInfo.description;
+ params.filename = null;
+ params.handlerApp = null;
+
+ window.openDialog("chrome://global/content/appPicker.xul", null,
+ "chrome,modal,centerscreen,titlebar,dialog=yes",
+ params);
+
+ if (this.isValidHandlerApp(params.handlerApp)) {
+ handlerApp = params.handlerApp;
+
+ // Add the app to the type's list of possible handlers.
+ handlerInfo.addPossibleApplicationHandler(handlerApp);
+ }
+
+ chooseAppCallback(handlerApp);
+#else
+ let winTitle = this._prefsBundle.getString("fpTitleChooseApp");
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ let fpCallback = function fpCallback_done(aResult) {
+ if (aResult == Ci.nsIFilePicker.returnOK && fp.file &&
+ this._isValidHandlerExecutable(fp.file)) {
+ handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
+ createInstance(Ci.nsILocalHandlerApp);
+ handlerApp.name = getFileDisplayName(fp.file);
+ handlerApp.executable = fp.file;
+
+ // Add the app to the type's list of possible handlers.
+ let handlerInfo = this._handledTypes[this._list.selectedItem.type];
+ handlerInfo.addPossibleApplicationHandler(handlerApp);
+
+ chooseAppCallback(handlerApp);
+ }
+ }.bind(this);
+
+ // Prompt the user to pick an app. If they pick one, and it's a valid
+ // selection, then add it to the list of possible handlers.
+ fp.init(window, winTitle, Ci.nsIFilePicker.modeOpen);
+ fp.appendFilters(Ci.nsIFilePicker.filterApps);
+ fp.open(fpCallback);
+#endif
+ },
+
+ // Mark which item in the list was last selected so we can reselect it
+ // when we rebuild the list or when the user returns to the prefpane.
+ onSelectionChanged: function() {
+ if (this._list.selectedItem)
+ this._list.setAttribute("lastSelectedType",
+ this._list.selectedItem.getAttribute("type"));
+ },
+
+ _setIconClassForPreferredAction: function(aHandlerInfo, aElement) {
+ // If this returns true, the attribute that CSS sniffs for was set to something
+ // so you shouldn't manually set an icon URI.
+ // This removes the existing actionIcon attribute if any, even if returning false.
+ aElement.removeAttribute("actionIcon");
+
+ if (aHandlerInfo.alwaysAskBeforeHandling) {
+ aElement.setAttribute(APP_ICON_ATTR_NAME, "ask");
+ return true;
+ }
+
+ switch (aHandlerInfo.preferredAction) {
+ case Ci.nsIHandlerInfo.saveToDisk:
+ aElement.setAttribute(APP_ICON_ATTR_NAME, "save");
+ return true;
+
+ case Ci.nsIHandlerInfo.handleInternally:
+ if (isFeedType(aHandlerInfo.type)) {
+ aElement.setAttribute(APP_ICON_ATTR_NAME, "feed");
+ return true;
+ } else if (aHandlerInfo instanceof InternalHandlerInfoWrapper) {
+ aElement.setAttribute(APP_ICON_ATTR_NAME, "ask");
+ return true;
+ }
+ break;
+
+ case kActionUsePlugin:
+ aElement.setAttribute(APP_ICON_ATTR_NAME, "plugin");
+ return true;
+ }
+ aElement.removeAttribute(APP_ICON_ATTR_NAME);
+ return false;
+ },
+
+ _getIconURLForPreferredAction: function(aHandlerInfo) {
+ switch (aHandlerInfo.preferredAction) {
+ case Ci.nsIHandlerInfo.useSystemDefault:
+ return this._getIconURLForSystemDefault(aHandlerInfo);
+
+ case Ci.nsIHandlerInfo.useHelperApp:
+ let preferredApp = aHandlerInfo.preferredApplicationHandler;
+ if (this.isValidHandlerApp(preferredApp))
+ return this._getIconURLForHandlerApp(preferredApp);
+ break;
+
+ // This should never happen, but if preferredAction is set to some weird
+ // value, then fall back to the generic application icon.
+ default:
+ return ICON_URL_APP;
+ }
+ },
+
+ _getIconURLForHandlerApp: function(aHandlerApp) {
+ if (aHandlerApp instanceof Ci.nsILocalHandlerApp)
+ return this._getIconURLForFile(aHandlerApp.executable);
+
+ if (aHandlerApp instanceof Ci.nsIWebHandlerApp)
+ return this._getIconURLForWebApp(aHandlerApp.uriTemplate);
+
+ if (aHandlerApp instanceof Ci.nsIWebContentHandlerInfo)
+ return this._getIconURLForWebApp(aHandlerApp.uri)
+
+ // We know nothing about other kinds of handler apps.
+ return "";
+ },
+
+ _getIconURLForFile: function(aFile) {
+ var fph = this._ioSvc.getProtocolHandler("file").
+ QueryInterface(Ci.nsIFileProtocolHandler);
+ var urlSpec = fph.getURLSpecFromFile(aFile);
+
+ return "moz-icon://" + urlSpec + "?size=16";
+ },
+
+ _getIconURLForWebApp: function(aWebAppURITemplate) {
+ var uri = this._ioSvc.newURI(aWebAppURITemplate, null, null);
+
+ // Unfortunately we can't use the favicon service to get the favicon,
+ // because the service looks for a record with the exact URL we give it, and
+ // users won't have such records for URLs they don't visit, and users won't
+ // visit the handler's URL template, they'll only visit URLs derived from
+ // that template (i.e. with %s in the template replaced by the URL of the
+ // content being handled).
+
+ if (/^https?$/.test(uri.scheme) && this._prefSvc.getBoolPref("browser.chrome.favicons"))
+ return uri.prePath + "/favicon.ico";
+
+ return "";
+ },
+
+ _getIconURLForSystemDefault: function(aHandlerInfo) {
+ // Handler info objects for MIME types on some OSes implement a property bag
+ // interface from which we can get an icon for the default app, so if we're
+ // dealing with a MIME type on one of those OSes, then try to get the icon.
+ if ("wrappedHandlerInfo" in aHandlerInfo) {
+ let wrappedHandlerInfo = aHandlerInfo.wrappedHandlerInfo;
+
+ if (wrappedHandlerInfo instanceof Ci.nsIMIMEInfo &&
+ wrappedHandlerInfo instanceof Ci.nsIPropertyBag) {
+ try {
+ let url = wrappedHandlerInfo.getProperty("defaultApplicationIconURL");
+ if (url)
+ return url + "?size=16";
+ }
+ catch(ex) {}
+ }
+ }
+
+ // If this isn't a MIME type object on an OS that supports retrieving
+ // the icon, or if we couldn't retrieve the icon for some other reason,
+ // then use a generic icon.
+ return ICON_URL_APP;
+ }
+
+};
diff --git a/components/preferences/applications.xul b/components/preferences/applications.xul
new file mode 100644
index 0000000..2e6fa54
--- /dev/null
+++ b/components/preferences/applications.xul
@@ -0,0 +1,99 @@
+<?xml version="1.0"?>
+
+<!-- -*- 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/. -->
+
+<!DOCTYPE overlay [
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+ <!ENTITY % applicationsDTD SYSTEM "chrome://browser/locale/preferences/applications.dtd">
+ %brandDTD;
+ %applicationsDTD;
+]>
+
+<overlay id="ApplicationsPaneOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <prefpane id="paneApplications"
+ onpaneload="gApplicationsPane.init();"
+ flex="1"
+ helpTopic="prefs-applications">
+
+ <preferences id="feedsPreferences">
+ <preference id="browser.feeds.handler"
+ name="browser.feeds.handler"
+ type="string"/>
+ <preference id="browser.feeds.handler.default"
+ name="browser.feeds.handler.default"
+ type="string"/>
+ <preference id="browser.feeds.handlers.application"
+ name="browser.feeds.handlers.application"
+ type="file"/>
+ <preference id="browser.feeds.handlers.webservice"
+ name="browser.feeds.handlers.webservice"
+ type="string"/>
+
+ <preference id="browser.videoFeeds.handler"
+ name="browser.videoFeeds.handler"
+ type="string"/>
+ <preference id="browser.videoFeeds.handler.default"
+ name="browser.videoFeeds.handler.default"
+ type="string"/>
+ <preference id="browser.videoFeeds.handlers.application"
+ name="browser.videoFeeds.handlers.application"
+ type="file"/>
+ <preference id="browser.videoFeeds.handlers.webservice"
+ name="browser.videoFeeds.handlers.webservice"
+ type="string"/>
+
+ <preference id="browser.audioFeeds.handler"
+ name="browser.audioFeeds.handler"
+ type="string"/>
+ <preference id="browser.audioFeeds.handler.default"
+ name="browser.audioFeeds.handler.default"
+ type="string"/>
+ <preference id="browser.audioFeeds.handlers.application"
+ name="browser.audioFeeds.handlers.application"
+ type="file"/>
+ <preference id="browser.audioFeeds.handlers.webservice"
+ name="browser.audioFeeds.handlers.webservice"
+ type="string"/>
+
+ <preference id="pref.downloads.disable_button.edit_actions"
+ name="pref.downloads.disable_button.edit_actions"
+ type="bool"/>
+ </preferences>
+
+ <script type="application/javascript" src="chrome://browser/content/preferences/applications.js"/>
+
+ <keyset>
+ <key key="&focusSearch1.key;" modifiers="accel" oncommand="gApplicationsPane.focusFilterBox();"/>
+ <key key="&focusSearch2.key;" modifiers="accel" oncommand="gApplicationsPane.focusFilterBox();"/>
+ </keyset>
+
+ <hbox>
+ <textbox id="filter" flex="1"
+ type="search"
+ placeholder="&filter.emptytext;"
+ aria-controls="handlersView"
+ oncommand="gApplicationsPane.filter();"/>
+ </hbox>
+
+ <separator class="thin"/>
+
+ <richlistbox id="handlersView" orient="vertical" persist="lastSelectedType"
+ preference="pref.downloads.disable_button.edit_actions"
+ onselect="gApplicationsPane.onSelectionChanged();">
+ <listheader equalsize="always" style="border: 0; padding: 0; -moz-appearance: none;">
+ <treecol id="typeColumn" label="&typeColumn.label;" value="type"
+ accesskey="&typeColumn.accesskey;" persist="sortDirection"
+ flex="1" onclick="gApplicationsPane.sort(event);"
+ sortDirection="ascending"/>
+ <treecol id="actionColumn" label="&actionColumn2.label;" value="action"
+ accesskey="&actionColumn2.accesskey;" persist="sortDirection"
+ flex="1" onclick="gApplicationsPane.sort(event);"/>
+ </listheader>
+ </richlistbox>
+ </prefpane>
+</overlay>
diff --git a/components/preferences/colors.xul b/components/preferences/colors.xul
new file mode 100644
index 0000000..f2109ae
--- /dev/null
+++ b/components/preferences/colors.xul
@@ -0,0 +1,102 @@
+<?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/" type="text/css"?>
+#ifdef XP_MACOSX
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?>
+#endif
+
+<!DOCTYPE prefwindow SYSTEM "chrome://browser/locale/preferences/colors.dtd" >
+
+<prefwindow id="ColorsDialog" type="child"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&colorsDialog.title;"
+ dlgbuttons="accept,cancel,help"
+ ondialoghelp="openPrefsHelp()"
+#ifdef XP_MACOSX
+ style="width: &window.macWidth; !important;">
+#else
+ style="width: &window.width; !important;">
+#endif
+
+ <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
+ <prefpane id="ColorsDialogPane"
+ helpTopic="prefs-fonts-and-colors">
+
+ <preferences>
+ <preference id="browser.display.document_color_use" name="browser.display.document_color_use" type="int"/>
+ <preference id="browser.anchor_color" name="browser.anchor_color" type="string"/>
+ <preference id="browser.visited_color" name="browser.visited_color" type="string"/>
+ <preference id="browser.underline_anchors" name="browser.underline_anchors" type="bool"/>
+ <preference id="browser.display.foreground_color" name="browser.display.foreground_color" type="string"/>
+ <preference id="browser.display.background_color" name="browser.display.background_color" type="string"/>
+ <preference id="browser.display.use_system_colors" name="browser.display.use_system_colors" type="bool"/>
+ </preferences>
+
+ <hbox>
+ <groupbox flex="1">
+ <caption label="&color;"/>
+ <hbox align="center">
+ <label value="&textColor.label;" accesskey="&textColor.accesskey;" control="foregroundtextmenu"/>
+ <spacer flex="1"/>
+ <colorpicker type="button" id="foregroundtextmenu" palettename="standard"
+ preference="browser.display.foreground_color"/>
+ </hbox>
+ <hbox align="center" style="margin-top: 5px">
+ <label value="&backgroundColor.label;" accesskey="&backgroundColor.accesskey;" control="backgroundmenu"/>
+ <spacer flex="1"/>
+ <colorpicker type="button" id="backgroundmenu" palettename="standard"
+ preference="browser.display.background_color"/>
+ </hbox>
+ <separator class="thin"/>
+ <hbox align="center">
+ <checkbox id="browserUseSystemColors" label="&useSystemColors.label;" accesskey="&useSystemColors.accesskey;"
+ preference="browser.display.use_system_colors"/>
+ </hbox>
+ </groupbox>
+
+ <groupbox flex="1">
+ <caption label="&links;"/>
+ <hbox align="center">
+ <label value="&linkColor.label;" accesskey="&linkColor.accesskey;" control="unvisitedlinkmenu"/>
+ <spacer flex="1"/>
+ <colorpicker type="button" id="unvisitedlinkmenu" palettename="standard"
+ preference="browser.anchor_color"/>
+ </hbox>
+ <hbox align="center" style="margin-top: 5px">
+ <label value="&visitedLinkColor.label;" accesskey="&visitedLinkColor.accesskey;" control="visitedlinkmenu"/>
+ <spacer flex="1"/>
+ <colorpicker type="button" id="visitedlinkmenu" palettename="standard"
+ preference="browser.visited_color"/>
+ </hbox>
+ <separator class="thin"/>
+ <hbox align="center">
+ <checkbox id="browserUnderlineAnchors" label="&underlineLinks.label;" accesskey="&underlineLinks.accesskey;"
+ preference="browser.underline_anchors"/>
+ </hbox>
+ </groupbox>
+ </hbox>
+#ifdef XP_WIN
+ <vbox align="start">
+#else
+ <vbox>
+#endif
+ <label accesskey="&overridePageColors.accesskey;"
+ control="useDocumentColors">&overridePageColors.label;</label>
+ <menulist id="useDocumentColors" preference="browser.display.document_color_use">
+ <menupopup>
+ <menuitem label="&overridePageColors.always.label;"
+ value="2" id="documentColorAlways"/>
+ <menuitem label="&overridePageColors.auto.label;"
+ value="0" id="documentColorAutomatic"/>
+ <menuitem label="&overridePageColors.never.label;"
+ value="1" id="documentColorNever"/>
+ </menupopup>
+ </menulist>
+ </vbox>
+ </prefpane>
+</prefwindow>
diff --git a/components/preferences/connection.js b/components/preferences/connection.js
new file mode 100644
index 0000000..da038c9
--- /dev/null
+++ b/components/preferences/connection.js
@@ -0,0 +1,200 @@
+/* -*- 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/. */
+
+var gConnectionsDialog = {
+ beforeAccept: function ()
+ {
+ var proxyTypePref = document.getElementById("network.proxy.type");
+ if (proxyTypePref.value == 2) {
+ this.doAutoconfigURLFixup();
+ return true;
+ }
+
+ if (proxyTypePref.value != 1)
+ return true;
+
+ var httpProxyURLPref = document.getElementById("network.proxy.http");
+ var httpProxyPortPref = document.getElementById("network.proxy.http_port");
+ var shareProxiesPref = document.getElementById("network.proxy.share_proxy_settings");
+ if (shareProxiesPref.value) {
+ var proxyPrefs = ["ssl", "ftp", "socks"];
+ for (var i = 0; i < proxyPrefs.length; ++i) {
+ var proxyServerURLPref = document.getElementById("network.proxy." + proxyPrefs[i]);
+ var proxyPortPref = document.getElementById("network.proxy." + proxyPrefs[i] + "_port");
+ var backupServerURLPref = document.getElementById("network.proxy.backup." + proxyPrefs[i]);
+ var backupPortPref = document.getElementById("network.proxy.backup." + proxyPrefs[i] + "_port");
+ backupServerURLPref.value = proxyServerURLPref.value;
+ backupPortPref.value = proxyPortPref.value;
+ proxyServerURLPref.value = httpProxyURLPref.value;
+ proxyPortPref.value = httpProxyPortPref.value;
+ }
+ }
+
+ this.sanitizeNoProxiesPref();
+
+ return true;
+ },
+
+ checkForSystemProxy: function ()
+ {
+ if ("@mozilla.org/system-proxy-settings;1" in Components.classes)
+ document.getElementById("systemPref").removeAttribute("hidden");
+ },
+
+ proxyTypeChanged: function ()
+ {
+ var proxyTypePref = document.getElementById("network.proxy.type");
+
+ // Update http
+ var httpProxyURLPref = document.getElementById("network.proxy.http");
+ httpProxyURLPref.disabled = proxyTypePref.value != 1;
+ var httpProxyPortPref = document.getElementById("network.proxy.http_port");
+ httpProxyPortPref.disabled = proxyTypePref.value != 1;
+
+ // Now update the other protocols
+ this.updateProtocolPrefs();
+
+ var shareProxiesPref = document.getElementById("network.proxy.share_proxy_settings");
+ shareProxiesPref.disabled = proxyTypePref.value != 1;
+
+ var autologinProxyPref = document.getElementById("signon.autologin.proxy");
+ autologinProxyPref.disabled = proxyTypePref.value == 0;
+
+ var noProxiesPref = document.getElementById("network.proxy.no_proxies_on");
+ noProxiesPref.disabled = proxyTypePref.value == 0;
+
+ var autoconfigURLPref = document.getElementById("network.proxy.autoconfig_url");
+ autoconfigURLPref.disabled = proxyTypePref.value != 2;
+
+ this.updateReloadButton();
+ },
+
+ updateDNSPref: function ()
+ {
+ var socksVersionPref = document.getElementById("network.proxy.socks_version");
+ var socksDNSPref = document.getElementById("network.proxy.socks_remote_dns");
+ var proxyTypePref = document.getElementById("network.proxy.type");
+ var isDefinitelySocks4 = !socksVersionPref.disabled && socksVersionPref.value == 4;
+ socksDNSPref.disabled = (isDefinitelySocks4 || proxyTypePref.value == 0);
+ return undefined;
+ },
+
+ updateReloadButton: function ()
+ {
+ // Disable the "Reload PAC" button if the selected proxy type is not PAC or
+ // if the current value of the PAC textbox does not match the value stored
+ // in prefs. Likewise, disable the reload button if PAC is not configured
+ // in prefs.
+
+ var typedURL = document.getElementById("networkProxyAutoconfigURL").value;
+ var proxyTypeCur = document.getElementById("network.proxy.type").value;
+
+ var prefs =
+ Components.classes["@mozilla.org/preferences-service;1"].
+ getService(Components.interfaces.nsIPrefBranch);
+ var pacURL = prefs.getCharPref("network.proxy.autoconfig_url");
+ var proxyType = prefs.getIntPref("network.proxy.type");
+
+ var disableReloadPref =
+ document.getElementById("pref.advanced.proxies.disable_button.reload");
+ disableReloadPref.disabled =
+ (proxyTypeCur != 2 || proxyType != 2 || typedURL != pacURL);
+ },
+
+ readProxyType: function ()
+ {
+ this.proxyTypeChanged();
+ return undefined;
+ },
+
+ updateProtocolPrefs: function ()
+ {
+ var proxyTypePref = document.getElementById("network.proxy.type");
+ var shareProxiesPref = document.getElementById("network.proxy.share_proxy_settings");
+ var proxyPrefs = ["ssl", "ftp", "socks"];
+ for (var i = 0; i < proxyPrefs.length; ++i) {
+ var proxyServerURLPref = document.getElementById("network.proxy." + proxyPrefs[i]);
+ var proxyPortPref = document.getElementById("network.proxy." + proxyPrefs[i] + "_port");
+
+ // Restore previous per-proxy custom settings, if present.
+ if (!shareProxiesPref.value) {
+ var backupServerURLPref = document.getElementById("network.proxy.backup." + proxyPrefs[i]);
+ var backupPortPref = document.getElementById("network.proxy.backup." + proxyPrefs[i] + "_port");
+ if (backupServerURLPref.hasUserValue) {
+ proxyServerURLPref.value = backupServerURLPref.value;
+ backupServerURLPref.reset();
+ }
+ if (backupPortPref.hasUserValue) {
+ proxyPortPref.value = backupPortPref.value;
+ backupPortPref.reset();
+ }
+ }
+
+ proxyServerURLPref.updateElements();
+ proxyPortPref.updateElements();
+ proxyServerURLPref.disabled = proxyTypePref.value != 1 || shareProxiesPref.value;
+ proxyPortPref.disabled = proxyServerURLPref.disabled;
+ }
+ var socksVersionPref = document.getElementById("network.proxy.socks_version");
+ socksVersionPref.disabled = proxyTypePref.value != 1 || shareProxiesPref.value;
+ this.updateDNSPref();
+ return undefined;
+ },
+
+ readProxyProtocolPref: function (aProtocol, aIsPort)
+ {
+ var shareProxiesPref = document.getElementById("network.proxy.share_proxy_settings");
+ if (shareProxiesPref.value) {
+ var pref = document.getElementById("network.proxy.http" + (aIsPort ? "_port" : ""));
+ return pref.value;
+ }
+
+ var backupPref = document.getElementById("network.proxy.backup." + aProtocol + (aIsPort ? "_port" : ""));
+ return backupPref.hasUserValue ? backupPref.value : undefined;
+ },
+
+ reloadPAC: function ()
+ {
+ Components.classes["@mozilla.org/network/protocol-proxy-service;1"].
+ getService().reloadPAC();
+ },
+
+ doAutoconfigURLFixup: function ()
+ {
+ var autoURL = document.getElementById("networkProxyAutoconfigURL");
+ var autoURLPref = document.getElementById("network.proxy.autoconfig_url");
+ var URIFixup = Components.classes["@mozilla.org/docshell/urifixup;1"]
+ .getService(Components.interfaces.nsIURIFixup);
+ try {
+ autoURLPref.value = autoURL.value = URIFixup.createFixupURI(autoURL.value, 0).spec;
+ } catch(ex) {}
+ },
+
+ sanitizeNoProxiesPref: function()
+ {
+ var noProxiesPref = document.getElementById("network.proxy.no_proxies_on");
+ // replace substrings of ; and \n with commas if they're neither immediately
+ // preceded nor followed by a valid separator character
+ noProxiesPref.value = noProxiesPref.value.replace(/([^, \n;])[;\n]+(?![,\n;])/g, '$1,');
+ // replace any remaining ; and \n since some may follow commas, etc.
+ noProxiesPref.value = noProxiesPref.value.replace(/[;\n]/g, '');
+ },
+
+ readHTTPProxyServer: function ()
+ {
+ var shareProxiesPref = document.getElementById("network.proxy.share_proxy_settings");
+ if (shareProxiesPref.value)
+ this.updateProtocolPrefs();
+ return undefined;
+ },
+
+ readHTTPProxyPort: function ()
+ {
+ var shareProxiesPref = document.getElementById("network.proxy.share_proxy_settings");
+ if (shareProxiesPref.value)
+ this.updateProtocolPrefs();
+ return undefined;
+ }
+};
diff --git a/components/preferences/connection.xul b/components/preferences/connection.xul
new file mode 100644
index 0000000..e6079dd
--- /dev/null
+++ b/components/preferences/connection.xul
@@ -0,0 +1,164 @@
+<?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/.
+
+<!DOCTYPE prefwindow SYSTEM "chrome://browser/locale/preferences/connection.dtd">
+
+<?xml-stylesheet href="chrome://global/skin/"?>
+
+<prefwindow id="ConnectionsDialog" type="child"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&connectionsDialog.title;"
+ dlgbuttons="accept,cancel,help"
+ onbeforeaccept="return gConnectionsDialog.beforeAccept();"
+ onload="gConnectionsDialog.checkForSystemProxy();"
+ ondialoghelp="openPrefsHelp()"
+#ifdef XP_MACOSX
+ style="width: &window.macWidth; !important;">
+#else
+ style="width: &window.width; !important;">
+#endif
+
+ <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
+
+ <prefpane id="ConnectionsDialogPane"
+ helpTopic="prefs-connection-settings">
+
+ <preferences>
+ <preference id="network.proxy.type" name="network.proxy.type" type="int"
+ onchange="gConnectionsDialog.proxyTypeChanged();"/>
+ <preference id="network.proxy.http" name="network.proxy.http" type="string"/>
+ <preference id="network.proxy.http_port" name="network.proxy.http_port" type="int"/>
+ <preference id="network.proxy.ftp" name="network.proxy.ftp" type="string"/>
+ <preference id="network.proxy.ftp_port" name="network.proxy.ftp_port" type="int"/>
+ <preference id="network.proxy.ssl" name="network.proxy.ssl" type="string"/>
+ <preference id="network.proxy.ssl_port" name="network.proxy.ssl_port" type="int"/>
+ <preference id="network.proxy.socks" name="network.proxy.socks" type="string"/>
+ <preference id="network.proxy.socks_port" name="network.proxy.socks_port" type="int"/>
+ <preference id="network.proxy.socks_version" name="network.proxy.socks_version" type="int"
+ onchange="gConnectionsDialog.updateDNSPref();"/>
+ <preference id="network.proxy.socks_remote_dns" name="network.proxy.socks_remote_dns" type="bool"/>
+ <preference id="network.proxy.no_proxies_on" name="network.proxy.no_proxies_on" type="string"/>
+ <preference id="network.proxy.autoconfig_url" name="network.proxy.autoconfig_url" type="string"/>
+ <preference id="network.proxy.share_proxy_settings" name="network.proxy.share_proxy_settings" type="bool"/>
+ <preference id="signon.autologin.proxy" name="signon.autologin.proxy" type="bool"/>
+ <preference id="pref.advanced.proxies.disable_button.reload"
+ name="pref.advanced.proxies.disable_button.reload" type="bool"/>
+ <preference id="network.proxy.backup.ftp" name="network.proxy.backup.ftp" type="string"/>
+ <preference id="network.proxy.backup.ftp_port" name="network.proxy.backup.ftp_port" type="int"/>
+ <preference id="network.proxy.backup.ssl" name="network.proxy.backup.ssl" type="string"/>
+ <preference id="network.proxy.backup.ssl_port" name="network.proxy.backup.ssl_port" type="int"/>
+ <preference id="network.proxy.backup.socks" name="network.proxy.backup.socks" type="string"/>
+ <preference id="network.proxy.backup.socks_port" name="network.proxy.backup.socks_port" type="int"/>
+ </preferences>
+
+ <script type="application/javascript" src="chrome://browser/content/preferences/connection.js"/>
+
+ <stringbundle id="preferencesBundle" src="chrome://browser/locale/preferences/preferences.properties"/>
+
+ <groupbox>
+ <caption label="&proxyTitle.label;"/>
+
+ <radiogroup id="networkProxyType" preference="network.proxy.type"
+ onsyncfrompreference="return gConnectionsDialog.readProxyType();">
+ <radio value="0" label="&noProxyTypeRadio.label;" accesskey="&noProxyTypeRadio.accesskey;"/>
+ <radio value="4" label="&WPADTypeRadio.label;" accesskey="&WPADTypeRadio.accesskey;"/>
+ <radio value="5" label="&systemTypeRadio.label;" accesskey="&systemTypeRadio.accesskey;" id="systemPref" hidden="true"/>
+ <radio value="1" label="&manualTypeRadio.label;" accesskey="&manualTypeRadio.accesskey;"/>
+ <grid class="indent" flex="1">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <hbox pack="end">
+ <label value="&http.label;" accesskey="&http.accesskey;" control="networkProxyHTTP"/>
+ </hbox>
+ <hbox align="center">
+ <textbox id="networkProxyHTTP" flex="1"
+ preference="network.proxy.http" onsyncfrompreference="return gConnectionsDialog.readHTTPProxyServer();"/>
+ <label value="&port.label;" accesskey="&HTTPport.accesskey;" control="networkProxyHTTP_Port"/>
+ <textbox id="networkProxyHTTP_Port" type="number" max="65535" size="5"
+ preference="network.proxy.http_port" onsyncfrompreference="return gConnectionsDialog.readHTTPProxyPort();"/>
+ </hbox>
+ </row>
+ <row>
+ <hbox/>
+ <hbox>
+ <checkbox id="shareAllProxies" label="&shareproxy.label;" accesskey="&shareproxy.accesskey;"
+ preference="network.proxy.share_proxy_settings"
+ onsyncfrompreference="return gConnectionsDialog.updateProtocolPrefs();"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <hbox pack="end">
+ <label value="&ssl.label;" accesskey="&ssl.accesskey;" control="networkProxySSL"/>
+ </hbox>
+ <hbox align="center">
+ <textbox id="networkProxySSL" flex="1" preference="network.proxy.ssl"
+ onsyncfrompreference="return gConnectionsDialog.readProxyProtocolPref('ssl', false);"/>
+ <label value="&port.label;" accesskey="&SSLport.accesskey;" control="networkProxySSL_Port"/>
+ <textbox id="networkProxySSL_Port" type="number" max="65535" size="5" preference="network.proxy.ssl_port"
+ onsyncfrompreference="return gConnectionsDialog.readProxyProtocolPref('ssl', true);"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <hbox pack="end">
+ <label value="&ftp.label;" accesskey="&ftp.accesskey;" control="networkProxyFTP"/>
+ </hbox>
+ <hbox align="center">
+ <textbox id="networkProxyFTP" flex="1" preference="network.proxy.ftp"
+ onsyncfrompreference="return gConnectionsDialog.readProxyProtocolPref('ftp', false);"/>
+ <label value="&port.label;" accesskey="&FTPport.accesskey;" control="networkProxyFTP_Port"/>
+ <textbox id="networkProxyFTP_Port" type="number" max="65535" size="5" preference="network.proxy.ftp_port"
+ onsyncfrompreference="return gConnectionsDialog.readProxyProtocolPref('ftp', true);"/>
+ </hbox>
+ </row>
+ <row align="center">
+ <hbox pack="end">
+ <label value="&socks.label;" accesskey="&socks.accesskey;" control="networkProxySOCKS"/>
+ </hbox>
+ <hbox align="center">
+ <textbox id="networkProxySOCKS" flex="1" preference="network.proxy.socks"
+ onsyncfrompreference="return gConnectionsDialog.readProxyProtocolPref('socks', false);"/>
+ <label value="&port.label;" accesskey="&SOCKSport.accesskey;" control="networkProxySOCKS_Port"/>
+ <textbox id="networkProxySOCKS_Port" type="number" max="65535" size="5" preference="network.proxy.socks_port"
+ onsyncfrompreference="return gConnectionsDialog.readProxyProtocolPref('socks', true);"/>
+ </hbox>
+ </row>
+ <row>
+ <spacer/>
+ <radiogroup id="networkProxySOCKSVersion" orient="horizontal"
+ preference="network.proxy.socks_version">
+ <radio id="networkProxySOCKSVersion4" value="4" label="&socks4.label;" accesskey="&socks4.accesskey;"/>
+ <radio id="networkProxySOCKSVersion5" value="5" label="&socks5.label;" accesskey="&socks5.accesskey;"/>
+ </radiogroup>
+ </row>
+ </rows>
+ </grid>
+ <radio value="2" label="&autoTypeRadio.label;" accesskey="&autoTypeRadio.accesskey;"/>
+ <hbox class="indent" flex="1" align="center">
+ <textbox id="networkProxyAutoconfigURL" flex="1" preference="network.proxy.autoconfig_url"
+ oninput="gConnectionsDialog.updateReloadButton();"/>
+ <button id="autoReload" icon="refresh"
+ label="&reload.label;" accesskey="&reload.accesskey;"
+ oncommand="gConnectionsDialog.reloadPAC();"
+ preference="pref.advanced.proxies.disable_button.reload"/>
+ </hbox>
+ </radiogroup>
+ <separator class="thin"/>
+ <label value="&noproxy.label;" accesskey="&noproxy.accesskey;" control="networkProxyNone"/>
+ <textbox id="networkProxyNone" preference="network.proxy.no_proxies_on" multiline="true" rows="2"/>
+ <label value="&noproxyExplain.label;" control="networkProxyNone"/>
+ <checkbox id="autologinProxy" preference="signon.autologin.proxy"
+ label="&autologinproxy.label;" accesskey="&autologinproxy.accesskey;"
+ tooltiptext="&autologinproxy.tooltip;"/>
+ <checkbox id="networkProxySOCKSRemoteDNS" preference="network.proxy.socks_remote_dns"
+ label="&socksRemoteDNS.label;" accesskey="&socksRemoteDNS.accesskey;"/>
+ </groupbox>
+ </prefpane>
+</prefwindow>
diff --git a/components/preferences/content.js b/components/preferences/content.js
new file mode 100644
index 0000000..5ae84c2
--- /dev/null
+++ b/components/preferences/content.js
@@ -0,0 +1,187 @@
+/* -*- 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/. */
+
+var gContentPane = {
+
+ /**
+ * Initializes the fonts dropdowns displayed in this pane.
+ */
+ init: function ()
+ {
+ this._rebuildFonts();
+ var menulist = document.getElementById("defaultFont");
+ if (menulist.selectedIndex == -1) {
+ menulist.insertItemAt(0, "", "", "");
+ menulist.selectedIndex = 0;
+ }
+ },
+
+ // UTILITY FUNCTIONS
+
+ /**
+ * Utility function to enable/disable the button specified by aButtonID based
+ * on the value of the Boolean preference specified by aPreferenceID.
+ */
+ updateButtons: function (aButtonID, aPreferenceID)
+ {
+ var button = document.getElementById(aButtonID);
+ var preference = document.getElementById(aPreferenceID);
+ button.disabled = preference.value != true;
+ return undefined;
+ },
+
+ /**
+ * Utility function to enable/disable the checkboxes for MSE options depending
+ * on the value of media.mediasource.enabled.
+ */
+ updateMSE: function ()
+ {
+ var checkboxMSEMP4 = document.getElementById('videoMSEMP4');
+ var checkboxMSEWebM = document.getElementById('videoMSEWebM');
+ var preference = document.getElementById('media.mediasource.enabled');
+ checkboxMSEMP4.disabled = preference.value != true;
+ checkboxMSEWebM.disabled = preference.value != true;
+ },
+
+ // BEGIN UI CODE
+
+ /*
+ * Preferences:
+ *
+ * dom.disable_open_during_load
+ * - true if popups are blocked by default, false otherwise
+ */
+
+ // POP-UPS
+
+ /**
+ * Displays the popup exceptions dialog where specific site popup preferences
+ * can be set.
+ */
+ showPopupExceptions: function ()
+ {
+ var bundlePreferences = document.getElementById("bundlePreferences");
+ var params = { blockVisible: false, sessionVisible: false, allowVisible: true, prefilledHost: "", permissionType: "popup" };
+ params.windowTitle = bundlePreferences.getString("popuppermissionstitle");
+ params.introText = bundlePreferences.getString("popuppermissionstext");
+ document.documentElement.openWindow("Browser:Permissions",
+ "chrome://browser/content/preferences/permissions.xul",
+ "", params);
+ },
+
+
+ // FONTS
+
+ /**
+ * Populates the default font list in UI.
+ */
+ _rebuildFonts: function ()
+ {
+ var langGroupPref = document.getElementById("font.language.group");
+ this._selectDefaultLanguageGroup(langGroupPref.value,
+ this._readDefaultFontTypeForLanguage(langGroupPref.value) == "serif");
+ },
+
+ /**
+ *
+ */
+ _selectDefaultLanguageGroup: function (aLanguageGroup, aIsSerif)
+ {
+ const kFontNameFmtSerif = "font.name.serif.%LANG%";
+ const kFontNameFmtSansSerif = "font.name.sans-serif.%LANG%";
+ const kFontNameListFmtSerif = "font.name-list.serif.%LANG%";
+ const kFontNameListFmtSansSerif = "font.name-list.sans-serif.%LANG%";
+ const kFontSizeFmtVariable = "font.size.variable.%LANG%";
+
+ var prefs = [{ format : aIsSerif ? kFontNameFmtSerif : kFontNameFmtSansSerif,
+ type : "fontname",
+ element : "defaultFont",
+ fonttype : aIsSerif ? "serif" : "sans-serif" },
+ { format : aIsSerif ? kFontNameListFmtSerif : kFontNameListFmtSansSerif,
+ type : "unichar",
+ element : null,
+ fonttype : aIsSerif ? "serif" : "sans-serif" },
+ { format : kFontSizeFmtVariable,
+ type : "int",
+ element : "defaultFontSize",
+ fonttype : null }];
+ var preferences = document.getElementById("contentPreferences");
+ for (var i = 0; i < prefs.length; ++i) {
+ var preference = document.getElementById(prefs[i].format.replace(/%LANG%/, aLanguageGroup));
+ if (!preference) {
+ preference = document.createElement("preference");
+ var name = prefs[i].format.replace(/%LANG%/, aLanguageGroup);
+ preference.id = name;
+ preference.setAttribute("name", name);
+ preference.setAttribute("type", prefs[i].type);
+ preferences.appendChild(preference);
+ }
+
+ if (!prefs[i].element)
+ continue;
+
+ var element = document.getElementById(prefs[i].element);
+ if (element) {
+ element.setAttribute("preference", preference.id);
+
+ if (prefs[i].fonttype)
+ FontBuilder.buildFontList(aLanguageGroup, prefs[i].fonttype, element);
+
+ preference.setElementValue(element);
+ }
+ }
+ },
+
+ /**
+ * Returns the type of the current default font for the language denoted by
+ * aLanguageGroup.
+ */
+ _readDefaultFontTypeForLanguage: function (aLanguageGroup)
+ {
+ const kDefaultFontType = "font.default.%LANG%";
+ var defaultFontTypePref = kDefaultFontType.replace(/%LANG%/, aLanguageGroup);
+ var preference = document.getElementById(defaultFontTypePref);
+ if (!preference) {
+ preference = document.createElement("preference");
+ preference.id = defaultFontTypePref;
+ preference.setAttribute("name", defaultFontTypePref);
+ preference.setAttribute("type", "string");
+ preference.setAttribute("onchange", "gContentPane._rebuildFonts();");
+ document.getElementById("contentPreferences").appendChild(preference);
+ }
+ return preference.value;
+ },
+
+ /**
+ * Displays the fonts dialog, where web page font names and sizes can be
+ * configured.
+ */
+ configureFonts: function ()
+ {
+ document.documentElement.openSubDialog("chrome://browser/content/preferences/fonts.xul",
+ "", null);
+ },
+
+ /**
+ * Displays the colors dialog, where default web page/link/etc. colors can be
+ * configured.
+ */
+ configureColors: function ()
+ {
+ document.documentElement.openSubDialog("chrome://browser/content/preferences/colors.xul",
+ "", null);
+ },
+
+ // LANGUAGES
+
+ /**
+ * Shows a dialog in which the preferred language for web content may be set.
+ */
+ showLanguages: function ()
+ {
+ document.documentElement.openSubDialog("chrome://browser/content/preferences/languages.xul",
+ "", null);
+ }
+};
diff --git a/components/preferences/content.xul b/components/preferences/content.xul
new file mode 100644
index 0000000..23d942e
--- /dev/null
+++ b/components/preferences/content.xul
@@ -0,0 +1,171 @@
+<?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/. -->
+
+<!DOCTYPE overlay [
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+ <!ENTITY % contentDTD SYSTEM "chrome://browser/locale/preferences/content.dtd">
+ %brandDTD;
+ %contentDTD;
+]>
+
+<overlay id="ContentPaneOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <prefpane id="paneContent"
+ onpaneload="gContentPane.init();"
+ helpTopic="prefs-content">
+
+ <preferences id="contentPreferences">
+ <!--XXX buttons prefs -->
+
+ <!-- POPUPS, IMAGES -->
+ <preference id="dom.disable_open_during_load" name="dom.disable_open_during_load" type="bool"/>
+ <preference id="permissions.default.image" name="permissions.default.image" type="int"/>
+
+ <!-- FONTS -->
+ <preference id="font.language.group"
+ name="font.language.group"
+ type="wstring"
+ onchange="gContentPane._rebuildFonts();"/>
+
+ <!-- VIDEO -->
+ <preference id="media.mediasource.enabled" name="media.mediasource.enabled" type="bool"/>
+ <preference id="media.mediasource.mp4.enabled" name="media.mediasource.mp4.enabled" type="bool"/>
+ <preference id="media.mediasource.webm.enabled" name="media.mediasource.webm.enabled" type="bool"/>
+
+ </preferences>
+
+ <script type="application/javascript" src="chrome://mozapps/content/preferences/fontbuilder.js"/>
+ <script type="application/javascript" src="chrome://browser/content/preferences/content.js"/>
+
+ <stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences/preferences.properties"/>
+
+ <!-- various checkboxes, font-fu -->
+ <groupbox id="miscGroup">
+ <grid id="contentGrid">
+ <columns>
+ <column flex="1"/>
+ <column/>
+ </columns>
+ <rows id="contentRows-1">
+ <row id="popupPolicyRow">
+ <vbox align="start">
+ <checkbox id="popupPolicy" preference="dom.disable_open_during_load"
+ label="&blockPopups.label;" accesskey="&blockPopups.accesskey;"
+ onsyncfrompreference="return gContentPane.updateButtons('popupPolicyButton',
+ 'dom.disable_open_during_load');"/>
+ </vbox>
+ <button id="popupPolicyButton" label="&popupExceptions.label;"
+ oncommand="gContentPane.showPopupExceptions();"
+ accesskey="&popupExceptions.accesskey;"/>
+ </row>
+ <row id="enableImagesRow">
+ <hbox align="center">
+ <label id="loadImages" control="loadImages-menu">&loadImages.label;</label>
+ <menulist id="loadImages-menu" preference="permissions.default.image" sizetopopup="always">
+ <menupopup>
+ <menuitem label="&loadImages.always;" value="1" />
+ <menuitem label="&loadImages.never;" value="2" />
+ <menuitem label="&loadImages.no3rdparty;" value="3" />
+ </menupopup>
+ </menulist>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <!-- Fonts and Colors -->
+ <groupbox id="fontsGroup">
+ <caption label="&fontsAndColors.label;"/>
+
+ <grid id="fontsGrid">
+ <columns>
+ <column flex="1"/>
+ <column/>
+ </columns>
+ <rows id="fontsRows">
+ <row id="fontRow">
+ <hbox align="center">
+ <label control="defaultFont" accesskey="&defaultFont.accesskey;">&defaultFont.label;</label>
+ <menulist id="defaultFont" flex="1"/>
+ <label control="defaultFontSize" accesskey="&defaultSize.accesskey;">&defaultSize.label;</label>
+ <menulist id="defaultFontSize">
+ <menupopup>
+ <menuitem value="9" label="9"/>
+ <menuitem value="10" label="10"/>
+ <menuitem value="11" label="11"/>
+ <menuitem value="12" label="12"/>
+ <menuitem value="13" label="13"/>
+ <menuitem value="14" label="14"/>
+ <menuitem value="15" label="15"/>
+ <menuitem value="16" label="16"/>
+ <menuitem value="17" label="17"/>
+ <menuitem value="18" label="18"/>
+ <menuitem value="20" label="20"/>
+ <menuitem value="22" label="22"/>
+ <menuitem value="24" label="24"/>
+ <menuitem value="26" label="26"/>
+ <menuitem value="28" label="28"/>
+ <menuitem value="30" label="30"/>
+ <menuitem value="32" label="32"/>
+ <menuitem value="34" label="34"/>
+ <menuitem value="36" label="36"/>
+ <menuitem value="40" label="40"/>
+ <menuitem value="44" label="44"/>
+ <menuitem value="48" label="48"/>
+ <menuitem value="56" label="56"/>
+ <menuitem value="64" label="64"/>
+ <menuitem value="72" label="72"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ <button id="advancedFonts" icon="select-font"
+ label="&advancedFonts.label;"
+ accesskey="&advancedFonts.accesskey;"
+ oncommand="gContentPane.configureFonts();"/>
+ </row>
+ <row id="colorsRow">
+ <hbox/>
+ <button id="colors" icon="select-color"
+ label="&colors.label;"
+ accesskey="&colors.accesskey;"
+ oncommand="gContentPane.configureColors();"/>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+
+ <!-- Languages -->
+ <groupbox id="languagesGroup">
+ <caption label="&languages.label;"/>
+
+ <hbox id="languagesBox" align="center">
+ <description flex="1" control="chooseLanguage">&chooseLanguage.label;</description>
+ <button id="chooseLanguage"
+ label="&chooseButton.label;"
+ accesskey="&chooseButton.accesskey;"
+ oncommand="gContentPane.showLanguages();"/>
+ </hbox>
+ </groupbox>
+
+ <!-- Video -->
+ <groupbox id="videoGroup">
+ <caption label="&video.label;"/>
+
+ <checkbox id="videoMSE" preference="media.mediasource.enabled"
+ label="&videoMSE.label;" accesskey="&videoMSE.accesskey;"
+ onsyncfrompreference="gContentPane.updateMSE();"/>
+ <checkbox class="indent" id="videoMSEMP4" preference="media.mediasource.mp4.enabled"
+ label="&videoMSEMP4.label;" accesskey="&videoMSEMP4.accesskey;"/>
+ <checkbox class="indent" id="videoMSEWebM" preference="media.mediasource.webm.enabled"
+ label="&videoMSEWebM.label;" accesskey="&videoMSEWebM.accesskey;"/>
+ </groupbox>
+
+ </prefpane>
+
+</overlay>
diff --git a/components/preferences/cookies.js b/components/preferences/cookies.js
new file mode 100644
index 0000000..4fa47ee
--- /dev/null
+++ b/components/preferences/cookies.js
@@ -0,0 +1,948 @@
+/* -*- 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 nsICookie = Components.interfaces.nsICookie;
+
+Components.utils.import("resource://gre/modules/PluralForm.jsm");
+
+var gCookiesWindow = {
+ _cm : Components.classes["@mozilla.org/cookiemanager;1"]
+ .getService(Components.interfaces.nsICookieManager),
+ _ds : Components.classes["@mozilla.org/intl/scriptabledateformat;1"]
+ .getService(Components.interfaces.nsIScriptableDateFormat),
+ _hosts : {},
+ _hostOrder : [],
+ _tree : null,
+ _bundle : null,
+
+ init: function () {
+ var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ os.addObserver(this, "cookie-changed", false);
+ os.addObserver(this, "perm-changed", false);
+
+ this._bundle = document.getElementById("bundlePreferences");
+ this._tree = document.getElementById("cookiesList");
+
+ let removeAllCookies = document.getElementById("removeAllCookies");
+ removeAllCookies.setAttribute("accesskey", this._bundle.getString("removeAllCookies.accesskey"));
+ let removeSelectedCookies = document.getElementById("removeSelectedCookies");
+ removeSelectedCookies.setAttribute("accesskey", this._bundle.getString("removeSelectedCookies.accesskey"));
+
+ this._populateList(true);
+
+ document.getElementById("filter").focus();
+ },
+
+ uninit: function () {
+ var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ os.removeObserver(this, "cookie-changed");
+ os.removeObserver(this, "perm-changed");
+ },
+
+ _populateList: function (aInitialLoad) {
+ this._loadCookies();
+ this._tree.view = this._view;
+ if (aInitialLoad)
+ this.sort("rawHost");
+ if (this._view.rowCount > 0)
+ this._tree.view.selection.select(0);
+
+ if (aInitialLoad) {
+ if ("arguments" in window &&
+ window.arguments[0] &&
+ window.arguments[0].filterString)
+ this.setFilter(window.arguments[0].filterString);
+ }
+ else {
+ if (document.getElementById("filter").value != "")
+ this.filter();
+ }
+
+ this._updateRemoveAllButton();
+
+ this._saveState();
+ },
+
+ _cookieEquals: function (aCookieA, aCookieB, aStrippedHost) {
+ return aCookieA.rawHost == aStrippedHost &&
+ aCookieA.name == aCookieB.name &&
+ aCookieA.path == aCookieB.path &&
+ ChromeUtils.isOriginAttributesEqual(aCookieA.originAttributes,
+ aCookieB.originAttributes);
+ },
+
+ observe: function (aCookie, aTopic, aData) {
+ if (aTopic != "cookie-changed")
+ return;
+
+ if (aCookie instanceof Components.interfaces.nsICookie) {
+ var strippedHost = this._makeStrippedHost(aCookie.host);
+ if (aData == "changed")
+ this._handleCookieChanged(aCookie, strippedHost);
+ else if (aData == "added")
+ this._handleCookieAdded(aCookie, strippedHost);
+ }
+ else if (aData == "cleared") {
+ this._hosts = {};
+ this._hostOrder = [];
+
+ var oldRowCount = this._view._rowCount;
+ this._view._rowCount = 0;
+ this._tree.treeBoxObject.rowCountChanged(0, -oldRowCount);
+ this._view.selection.clearSelection();
+ this._updateRemoveAllButton();
+ }
+ else if (aData == "reload") {
+ // first, clear any existing entries
+ this.observe(aCookie, aTopic, "cleared");
+
+ // then, reload the list
+ this._populateList(false);
+ }
+
+ // We don't yet handle aData == "deleted" - it's a less common case
+ // and is rather complicated as selection tracking is difficult
+ },
+
+ _handleCookieChanged: function (changedCookie, strippedHost) {
+ var rowIndex = 0;
+ var cookieItem = null;
+ if (!this._view._filtered) {
+ for (var i = 0; i < this._hostOrder.length; ++i) { // (var host in this._hosts) {
+ ++rowIndex;
+ var hostItem = this._hosts[this._hostOrder[i]]; // var hostItem = this._hosts[host];
+ if (this._hostOrder[i] == strippedHost) { // host == strippedHost) {
+ // Host matches, look for the cookie within this Host collection
+ // and update its data
+ for (var j = 0; j < hostItem.cookies.length; ++j) {
+ ++rowIndex;
+ var currCookie = hostItem.cookies[j];
+ if (this._cookieEquals(currCookie, changedCookie, strippedHost)) {
+ currCookie.value = changedCookie.value;
+ currCookie.isSecure = changedCookie.isSecure;
+ currCookie.isDomain = changedCookie.isDomain;
+ currCookie.expires = changedCookie.expires;
+ cookieItem = currCookie;
+ break;
+ }
+ }
+ }
+ else if (hostItem.open)
+ rowIndex += hostItem.cookies.length;
+ }
+ }
+ else {
+ // Just walk the filter list to find the item. It doesn't matter that
+ // we don't update the main Host collection when we do this, because
+ // when the filter is reset the Host collection is rebuilt anyway.
+ for (rowIndex = 0; rowIndex < this._view._filterSet.length; ++rowIndex) {
+ currCookie = this._view._filterSet[rowIndex];
+ if (this._cookieEquals(currCookie, changedCookie, strippedHost)) {
+ currCookie.value = changedCookie.value;
+ currCookie.isSecure = changedCookie.isSecure;
+ currCookie.isDomain = changedCookie.isDomain;
+ currCookie.expires = changedCookie.expires;
+ cookieItem = currCookie;
+ break;
+ }
+ }
+ }
+
+ // Make sure the tree display is up to date...
+ this._tree.treeBoxObject.invalidateRow(rowIndex);
+ // ... and if the cookie is selected, update the displayed metadata too
+ if (cookieItem != null && this._view.selection.currentIndex == rowIndex)
+ this._updateCookieData(cookieItem);
+ },
+
+ _handleCookieAdded: function (changedCookie, strippedHost) {
+ var rowCountImpact = 0;
+ var addedHost = { value: 0 };
+ this._addCookie(strippedHost, changedCookie, addedHost);
+ if (!this._view._filtered) {
+ // The Host collection for this cookie already exists, and it's not open,
+ // so don't increment the rowCountImpact becaues the user is not going to
+ // see the additional rows as they're hidden.
+ if (addedHost.value || this._hosts[strippedHost].open)
+ ++rowCountImpact;
+ }
+ else {
+ // We're in search mode, and the cookie being added matches
+ // the search condition, so add it to the list.
+ var c = this._makeCookieObject(strippedHost, changedCookie);
+ if (this._cookieMatchesFilter(c)) {
+ this._view._filterSet.push(this._makeCookieObject(strippedHost, changedCookie));
+ ++rowCountImpact;
+ }
+ }
+ // Now update the tree display at the end (we could/should re run the sort
+ // if any to get the position correct.)
+ var oldRowCount = this._rowCount;
+ this._view._rowCount += rowCountImpact;
+ this._tree.treeBoxObject.rowCountChanged(oldRowCount - 1, rowCountImpact);
+
+ this._updateRemoveAllButton();
+ },
+
+ _view: {
+ _filtered : false,
+ _filterSet : [],
+ _filterValue: "",
+ _rowCount : 0,
+ _cacheValid : 0,
+ _cacheItems : [],
+ get rowCount() {
+ return this._rowCount;
+ },
+
+ _getItemAtIndex: function (aIndex) {
+ if (this._filtered)
+ return this._filterSet[aIndex];
+
+ var start = 0;
+ var count = 0, hostIndex = 0;
+
+ var cacheIndex = Math.min(this._cacheValid, aIndex);
+ if (cacheIndex > 0) {
+ var cacheItem = this._cacheItems[cacheIndex];
+ start = cacheItem['start'];
+ count = hostIndex = cacheItem['count'];
+ }
+
+ for (var i = start; i < gCookiesWindow._hostOrder.length; ++i) { // var host in gCookiesWindow._hosts) {
+ var currHost = gCookiesWindow._hosts[gCookiesWindow._hostOrder[i]]; // gCookiesWindow._hosts[host];
+ if (!currHost) continue;
+ if (count == aIndex)
+ return currHost;
+ hostIndex = count;
+
+ var cacheEntry = { 'start' : i, 'count' : count };
+ var cacheStart = count;
+
+ if (currHost.open) {
+ if (count < aIndex && aIndex <= (count + currHost.cookies.length)) {
+ // We are looking for an entry within this host's children,
+ // enumerate them looking for the index.
+ ++count;
+ for (var i = 0; i < currHost.cookies.length; ++i) {
+ if (count == aIndex) {
+ var cookie = currHost.cookies[i];
+ cookie.parentIndex = hostIndex;
+ return cookie;
+ }
+ ++count;
+ }
+ }
+ else {
+ // A host entry was open, but we weren't looking for an index
+ // within that host entry's children, so skip forward over the
+ // entry's children. We need to add one to increment for the
+ // host value too.
+ count += currHost.cookies.length + 1;
+ }
+ }
+ else
+ ++count;
+
+ for (var j = cacheStart; j < count; j++)
+ this._cacheItems[j] = cacheEntry;
+ this._cacheValid = count - 1;
+ }
+ return null;
+ },
+
+ _removeItemAtIndex: function (aIndex, aCount) {
+ var removeCount = aCount === undefined ? 1 : aCount;
+ if (this._filtered) {
+ // remove the cookies from the unfiltered set so that they
+ // don't reappear when the filter is changed. See bug 410863.
+ for (var i = aIndex; i < aIndex + removeCount; ++i) {
+ var item = this._filterSet[i];
+ var parent = gCookiesWindow._hosts[item.rawHost];
+ for (var j = 0; j < parent.cookies.length; ++j) {
+ if (item == parent.cookies[j]) {
+ parent.cookies.splice(j, 1);
+ break;
+ }
+ }
+ }
+ this._filterSet.splice(aIndex, removeCount);
+ return;
+ }
+
+ var item = this._getItemAtIndex(aIndex);
+ if (!item) return;
+ this._invalidateCache(aIndex - 1);
+ if (item.container) {
+ gCookiesWindow._hosts[item.rawHost] = null;
+ } else {
+ var parent = this._getItemAtIndex(item.parentIndex);
+ for (var i = 0; i < parent.cookies.length; ++i) {
+ var cookie = parent.cookies[i];
+ if (item.rawHost == cookie.rawHost &&
+ item.name == cookie.name &&
+ item.path == cookie.path &&
+ ChromeUtils.isOriginAttributesEqual(item.originAttributes,
+ cookie.originAttributes)) {
+ parent.cookies.splice(i, removeCount);
+ }
+ }
+ }
+ },
+
+ _invalidateCache: function (aIndex) {
+ this._cacheValid = Math.min(this._cacheValid, aIndex);
+ },
+
+ getCellText: function (aIndex, aColumn) {
+ if (!this._filtered) {
+ var item = this._getItemAtIndex(aIndex);
+ if (!item)
+ return "";
+ if (aColumn.id == "domainCol")
+ return item.rawHost;
+ else if (aColumn.id == "nameCol")
+ return item.name;
+ }
+ else {
+ if (aColumn.id == "domainCol")
+ return this._filterSet[aIndex].rawHost;
+ else if (aColumn.id == "nameCol")
+ return this._filterSet[aIndex].name;
+ }
+ return "";
+ },
+
+ _selection: null,
+ get selection () { return this._selection; },
+ set selection (val) { this._selection = val; return val; },
+ getRowProperties: function (aIndex) { return ""; },
+ getCellProperties: function (aIndex, aColumn) { return ""; },
+ getColumnProperties: function (aColumn) { return ""; },
+ isContainer: function (aIndex) {
+ if (!this._filtered) {
+ var item = this._getItemAtIndex(aIndex);
+ if (!item) return false;
+ return item.container;
+ }
+ return false;
+ },
+ isContainerOpen: function (aIndex) {
+ if (!this._filtered) {
+ var item = this._getItemAtIndex(aIndex);
+ if (!item) return false;
+ return item.open;
+ }
+ return false;
+ },
+ isContainerEmpty: function (aIndex) {
+ if (!this._filtered) {
+ var item = this._getItemAtIndex(aIndex);
+ if (!item) return false;
+ return item.cookies.length == 0;
+ }
+ return false;
+ },
+ isSeparator: function (aIndex) { return false; },
+ isSorted: function (aIndex) { return false; },
+ canDrop: function (aIndex, aOrientation) { return false; },
+ drop: function (aIndex, aOrientation) {},
+ getParentIndex: function (aIndex) {
+ if (!this._filtered) {
+ var item = this._getItemAtIndex(aIndex);
+ // If an item has no parent index (i.e. it is at the top level) this
+ // function MUST return -1 otherwise we will go into an infinite loop.
+ // Containers are always top level items in the cookies tree, so make
+ // sure to return the appropriate value here.
+ if (!item || item.container) return -1;
+ return item.parentIndex;
+ }
+ return -1;
+ },
+ hasNextSibling: function (aParentIndex, aIndex) {
+ if (!this._filtered) {
+ // |aParentIndex| appears to be bogus, but we can get the real
+ // parent index by getting the entry for |aIndex| and reading the
+ // parentIndex field.
+ // The index of the last item in this host collection is the
+ // index of the parent + the size of the host collection, and
+ // aIndex has a next sibling if it is less than this value.
+ var item = this._getItemAtIndex(aIndex);
+ if (item) {
+ if (item.container) {
+ for (var i = aIndex + 1; i < this.rowCount; ++i) {
+ var subsequent = this._getItemAtIndex(i);
+ if (subsequent.container)
+ return true;
+ }
+ return false;
+ }
+ else {
+ var parent = this._getItemAtIndex(item.parentIndex);
+ if (parent && parent.container)
+ return aIndex < item.parentIndex + parent.cookies.length;
+ }
+ }
+ }
+ return aIndex < this.rowCount - 1;
+ },
+ hasPreviousSibling: function (aIndex) {
+ if (!this._filtered) {
+ var item = this._getItemAtIndex(aIndex);
+ if (!item) return false;
+ var parent = this._getItemAtIndex(item.parentIndex);
+ if (parent && parent.container)
+ return aIndex > item.parentIndex + 1;
+ }
+ return aIndex > 0;
+ },
+ getLevel: function (aIndex) {
+ if (!this._filtered) {
+ var item = this._getItemAtIndex(aIndex);
+ if (!item) return 0;
+ return item.level;
+ }
+ return 0;
+ },
+ getImageSrc: function (aIndex, aColumn) {},
+ getProgressMode: function (aIndex, aColumn) {},
+ getCellValue: function (aIndex, aColumn) {},
+ setTree: function (aTree) {},
+ toggleOpenState: function (aIndex) {
+ if (!this._filtered) {
+ var item = this._getItemAtIndex(aIndex);
+ if (!item) return;
+ this._invalidateCache(aIndex);
+ var multiplier = item.open ? -1 : 1;
+ var delta = multiplier * item.cookies.length;
+ this._rowCount += delta;
+ item.open = !item.open;
+ gCookiesWindow._tree.treeBoxObject.rowCountChanged(aIndex + 1, delta);
+ gCookiesWindow._tree.treeBoxObject.invalidateRow(aIndex);
+ }
+ },
+ cycleHeader: function (aColumn) {},
+ selectionChanged: function () {},
+ cycleCell: function (aIndex, aColumn) {},
+ isEditable: function (aIndex, aColumn) {
+ return false;
+ },
+ isSelectable: function (aIndex, aColumn) {
+ return false;
+ },
+ setCellValue: function (aIndex, aColumn, aValue) {},
+ setCellText: function (aIndex, aColumn, aValue) {},
+ performAction: function (aAction) {},
+ performActionOnRow: function (aAction, aIndex) {},
+ performActionOnCell: function (aAction, aindex, aColumn) {}
+ },
+
+ _makeStrippedHost: function (aHost) {
+ var formattedHost = aHost.charAt(0) == "." ? aHost.substring(1, aHost.length) : aHost;
+ return formattedHost.substring(0, 4) == "www." ? formattedHost.substring(4, formattedHost.length) : formattedHost;
+ },
+
+ _addCookie: function (aStrippedHost, aCookie, aHostCount) {
+ if (!(aStrippedHost in this._hosts) || !this._hosts[aStrippedHost]) {
+ this._hosts[aStrippedHost] = { cookies : [],
+ rawHost : aStrippedHost,
+ level : 0,
+ open : false,
+ container : true };
+ this._hostOrder.push(aStrippedHost);
+ ++aHostCount.value;
+ }
+
+ var c = this._makeCookieObject(aStrippedHost, aCookie);
+ this._hosts[aStrippedHost].cookies.push(c);
+ },
+
+ _makeCookieObject: function (aStrippedHost, aCookie) {
+ var host = aCookie.host;
+ var formattedHost = host.charAt(0) == "." ? host.substring(1, host.length) : host;
+ var c = { name : aCookie.name,
+ value : aCookie.value,
+ isDomain : aCookie.isDomain,
+ host : aCookie.host,
+ rawHost : aStrippedHost,
+ path : aCookie.path,
+ isSecure : aCookie.isSecure,
+ expires : aCookie.expires,
+ level : 1,
+ container : false,
+ originAttributes: aCookie.originAttributes };
+ return c;
+ },
+
+ _loadCookies: function () {
+ var e = this._cm.enumerator;
+ var hostCount = { value: 0 };
+ this._hosts = {};
+ this._hostOrder = [];
+ while (e.hasMoreElements()) {
+ var cookie = e.getNext();
+ if (cookie && cookie instanceof Components.interfaces.nsICookie) {
+ var strippedHost = this._makeStrippedHost(cookie.host);
+ this._addCookie(strippedHost, cookie, hostCount);
+ }
+ else
+ break;
+ }
+ this._view._rowCount = hostCount.value;
+ },
+
+ formatExpiresString: function (aExpires) {
+ if (aExpires) {
+ var date = new Date(1000 * aExpires);
+ return this._ds.FormatDateTime("", this._ds.dateFormatLong,
+ this._ds.timeFormatSeconds,
+ date.getFullYear(),
+ date.getMonth() + 1,
+ date.getDate(),
+ date.getHours(),
+ date.getMinutes(),
+ date.getSeconds());
+ }
+ return this._bundle.getString("expireAtEndOfSession");
+ },
+
+ _updateCookieData: function (aItem) {
+ var seln = this._view.selection;
+ var ids = ["name", "value", "host", "path", "isSecure", "expires"];
+ var properties;
+
+ if (aItem && !aItem.container && seln.count > 0) {
+ properties = { name: aItem.name, value: aItem.value, host: aItem.host,
+ path: aItem.path, expires: this.formatExpiresString(aItem.expires),
+ isDomain: aItem.isDomain ? this._bundle.getString("domainColon")
+ : this._bundle.getString("hostColon"),
+ isSecure: aItem.isSecure ? this._bundle.getString("forSecureOnly")
+ : this._bundle.getString("forAnyConnection") };
+ for (var i = 0; i < ids.length; ++i)
+ document.getElementById(ids[i]).disabled = false;
+ }
+ else {
+ var noneSelected = this._bundle.getString("noCookieSelected");
+ properties = { name: noneSelected, value: noneSelected, host: noneSelected,
+ path: noneSelected, expires: noneSelected,
+ isSecure: noneSelected };
+ for (i = 0; i < ids.length; ++i)
+ document.getElementById(ids[i]).disabled = true;
+ }
+ for (var property in properties)
+ document.getElementById(property).value = properties[property];
+ },
+
+ onCookieSelected: function () {
+ var properties, item;
+ var seln = this._tree.view.selection;
+ var hasRows = this._tree.view.rowCount > 0;
+ var hasSelection = seln.count > 0;
+ if (!this._view._filtered)
+ item = this._view._getItemAtIndex(seln.currentIndex);
+ else
+ item = this._view._filterSet[seln.currentIndex];
+
+ this._updateCookieData(item);
+
+ var rangeCount = seln.getRangeCount();
+ var selectedCookieCount = 0;
+ for (var i = 0; i < rangeCount; ++i) {
+ var min = {}; var max = {};
+ seln.getRangeAt(i, min, max);
+ for (var j = min.value; j <= max.value; ++j) {
+ item = this._view._getItemAtIndex(j);
+ if (!item) continue;
+ if (item.container && !item.open)
+ selectedCookieCount += item.cookies.length;
+ else if (!item.container)
+ ++selectedCookieCount;
+ }
+ }
+ var item = this._view._getItemAtIndex(seln.currentIndex);
+ if (item && seln.count == 1 && item.container && item.open)
+ selectedCookieCount += 2;
+
+ let buttonLabel = this._bundle.getString("removeSelectedCookies.label");
+ let removeSelectedCookies = document.getElementById("removeSelectedCookies");
+ removeSelectedCookies.label = PluralForm.get(selectedCookieCount, buttonLabel)
+ .replace("#1", selectedCookieCount);
+
+ removeSelectedCookies.disabled = !hasRows || !hasSelection;
+ },
+
+ performDeletion: function gCookiesWindow_performDeletion(deleteItems) {
+ var psvc = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ var blockFutureCookies = false;
+ if (psvc.prefHasUserValue("network.cookie.blockFutureCookies"))
+ blockFutureCookies = psvc.getBoolPref("network.cookie.blockFutureCookies");
+ for (var i = 0; i < deleteItems.length; ++i) {
+ var item = deleteItems[i];
+ this._cm.remove(item.host, item.name, item.path,
+ blockFutureCookies, item.originAttributes);
+ }
+ },
+
+ deleteCookie: function () {
+ // Selection Notes
+ // - Selection always moves to *NEXT* adjacent item unless item
+ // is last child at a given level in which case it moves to *PREVIOUS*
+ // item
+ //
+ // Selection Cases (Somewhat Complicated)
+ //
+ // 1) Single cookie selected, host has single child
+ // v cnn.com
+ // //// cnn.com ///////////// goksdjf@ ////
+ // > atwola.com
+ //
+ // Before SelectedIndex: 1 Before RowCount: 3
+ // After SelectedIndex: 0 After RowCount: 1
+ //
+ // 2) Host selected, host open
+ // v goats.com ////////////////////////////
+ // goats.com sldkkfjl
+ // goat.scom flksj133
+ // > atwola.com
+ //
+ // Before SelectedIndex: 0 Before RowCount: 4
+ // After SelectedIndex: 0 After RowCount: 1
+ //
+ // 3) Host selected, host closed
+ // > goats.com ////////////////////////////
+ // > atwola.com
+ //
+ // Before SelectedIndex: 0 Before RowCount: 2
+ // After SelectedIndex: 0 After RowCount: 1
+ //
+ // 4) Single cookie selected, host has many children
+ // v goats.com
+ // goats.com sldkkfjl
+ // //// goats.com /////////// flksjl33 ////
+ // > atwola.com
+ //
+ // Before SelectedIndex: 2 Before RowCount: 4
+ // After SelectedIndex: 1 After RowCount: 3
+ //
+ // 5) Single cookie selected, host has many children
+ // v goats.com
+ // //// goats.com /////////// flksjl33 ////
+ // goats.com sldkkfjl
+ // > atwola.com
+ //
+ // Before SelectedIndex: 1 Before RowCount: 4
+ // After SelectedIndex: 1 After RowCount: 3
+ var seln = this._view.selection;
+ var tbo = this._tree.treeBoxObject;
+
+ if (seln.count < 1) return;
+
+ var nextSelected = 0;
+ var rowCountImpact = 0;
+ var deleteItems = [];
+ if (!this._view._filtered) {
+ var ci = seln.currentIndex;
+ nextSelected = ci;
+ var invalidateRow = -1;
+ var item = this._view._getItemAtIndex(ci);
+ if (item.container) {
+ rowCountImpact -= (item.open ? item.cookies.length : 0) + 1;
+ deleteItems = deleteItems.concat(item.cookies);
+ if (!this._view.hasNextSibling(-1, ci))
+ --nextSelected;
+ this._view._removeItemAtIndex(ci);
+ }
+ else {
+ var parent = this._view._getItemAtIndex(item.parentIndex);
+ --rowCountImpact;
+ if (parent.cookies.length == 1) {
+ --rowCountImpact;
+ deleteItems.push(item);
+ if (!this._view.hasNextSibling(-1, ci))
+ --nextSelected;
+ if (!this._view.hasNextSibling(-1, item.parentIndex))
+ --nextSelected;
+ this._view._removeItemAtIndex(item.parentIndex);
+ invalidateRow = item.parentIndex;
+ }
+ else {
+ deleteItems.push(item);
+ if (!this._view.hasNextSibling(-1, ci))
+ --nextSelected;
+ this._view._removeItemAtIndex(ci);
+ }
+ }
+ this._view._rowCount += rowCountImpact;
+ tbo.rowCountChanged(ci, rowCountImpact);
+ if (invalidateRow != -1)
+ tbo.invalidateRow(invalidateRow);
+ }
+ else {
+ var rangeCount = seln.getRangeCount();
+ // Traverse backwards through selections to avoid messing
+ // up the indices when they are deleted.
+ // See bug 388079.
+ for (var i = rangeCount - 1; i >= 0; --i) {
+ var min = {}; var max = {};
+ seln.getRangeAt(i, min, max);
+ nextSelected = min.value;
+ for (var j = min.value; j <= max.value; ++j) {
+ deleteItems.push(this._view._getItemAtIndex(j));
+ if (!this._view.hasNextSibling(-1, max.value))
+ --nextSelected;
+ }
+ var delta = max.value - min.value + 1;
+ this._view._removeItemAtIndex(min.value, delta);
+ rowCountImpact = -1 * delta;
+ this._view._rowCount += rowCountImpact;
+ tbo.rowCountChanged(min.value, rowCountImpact);
+ }
+ }
+
+ this.performDeletion(deleteItems);
+
+ if (nextSelected < 0)
+ seln.clearSelection();
+ else {
+ seln.select(nextSelected);
+ this._tree.focus();
+ }
+ },
+
+ deleteAllCookies: function () {
+ if (this._view._filtered) {
+ var rowCount = this._view.rowCount;
+ var deleteItems = [];
+ for (var index = 0; index < rowCount; index++) {
+ deleteItems.push(this._view._getItemAtIndex(index));
+ }
+ this._view._removeItemAtIndex(0, rowCount);
+ this._view._rowCount = 0;
+ this._tree.treeBoxObject.rowCountChanged(0, -rowCount);
+ this.performDeletion(deleteItems);
+ }
+ else {
+ this._cm.removeAll();
+ }
+ this._updateRemoveAllButton();
+ this.focusFilterBox();
+ },
+
+ onCookieKeyPress: function (aEvent) {
+ if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE
+#ifdef XP_MACOSX
+ || aEvent.keyCode == KeyEvent.DOM_VK_BACK_SPACE
+#endif
+ ) {
+ this.deleteCookie();
+ }
+ },
+
+ _lastSortProperty : "",
+ _lastSortAscending: false,
+ sort: function (aProperty) {
+ var ascending = (aProperty == this._lastSortProperty) ? !this._lastSortAscending : true;
+ // Sort the Non-Filtered Host Collections
+ if (aProperty == "rawHost") {
+ function sortByHost(a, b) {
+ return a.toLowerCase().localeCompare(b.toLowerCase());
+ }
+ this._hostOrder.sort(sortByHost);
+ if (!ascending)
+ this._hostOrder.reverse();
+ }
+
+ function sortByProperty(a, b) {
+ return a[aProperty].toLowerCase().localeCompare(b[aProperty].toLowerCase());
+ }
+ for (var host in this._hosts) {
+ var cookies = this._hosts[host].cookies;
+ cookies.sort(sortByProperty);
+ if (!ascending)
+ cookies.reverse();
+ }
+ // Sort the Filtered List, if in Filtered mode
+ if (this._view._filtered) {
+ this._view._filterSet.sort(sortByProperty);
+ if (!ascending)
+ this._view._filterSet.reverse();
+ }
+
+ // Adjust the Sort Indicator
+ var domainCol = document.getElementById("domainCol");
+ var nameCol = document.getElementById("nameCol");
+ var sortOrderString = ascending ? "ascending" : "descending";
+ if (aProperty == "rawHost") {
+ domainCol.setAttribute("sortDirection", sortOrderString);
+ nameCol.removeAttribute("sortDirection");
+ }
+ else {
+ nameCol.setAttribute("sortDirection", sortOrderString);
+ domainCol.removeAttribute("sortDirection");
+ }
+
+ this._view._invalidateCache(0);
+ this._view.selection.clearSelection();
+ if (this._view.rowCount > 0) {
+ this._view.selection.select(0);
+ }
+ this._tree.treeBoxObject.invalidate();
+ this._tree.treeBoxObject.ensureRowIsVisible(0);
+
+ this._lastSortAscending = ascending;
+ this._lastSortProperty = aProperty;
+ },
+
+ clearFilter: function () {
+ // Revert to single-select in the tree
+ this._tree.setAttribute("seltype", "single");
+
+ // Clear the Tree Display
+ this._view._filtered = false;
+ this._view._rowCount = 0;
+ this._tree.treeBoxObject.rowCountChanged(0, -this._view._filterSet.length);
+ this._view._filterSet = [];
+
+ // Just reload the list to make sure deletions are respected
+ this._loadCookies();
+ this._tree.view = this._view;
+
+ // Restore sort order
+ var sortby = this._lastSortProperty;
+ if (sortby == "") {
+ this._lastSortAscending = false;
+ this.sort("rawHost");
+ }
+ else {
+ this._lastSortAscending = !this._lastSortAscending;
+ this.sort(sortby);
+ }
+
+ // Restore open state
+ for (var i = 0; i < this._openIndices.length; ++i)
+ this._view.toggleOpenState(this._openIndices[i]);
+ this._openIndices = [];
+
+ // Restore selection
+ this._view.selection.clearSelection();
+ for (i = 0; i < this._lastSelectedRanges.length; ++i) {
+ var range = this._lastSelectedRanges[i];
+ this._view.selection.rangedSelect(range.min, range.max, true);
+ }
+ this._lastSelectedRanges = [];
+
+ document.getElementById("cookiesIntro").value = this._bundle.getString("cookiesAll");
+ this._updateRemoveAllButton();
+ },
+
+ _cookieMatchesFilter: function (aCookie) {
+ return aCookie.rawHost.indexOf(this._view._filterValue) != -1 ||
+ aCookie.name.indexOf(this._view._filterValue) != -1 ||
+ aCookie.value.indexOf(this._view._filterValue) != -1;
+ },
+
+ _filterCookies: function (aFilterValue) {
+ this._view._filterValue = aFilterValue;
+ var cookies = [];
+ for (var i = 0; i < gCookiesWindow._hostOrder.length; ++i) { //var host in gCookiesWindow._hosts) {
+ var currHost = gCookiesWindow._hosts[gCookiesWindow._hostOrder[i]]; // gCookiesWindow._hosts[host];
+ if (!currHost) continue;
+ for (var j = 0; j < currHost.cookies.length; ++j) {
+ var cookie = currHost.cookies[j];
+ if (this._cookieMatchesFilter(cookie))
+ cookies.push(cookie);
+ }
+ }
+ return cookies;
+ },
+
+ _lastSelectedRanges: [],
+ _openIndices: [],
+ _saveState: function () {
+ // Save selection
+ var seln = this._view.selection;
+ this._lastSelectedRanges = [];
+ var rangeCount = seln.getRangeCount();
+ for (var i = 0; i < rangeCount; ++i) {
+ var min = {}; var max = {};
+ seln.getRangeAt(i, min, max);
+ this._lastSelectedRanges.push({ min: min.value, max: max.value });
+ }
+
+ // Save open states
+ this._openIndices = [];
+ for (i = 0; i < this._view.rowCount; ++i) {
+ var item = this._view._getItemAtIndex(i);
+ if (item && item.container && item.open)
+ this._openIndices.push(i);
+ }
+ },
+
+ _updateRemoveAllButton: function gCookiesWindow__updateRemoveAllButton() {
+ let removeAllCookies = document.getElementById("removeAllCookies");
+ removeAllCookies.disabled = this._view._rowCount == 0;
+
+ let labelStringID = "removeAllCookies.label";
+ let accessKeyStringID = "removeAllCookies.accesskey";
+ if (this._view._filtered) {
+ labelStringID = "removeAllShownCookies.label";
+ accessKeyStringID = "removeAllShownCookies.accesskey";
+ }
+ removeAllCookies.setAttribute("label", this._bundle.getString(labelStringID));
+ removeAllCookies.setAttribute("accesskey", this._bundle.getString(accessKeyStringID));
+ },
+
+ filter: function () {
+ var filter = document.getElementById("filter").value;
+ if (filter == "") {
+ gCookiesWindow.clearFilter();
+ return;
+ }
+ var view = gCookiesWindow._view;
+ view._filterSet = gCookiesWindow._filterCookies(filter);
+ if (!view._filtered) {
+ // Save Display Info for the Non-Filtered mode when we first
+ // enter Filtered mode.
+ gCookiesWindow._saveState();
+ view._filtered = true;
+ }
+ // Move to multi-select in the tree
+ gCookiesWindow._tree.setAttribute("seltype", "multiple");
+
+ // Clear the display
+ var oldCount = view._rowCount;
+ view._rowCount = 0;
+ gCookiesWindow._tree.treeBoxObject.rowCountChanged(0, -oldCount);
+ // Set up the filtered display
+ view._rowCount = view._filterSet.length;
+ gCookiesWindow._tree.treeBoxObject.rowCountChanged(0, view.rowCount);
+
+ // if the view is not empty then select the first item
+ if (view.rowCount > 0)
+ view.selection.select(0);
+
+ document.getElementById("cookiesIntro").value = gCookiesWindow._bundle.getString("cookiesFiltered");
+ this._updateRemoveAllButton();
+ },
+
+ setFilter: function (aFilterString) {
+ document.getElementById("filter").value = aFilterString;
+ this.filter();
+ },
+
+ focusFilterBox: function () {
+ var filter = document.getElementById("filter");
+ filter.focus();
+ filter.select();
+ },
+
+ onWindowKeyPress: function (aEvent) {
+ if (aEvent.keyCode == KeyEvent.DOM_VK_ESCAPE)
+ window.close();
+ }
+};
diff --git a/components/preferences/cookies.xul b/components/preferences/cookies.xul
new file mode 100644
index 0000000..60725e9
--- /dev/null
+++ b/components/preferences/cookies.xul
@@ -0,0 +1,106 @@
+<?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/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://browser/locale/preferences/cookies.dtd" >
+
+<window id="CookiesDialog" windowtype="Browser:Cookies"
+ class="windowDialog" title="&window.title;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ style="width: &window.width;;"
+ onload="gCookiesWindow.init();"
+ onunload="gCookiesWindow.uninit();"
+ persist="screenX screenY width height"
+ onkeypress="gCookiesWindow.onWindowKeyPress(event);">
+
+ <script src="chrome://browser/content/preferences/cookies.js"/>
+
+ <stringbundle id="bundlePreferences"
+ src="chrome://browser/locale/preferences/preferences.properties"/>
+
+ <keyset>
+ <key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/>
+ <key key="&focusSearch1.key;" modifiers="accel" oncommand="gCookiesWindow.focusFilterBox();"/>
+ <key key="&focusSearch2.key;" modifiers="accel" oncommand="gCookiesWindow.focusFilterBox();"/>
+ </keyset>
+
+ <vbox flex="1" class="contentPane">
+ <hbox align="center">
+ <label accesskey="&filter.accesskey;" control="filter">&filter.label;</label>
+ <textbox type="search" id="filter" flex="1"
+ aria-controls="cookiesList"
+ oncommand="gCookiesWindow.filter();"/>
+ </hbox>
+ <separator class="thin"/>
+ <label control="cookiesList" id="cookiesIntro" value="&cookiesonsystem.label;"/>
+ <separator class="thin"/>
+ <tree id="cookiesList" flex="1" style="height: 10em;"
+ onkeypress="gCookiesWindow.onCookieKeyPress(event)"
+ onselect="gCookiesWindow.onCookieSelected();"
+ hidecolumnpicker="true" seltype="single">
+ <treecols>
+ <treecol id="domainCol" label="&cookiedomain.label;" flex="2" primary="true"
+ persist="width" onclick="gCookiesWindow.sort('rawHost');"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="nameCol" label="&cookiename.label;" flex="1"
+ persist="width"
+ onclick="gCookiesWindow.sort('name');"/>
+ </treecols>
+ <treechildren id="cookiesChildren"/>
+ </tree>
+ <hbox id="cookieInfoBox">
+ <grid flex="1" id="cookieInfoGrid">
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <hbox pack="end"><label id="nameLabel" control="name" value="&props.name.label;"/></hbox>
+ <textbox id="name" readonly="true" class="plain"/>
+ </row>
+ <row align="center">
+ <hbox pack="end"><label id="valueLabel" control="value" value="&props.value.label;"/></hbox>
+ <textbox id="value" readonly="true" class="plain"/>
+ </row>
+ <row align="center">
+ <hbox pack="end"><label id="isDomain" control="host" value="&props.domain.label;"/></hbox>
+ <textbox id="host" readonly="true" class="plain"/>
+ </row>
+ <row align="center">
+ <hbox pack="end"><label id="pathLabel" control="path" value="&props.path.label;"/></hbox>
+ <textbox id="path" readonly="true" class="plain"/>
+ </row>
+ <row align="center">
+ <hbox pack="end"><label id="isSecureLabel" control="isSecure" value="&props.secure.label;"/></hbox>
+ <textbox id="isSecure" readonly="true" class="plain"/>
+ </row>
+ <row align="center">
+ <hbox pack="end"><label id="expiresLabel" control="expires" value="&props.expires.label;"/></hbox>
+ <textbox id="expires" readonly="true" class="plain"/>
+ </row>
+ </rows>
+ </grid>
+ </hbox>
+ </vbox>
+ <hbox align="end">
+ <hbox class="actionButtons" flex="1">
+ <button id="removeSelectedCookies" disabled="true" icon="clear"
+ oncommand="gCookiesWindow.deleteCookie();"/>
+ <button id="removeAllCookies" disabled="true" icon="clear"
+ oncommand="gCookiesWindow.deleteAllCookies();"/>
+ <spacer flex="1"/>
+#ifndef XP_MACOSX
+ <button oncommand="close();" icon="close"
+ label="&button.close.label;" accesskey="&button.close.accesskey;"/>
+#endif
+ </hbox>
+ <resizer type="window" dir="bottomend"/>
+ </hbox>
+</window>
diff --git a/components/preferences/fonts.js b/components/preferences/fonts.js
new file mode 100644
index 0000000..e9f93a2
--- /dev/null
+++ b/components/preferences/fonts.js
@@ -0,0 +1,144 @@
+/* -*- 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/. */
+
+// browser.display.languageList LOCK ALL when LOCKED
+
+const kDefaultFontType = "font.default.%LANG%";
+const kFontNameFmtSerif = "font.name.serif.%LANG%";
+const kFontNameFmtSansSerif = "font.name.sans-serif.%LANG%";
+const kFontNameFmtMonospace = "font.name.monospace.%LANG%";
+const kFontNameListFmtSerif = "font.name-list.serif.%LANG%";
+const kFontNameListFmtSansSerif = "font.name-list.sans-serif.%LANG%";
+const kFontNameListFmtMonospace = "font.name-list.monospace.%LANG%";
+const kFontSizeFmtVariable = "font.size.variable.%LANG%";
+const kFontSizeFmtFixed = "font.size.fixed.%LANG%";
+const kFontMinSizeFmt = "font.minimum-size.%LANG%";
+
+var gFontsDialog = {
+ _selectLanguageGroup: function (aLanguageGroup)
+ {
+ var prefs = [{ format: kDefaultFontType, type: "string", element: "defaultFontType", fonttype: null},
+ { format: kFontNameFmtSerif, type: "fontname", element: "serif", fonttype: "serif" },
+ { format: kFontNameFmtSansSerif, type: "fontname", element: "sans-serif", fonttype: "sans-serif" },
+ { format: kFontNameFmtMonospace, type: "fontname", element: "monospace", fonttype: "monospace" },
+ { format: kFontNameListFmtSerif, type: "unichar", element: null, fonttype: "serif" },
+ { format: kFontNameListFmtSansSerif, type: "unichar", element: null, fonttype: "sans-serif" },
+ { format: kFontNameListFmtMonospace, type: "unichar", element: null, fonttype: "monospace" },
+ { format: kFontSizeFmtVariable, type: "int", element: "sizeVar", fonttype: null },
+ { format: kFontSizeFmtFixed, type: "int", element: "sizeMono", fonttype: null },
+ { format: kFontMinSizeFmt, type: "int", element: "minSize", fonttype: null }];
+ var preferences = document.getElementById("fontPreferences");
+ for (var i = 0; i < prefs.length; ++i) {
+ var preference = document.getElementById(prefs[i].format.replace(/%LANG%/, aLanguageGroup));
+ if (!preference) {
+ preference = document.createElement("preference");
+ var name = prefs[i].format.replace(/%LANG%/, aLanguageGroup);
+ preference.id = name;
+ preference.setAttribute("name", name);
+ preference.setAttribute("type", prefs[i].type);
+ preferences.appendChild(preference);
+ }
+
+ if (!prefs[i].element)
+ continue;
+
+ var element = document.getElementById(prefs[i].element);
+ if (element) {
+ element.setAttribute("preference", preference.id);
+
+ if (prefs[i].fonttype)
+ FontBuilder.buildFontList(aLanguageGroup, prefs[i].fonttype, element);
+
+ preference.setElementValue(element);
+ }
+ }
+ },
+
+ readFontLanguageGroup: function ()
+ {
+ var languagePref = document.getElementById("font.language.group");
+ this._selectLanguageGroup(languagePref.value);
+ return undefined;
+ },
+
+ readFontSelection: function (aElement)
+ {
+ // Determine the appropriate value to select, for the following cases:
+ // - there is no setting
+ // - the font selected by the user is no longer present (e.g. deleted from
+ // fonts folder)
+ var preference = document.getElementById(aElement.getAttribute("preference"));
+ if (preference.value) {
+ var fontItems = aElement.getElementsByAttribute("value", preference.value);
+
+ // There is a setting that actually is in the list. Respect it.
+ if (fontItems.length > 0)
+ return undefined;
+ }
+
+ var defaultValue = aElement.firstChild.firstChild.getAttribute("value");
+ var languagePref = document.getElementById("font.language.group");
+ preference = document.getElementById("font.name-list." + aElement.id + "." + languagePref.value);
+ if (!preference || !preference.hasUserValue)
+ return defaultValue;
+
+ var fontNames = preference.value.split(",");
+ var stripWhitespace = /^\s*(.*)\s*$/;
+
+ for (var i = 0; i < fontNames.length; ++i) {
+ var fontName = fontNames[i].replace(stripWhitespace, "$1");
+ fontItems = aElement.getElementsByAttribute("value", fontName);
+ if (fontItems.length)
+ break;
+ }
+ if (fontItems.length)
+ return fontItems[0].getAttribute("value");
+ return defaultValue;
+ },
+
+ readUseDocumentFonts: function ()
+ {
+ var preference = document.getElementById("browser.display.use_document_fonts");
+ return preference.value == 1;
+ },
+
+ writeUseDocumentFonts: function ()
+ {
+ var useDocumentFonts = document.getElementById("useDocumentFonts");
+ return useDocumentFonts.checked ? 1 : 0;
+ },
+
+ onBeforeAccept: function ()
+ {
+ // Only care in in-content prefs
+ if (!window.frameElement) {
+ return true;
+ }
+
+ let preferences = document.querySelectorAll("preference[id*='font.minimum-size']");
+ // It would be good if we could avoid touching languages the pref pages won't use, but
+ // unfortunately the language group APIs (deducing language groups from language codes)
+ // are C++ - only. So we just check all the things the user touched:
+ // Don't care about anything up to 24px, or if this value is the same as set previously:
+ preferences = Array.filter(preferences, prefEl => {
+ return prefEl.value > 24 && prefEl.value != prefEl.valueFromPreferences;
+ });
+ if (!preferences.length) {
+ return;
+ }
+
+ let strings = document.getElementById("bundlePreferences");
+ let title = strings.getString("veryLargeMinimumFontTitle");
+ let confirmLabel = strings.getString("acceptVeryLargeMinimumFont");
+ let warningMessage = strings.getString("veryLargeMinimumFontWarning");
+ let {Services} = Components.utils.import("resource://gre/modules/Services.jsm", {});
+ let flags = Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL |
+ Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING |
+ Services.prompt.BUTTON_POS_1_DEFAULT;
+ let buttonChosen = Services.prompt.confirmEx(window, title, warningMessage, flags, confirmLabel, null, "", "", {});
+ return buttonChosen == 0;
+ },
+};
+
diff --git a/components/preferences/fonts.xul b/components/preferences/fonts.xul
new file mode 100644
index 0000000..97c6869
--- /dev/null
+++ b/components/preferences/fonts.xul
@@ -0,0 +1,279 @@
+<?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/" type="text/css"?>
+#ifdef XP_MACOSX
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?>
+#endif
+
+<!DOCTYPE prefwindow SYSTEM "chrome://browser/locale/preferences/fonts.dtd" >
+
+<prefwindow id="FontsDialog" type="child"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&fontsDialog.title;"
+ dlgbuttons="accept,cancel,help"
+ ondialoghelp="openPrefsHelp()"
+ onbeforeaccept="return gFontsDialog.onBeforeAccept();"
+ style="">
+
+ <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
+
+ <prefpane id="FontsDialogPane"
+ class="largeDialogContainer"
+ helpTopic="prefs-fonts-and-colors">
+
+ <preferences id="fontPreferences">
+ <preference id="font.language.group" name="font.language.group" type="wstring"/>
+ <preference id="browser.display.use_document_fonts"
+ name="browser.display.use_document_fonts"
+ type="int"/>
+ <preference id="intl.charset.fallback.override" name="intl.charset.fallback.override" type="string"/>
+ </preferences>
+
+ <stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences/preferences.properties"/>
+ <script type="application/javascript" src="chrome://mozapps/content/preferences/fontbuilder.js"/>
+ <script type="application/javascript" src="chrome://browser/content/preferences/fonts.js"/>
+
+ <!-- Fonts for: [ Language ] -->
+ <groupbox>
+ <caption>
+ <hbox align="center">
+ <label accesskey="&language.accesskey;" control="selectLangs">&language.label;</label>
+ </hbox>
+ <menulist id="selectLangs" preference="font.language.group"
+ onsyncfrompreference="return gFontsDialog.readFontLanguageGroup();">
+ <menupopup>
+ <menuitem value="ar" label="&font.langGroup.arabic;"/>
+ <menuitem value="x-armn" label="&font.langGroup.armenian;"/>
+ <menuitem value="x-beng" label="&font.langGroup.bengali;"/>
+ <menuitem value="zh-CN" label="&font.langGroup.simpl-chinese;"/>
+ <menuitem value="zh-HK" label="&font.langGroup.trad-chinese-hk;"/>
+ <menuitem value="zh-TW" label="&font.langGroup.trad-chinese;"/>
+ <menuitem value="x-cyrillic" label="&font.langGroup.cyrillic;"/>
+ <menuitem value="x-devanagari" label="&font.langGroup.devanagari;"/>
+ <menuitem value="x-ethi" label="&font.langGroup.ethiopic;"/>
+ <menuitem value="x-geor" label="&font.langGroup.georgian;"/>
+ <menuitem value="el" label="&font.langGroup.el;"/>
+ <menuitem value="x-gujr" label="&font.langGroup.gujarati;"/>
+ <menuitem value="x-guru" label="&font.langGroup.gurmukhi;"/>
+ <menuitem value="he" label="&font.langGroup.hebrew;"/>
+ <menuitem value="ja" label="&font.langGroup.japanese;"/>
+ <menuitem value="x-knda" label="&font.langGroup.kannada;"/>
+ <menuitem value="x-khmr" label="&font.langGroup.khmer;"/>
+ <menuitem value="ko" label="&font.langGroup.korean;"/>
+ <menuitem value="x-western" label="&font.langGroup.latin;"/>
+ <menuitem value="x-mlym" label="&font.langGroup.malayalam;"/>
+ <menuitem value="x-orya" label="&font.langGroup.oriya;"/>
+ <menuitem value="x-sinh" label="&font.langGroup.sinhala;"/>
+ <menuitem value="x-tamil" label="&font.langGroup.tamil;"/>
+ <menuitem value="x-telu" label="&font.langGroup.telugu;"/>
+ <menuitem value="th" label="&font.langGroup.thai;"/>
+ <menuitem value="x-tibt" label="&font.langGroup.tibetan;"/>
+ <menuitem value="x-cans" label="&font.langGroup.canadian;"/>
+ <menuitem value="x-unicode" label="&font.langGroup.other;"/>
+ </menupopup>
+ </menulist>
+ </caption>
+
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ <column/>
+ <column/>
+ </columns>
+
+ <rows>
+ <row>
+ <separator class="thin"/>
+ </row>
+
+ <row align="center">
+ <hbox align="center" pack="end">
+ <label accesskey="&proportional.accesskey;" control="defaultFontType">&proportional.label;</label>
+ </hbox>
+ <menulist id="defaultFontType" flex="1" style="width: 0px;">
+ <menupopup>
+ <menuitem value="serif" label="&useDefaultFontSerif.label;"/>
+ <menuitem value="sans-serif" label="&useDefaultFontSansSerif.label;"/>
+ </menupopup>
+ </menulist>
+ <hbox align="center" pack="end">
+ <label value="&size.label;"
+ accesskey="&sizeProportional.accesskey;"
+ control="sizeVar"/>
+ </hbox>
+ <menulist id="sizeVar">
+ <menupopup>
+ <menuitem value="9" label="9"/>
+ <menuitem value="10" label="10"/>
+ <menuitem value="11" label="11"/>
+ <menuitem value="12" label="12"/>
+ <menuitem value="13" label="13"/>
+ <menuitem value="14" label="14"/>
+ <menuitem value="15" label="15"/>
+ <menuitem value="16" label="16"/>
+ <menuitem value="17" label="17"/>
+ <menuitem value="18" label="18"/>
+ <menuitem value="20" label="20"/>
+ <menuitem value="22" label="22"/>
+ <menuitem value="24" label="24"/>
+ <menuitem value="26" label="26"/>
+ <menuitem value="28" label="28"/>
+ <menuitem value="30" label="30"/>
+ <menuitem value="32" label="32"/>
+ <menuitem value="34" label="34"/>
+ <menuitem value="36" label="36"/>
+ <menuitem value="40" label="40"/>
+ <menuitem value="44" label="44"/>
+ <menuitem value="48" label="48"/>
+ <menuitem value="56" label="56"/>
+ <menuitem value="64" label="64"/>
+ <menuitem value="72" label="72"/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row align="center">
+ <hbox align="center" pack="end">
+ <label accesskey="&serif.accesskey;" control="serif">&serif.label;</label>
+ </hbox>
+ <menulist id="serif" flex="1" style="width: 0px;"
+ onsyncfrompreference="return gFontsDialog.readFontSelection(document.getElementById('serif'));"/>
+ <spacer/>
+ </row>
+ <row align="center">
+ <hbox align="center" pack="end">
+ <label accesskey="&sans-serif.accesskey;" control="sans-serif">&sans-serif.label;</label>
+ </hbox>
+ <menulist id="sans-serif" flex="1" style="width: 0px;"
+ onsyncfrompreference="return gFontsDialog.readFontSelection(document.getElementById('sans-serif'));"/>
+ <spacer/>
+ </row>
+ <row align="center">
+ <hbox align="center" pack="end">
+ <label accesskey="&monospace.accesskey;" control="monospace">&monospace.label;</label>
+ </hbox>
+ <menulist id="monospace" flex="1" style="width: 0px;" crop="right"
+ onsyncfrompreference="return gFontsDialog.readFontSelection(document.getElementById('monospace'));"/>
+ <hbox align="center" pack="end">
+ <label value="&size.label;"
+ accesskey="&sizeMonospace.accesskey;"
+ control="sizeMono"/>
+ </hbox>
+ <menulist id="sizeMono">
+ <menupopup>
+ <menuitem value="9" label="9"/>
+ <menuitem value="10" label="10"/>
+ <menuitem value="11" label="11"/>
+ <menuitem value="12" label="12"/>
+ <menuitem value="13" label="13"/>
+ <menuitem value="14" label="14"/>
+ <menuitem value="15" label="15"/>
+ <menuitem value="16" label="16"/>
+ <menuitem value="17" label="17"/>
+ <menuitem value="18" label="18"/>
+ <menuitem value="20" label="20"/>
+ <menuitem value="22" label="22"/>
+ <menuitem value="24" label="24"/>
+ <menuitem value="26" label="26"/>
+ <menuitem value="28" label="28"/>
+ <menuitem value="30" label="30"/>
+ <menuitem value="32" label="32"/>
+ <menuitem value="34" label="34"/>
+ <menuitem value="36" label="36"/>
+ <menuitem value="40" label="40"/>
+ <menuitem value="44" label="44"/>
+ <menuitem value="48" label="48"/>
+ <menuitem value="56" label="56"/>
+ <menuitem value="64" label="64"/>
+ <menuitem value="72" label="72"/>
+ </menupopup>
+ </menulist>
+ </row>
+ </rows>
+ </grid>
+ <separator class="thin"/>
+ <hbox flex="1">
+ <spacer flex="1"/>
+ <hbox align="center" pack="end">
+ <label accesskey="&minSize.accesskey;" control="minSize">&minSize.label;</label>
+ <menulist id="minSize">
+ <menupopup>
+ <menuitem value="0" label="&minSize.none;"/>
+ <menuitem value="9" label="9"/>
+ <menuitem value="10" label="10"/>
+ <menuitem value="11" label="11"/>
+ <menuitem value="12" label="12"/>
+ <menuitem value="13" label="13"/>
+ <menuitem value="14" label="14"/>
+ <menuitem value="15" label="15"/>
+ <menuitem value="16" label="16"/>
+ <menuitem value="17" label="17"/>
+ <menuitem value="18" label="18"/>
+ <menuitem value="20" label="20"/>
+ <menuitem value="22" label="22"/>
+ <menuitem value="24" label="24"/>
+ <menuitem value="26" label="26"/>
+ <menuitem value="28" label="28"/>
+ <menuitem value="30" label="30"/>
+ <menuitem value="32" label="32"/>
+ <menuitem value="34" label="34"/>
+ <menuitem value="36" label="36"/>
+ <menuitem value="40" label="40"/>
+ <menuitem value="44" label="44"/>
+ <menuitem value="48" label="48"/>
+ <menuitem value="56" label="56"/>
+ <menuitem value="64" label="64"/>
+ <menuitem value="72" label="72"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ </hbox>
+ <separator/>
+ <separator class="groove"/>
+ <hbox>
+ <checkbox id="useDocumentFonts"
+ label="&allowPagesToUse.label;" accesskey="&allowPagesToUse.accesskey;"
+ preference="browser.display.use_document_fonts"
+ onsyncfrompreference="return gFontsDialog.readUseDocumentFonts();"
+ onsynctopreference="return gFontsDialog.writeUseDocumentFonts();"/>
+ </hbox>
+ </groupbox>
+
+ <!-- Character Encoding -->
+ <groupbox>
+ <caption label="&languages.customize.Fallback.grouplabel;"/>
+ <description>&languages.customize.Fallback.desc;</description>
+ <hbox align="center">
+ <label value="&languages.customize.Fallback.label;"
+ accesskey="&languages.customize.Fallback.accesskey;"
+ control="DefaultCharsetList"/>
+ <menulist id="DefaultCharsetList" preference="intl.charset.fallback.override">
+ <menupopup>
+ <menuitem label="&languages.customize.Fallback.auto;" value="*"/>
+ <menuitem label="&languages.customize.Fallback.utf8;" value="UTF-8"/>
+ <menuitem label="&languages.customize.Fallback.arabic;" value="windows-1256"/>
+ <menuitem label="&languages.customize.Fallback.baltic;" value="windows-1257"/>
+ <menuitem label="&languages.customize.Fallback.ceiso;" value="ISO-8859-2"/>
+ <menuitem label="&languages.customize.Fallback.cewindows;" value="windows-1250"/>
+ <menuitem label="&languages.customize.Fallback.simplified;" value="gbk"/>
+ <menuitem label="&languages.customize.Fallback.traditional;" value="Big5"/>
+ <menuitem label="&languages.customize.Fallback.cyrillic;" value="windows-1251"/>
+ <menuitem label="&languages.customize.Fallback.greek;" value="ISO-8859-7"/>
+ <menuitem label="&languages.customize.Fallback.hebrew;" value="windows-1255"/>
+ <menuitem label="&languages.customize.Fallback.japanese;" value="Shift_JIS"/>
+ <menuitem label="&languages.customize.Fallback.korean;" value="EUC-KR"/>
+ <menuitem label="&languages.customize.Fallback.thai;" value="windows-874"/>
+ <menuitem label="&languages.customize.Fallback.turkish;" value="windows-1254"/>
+ <menuitem label="&languages.customize.Fallback.vietnamese;" value="windows-1258"/>
+ <menuitem label="&languages.customize.Fallback.other;" value="windows-1252"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ </groupbox>
+ </prefpane>
+</prefwindow>
diff --git a/components/preferences/handlers.css b/components/preferences/handlers.css
new file mode 100644
index 0000000..9a1d474
--- /dev/null
+++ b/components/preferences/handlers.css
@@ -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/. */
+
+richlistitem {
+ -moz-binding: url("chrome://browser/content/preferences/handlers.xml#handler");
+}
+
+richlistitem[selected="true"] {
+ -moz-binding: url("chrome://browser/content/preferences/handlers.xml#handler-selected");
+}
+
+/**
+ * Make the icons appear.
+ * Note: we display the icon box for every item whether or not it has an icon
+ * so the labels of all the items align vertically.
+ */
+.actionsMenu > menupopup > menuitem > .menu-iconic-left {
+ display: -moz-box;
+ min-width: 16px;
+}
+
+listitem.offlineapp {
+ -moz-binding: url("chrome://browser/content/preferences/handlers.xml#offlineapp");
+}
diff --git a/components/preferences/handlers.xml b/components/preferences/handlers.xml
new file mode 100644
index 0000000..5fb915c
--- /dev/null
+++ b/components/preferences/handlers.xml
@@ -0,0 +1,81 @@
+<?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">
+ <!ENTITY % applicationsDTD SYSTEM "chrome://browser/locale/preferences/applications.dtd">
+ %brandDTD;
+ %applicationsDTD;
+]>
+
+<bindings id="handlerBindings"
+ 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="handler-base" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+ <implementation>
+ <property name="type" readonly="true">
+ <getter>
+ return this.getAttribute("type");
+ </getter>
+ </property>
+ </implementation>
+ </binding>
+
+ <binding id="handler" extends="chrome://browser/content/preferences/handlers.xml#handler-base">
+ <content>
+ <xul:hbox flex="1" equalsize="always">
+ <xul:hbox flex="1" align="center" xbl:inherits="tooltiptext=typeDescription">
+ <xul:image src="moz-icon://goat?size=16" class="typeIcon"
+ xbl:inherits="src=typeIcon" height="16" width="16"/>
+ <xul:label flex="1" crop="end" xbl:inherits="value=typeDescription"/>
+ </xul:hbox>
+ <xul:hbox flex="1" align="center" xbl:inherits="tooltiptext=actionDescription">
+ <xul:image xbl:inherits="src=actionIcon" height="16" width="16" class="actionIcon"/>
+ <xul:label flex="1" crop="end" xbl:inherits="value=actionDescription"/>
+ </xul:hbox>
+ </xul:hbox>
+ </content>
+ </binding>
+
+ <binding id="handler-selected" extends="chrome://browser/content/preferences/handlers.xml#handler-base">
+ <content>
+ <xul:hbox flex="1" equalsize="always">
+ <xul:hbox flex="1" align="center" xbl:inherits="tooltiptext=typeDescription">
+ <xul:image src="moz-icon://goat?size=16" class="typeIcon"
+ xbl:inherits="src=typeIcon" height="16" width="16"/>
+ <xul:label flex="1" crop="end" xbl:inherits="value=typeDescription"/>
+ </xul:hbox>
+ <xul:hbox flex="1">
+ <xul:menulist class="actionsMenu" flex="1" crop="end" selectedIndex="1"
+ xbl:inherits="tooltiptext=actionDescription"
+ oncommand="gApplicationsPane.onSelectAction(event.originalTarget)">
+ <xul:menupopup/>
+ </xul:menulist>
+ </xul:hbox>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <constructor>
+ gApplicationsPane.rebuildActionsMenu();
+ </constructor>
+ </implementation>
+
+ </binding>
+
+ <binding id="offlineapp"
+ extends="chrome://global/content/bindings/listbox.xml#listitem">
+ <content>
+ <children>
+ <xul:listcell xbl:inherits="label=origin"/>
+ <xul:listcell xbl:inherits="label=usage"/>
+ </children>
+ </content>
+ </binding>
+
+</bindings>
diff --git a/components/preferences/jar.mn b/components/preferences/jar.mn
new file mode 100644
index 0000000..6e143de
--- /dev/null
+++ b/components/preferences/jar.mn
@@ -0,0 +1,44 @@
+# 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/preferences/advanced.xul
+* content/browser/preferences/advanced.js
+ content/browser/preferences/applications.xul
+* content/browser/preferences/applications.js
+ content/browser/preferences/applicationManager.xul
+* content/browser/preferences/applicationManager.js
+* content/browser/preferences/colors.xul
+* content/browser/preferences/cookies.xul
+* content/browser/preferences/cookies.js
+ content/browser/preferences/content.xul
+ content/browser/preferences/content.js
+* content/browser/preferences/connection.xul
+ content/browser/preferences/connection.js
+* content/browser/preferences/fonts.xul
+ content/browser/preferences/fonts.js
+ content/browser/preferences/handlers.xml
+ content/browser/preferences/handlers.css
+* content/browser/preferences/languages.xul
+ content/browser/preferences/languages.js
+* content/browser/preferences/main.xul
+ content/browser/preferences/main.js
+ content/browser/preferences/newtaburl.js
+ content/browser/preferences/permissions.xul
+* content/browser/preferences/permissions.js
+* content/browser/preferences/preferences.xul
+ content/browser/preferences/privacy.xul
+ content/browser/preferences/privacy.js
+ content/browser/preferences/sanitize.xul
+ content/browser/preferences/sanitize.js
+ content/browser/preferences/security.xul
+ content/browser/preferences/security.js
+ content/browser/preferences/selectBookmark.xul
+ content/browser/preferences/selectBookmark.js
+#ifdef MOZ_SERVICES_SYNC
+ content/browser/preferences/sync.xul
+ content/browser/preferences/sync.js
+#endif
+* content/browser/preferences/tabs.xul
+* content/browser/preferences/tabs.js
diff --git a/components/preferences/languages.js b/components/preferences/languages.js
new file mode 100644
index 0000000..8d2b394
--- /dev/null
+++ b/components/preferences/languages.js
@@ -0,0 +1,304 @@
+/* -*- 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/. */
+
+var gLanguagesDialog = {
+
+ _availableLanguagesList : [],
+ _acceptLanguages : { },
+
+ _selectedItemID : null,
+
+ init: function ()
+ {
+ if (!this._availableLanguagesList.length)
+ this._loadAvailableLanguages();
+ },
+
+ get _activeLanguages()
+ {
+ return document.getElementById("activeLanguages");
+ },
+
+ get _availableLanguages()
+ {
+ return document.getElementById("availableLanguages");
+ },
+
+ _loadAvailableLanguages: function ()
+ {
+ // This is a parser for: resource://gre/res/language.properties
+ // The file is formatted like so:
+ // ab[-cd].accept=true|false
+ // ab = language
+ // cd = region
+ var bundleAccepted = document.getElementById("bundleAccepted");
+ var bundleRegions = document.getElementById("bundleRegions");
+ var bundleLanguages = document.getElementById("bundleLanguages");
+ var bundlePreferences = document.getElementById("bundlePreferences");
+
+ function LanguageInfo(aName, aABCD, aIsVisible)
+ {
+ this.name = aName;
+ this.abcd = aABCD;
+ this.isVisible = aIsVisible;
+ }
+
+ // 1) Read the available languages out of language.properties
+ var strings = bundleAccepted.strings;
+ while (strings.hasMoreElements()) {
+ var currString = strings.getNext();
+ if (!(currString instanceof Components.interfaces.nsIPropertyElement))
+ break;
+
+ var property = currString.key.split("."); // ab[-cd].accept
+ if (property[1] == "accept") {
+ var abCD = property[0];
+ var abCDPairs = abCD.split("-"); // ab[-cd]
+ var useABCDFormat = abCDPairs.length > 1;
+ var ab = useABCDFormat ? abCDPairs[0] : abCD;
+ var cd = useABCDFormat ? abCDPairs[1] : "";
+ if (ab) {
+ var language = "";
+ try {
+ language = bundleLanguages.getString(ab);
+ }
+ catch (e) { continue; };
+
+ var region = "";
+ if (useABCDFormat) {
+ try {
+ region = bundleRegions.getString(cd);
+ }
+ catch (e) { continue; }
+ }
+
+ var name = "";
+ if (useABCDFormat)
+ name = bundlePreferences.getFormattedString("languageRegionCodeFormat",
+ [language, region, abCD]);
+ else
+ name = bundlePreferences.getFormattedString("languageCodeFormat",
+ [language, abCD]);
+
+ if (name && abCD) {
+ var isVisible = currString.value == "true" &&
+ (!(abCD in this._acceptLanguages) || !this._acceptLanguages[abCD]);
+ var li = new LanguageInfo(name, abCD, isVisible);
+ this._availableLanguagesList.push(li);
+ }
+ }
+ }
+ }
+ this._buildAvailableLanguageList();
+ },
+
+ _buildAvailableLanguageList: function ()
+ {
+ var availableLanguagesPopup = document.getElementById("availableLanguagesPopup");
+ while (availableLanguagesPopup.hasChildNodes())
+ availableLanguagesPopup.removeChild(availableLanguagesPopup.firstChild);
+
+ // Sort the list of languages by name
+ this._availableLanguagesList.sort(function (a, b) {
+ return a.name.localeCompare(b.name);
+ });
+
+ // Load the UI with the data
+ for (var i = 0; i < this._availableLanguagesList.length; ++i) {
+ var abCD = this._availableLanguagesList[i].abcd;
+ if (this._availableLanguagesList[i].isVisible &&
+ (!(abCD in this._acceptLanguages) || !this._acceptLanguages[abCD])) {
+ var menuitem = document.createElement("menuitem");
+ menuitem.id = this._availableLanguagesList[i].abcd;
+ availableLanguagesPopup.appendChild(menuitem);
+ menuitem.setAttribute("label", this._availableLanguagesList[i].name);
+ }
+ }
+ },
+
+ readAcceptLanguages: function ()
+ {
+ while (this._activeLanguages.hasChildNodes())
+ this._activeLanguages.removeChild(this._activeLanguages.firstChild);
+
+ var selectedIndex = 0;
+ var preference = document.getElementById("intl.accept_languages");
+ if (preference.value == "")
+ return undefined;
+ var languages = preference.value.toLowerCase().split(/\s*,\s*/);
+ for (var i = 0; i < languages.length; ++i) {
+ var name = this._getLanguageName(languages[i]);
+ if (!name)
+ name = "[" + languages[i] + "]";
+ var listitem = document.createElement("listitem");
+ listitem.id = languages[i];
+ if (languages[i] == this._selectedItemID)
+ selectedIndex = i;
+ this._activeLanguages.appendChild(listitem);
+ listitem.setAttribute("label", name);
+
+ // Hash this language as an "Active" language so we don't
+ // show it in the list that can be added.
+ this._acceptLanguages[languages[i]] = true;
+ }
+
+ if (this._activeLanguages.childNodes.length > 0) {
+ this._activeLanguages.ensureIndexIsVisible(selectedIndex);
+ this._activeLanguages.selectedIndex = selectedIndex;
+ }
+
+ return undefined;
+ },
+
+ writeAcceptLanguages: function ()
+ {
+ return undefined;
+ },
+
+ onAvailableLanguageSelect: function ()
+ {
+ var addButton = document.getElementById("addButton");
+ addButton.disabled = false;
+
+ this._availableLanguages.removeAttribute("accesskey");
+ },
+
+ addLanguage: function ()
+ {
+ var selectedID = this._availableLanguages.selectedItem.id;
+ var preference = document.getElementById("intl.accept_languages");
+ var arrayOfPrefs = preference.value.toLowerCase().split(/\s*,\s*/);
+ for (var i = 0; i < arrayOfPrefs.length; ++i ){
+ if (arrayOfPrefs[i] == selectedID)
+ return;
+ }
+
+ this._selectedItemID = selectedID;
+
+ if (preference.value == "")
+ preference.value = selectedID;
+ else {
+ arrayOfPrefs.unshift(selectedID);
+ preference.value = arrayOfPrefs.join(",");
+ }
+
+ this._acceptLanguages[selectedID] = true;
+ this._availableLanguages.selectedItem = null;
+
+ // Rebuild the available list with the added item removed...
+ this._buildAvailableLanguageList();
+
+ this._availableLanguages.setAttribute("label", this._availableLanguages.getAttribute("label2"));
+ },
+
+ removeLanguage: function ()
+ {
+ // Build the new preference value string.
+ var languagesArray = [];
+ for (var i = 0; i < this._activeLanguages.childNodes.length; ++i) {
+ var item = this._activeLanguages.childNodes[i];
+ if (!item.selected)
+ languagesArray.push(item.id);
+ else
+ this._acceptLanguages[item.id] = false;
+ }
+ var string = languagesArray.join(",");
+
+ // Get the item to select after the remove operation completes.
+ var selection = this._activeLanguages.selectedItems;
+ var lastSelected = selection[selection.length-1];
+ var selectItem = lastSelected.nextSibling || lastSelected.previousSibling;
+ selectItem = selectItem ? selectItem.id : null;
+
+ this._selectedItemID = selectItem;
+
+ // Update the preference and force a UI rebuild
+ var preference = document.getElementById("intl.accept_languages");
+ preference.value = string;
+
+ this._buildAvailableLanguageList();
+ },
+
+ _getLanguageName: function (aABCD)
+ {
+ if (!this._availableLanguagesList.length)
+ this._loadAvailableLanguages();
+ for (var i = 0; i < this._availableLanguagesList.length; ++i) {
+ if (aABCD == this._availableLanguagesList[i].abcd)
+ return this._availableLanguagesList[i].name;
+ }
+ return "";
+ },
+
+ moveUp: function ()
+ {
+ var selectedItem = this._activeLanguages.selectedItems[0];
+ var previousItem = selectedItem.previousSibling;
+
+ var string = "";
+ for (var i = 0; i < this._activeLanguages.childNodes.length; ++i) {
+ var item = this._activeLanguages.childNodes[i];
+ string += (i == 0 ? "" : ",");
+ if (item.id == previousItem.id)
+ string += selectedItem.id;
+ else if (item.id == selectedItem.id)
+ string += previousItem.id;
+ else
+ string += item.id;
+ }
+
+ this._selectedItemID = selectedItem.id;
+
+ // Update the preference and force a UI rebuild
+ var preference = document.getElementById("intl.accept_languages");
+ preference.value = string;
+ },
+
+ moveDown: function ()
+ {
+ var selectedItem = this._activeLanguages.selectedItems[0];
+ var nextItem = selectedItem.nextSibling;
+
+ var string = "";
+ for (var i = 0; i < this._activeLanguages.childNodes.length; ++i) {
+ var item = this._activeLanguages.childNodes[i];
+ string += (i == 0 ? "" : ",");
+ if (item.id == nextItem.id)
+ string += selectedItem.id;
+ else if (item.id == selectedItem.id)
+ string += nextItem.id;
+ else
+ string += item.id;
+ }
+
+ this._selectedItemID = selectedItem.id;
+
+ // Update the preference and force a UI rebuild
+ var preference = document.getElementById("intl.accept_languages");
+ preference.value = string;
+ },
+
+ onLanguageSelect: function ()
+ {
+ var upButton = document.getElementById("up");
+ var downButton = document.getElementById("down");
+ var removeButton = document.getElementById("remove");
+ switch (this._activeLanguages.selectedCount) {
+ case 0:
+ upButton.disabled = downButton.disabled = removeButton.disabled = true;
+ break;
+ case 1:
+ upButton.disabled = this._activeLanguages.selectedIndex == 0;
+ downButton.disabled = this._activeLanguages.selectedIndex == this._activeLanguages.childNodes.length - 1;
+ removeButton.disabled = false;
+ break;
+ default:
+ upButton.disabled = true;
+ downButton.disabled = true;
+ removeButton.disabled = false;
+ }
+ }
+};
+
diff --git a/components/preferences/languages.xul b/components/preferences/languages.xul
new file mode 100644
index 0000000..6fd8232
--- /dev/null
+++ b/components/preferences/languages.xul
@@ -0,0 +1,98 @@
+<?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/.
+
+<!DOCTYPE prefwindow SYSTEM "chrome://browser/locale/preferences/languages.dtd">
+
+<?xml-stylesheet href="chrome://global/skin/"?>
+#ifdef XP_MACOSX
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?>
+#endif
+
+<prefwindow id="LanguagesDialog" type="child"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&languages.customize.Header;"
+ dlgbuttons="accept,cancel,help"
+ ondialoghelp="openPrefsHelp()"
+ style="width: &window.width;;">
+
+ <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
+
+ <prefpane id="LanguagesDialogPane"
+ onpaneload="gLanguagesDialog.init();"
+ helpTopic="prefs-languages">
+
+ <preferences>
+ <preference id="intl.accept_languages" name="intl.accept_languages" type="wstring"/>
+ <preference id="pref.browser.language.disable_button.up"
+ name="pref.browser.language.disable_button.up"
+ type="bool"/>
+ <preference id="pref.browser.language.disable_button.down"
+ name="pref.browser.language.disable_button.down"
+ type="bool"/>
+ <preference id="pref.browser.language.disable_button.remove"
+ name="pref.browser.language.disable_button.remove"
+ type="bool"/>
+ </preferences>
+
+ <script type="application/javascript" src="chrome://browser/content/preferences/languages.js"/>
+
+ <stringbundleset id="languageSet">
+ <stringbundle id="bundleRegions" src="chrome://global/locale/regionNames.properties"/>
+ <stringbundle id="bundleLanguages" src="chrome://global/locale/languageNames.properties"/>
+ <stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences/preferences.properties"/>
+ <stringbundle id="bundleAccepted" src="resource://gre/res/language.properties"/>
+ </stringbundleset>
+
+ <description>&languages.customize.prefLangDescript;</description>
+ <label>&languages.customize.active.label;</label>
+ <grid flex="1">
+ <columns>
+ <column flex="1"/>
+ <column/>
+ </columns>
+ <rows>
+ <row flex="1">
+ <listbox id="activeLanguages" flex="1" rows="6"
+ seltype="multiple" onselect="gLanguagesDialog.onLanguageSelect();"
+ preference="intl.accept_languages"
+ onsyncfrompreference="return gLanguagesDialog.readAcceptLanguages();"
+ onsynctopreference="return gLanguagesDialog.writeAcceptLanguages();"/>
+ <vbox>
+ <button id="up" class="up" oncommand="gLanguagesDialog.moveUp();" disabled="true"
+ label="&languages.customize.moveUp.label;"
+ accesskey="&languages.customize.moveUp.accesskey;"
+ preference="pref.browser.language.disable_button.up"/>
+ <button id="down" class="down" oncommand="gLanguagesDialog.moveDown();" disabled="true"
+ label="&languages.customize.moveDown.label;"
+ accesskey="&languages.customize.moveDown.accesskey;"
+ preference="pref.browser.language.disable_button.down"/>
+ <button id="remove" oncommand="gLanguagesDialog.removeLanguage();" disabled="true"
+ label="&languages.customize.deleteButton.label;"
+ accesskey="&languages.customize.deleteButton.accesskey;"
+ preference="pref.browser.language.disable_button.remove"/>
+ </vbox>
+ </row>
+ <row>
+ <separator class="thin"/>
+ </row>
+ <row>
+ <menulist id="availableLanguages" oncommand="gLanguagesDialog.onAvailableLanguageSelect();"
+ label="&languages.customize.selectLanguage.label;"
+ label2="&languages.customize.selectLanguage.label;">
+ <menupopup id="availableLanguagesPopup"/>
+ </menulist>
+ <button id="addButton" oncommand="gLanguagesDialog.addLanguage();" disabled="true"
+ label="&languages.customize.addButton.label;"
+ accesskey="&languages.customize.addButton.accesskey;"/>
+ </row>
+ </rows>
+ </grid>
+ <separator/>
+ <separator/>
+ </prefpane>
+</prefwindow>
+
diff --git a/components/preferences/main.js b/components/preferences/main.js
new file mode 100644
index 0000000..07e09ff
--- /dev/null
+++ b/components/preferences/main.js
@@ -0,0 +1,473 @@
+/* -*- 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/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DownloadsCommon",
+ "resource:///modules/DownloadsCommon.jsm");
+
+var gMainPane = {
+ _pane: null,
+
+ /**
+ * Initialization of this.
+ */
+ init: function ()
+ {
+ this._pane = document.getElementById("paneMain");
+
+ // set up the "use current page" label-changing listener
+ this._updateUseCurrentButton();
+ window.addEventListener("focus", this._updateUseCurrentButton.bind(this), false);
+
+ this.updateBrowserStartupLastSession();
+
+ this.setupDownloadsWindowOptions();
+
+ // Notify observers that the UI is now ready
+ Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService)
+ .notifyObservers(window, "main-pane-loaded", null);
+ },
+
+ setupDownloadsWindowOptions: function ()
+ {
+ let showWhenDownloading = document.getElementById("showWhenDownloading");
+ let closeWhenDone = document.getElementById("closeWhenDone");
+
+ // These radio buttons should be hidden when the Downloads Panel is enabled.
+ let shouldHide = !DownloadsCommon.useToolkitUI;
+ showWhenDownloading.hidden = shouldHide;
+ closeWhenDone.hidden = shouldHide;
+ },
+
+ // HOME PAGE
+
+ /*
+ * Preferences:
+ *
+ * browser.startup.homepage
+ * - the user's home page, as a string; if the home page is a set of tabs,
+ * this will be those URLs separated by the pipe character "|"
+ * browser.startup.page
+ * - what page(s) to show when the user starts the application, as an integer:
+ *
+ * 0: a blank page
+ * 1: the home page (as set by the browser.startup.homepage pref)
+ * 2: the last page the user visited (DEPRECATED)
+ * 3: windows and tabs from the last session (a.k.a. session restore)
+ *
+ * The deprecated option is not exposed in UI; however, if the user has it
+ * selected and doesn't change the UI for this preference, the deprecated
+ * option is preserved.
+ */
+
+ syncFromHomePref: function ()
+ {
+ let homePref = document.getElementById("browser.startup.homepage");
+
+ // If the pref is set to about:home, set the value to "" to show the
+ // placeholder text (about:home title).
+ if (homePref.value.toLowerCase() == "about:home")
+ return "";
+
+ // If the pref is actually "", show a blank page. The actual home page
+ // loading code treats them the same, and we don't want the placeholder text
+ // to be shown.
+ if (homePref.value == "")
+ return "about:logopage";
+
+ // Otherwise, show the actual pref value.
+ return undefined;
+ },
+
+ syncToHomePref: function (value)
+ {
+ // If the value is "", use about:home.
+ if (value == "")
+ return "about:home";
+
+ // Otherwise, use the actual textbox value.
+ return undefined;
+ },
+
+ /**
+ * Sets the home page to the current displayed page (or frontmost tab, if the
+ * most recent browser window contains multiple tabs), updating preference
+ * window UI to reflect this.
+ */
+ setHomePageToCurrent: function ()
+ {
+ let homePage = document.getElementById("browser.startup.homepage");
+ let tabs = this._getTabsForHomePage();
+ function getTabURI(t) t.linkedBrowser.currentURI.spec;
+
+ // FIXME Bug 244192: using dangerous "|" joiner!
+ if (tabs.length)
+ homePage.value = tabs.map(getTabURI).join("|");
+ },
+
+ /**
+ * Displays a dialog in which the user can select a bookmark to use as home
+ * page. If the user selects a bookmark, that bookmark's name is displayed in
+ * UI and the bookmark's address is stored to the home page preference.
+ */
+ setHomePageToBookmark: function ()
+ {
+ var rv = { urls: null, names: null };
+ document.documentElement.openSubDialog("chrome://browser/content/preferences/selectBookmark.xul",
+ "resizable", rv);
+ if (rv.urls && rv.names) {
+ var homePage = document.getElementById("browser.startup.homepage");
+
+ // XXX still using dangerous "|" joiner!
+ homePage.value = rv.urls.join("|");
+ }
+ },
+
+ /**
+ * Switches the "Use Current Page" button between its singular and plural
+ * forms.
+ */
+ _updateUseCurrentButton: function () {
+ let useCurrent = document.getElementById("useCurrent");
+
+ let tabs = this._getTabsForHomePage();
+ if (tabs.length > 1)
+ useCurrent.label = useCurrent.getAttribute("label2");
+ else
+ useCurrent.label = useCurrent.getAttribute("label1");
+
+ // In this case, the button's disabled state is set by preferences.xml.
+ if (document.getElementById
+ ("pref.browser.homepage.disable_button.current_page").locked)
+ return;
+
+ useCurrent.disabled = !tabs.length
+ },
+
+ _getTabsForHomePage: function ()
+ {
+ var win;
+ var tabs = [];
+ if (document.documentElement.instantApply) {
+ const Cc = Components.classes, Ci = Components.interfaces;
+ // If we're in instant-apply mode, use the most recent browser window
+ var wm = Cc["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Ci.nsIWindowMediator);
+ win = wm.getMostRecentWindow("navigator:browser");
+ }
+ else {
+ win = window.opener;
+ }
+
+ if (win && win.document.documentElement
+ .getAttribute("windowtype") == "navigator:browser") {
+ // We should only include visible & non-pinned tabs
+ tabs = win.gBrowser.visibleTabs.slice(win.gBrowser._numPinnedTabs);
+ }
+
+ return tabs;
+ },
+
+ /**
+ * Restores the default home page as the user's home page.
+ */
+ restoreDefaultHomePage: function ()
+ {
+ var homePage = document.getElementById("browser.startup.homepage");
+ homePage.value = homePage.defaultValue;
+ },
+
+ // DOWNLOADS
+
+ /*
+ * Preferences:
+ *
+ * browser.download.showWhenStarting - bool
+ * True if the Download Manager should be opened when a download is
+ * started, false if it shouldn't be opened.
+ * browser.download.closeWhenDone - bool
+ * True if the Download Manager should be closed when all downloads
+ * complete, false if it should be left open.
+ * browser.download.useDownloadDir - bool
+ * True - Save files directly to the folder configured via the
+ * browser.download.folderList preference.
+ * False - Always ask the user where to save a file and default to
+ * browser.download.lastDir when displaying a folder picker dialog.
+ * browser.download.dir - local file handle
+ * A local folder the user may have selected for downloaded files to be
+ * saved. Migration of other browser settings may also set this path.
+ * This folder is enabled when folderList equals 2.
+ * browser.download.lastDir - local file handle
+ * May contain the last folder path accessed when the user browsed
+ * via the file save-as dialog. (see contentAreaUtils.js)
+ * browser.download.folderList - int
+ * Indicates the location users wish to save downloaded files too.
+ * It is also used to display special file labels when the default
+ * download location is either the Desktop or the Downloads folder.
+ * Values:
+ * 0 - The desktop is the default download location.
+ * 1 - The system's downloads folder is the default download location.
+ * 2 - The default download location is elsewhere as specified in
+ * browser.download.dir.
+ * browser.download.downloadDir
+ * deprecated.
+ * browser.download.defaultFolder
+ * deprecated.
+ */
+
+ /**
+ * Updates preferences which depend upon the value of the preference which
+ * determines whether the Downloads manager is opened at the start of a
+ * download.
+ */
+ readShowDownloadsWhenStarting: function ()
+ {
+ this.showDownloadsWhenStartingPrefChanged();
+
+ // don't override the preference's value in UI
+ return undefined;
+ },
+
+ /**
+ * Enables or disables the "close Downloads manager when downloads finished"
+ * preference element, consequently updating the associated UI.
+ */
+ showDownloadsWhenStartingPrefChanged: function ()
+ {
+ var showWhenStartingPref = document.getElementById("browser.download.manager.showWhenStarting");
+ var closeWhenDonePref = document.getElementById("browser.download.manager.closeWhenDone");
+ closeWhenDonePref.disabled = !showWhenStartingPref.value;
+ },
+
+ /**
+ * Enables/disables the folder field and Browse button based on whether a
+ * default download directory is being used.
+ */
+ readUseDownloadDir: function ()
+ {
+ var downloadFolder = document.getElementById("downloadFolder");
+ var chooseFolder = document.getElementById("chooseFolder");
+ var preference = document.getElementById("browser.download.useDownloadDir");
+ downloadFolder.disabled = !preference.value;
+ chooseFolder.disabled = !preference.value;
+
+ // don't override the preference's value in UI
+ return undefined;
+ },
+
+ /**
+ * Displays a file picker in which the user can choose the location where
+ * downloads are automatically saved, updating preferences and UI in
+ * response to the choice, if one is made.
+ */
+ chooseFolder: function ()
+ {
+ const nsIFilePicker = Components.interfaces.nsIFilePicker;
+ const nsILocalFile = Components.interfaces.nsILocalFile;
+
+ let bundlePreferences = document.getElementById("bundlePreferences");
+ let title = bundlePreferences.getString("chooseDownloadFolderTitle");
+ let folderListPref = document.getElementById("browser.download.folderList");
+ let currentDirPref = this._indexToFolder(folderListPref.value); // file
+ let defDownloads = this._indexToFolder(1); // file
+ let fp = Components.classes["@mozilla.org/filepicker;1"].
+ createInstance(nsIFilePicker);
+ let fpCallback = function fpCallback_done(aResult) {
+ if (aResult == nsIFilePicker.returnOK) {
+ let file = fp.file.QueryInterface(nsILocalFile);
+ let downloadDirPref = document.getElementById("browser.download.dir");
+
+ downloadDirPref.value = file;
+ folderListPref.value = this._folderToIndex(file);
+ // Note, the real prefs will not be updated yet, so dnld manager's
+ // userDownloadsDirectory may not return the right folder after
+ // this code executes. displayDownloadDirPref will be called on
+ // the assignment above to update the UI.
+ }
+ }.bind(this);
+
+ fp.init(window, title, nsIFilePicker.modeGetFolder);
+ fp.appendFilters(nsIFilePicker.filterAll);
+ // First try to open what's currently configured
+ if (currentDirPref && currentDirPref.exists()) {
+ fp.displayDirectory = currentDirPref;
+ } // Try the system's download dir
+ else if (defDownloads && defDownloads.exists()) {
+ fp.displayDirectory = defDownloads;
+ } // Fall back to Desktop
+ else {
+ fp.displayDirectory = this._indexToFolder(0);
+ }
+ fp.open(fpCallback);
+ },
+
+ /**
+ * Initializes the download folder display settings based on the user's
+ * preferences.
+ */
+ displayDownloadDirPref: function ()
+ {
+ var folderListPref = document.getElementById("browser.download.folderList");
+ var bundlePreferences = document.getElementById("bundlePreferences");
+ var downloadFolder = document.getElementById("downloadFolder");
+ var currentDirPref = document.getElementById("browser.download.dir");
+
+ // Used in defining the correct path to the folder icon.
+ var ios = Components.classes["@mozilla.org/network/io-service;1"]
+ .getService(Components.interfaces.nsIIOService);
+ var fph = ios.getProtocolHandler("file")
+ .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
+ var iconUrlSpec;
+
+ // Display a 'pretty' label or the path in the UI.
+ if (folderListPref.value == 2) {
+ // Custom path selected and is configured
+ downloadFolder.label = this._getDisplayNameOfFile(currentDirPref.value);
+ iconUrlSpec = fph.getURLSpecFromFile(currentDirPref.value);
+ } else if (folderListPref.value == 1) {
+ // 'Downloads'
+ // In 1.5, this pointed to a folder we created called 'My Downloads'
+ // and was available as an option in the 1.5 drop down. On XP this
+ // was in My Documents, on OSX it was in User Docs. In 2.0, we did
+ // away with the drop down option, although the special label was
+ // still supported for the folder if it existed. Because it was
+ // not exposed it was rarely used.
+ // With 3.0, a new desktop folder - 'Downloads' was introduced for
+ // platforms and versions that don't support a default system downloads
+ // folder. See nsDownloadManager for details.
+ downloadFolder.label = bundlePreferences.getString("downloadsFolderName");
+ iconUrlSpec = fph.getURLSpecFromFile(this._indexToFolder(1));
+ } else {
+ // 'Desktop'
+ downloadFolder.label = bundlePreferences.getString("desktopFolderName");
+ iconUrlSpec = fph.getURLSpecFromFile(this._getDownloadsFolder("Desktop"));
+ }
+ downloadFolder.image = "moz-icon://" + iconUrlSpec + "?size=16";
+
+ // don't override the preference's value in UI
+ return undefined;
+ },
+
+ /**
+ * Returns the textual path of a folder in readable form.
+ */
+ _getDisplayNameOfFile: function (aFolder)
+ {
+ // TODO: would like to add support for 'Downloads on Macintosh HD'
+ // for OS X users.
+ return aFolder ? aFolder.path : "";
+ },
+
+ /**
+ * Returns the Downloads folder. If aFolder is "Desktop", then the Downloads
+ * folder returned is the desktop folder; otherwise, it is a folder whose name
+ * indicates that it is a download folder and whose path is as determined by
+ * the XPCOM directory service via the download manager's attribute
+ * defaultDownloadsDirectory.
+ *
+ * @throws if aFolder is not "Desktop" or "Downloads"
+ */
+ _getDownloadsFolder: function (aFolder)
+ {
+ switch (aFolder) {
+ case "Desktop":
+ var fileLoc = Components.classes["@mozilla.org/file/directory_service;1"]
+ .getService(Components.interfaces.nsIProperties);
+ return fileLoc.get("Desk", Components.interfaces.nsILocalFile);
+ break;
+ case "Downloads":
+ var dnldMgr = Components.classes["@mozilla.org/download-manager;1"]
+ .getService(Components.interfaces.nsIDownloadManager);
+ return dnldMgr.defaultDownloadsDirectory;
+ break;
+ }
+ throw "ASSERTION FAILED: folder type should be 'Desktop' or 'Downloads'";
+ },
+
+ /**
+ * Determines the type of the given folder.
+ *
+ * @param aFolder
+ * the folder whose type is to be determined
+ * @returns integer
+ * 0 if aFolder is the Desktop or is unspecified,
+ * 1 if aFolder is the Downloads folder,
+ * 2 otherwise
+ */
+ _folderToIndex: function (aFolder)
+ {
+ if (!aFolder || aFolder.equals(this._getDownloadsFolder("Desktop")))
+ return 0;
+ else if (aFolder.equals(this._getDownloadsFolder("Downloads")))
+ return 1;
+ return 2;
+ },
+
+ /**
+ * Converts an integer into the corresponding folder.
+ *
+ * @param aIndex
+ * an integer
+ * @returns the Desktop folder if aIndex == 0,
+ * the Downloads folder if aIndex == 1,
+ * the folder stored in browser.download.dir
+ */
+ _indexToFolder: function (aIndex)
+ {
+ switch (aIndex) {
+ case 0:
+ return this._getDownloadsFolder("Desktop");
+ case 1:
+ return this._getDownloadsFolder("Downloads");
+ }
+ var currentDirPref = document.getElementById("browser.download.dir");
+ return currentDirPref.value;
+ },
+
+ /**
+ * Returns the value for the browser.download.folderList preference.
+ */
+ getFolderListPref: function ()
+ {
+ var folderListPref = document.getElementById("browser.download.folderList");
+ switch (folderListPref.value) {
+ case 0: // Desktop
+ case 1: // Downloads
+ return folderListPref.value;
+ break;
+ case 2: // Custom
+ var currentDirPref = document.getElementById("browser.download.dir");
+ if (currentDirPref.value) {
+ // Resolve to a known location if possible. We are writing out
+ // to prefs on this call, so now would be a good time to do it.
+ return this._folderToIndex(currentDirPref.value);
+ }
+ return 0;
+ break;
+ }
+ },
+
+ /**
+ * Hide/show the "Show my windows and tabs from last time" option based
+ * on the value of the browser.privatebrowsing.autostart pref.
+ */
+ updateBrowserStartupLastSession: function()
+ {
+ let pbAutoStartPref = document.getElementById("browser.privatebrowsing.autostart");
+ let startupPref = document.getElementById("browser.startup.page");
+ let menu = document.getElementById("browserStartupPage");
+ let option = document.getElementById("browserStartupLastSession");
+ if (pbAutoStartPref.value) {
+ option.setAttribute("disabled", "true");
+ if (option.selected) {
+ menu.selectedItem = document.getElementById("browserStartupHomePage");
+ }
+ } else {
+ option.removeAttribute("disabled");
+ startupPref.updateElements(); // select the correct index in the startup menulist
+ }
+ }
+};
diff --git a/components/preferences/main.xul b/components/preferences/main.xul
new file mode 100644
index 0000000..bb51947
--- /dev/null
+++ b/components/preferences/main.xul
@@ -0,0 +1,188 @@
+<?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/.
+
+<!DOCTYPE overlay [
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+ <!ENTITY % mainDTD SYSTEM "chrome://browser/locale/preferences/main.dtd">
+ <!ENTITY % aboutHomeDTD SYSTEM "chrome://browser/locale/aboutHome.dtd">
+ %brandDTD;
+ %mainDTD;
+ %aboutHomeDTD;
+]>
+
+<overlay id="MainPaneOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <prefpane id="paneMain"
+ onpaneload="gMainPane.init();"
+ helpTopic="prefs-main">
+
+ <script type="application/javascript" src="chrome://browser/content/preferences/main.js"/>
+
+ <preferences id="mainPreferences">
+ <!-- XXX Button preferences -->
+
+ <!-- Startup -->
+ <preference id="browser.startup.page"
+ name="browser.startup.page"
+ type="int"/>
+ <preference id="browser.startup.homepage"
+ name="browser.startup.homepage"
+ type="wstring"/>
+
+ <preference id="pref.browser.homepage.disable_button.current_page"
+ name="pref.browser.homepage.disable_button.current_page"
+ type="bool"/>
+ <preference id="pref.browser.homepage.disable_button.bookmark_page"
+ name="pref.browser.homepage.disable_button.bookmark_page"
+ type="bool"/>
+ <preference id="pref.browser.homepage.disable_button.restore_default"
+ name="pref.browser.homepage.disable_button.restore_default"
+ type="bool"/>
+
+ <preference id="browser.privatebrowsing.autostart"
+ name="browser.privatebrowsing.autostart"
+ type="bool"
+ onchange="gMainPane.updateBrowserStartupLastSession();"/>
+
+ <!-- Downloads -->
+ <preference id="browser.download.manager.showWhenStarting"
+ name="browser.download.manager.showWhenStarting"
+ type="bool"
+ onchange="gMainPane.showDownloadsWhenStartingPrefChanged();"/>
+ <preference id="browser.download.manager.closeWhenDone"
+ name="browser.download.manager.closeWhenDone"
+ type="bool"/>
+ <preference id="browser.download.useDownloadDir"
+ name="browser.download.useDownloadDir"
+ type="bool"/>
+ <preference id="browser.download.dir"
+ name="browser.download.dir"
+ type="file"
+ onchange="gMainPane.displayDownloadDirPref();"/>
+ <preference id="browser.download.folderList" name="browser.download.folderList" type="int"/>
+ <preference id="browser.download.useToolkitUI" name="browser.download.useToolkitUI" type="bool" />
+#ifdef XP_WIN
+ <preference id="browser.download.saveZoneInformation" name="browser.download.saveZoneInformation" type="int" />
+#endif
+
+ </preferences>
+
+ <stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences/preferences.properties"/>
+
+ <!-- Startup -->
+ <groupbox id="startupGroup">
+ <caption label="&startup.label;"/>
+
+ <hbox align="center">
+ <label value="&startupPage.label;" accesskey="&startupPage.accesskey;"
+ control="browserStartupPage"/>
+ <menulist id="browserStartupPage" preference="browser.startup.page">
+ <menupopup>
+ <menuitem label="&startupHomePage.label;" value="1" id="browserStartupHomePage"/>
+ <menuitem label="&startupBlankPage.label;" value="0" id="browserStartupBlank"/>
+ <menuitem label="&startupLastSession.label;" value="3" id="browserStartupLastSession"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ <separator class="thin"/>
+ <hbox align="center">
+ <label value="&homepage.label;" accesskey="&homepage.accesskey;" control="browserHomePage"/>
+ <textbox id="browserHomePage" class="padded uri-element" flex="1"
+ type="autocomplete" autocompletesearch="history"
+ onsyncfrompreference="return gMainPane.syncFromHomePref();"
+ onsynctopreference="return gMainPane.syncToHomePref(this.value);"
+ oninput="gNewtabUrl.writeNewtabUrl(null, this.value);"
+ placeholder="&abouthome.pageTitle;"
+ preference="browser.startup.homepage"/>
+ </hbox>
+ <hbox align="center" pack="end">
+ <button label="" accesskey="&useCurrentPage.accesskey;"
+ label1="&useCurrentPage.label;"
+ label2="&useMultiple.label;"
+ oncommand="gMainPane.setHomePageToCurrent(); gNewtabUrl.writeNewtabUrl();"
+ id="useCurrent"
+ preference="pref.browser.homepage.disable_button.current_page"/>
+ <button label="&chooseBookmark.label;" accesskey="&chooseBookmark.accesskey;"
+ oncommand="gMainPane.setHomePageToBookmark(); gNewtabUrl.writeNewtabUrl();"
+ id="useBookmark"
+ preference="pref.browser.homepage.disable_button.bookmark_page"/>
+ <button label="&restoreDefault.label;" accesskey="&restoreDefault.accesskey;"
+ oncommand="gMainPane.restoreDefaultHomePage(); gNewtabUrl.writeNewtabUrl();"
+ id="restoreDefaultHomePage"
+ preference="pref.browser.homepage.disable_button.restore_default"/>
+ </hbox>
+ </groupbox>
+
+ <!-- Downloads -->
+ <groupbox id="downloadsGroup">
+ <caption label="&downloads.label;"/>
+
+ <checkbox id="showWhenDownloading" label="&showWhenDownloading.label;"
+ accesskey="&showWhenDownloading.accesskey;"
+ preference="browser.download.manager.showWhenStarting"
+ onsyncfrompreference="return gMainPane.readShowDownloadsWhenStarting();"/>
+ <checkbox id="closeWhenDone" label="&closeWhenDone.label;"
+ accesskey="&closeWhenDone.accesskey;" class="indent"
+ preference="browser.download.manager.closeWhenDone"/>
+
+ <separator class="thin"/>
+
+ <radiogroup id="saveWhere"
+ preference="browser.download.useDownloadDir"
+ onsyncfrompreference="return gMainPane.readUseDownloadDir();">
+ <hbox id="saveToRow">
+ <radio id="saveTo" value="true"
+ label="&saveTo.label;"
+ accesskey="&saveTo.accesskey;"
+ aria-labelledby="saveTo downloadFolder"/>
+ <filefield id="downloadFolder" flex="1"
+ preference="browser.download.folderList"
+ preference-editable="true"
+ aria-labelledby="saveTo"
+ onsyncfrompreference="return gMainPane.displayDownloadDirPref();"
+ onsynctopreference="return gMainPane.getFolderListPref()"/>
+ <button id="chooseFolder" oncommand="gMainPane.chooseFolder();"
+#ifdef XP_MACOSX
+ accesskey="&chooseFolderMac.accesskey;"
+ label="&chooseFolderMac.label;"
+#else
+ accesskey="&chooseFolderWin.accesskey;"
+ label="&chooseFolderWin.label;"
+#endif
+ preference="browser.download.folderList"
+ onsynctopreference="return gMainPane.getFolderListPref();"/>
+ </hbox>
+ <radio id="alwaysAsk" value="false"
+ label="&alwaysAsk.label;"
+ accesskey="&alwaysAsk.accesskey;"/>
+ </radiogroup>
+#if 0
+<!-- Disabled for now -- ToolkitUI DM is nonfunctional. -->
+ <checkbox id="classicDownloadWindow"
+ preference="browser.download.useToolkitUI"
+ label="&toolkit.classic.download.window.label;" />
+#endif
+#ifdef XP_WIN
+ <hbox align="center">
+ <label id="zoneInfoLabel" control="zoneInfo-menu">&zoneInfo.label;</label>
+ <menulist id="zoneInfo-menu"
+ preference="browser.download.saveZoneInformation"
+ sizetopopup="always">
+ <menupopup>
+ <menuitem label="&zoneInfo.never;" value="0" />
+ <menuitem label="&zoneInfo.always;" value="1" />
+ <menuitem label="&zoneInfo.system;" value="2" />
+ </menupopup>
+ </menulist>
+ </hbox>
+#endif
+ </groupbox>
+
+ </prefpane>
+
+</overlay>
diff --git a/components/preferences/moz.build b/components/preferences/moz.build
new file mode 100644
index 0000000..31fddae
--- /dev/null
+++ b/components/preferences/moz.build
@@ -0,0 +1,14 @@
+# -*- 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/.
+
+
+for var in ('MOZ_APP_NAME', 'MOZ_MACBUNDLE_NAME'):
+ DEFINES[var] = CONFIG[var]
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('windows', 'gtk2', 'gtk3', 'cocoa'):
+ DEFINES['HAVE_SHELL_SERVICE'] = 1
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/components/preferences/newtaburl.js b/components/preferences/newtaburl.js
new file mode 100644
index 0000000..3c82df8
--- /dev/null
+++ b/components/preferences/newtaburl.js
@@ -0,0 +1,102 @@
+/* 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 gNewtabUrl = {
+ /**
+ * Writes browser.newtab.url with the appropriate value.
+ * If the choice is "my home page", get and sanitize
+ * the browser home page URL to make it suitable for newtab use.
+ *
+ * Called from prefwindow ondialogaccept in preferences.xul,
+ * newtabPage oncommand in tabs.xul, browserHomePage oninput,
+ * useCurrent, useBookmark and restoreDefaultHomePage oncommand
+ * in main.xul to consider instantApply.
+ */
+ writeNewtabUrl: function(newtabUrlChoice, browserHomepageUrl) {
+ try {
+ if (newtabUrlChoice) {
+ if (Services.prefs.getBoolPref("browser.preferences.instantApply")) {
+ newtabUrlChoice = parseInt(newtabUrlChoice);
+ } else {
+ return;
+ }
+ } else {
+ if (this.newtabUrlChoiceIsSet) {
+ newtabUrlChoice = Services.prefs.getIntPref("browser.newtab.choice");
+ } else {
+ newtabUrlChoice = this.getNewtabChoice();
+ }
+ }
+ if (browserHomepageUrl || browserHomepageUrl == "") {
+ if (Services.prefs.getBoolPref("browser.preferences.instantApply")) {
+ if (browserHomepageUrl == "") {
+ browserHomepageUrl = "about:home";
+ }
+ } else {
+ return;
+ }
+ } else {
+ browserHomepageUrl = Services.prefs.getComplexValue("browser.startup.homepage",
+ Components.interfaces.nsIPrefLocalizedString).data;
+ }
+ let newtabUrlPref = Services.prefs.getCharPref("browser.newtab.url");
+ switch (newtabUrlChoice) {
+ case 1:
+ newtabUrlPref = "about:logopage";
+ break;
+ case 2:
+ newtabUrlPref = Services.prefs.getDefaultBranch("browser.")
+ .getComplexValue("startup.homepage",
+ Components.interfaces.nsIPrefLocalizedString).data;
+ break;
+ case 3:
+ // If url is a pipe-delimited set of pages, just take the first one.
+ let newtabUrlSanitizedPref=browserHomepageUrl.split("|")[0];
+ // XXX: do we need extra sanitation here, e.g. for invalid URLs?
+ Services.prefs.setCharPref("browser.newtab.myhome", newtabUrlSanitizedPref);
+ newtabUrlPref = newtabUrlSanitizedPref;
+ break;
+ case 4:
+ newtabUrlPref = "about:newtab";
+ break;
+ default:
+ // In case of any other value it's a custom URL, consider instantApply.
+ if (this.newtabPageCustom) {
+ newtabUrlPref = this.newtabPageCustom;
+ }
+ }
+ Services.prefs.setCharPref("browser.newtab.url",newtabUrlPref);
+ } catch(e) { console.error(e); }
+ },
+
+ /**
+ * Determines the value of browser.newtab.choice based
+ * on the value of browser.newtab.url
+ *
+ * @returns the value of browser.newtab.choice
+ */
+ getNewtabChoice: function() {
+ let newtabUrlPref = Services.prefs.getCharPref("browser.newtab.url");
+ let browserHomepageUrl = Services.prefs.getComplexValue("browser.startup.homepage",
+ Components.interfaces.nsIPrefLocalizedString).data;
+ let newtabUrlSanitizedPref = browserHomepageUrl.split("|")[0];
+ let defaultStartupHomepage = Services.prefs.getDefaultBranch("browser.")
+ .getComplexValue("startup.homepage",
+ Components.interfaces.nsIPrefLocalizedString).data;
+ switch (newtabUrlPref) {
+ case "about:logopage":
+ return 1;
+ case defaultStartupHomepage:
+ return 2;
+ case newtabUrlSanitizedPref:
+ return 3;
+ case "about:newtab":
+ return 4;
+ default: // Custom URL entered.
+ // We need this to consider instantApply.
+ this.newtabPageCustom = newtabUrlPref;
+ return 0;
+ }
+ }
+};
diff --git a/components/preferences/permissions.js b/components/preferences/permissions.js
new file mode 100644
index 0000000..4b1bf41
--- /dev/null
+++ b/components/preferences/permissions.js
@@ -0,0 +1,463 @@
+/* -*- 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/Services.jsm");
+
+const nsIPermissionManager = Components.interfaces.nsIPermissionManager;
+const nsICookiePermission = Components.interfaces.nsICookiePermission;
+
+const NOTIFICATION_FLUSH_PERMISSIONS = "flush-pending-permissions";
+
+function Permission(principal, type, capability)
+{
+ this.principal = principal;
+ this.origin = principal.origin;
+ this.type = type;
+ this.capability = capability;
+}
+
+var gPermissionManager = {
+ _type : "",
+ _permissions : [],
+ _permissionsToAdd : new Map(),
+ _permissionsToDelete : new Map(),
+ _bundle : null,
+ _tree : null,
+ _observerRemoved : false,
+
+ _view: {
+ _rowCount: 0,
+ get rowCount()
+ {
+ return this._rowCount;
+ },
+ getCellText: function (aRow, aColumn)
+ {
+ if (aColumn.id == "siteCol")
+ return gPermissionManager._permissions[aRow].origin;
+ else if (aColumn.id == "statusCol")
+ return gPermissionManager._permissions[aRow].capability;
+ return "";
+ },
+
+ isSeparator: function(aIndex) { return false; },
+ isSorted: function() { return false; },
+ isContainer: function(aIndex) { return false; },
+ setTree: function(aTree){},
+ getImageSrc: function(aRow, aColumn) {},
+ getProgressMode: function(aRow, aColumn) {},
+ getCellValue: function(aRow, aColumn) {},
+ cycleHeader: function(column) {},
+ getRowProperties: function(row){ return ""; },
+ getColumnProperties: function(column){ return ""; },
+ getCellProperties: function(row,column){
+ if (column.element.getAttribute("id") == "siteCol")
+ return "ltr";
+
+ return "";
+ }
+ },
+
+ _getCapabilityString: function (aCapability)
+ {
+ var stringKey = null;
+ switch (aCapability) {
+ case nsIPermissionManager.ALLOW_ACTION:
+ stringKey = "can";
+ break;
+ case nsIPermissionManager.DENY_ACTION:
+ stringKey = "cannot";
+ break;
+ case nsICookiePermission.ACCESS_ALLOW_FIRST_PARTY_ONLY:
+ stringKey = "canAccessFirstParty";
+ break;
+ case nsICookiePermission.ACCESS_SESSION:
+ stringKey = "canSession";
+ break;
+ }
+ return this._bundle.getString(stringKey);
+ },
+
+ addPermission: function (aCapability)
+ {
+ var textbox = document.getElementById("url");
+ var input_url = textbox.value.replace(/^\s*/, ""); // trim any leading space
+ let principal;
+ try {
+ // The origin accessor on the principal object will throw if the
+ // principal doesn't have a canonical origin representation. This will
+ // help catch cases where the URI parser parsed something like
+ // `localhost:8080` as having the scheme `localhost`, rather than being
+ // an invalid URI. A canonical origin representation is required by the
+ // permission manager for storage, so this won't prevent any valid
+ // permissions from being entered by the user.
+ let uri;
+ try {
+ uri = Services.io.newURI(input_url, null, null);
+ principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(uri);
+ // If we have ended up with an unknown scheme, the following will throw.
+ principal.origin;
+ } catch(ex) {
+ uri = Services.io.newURI("http://" + input_url, null, null);
+ principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(uri);
+ // If we have ended up with an unknown scheme, the following will throw.
+ principal.origin;
+ }
+ } catch(ex) {
+ var message = this._bundle.getString("invalidURI");
+ var title = this._bundle.getString("invalidURITitle");
+ Services.prompt.alert(window, title, message);
+ return;
+ }
+
+ var capabilityString = this._getCapabilityString(aCapability);
+
+ // check whether the permission already exists, if not, add it
+ let permissionExists = false;
+ let capabilityExists = false;
+ for (var i = 0; i < this._permissions.length; ++i) {
+ if (this._permissions[i].principal.equals(principal)) {
+ permissionExists = true;
+ capabilityExists = this._permissions[i].capability == capabilityString;
+ if (!capabilityExists) {
+ this._permissions[i].capability = capabilityString;
+ }
+ break;
+ }
+ }
+
+
+ let permissionParams = {principal: principal, type: this._type, capability: aCapability};
+ if (!permissionExists) {
+ this._permissionsToAdd.set(principal.origin, permissionParams);
+ this._addPermission(permissionParams);
+ }
+ else if (!capabilityExists) {
+ this._permissionsToAdd.set(principal.origin, permissionParams);
+ this._handleCapabilityChange();
+ }
+
+ textbox.value = "";
+ textbox.focus();
+
+ // covers a case where the site exists already, so the buttons don't disable
+ this.onHostInput(textbox);
+
+ // enable "remove all" button as needed
+ document.getElementById("removeAllPermissions").disabled = this._permissions.length == 0;
+ },
+
+ _removePermission: function(aPermission)
+ {
+ this._removePermissionFromList(aPermission.principal);
+
+ // If this permission was added during this session, let's remove
+ // it from the pending adds list to prevent calls to the
+ // permission manager.
+ let isNewPermission = this._permissionsToAdd.delete(aPermission.principal.origin);
+
+ if (!isNewPermission) {
+ this._permissionsToDelete.set(aPermission.principal.origin, aPermission);
+ }
+
+ },
+
+ _handleCapabilityChange: function ()
+ {
+ // Re-do the sort, if the status changed from Block to Allow
+ // or vice versa, since if we're sorted on status, we may no
+ // longer be in order.
+ if (this._lastPermissionSortColumn == "statusCol") {
+ this._resortPermissions();
+ }
+ this._tree.treeBoxObject.invalidate();
+ },
+
+ _addPermission: function(aPermission)
+ {
+ this._addPermissionToList(aPermission);
+ ++this._view._rowCount;
+ this._tree.treeBoxObject.rowCountChanged(this._view.rowCount - 1, 1);
+ // Re-do the sort, since we inserted this new item at the end.
+ this._resortPermissions();
+ },
+
+ _resortPermissions: function()
+ {
+ gTreeUtils.sort(this._tree, this._view, this._permissions,
+ this._lastPermissionSortColumn,
+ this._permissionsComparator,
+ this._lastPermissionSortColumn,
+ !this._lastPermissionSortAscending); // keep sort direction
+ },
+
+ onHostInput: function (aSiteField)
+ {
+ document.getElementById("btnSession").disabled = !aSiteField.value;
+ document.getElementById("btnBlock").disabled = !aSiteField.value;
+ document.getElementById("btnAllow").disabled = !aSiteField.value;
+ },
+
+ onWindowKeyPress: function (aEvent)
+ {
+ if (aEvent.keyCode == KeyEvent.DOM_VK_ESCAPE)
+ window.close();
+ },
+
+ onHostKeyPress: function (aEvent)
+ {
+ if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN)
+ document.getElementById("btnAllow").click();
+ },
+
+ onLoad: function ()
+ {
+ this._bundle = document.getElementById("bundlePreferences");
+ var params = window.arguments[0];
+ this.init(params);
+ },
+
+ init: function (aParams)
+ {
+ if (this._type) {
+ // reusing an open dialog, clear the old observer
+ this.uninit();
+ }
+
+ this._type = aParams.permissionType;
+ this._manageCapability = aParams.manageCapability;
+
+ var permissionsText = document.getElementById("permissionsText");
+ while (permissionsText.hasChildNodes())
+ permissionsText.removeChild(permissionsText.firstChild);
+ permissionsText.appendChild(document.createTextNode(aParams.introText));
+
+ document.title = aParams.windowTitle;
+
+ document.getElementById("btnBlock").hidden = !aParams.blockVisible;
+ document.getElementById("btnSession").hidden = !aParams.sessionVisible;
+ document.getElementById("btnAllow").hidden = !aParams.allowVisible;
+
+ var urlFieldVisible = (aParams.blockVisible || aParams.sessionVisible || aParams.allowVisible);
+
+ var urlField = document.getElementById("url");
+ urlField.value = aParams.prefilledHost;
+ urlField.hidden = !urlFieldVisible;
+
+ this.onHostInput(urlField);
+
+ var urlLabel = document.getElementById("urlLabel");
+ urlLabel.hidden = !urlFieldVisible;
+
+ let treecols = document.getElementsByTagName("treecols")[0];
+ treecols.addEventListener("click", event => {
+ if (event.target.nodeName != "treecol" || event.button != 0) {
+ return;
+ }
+
+ let sortField = event.target.getAttribute("data-field-name");
+ if (!sortField) {
+ return;
+ }
+
+ gPermissionManager.onPermissionSort(sortField);
+ });
+
+ Services.obs.notifyObservers(null, NOTIFICATION_FLUSH_PERMISSIONS, this._type);
+ Services.obs.addObserver(this, "perm-changed", false);
+
+ this._loadPermissions();
+
+ urlField.focus();
+ },
+
+ uninit: function ()
+ {
+ if (!this._observerRemoved) {
+ Services.obs.removeObserver(this, "perm-changed");
+
+ this._observerRemoved = true;
+ }
+ },
+
+ observe: function (aSubject, aTopic, aData)
+ {
+ if (aTopic == "perm-changed") {
+ var permission = aSubject.QueryInterface(Components.interfaces.nsIPermission);
+
+ // Ignore unrelated permission types.
+ if (permission.type != this._type)
+ return;
+
+ if (aData == "added") {
+ this._addPermission(permission);
+ }
+ else if (aData == "changed") {
+ for (var i = 0; i < this._permissions.length; ++i) {
+ if (permission.matches(this._permissions[i].principal, true)) {
+ this._permissions[i].capability = this._getCapabilityString(permission.capability);
+ break;
+ }
+ }
+ this._handleCapabilityChange();
+ }
+ else if (aData == "deleted") {
+ this._removePermissionFromList(permission.principal);
+ }
+ }
+ },
+
+ onPermissionSelected: function ()
+ {
+ var hasSelection = this._tree.view.selection.count > 0;
+ var hasRows = this._tree.view.rowCount > 0;
+ document.getElementById("removePermission").disabled = !hasRows || !hasSelection;
+ document.getElementById("removeAllPermissions").disabled = !hasRows;
+ },
+
+ onPermissionDeleted: function ()
+ {
+ if (!this._view.rowCount)
+ return;
+ var removedPermissions = [];
+ gTreeUtils.deleteSelectedItems(this._tree, this._view, this._permissions, removedPermissions);
+ for (var i = 0; i < removedPermissions.length; ++i) {
+ var p = removedPermissions[i];
+ this._removePermission(p);
+ }
+ document.getElementById("removePermission").disabled = !this._permissions.length;
+ document.getElementById("removeAllPermissions").disabled = !this._permissions.length;
+ },
+
+ onAllPermissionsDeleted: function ()
+ {
+ if (!this._view.rowCount)
+ return;
+ var removedPermissions = [];
+ gTreeUtils.deleteAll(this._tree, this._view, this._permissions, removedPermissions);
+ for (var i = 0; i < removedPermissions.length; ++i) {
+ var p = removedPermissions[i];
+ this._removePermission(p);
+ }
+ document.getElementById("removePermission").disabled = true;
+ document.getElementById("removeAllPermissions").disabled = true;
+ },
+
+ onPermissionKeyPress: function (aEvent)
+ {
+ if (aEvent.keyCode == KeyEvent.DOM_VK_DELETE
+#ifdef XP_MACOSX
+ || aEvent.keyCode == KeyEvent.DOM_VK_BACK_SPACE
+#endif
+ ) {
+ this.onPermissionDeleted();
+ }
+ },
+
+ _lastPermissionSortColumn: "",
+ _lastPermissionSortAscending: false,
+ _permissionsComparator : function (a, b)
+ {
+ return a.toLowerCase().localeCompare(b.toLowerCase());
+ },
+
+
+ onPermissionSort: function (aColumn)
+ {
+ this._lastPermissionSortAscending = gTreeUtils.sort(this._tree,
+ this._view,
+ this._permissions,
+ aColumn,
+ this._permissionsComparator,
+ this._lastPermissionSortColumn,
+ this._lastPermissionSortAscending);
+ this._lastPermissionSortColumn = aColumn;
+ },
+
+ onApplyChanges: function()
+ {
+ // Stop observing permission changes since we are about
+ // to write out the pending adds/deletes and don't need
+ // to update the UI
+ this.uninit();
+
+ for (let permissionParams of this._permissionsToAdd.values()) {
+ Services.perms.addFromPrincipal(permissionParams.principal, permissionParams.type, permissionParams.capability);
+ }
+
+ for (let p of this._permissionsToDelete.values()) {
+ Services.perms.removeFromPrincipal(p.principal, p.type);
+ }
+
+ window.close();
+ },
+
+ _loadPermissions: function ()
+ {
+ this._tree = document.getElementById("permissionsTree");
+ this._permissions = [];
+
+ // load permissions into a table
+ var count = 0;
+ var enumerator = Services.perms.enumerator;
+ while (enumerator.hasMoreElements()) {
+ var nextPermission = enumerator.getNext().QueryInterface(Components.interfaces.nsIPermission);
+ this._addPermissionToList(nextPermission);
+ }
+
+ this._view._rowCount = this._permissions.length;
+
+ // sort and display the table
+ this._tree.view = this._view;
+ this.onPermissionSort("origin");
+
+ // disable "remove all" button if there are none
+ document.getElementById("removeAllPermissions").disabled = this._permissions.length == 0;
+ },
+
+ _addPermissionToList: function (aPermission)
+ {
+ if (aPermission.type == this._type &&
+ (!this._manageCapability ||
+ (aPermission.capability == this._manageCapability))) {
+
+ var principal = aPermission.principal;
+ var capabilityString = this._getCapabilityString(aPermission.capability);
+ var p = new Permission(principal,
+ aPermission.type,
+ capabilityString);
+ this._permissions.push(p);
+ }
+ },
+
+ _removePermissionFromList: function (aPrincipal)
+ {
+ for (let i = 0; i < this._permissions.length; ++i) {
+ if (this._permissions[i].principal.equals(aPrincipal)) {
+ this._permissions.splice(i, 1);
+ this._view._rowCount--;
+ this._tree.treeBoxObject.rowCountChanged(this._view.rowCount - 1, -1);
+ this._tree.treeBoxObject.invalidate();
+ break;
+ }
+ }
+ },
+
+ setOrigin: function (aOrigin)
+ {
+ document.getElementById("url").value = aOrigin;
+ }
+};
+
+function setOrigin(aOrigin)
+{
+ gPermissionManager.setOrigin(aOrigin);
+}
+
+function initWithParams(aParams)
+{
+ gPermissionManager.init(aParams);
+}
+
diff --git a/components/preferences/permissions.xul b/components/preferences/permissions.xul
new file mode 100644
index 0000000..33806cc
--- /dev/null
+++ b/components/preferences/permissions.xul
@@ -0,0 +1,85 @@
+<?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/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://browser/locale/preferences/permissions.dtd" >
+
+<window id="PermissionsDialog" class="windowDialog"
+ windowtype="Browser:Permissions"
+ title="&window.title;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ style="width: &window.width;;"
+ onload="gPermissionManager.onLoad();"
+ onunload="gPermissionManager.uninit();"
+ persist="screenX screenY width height"
+ onkeypress="gPermissionManager.onWindowKeyPress(event);">
+
+ <script src="chrome://global/content/treeUtils.js"/>
+ <script src="chrome://browser/content/preferences/permissions.js"/>
+
+ <stringbundle id="bundlePreferences"
+ src="chrome://browser/locale/preferences/preferences.properties"/>
+
+ <keyset>
+ <key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/>
+ </keyset>
+
+ <vbox class="contentPane" flex="1">
+ <description id="permissionsText" control="url"/>
+ <separator class="thin"/>
+ <label id="urlLabel" control="url" value="&address.label;" accesskey="&address.accesskey;"/>
+ <hbox align="start">
+ <textbox id="url" flex="1"
+ oninput="gPermissionManager.onHostInput(event.target);"
+ onkeypress="gPermissionManager.onHostKeyPress(event);"/>
+ </hbox>
+ <hbox pack="end">
+ <button id="btnBlock" disabled="true" label="&block.label;" accesskey="&block.accesskey;"
+ oncommand="gPermissionManager.addPermission(nsIPermissionManager.DENY_ACTION);"/>
+ <button id="btnSession" disabled="true" label="&session.label;" accesskey="&session.accesskey;"
+ oncommand="gPermissionManager.addPermission(nsICookiePermission.ACCESS_SESSION);"/>
+ <button id="btnAllow" disabled="true" label="&allow.label;" default="true" accesskey="&allow.accesskey;"
+ oncommand="gPermissionManager.addPermission(nsIPermissionManager.ALLOW_ACTION);"/>
+ </hbox>
+ <separator class="thin"/>
+ <tree id="permissionsTree" flex="1" style="height: 18em;"
+ hidecolumnpicker="true"
+ onkeypress="gPermissionManager.onPermissionKeyPress(event)"
+ onselect="gPermissionManager.onPermissionSelected();">
+ <treecols>
+ <treecol id="siteCol" label="&treehead.sitename.label;" flex="3"
+ data-field-name="origin" persist="width"/>
+ <splitter class="tree-splitter"/>
+ <treecol id="statusCol" label="&treehead.status.label;" flex="1"
+ data-field-name="capability" persist="width"/>
+ </treecols>
+ <treechildren/>
+ </tree>
+ </vbox>
+ <vbox>
+ <hbox class="actionButtons" align="left" flex="1">
+ <button id="removePermission" disabled="true"
+ accesskey="&removepermission.accesskey;"
+ icon="remove" label="&removepermission.label;"
+ oncommand="gPermissionManager.onPermissionDeleted();"/>
+ <button id="removeAllPermissions"
+ icon="clear" label="&removeallpermissions.label;"
+ accesskey="&removeallpermissions.accesskey;"
+ oncommand="gPermissionManager.onAllPermissionsDeleted();"/>
+ </hbox>
+ <spacer flex="1"/>
+ <hbox class="actionButtons" align="right" flex="1">
+ <button oncommand="close();" icon="close"
+ label="&button.cancel.label;" accesskey="&button.cancel.accesskey;" />
+ <button id="btnApplyChanges" oncommand="gPermissionManager.onApplyChanges();" icon="save"
+ label="&button.ok.label;" accesskey="&button.ok.accesskey;"/>
+ </hbox>
+ <resizer type="window" dir="bottomend"/>
+ </vbox>
+</window>
diff --git a/components/preferences/preferences.xul b/components/preferences/preferences.xul
new file mode 100644
index 0000000..a1d9c8c
--- /dev/null
+++ b/components/preferences/preferences.xul
@@ -0,0 +1,92 @@
+<?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/global.css"?>
+<?xml-stylesheet href="chrome://mozapps/content/preferences/preferences.css"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/preferences.css"?>
+
+<!-- XXX This should be in applications.xul, but bug 393953 means putting it
+ - there causes the Applications pane not to work the first time you open
+ - the Preferences dialog in a browsing session, so we work around the problem
+ - by putting it here instead.
+ -->
+<?xml-stylesheet href="chrome://browser/content/preferences/handlers.css"?>
+<?xml-stylesheet href="chrome://browser/skin/preferences/applications.css"?>
+
+<!DOCTYPE prefwindow [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+<!ENTITY % preferencesDTD SYSTEM "chrome://browser/locale/preferences/preferences.dtd">
+%brandDTD;
+%preferencesDTD;
+]>
+
+#ifdef XP_WIN
+#define USE_WIN_TITLE_STYLE
+#endif
+
+#ifdef XP_MACOSX
+<?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?>
+#endif
+
+<prefwindow type="prefwindow"
+ id="BrowserPreferences"
+ windowtype="Browser:Preferences"
+ ondialoghelp="openPrefsHelp()"
+#ifdef USE_WIN_TITLE_STYLE
+ title="&prefWindow.titleWin;"
+#else
+#ifdef XP_UNIX
+#ifndef XP_MACOSX
+ title="&prefWindow.titleGNOME;"
+#endif
+#endif
+#endif
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+#ifdef USE_WIN_TITLE_STYLE
+ style="&prefWinMinSize.styleWin2;"
+#else
+#ifdef XP_MACOSX
+ style="&prefWinMinSize.styleMac;"
+#else
+ style="&prefWinMinSize.styleGNOME;"
+#endif
+#endif
+ onunload="if (typeof gSecurityPane != 'undefined') gSecurityPane.syncAddonSecurityLevel();"
+ ondialogaccept="gNewtabUrl.writeNewtabUrl();">
+
+ <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
+ <script type="application/javascript" src="chrome://browser/content/preferences/newtaburl.js"/>
+
+ <stringbundle id="bundleBrand" src="chrome://branding/locale/brand.properties"/>
+ <stringbundle id="bundlePreferences"
+ src="chrome://browser/locale/preferences/preferences.properties"/>
+
+ <prefpane id="paneMain" label="&paneGeneral.title;"
+ src="chrome://browser/content/preferences/main.xul"/>
+ <prefpane id="paneTabs" label="&paneTabs.title;"
+ src="chrome://browser/content/preferences/tabs.xul"/>
+ <prefpane id="paneContent" label="&paneContent.title;"
+ src="chrome://browser/content/preferences/content.xul"/>
+ <prefpane id="paneApplications" label="&paneApplications.title;"
+ src="chrome://browser/content/preferences/applications.xul"/>
+ <prefpane id="panePrivacy" label="&panePrivacy.title;"
+ src="chrome://browser/content/preferences/privacy.xul"/>
+ <prefpane id="paneSecurity" label="&paneSecurity.title;"
+ src="chrome://browser/content/preferences/security.xul"/>
+#ifdef MOZ_SERVICES_SYNC
+ <prefpane id="paneSync" label="&paneSync.title;"
+ src="chrome://browser/content/preferences/sync.xul"/>
+#endif
+ <prefpane id="paneAdvanced" label="&paneAdvanced.title;"
+ src="chrome://browser/content/preferences/advanced.xul"/>
+
+#ifdef XP_MACOSX
+#include ../../base/content/browserMountPoints.inc
+#endif
+
+</prefwindow>
+
diff --git a/components/preferences/privacy.js b/components/preferences/privacy.js
new file mode 100644
index 0000000..e2a871a
--- /dev/null
+++ b/components/preferences/privacy.js
@@ -0,0 +1,485 @@
+/* -*- 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");
+
+var gPrivacyPane = {
+
+ /**
+ * Whether the use has selected the auto-start private browsing mode in the UI.
+ */
+ _autoStartPrivateBrowsing: false,
+
+ /**
+ * Whether the prompt to restart Firefox should appear when changing the autostart pref.
+ */
+ _shouldPromptForRestart: true,
+
+ /**
+ * Sets up the UI for the number of days of history to keep, and updates the
+ * label of the "Clear Now..." button.
+ * Also restores the previously selected tab or tab index passed as argument.
+ */
+ init: function ()
+ {
+ this._inited = true;
+ var privacyPrefs = document.getElementById("privacyPrefs");
+
+ var extraArgs = window.arguments[1];
+ if (extraArgs && extraArgs["privacyTab"]){
+ privacyPrefs.selectedTab = document.getElementById(extraArgs["privacyTab"]);
+ } else {
+ var preference = document.getElementById("browser.preferences.privacy.selectedTabIndex");
+ if (preference.value !== null)
+ privacyPrefs.selectedIndex = preference.value;
+ }
+
+ this._updateSanitizeSettingsButton();
+ this.initializeHistoryMode();
+ this.updateHistoryModePane();
+ this.updatePrivacyMicroControls();
+ this.initAutoStartPrivateBrowsingReverter();
+ },
+
+ /**
+ * Stores the identity of the current tab in preferences so that the selected
+ * tab can be persisted between openings of the preferences window.
+ */
+ tabSelectionChanged: function ()
+ {
+ if (!this._inited)
+ return;
+ var privacyPrefs = document.getElementById("privacyPrefs");
+ var preference = document.getElementById("browser.preferences.privacy.selectedTabIndex");
+ preference.valueFromPreferences = privacyPrefs.selectedIndex;
+ },
+
+ // HISTORY MODE
+
+ /**
+ * The list of preferences which affect the initial history mode settings.
+ * If the auto start private browsing mode pref is active, the initial
+ * history mode would be set to "Don't remember anything".
+ * If all of these preferences have their default values, and the auto-start
+ * private browsing mode is not active, the initial history mode would be
+ * set to "Remember everything".
+ * Otherwise, the initial history mode would be set to "Custom".
+ *
+ * Extensions adding their own preferences can append their IDs to this array if needed.
+ */
+ prefsForDefault: [
+ "places.history.enabled",
+ "browser.formfill.enable",
+ "network.cookie.cookieBehavior",
+ "network.cookie.lifetimePolicy",
+ "privacy.sanitize.sanitizeOnShutdown"
+ ],
+
+ /**
+ * The list of control IDs which are dependent on the auto-start private
+ * browsing setting, such that in "Custom" mode they would be disabled if
+ * the auto-start private browsing checkbox is checked, and enabled otherwise.
+ *
+ * Extensions adding their own controls can append their IDs to this array if needed.
+ */
+ dependentControls: [
+ "rememberHistory",
+ "rememberForms",
+ "keepUntil",
+ "keepCookiesUntil",
+ "alwaysClear",
+ "clearDataSettings"
+ ],
+
+ /**
+ * Check whether all the preferences values are set to their default values
+ *
+ * @param aPrefs an array of pref names to check for
+ * @returns boolean true if all of the prefs are set to their default values,
+ * false otherwise
+ */
+ _checkDefaultValues: function(aPrefs) {
+ for (let i = 0; i < aPrefs.length; ++i) {
+ let pref = document.getElementById(aPrefs[i]);
+ if (pref.value != pref.defaultValue)
+ return false;
+ }
+ return true;
+ },
+
+ /**
+ * Initialize the history mode menulist based on the privacy preferences
+ */
+ initializeHistoryMode: function PPP_initializeHistoryMode()
+ {
+ let mode;
+ let getVal = function (aPref)
+ document.getElementById(aPref).value;
+
+ if (this._checkDefaultValues(this.prefsForDefault)) {
+ if (getVal("browser.privatebrowsing.autostart"))
+ mode = "dontremember";
+ else
+ mode = "remember";
+ }
+ else
+ mode = "custom";
+
+ document.getElementById("historyMode").value = mode;
+ },
+
+ /**
+ * Update the selected pane based on the history mode menulist
+ */
+ updateHistoryModePane: function PPP_updateHistoryModePane()
+ {
+ let selectedIndex = -1;
+ switch (document.getElementById("historyMode").value) {
+ case "remember":
+ selectedIndex = 0;
+ break;
+ case "dontremember":
+ selectedIndex = 1;
+ break;
+ case "custom":
+ selectedIndex = 2;
+ break;
+ }
+ document.getElementById("historyPane").selectedIndex = selectedIndex;
+ },
+
+ /**
+ * Update the private browsing auto-start pref and the history mode
+ * micro-management prefs based on the history mode menulist
+ */
+ updateHistoryModePrefs: function PPP_updateHistoryModePrefs()
+ {
+ let pref = document.getElementById("browser.privatebrowsing.autostart");
+ switch (document.getElementById("historyMode").value) {
+ case "remember":
+ if (pref.value)
+ pref.value = false;
+
+ // select the remember history option
+ document.getElementById("places.history.enabled").value = true;
+
+ // select the remember forms history option
+ document.getElementById("browser.formfill.enable").value = true;
+
+ // select the accept cookies option
+ document.getElementById("network.cookie.cookieBehavior").value = 0;
+ // select the cookie lifetime policy option
+ document.getElementById("network.cookie.lifetimePolicy").value = 0;
+
+ // select the clear on close option
+ document.getElementById("privacy.sanitize.sanitizeOnShutdown").value = false;
+ break;
+ case "dontremember":
+ if (!pref.value)
+ pref.value = true;
+ break;
+ }
+ },
+
+ /**
+ * Update the privacy micro-management controls based on the
+ * value of the private browsing auto-start checkbox.
+ */
+ updatePrivacyMicroControls: function PPP_updatePrivacyMicroControls()
+ {
+ if (document.getElementById("historyMode").value == "custom") {
+ let disabled = this._autoStartPrivateBrowsing =
+ document.getElementById("privateBrowsingAutoStart").checked;
+ this.dependentControls
+ .forEach(function (aElement)
+ document.getElementById(aElement).disabled = disabled);
+
+ const Ci = Components.interfaces;
+ // adjust the cookie controls status
+ this.readAcceptCookies();
+ let lifetimePolicy = document.getElementById("network.cookie.lifetimePolicy").value;
+ if (lifetimePolicy != Ci.nsICookieService.ACCEPT_NORMALLY &&
+ lifetimePolicy != Ci.nsICookieService.ACCEPT_SESSION &&
+ lifetimePolicy != Ci.nsICookieService.ACCEPT_FOR_N_DAYS) {
+ lifetimePolicy = Ci.nsICookieService.ACCEPT_NORMALLY;
+ }
+ document.getElementById("keepCookiesUntil").value = disabled ? 2 : lifetimePolicy;
+
+ // adjust the checked state of the sanitizeOnShutdown checkbox
+ document.getElementById("alwaysClear").checked = disabled ? false :
+ document.getElementById("privacy.sanitize.sanitizeOnShutdown").value;
+
+ // adjust the checked state of the remember history checkboxes
+ document.getElementById("rememberHistory").checked = disabled ? false :
+ document.getElementById("places.history.enabled").value;
+ document.getElementById("rememberForms").checked = disabled ? false :
+ document.getElementById("browser.formfill.enable").value;
+
+ if (!disabled) {
+ // adjust the Settings button for sanitizeOnShutdown
+ this._updateSanitizeSettingsButton();
+ }
+ }
+ },
+
+ // PRIVATE BROWSING
+
+ /**
+ * Initialize the starting state for the auto-start private browsing mode pref reverter.
+ */
+ initAutoStartPrivateBrowsingReverter: function PPP_initAutoStartPrivateBrowsingReverter()
+ {
+ let mode = document.getElementById("historyMode");
+ let autoStart = document.getElementById("privateBrowsingAutoStart");
+ this._lastMode = mode.selectedIndex;
+ this._lastCheckState = autoStart.hasAttribute('checked');
+ },
+
+ _lastMode: null,
+ _lasCheckState: null,
+ updateAutostart: function PPP_updateAutostart() {
+ let mode = document.getElementById("historyMode");
+ let autoStart = document.getElementById("privateBrowsingAutoStart");
+ let pref = document.getElementById("browser.privatebrowsing.autostart");
+ if ((mode.value == "custom" && this._lastCheckState == autoStart.checked) ||
+ (mode.value == "remember" && !this._lastCheckState) ||
+ (mode.value == "dontremember" && this._lastCheckState)) {
+ // These are all no-op changes, so we don't need to prompt.
+ this._lastMode = mode.selectedIndex;
+ this._lastCheckState = autoStart.hasAttribute('checked');
+ return;
+ }
+
+ if (!this._shouldPromptForRestart) {
+ // We're performing a revert. Just let it happen.
+ return;
+ }
+
+ const Cc = Components.classes, Ci = Components.interfaces;
+ let brandName = document.getElementById("bundleBrand").getString("brandShortName");
+ let bundle = document.getElementById("bundlePreferences");
+ let msg = bundle.getFormattedString(autoStart.checked ?
+ "featureEnableRequiresRestart" : "featureDisableRequiresRestart",
+ [brandName]);
+ let title = bundle.getFormattedString("shouldRestartTitle", [brandName]);
+ let prompts = Cc["@mozilla.org/embedcomp/prompt-service;1"].getService(Ci.nsIPromptService);
+ let shouldProceed = prompts.confirm(window, title, msg)
+ if (shouldProceed) {
+ let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
+ .createInstance(Ci.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
+ "restart");
+ shouldProceed = !cancelQuit.data;
+
+ if (shouldProceed) {
+ pref.value = autoStart.hasAttribute('checked');
+ document.documentElement.acceptDialog();
+ let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
+ .getService(Ci.nsIAppStartup);
+ appStartup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
+ return;
+ }
+ }
+
+ this._shouldPromptForRestart = false;
+
+ if (this._lastCheckState) {
+ autoStart.checked = "checked";
+ } else {
+ autoStart.removeAttribute('checked');
+ }
+ mode.selectedIndex = this._lastMode;
+ mode.doCommand();
+
+ this._shouldPromptForRestart = true;
+ },
+
+ // HISTORY
+
+ /*
+ * Preferences:
+ *
+ * places.history.enabled
+ * - whether history is enabled or not
+ * browser.formfill.enable
+ * - true if entries in forms and the search bar should be saved, false
+ * otherwise
+ */
+
+ // COOKIES
+
+ /*
+ * Preferences:
+ *
+ * network.cookie.cookieBehavior
+ * - determines how the browser should handle cookies:
+ * 0 means enable all cookies
+ * 1 means reject all third party cookies
+ * 2 means disable all cookies
+ * 3 means reject third party cookies unless at least one is already set for the eTLD
+ * see netwerk/cookie/src/nsCookieService.cpp for details
+ * network.cookie.lifetimePolicy
+ * - determines how long cookies are stored:
+ * 0 means keep cookies until they expire
+ * 2 means keep cookies until the browser is closed
+ */
+
+ /**
+ * Reads the network.cookie.cookieBehavior preference value and
+ * enables/disables the rest of the cookie UI accordingly, returning true
+ * if cookies are enabled.
+ */
+ readAcceptCookies: function ()
+ {
+ var pref = document.getElementById("network.cookie.cookieBehavior");
+ var acceptThirdPartyLabel = document.getElementById("acceptThirdPartyLabel");
+ var acceptThirdPartyMenu = document.getElementById("acceptThirdPartyMenu");
+ var keepUntil = document.getElementById("keepUntil");
+ var menu = document.getElementById("keepCookiesUntil");
+
+ // enable the rest of the UI for anything other than "disable all cookies"
+ var acceptCookies = (pref.value != 2);
+
+ acceptThirdPartyLabel.disabled = acceptThirdPartyMenu.disabled = !acceptCookies;
+ keepUntil.disabled = menu.disabled = this._autoStartPrivateBrowsing || !acceptCookies;
+
+ return acceptCookies;
+ },
+
+ /**
+ * Enables/disables the "keep until" label and menulist in response to the
+ * "accept cookies" checkbox being checked or unchecked.
+ */
+ writeAcceptCookies: function ()
+ {
+ var accept = document.getElementById("acceptCookies");
+ var acceptThirdPartyMenu = document.getElementById("acceptThirdPartyMenu");
+
+ // if we're enabling cookies, automatically select 'accept third party always'
+ if (accept.checked)
+ acceptThirdPartyMenu.selectedIndex = 0;
+
+ return accept.checked ? 0 : 2;
+ },
+
+ /**
+ * Converts between network.cookie.cookieBehavior and the third-party cookie UI
+ */
+ readAcceptThirdPartyCookies: function ()
+ {
+ var pref = document.getElementById("network.cookie.cookieBehavior");
+ switch (pref.value)
+ {
+ case 0:
+ return "always";
+ case 1:
+ return "never";
+ case 2:
+ return "never";
+ case 3:
+ return "visited";
+ default:
+ return undefined;
+ }
+ },
+
+ writeAcceptThirdPartyCookies: function ()
+ {
+ var accept = document.getElementById("acceptThirdPartyMenu").selectedItem;
+ switch (accept.value)
+ {
+ case "always":
+ return 0;
+ case "visited":
+ return 3;
+ case "never":
+ return 1;
+ default:
+ return undefined;
+ }
+ },
+
+ /**
+ * Displays fine-grained, per-site preferences for cookies.
+ */
+ showCookieExceptions: function ()
+ {
+ var bundlePreferences = document.getElementById("bundlePreferences");
+ var params = { blockVisible : true,
+ sessionVisible : true,
+ allowVisible : true,
+ prefilledHost : "",
+ permissionType : "cookie",
+ windowTitle : bundlePreferences.getString("cookiepermissionstitle"),
+ introText : bundlePreferences.getString("cookiepermissionstext") };
+ document.documentElement.openWindow("Browser:Permissions",
+ "chrome://browser/content/preferences/permissions.xul",
+ "", params);
+ },
+
+ /**
+ * Displays all the user's cookies in a dialog.
+ */
+ showCookies: function (aCategory)
+ {
+ document.documentElement.openWindow("Browser:Cookies",
+ "chrome://browser/content/preferences/cookies.xul",
+ "", null);
+ },
+
+ // CLEAR PRIVATE DATA
+
+ /*
+ * Preferences:
+ *
+ * privacy.sanitize.sanitizeOnShutdown
+ * - true if the user's private data is cleared on startup according to the
+ * Clear Private Data settings, false otherwise
+ */
+
+ /**
+ * Displays the Clear Private Data settings dialog.
+ */
+ showClearPrivateDataSettings: function ()
+ {
+ document.documentElement.openSubDialog("chrome://browser/content/preferences/sanitize.xul",
+ "", null);
+ },
+
+
+ /**
+ * Displays a dialog from which individual parts of private data may be
+ * cleared.
+ */
+ clearPrivateDataNow: function (aClearEverything)
+ {
+ var ts = document.getElementById("privacy.sanitize.timeSpan");
+ var timeSpanOrig = ts.value;
+ if (aClearEverything)
+ ts.value = 0;
+
+ const Cc = Components.classes, Ci = Components.interfaces;
+ var glue = Cc["@mozilla.org/browser/browserglue;1"]
+ .getService(Ci.nsIBrowserGlue);
+ glue.sanitize(window);
+
+ // reset the timeSpan pref
+ if (aClearEverything)
+ ts.value = timeSpanOrig;
+ Services.obs.notifyObservers(null, "clear-private-data", null);
+ },
+
+ /**
+ * Enables or disables the "Settings..." button depending
+ * on the privacy.sanitize.sanitizeOnShutdown preference value
+ */
+ _updateSanitizeSettingsButton: function () {
+ var settingsButton = document.getElementById("clearDataSettings");
+ var sanitizeOnShutdownPref = document.getElementById("privacy.sanitize.sanitizeOnShutdown");
+
+ settingsButton.disabled = !sanitizeOnShutdownPref.value;
+ }
+
+};
diff --git a/components/preferences/privacy.xul b/components/preferences/privacy.xul
new file mode 100644
index 0000000..d2f8106
--- /dev/null
+++ b/components/preferences/privacy.xul
@@ -0,0 +1,273 @@
+<?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/. -->
+
+<!DOCTYPE overlay [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+<!ENTITY % privacyDTD SYSTEM "chrome://browser/locale/preferences/privacy.dtd">
+%brandDTD;
+%privacyDTD;
+]>
+
+<overlay id="PrivacyPaneOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+
+ <prefpane id="panePrivacy"
+ onpaneload="gPrivacyPane.init();"
+ helpTopic="prefs-privacy">
+
+ <preferences id="privacyPreferences">
+
+ <preference id="browser.preferences.privacy.selectedTabIndex"
+ name="browser.preferences.privacy.selectedTabIndex"
+ type="int"/>
+
+ <!-- Tracking -->
+ <preference id="privacy.donottrackheader.enabled"
+ name="privacy.donottrackheader.enabled"
+ type="bool"/>
+
+ <!-- XXX button prefs -->
+ <preference id="pref.privacy.disable_button.cookie_exceptions"
+ name="pref.privacy.disable_button.cookie_exceptions"
+ type="bool"/>
+ <preference id="pref.privacy.disable_button.view_cookies"
+ name="pref.privacy.disable_button.view_cookies"
+ type="bool"/>
+
+ <!-- Location Bar -->
+ <preference id="browser.urlbar.autocomplete.enabled"
+ name="browser.urlbar.autocomplete.enabled"
+ type="bool"/>
+ <preference id="browser.urlbar.suggest.bookmark"
+ name="browser.urlbar.suggest.bookmark"
+ type="bool"/>
+ <preference id="browser.urlbar.suggest.history"
+ name="browser.urlbar.suggest.history"
+ type="bool"/>
+ <preference id="browser.urlbar.suggest.openpage"
+ name="browser.urlbar.suggest.openpage"
+ type="bool"/>
+
+ <!-- History -->
+ <preference id="places.history.enabled"
+ name="places.history.enabled"
+ type="bool"/>
+ <preference id="browser.formfill.enable"
+ name="browser.formfill.enable"
+ type="bool"/>
+
+ <!-- Cookies -->
+ <preference id="network.cookie.cookieBehavior" name="network.cookie.cookieBehavior" type="int"/>
+ <preference id="network.cookie.lifetimePolicy" name="network.cookie.lifetimePolicy" type="int"/>
+ <preference id="network.cookie.blockFutureCookies" name="network.cookie.blockFutureCookies" type="bool"/>
+
+ <!-- Clear Private Data -->
+ <preference id="privacy.sanitize.sanitizeOnShutdown"
+ name="privacy.sanitize.sanitizeOnShutdown"
+ onchange="gPrivacyPane._updateSanitizeSettingsButton();"
+ type="bool"/>
+ <preference id="privacy.sanitize.timeSpan"
+ name="privacy.sanitize.timeSpan"
+ type="int"/>
+
+ <!-- Private Browsing -->
+ <preference id="browser.privatebrowsing.autostart"
+ name="browser.privatebrowsing.autostart"
+ onchange="gPrivacyPane.updatePrivacyMicroControls();"
+ type="bool"/>
+
+ </preferences>
+
+ <stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences/preferences.properties"/>
+
+ <script type="application/javascript" src="chrome://browser/content/preferences/privacy.js"/>
+
+ <tabbox id="privacyPrefs" flex="1"
+ onselect="gPrivacyPane.tabSelectionChanged();">
+
+ <tabs id="tabsElement">
+ <tab id="historyTab" label="&history.label;"/>
+ <tab id="trackingTab" label="&tracking.label;"/>
+ <tab id="locationBarTab" label="&locationBar.label;"/>
+ </tabs>
+
+ <tabpanels flex="1">
+
+ <!-- History -->
+ <tabpanel id="historyPanel" orient="vertical">
+
+ <hbox align="center">
+ <label id="historyModeLabel"
+ control="historyMode"
+ accesskey="&historyHeader.pre.accesskey;">&historyHeader.pre.label;</label>
+ <menulist id="historyMode"
+ oncommand="gPrivacyPane.updateHistoryModePane();
+ gPrivacyPane.updateHistoryModePrefs();
+ gPrivacyPane.updatePrivacyMicroControls();
+ gPrivacyPane.updateAutostart();">
+ <menupopup>
+ <menuitem label="&historyHeader.remember.label;" value="remember"/>
+ <menuitem label="&historyHeader.dontremember.label;" value="dontremember"/>
+ <menuitem label="&historyHeader.custom.label;" value="custom"/>
+ </menupopup>
+ </menulist>
+ <label>&historyHeader.post.label;</label>
+ </hbox>
+
+ <deck id="historyPane">
+ <vbox align="center" id="historyRememberPane">
+ <hbox align="center" flex="1">
+ <spacer flex="1" class="indent"/>
+ <vbox flex="2">
+ <description>&rememberDescription.label;</description>
+ <separator/>
+ <description>&rememberActions.pre.label;<html:a
+ class="inline-link" href="#"
+ onclick="gPrivacyPane.clearPrivateDataNow(false); return false;"
+ >&rememberActions.clearHistory.label;</html:a>&rememberActions.middle.label;<html:a
+ class="inline-link" href="#"
+ onclick="gPrivacyPane.showCookies(); return false;"
+ >&rememberActions.removeCookies.label;</html:a>&rememberActions.post.label;</description>
+ </vbox>
+ <spacer flex="1" class="indent"/>
+ </hbox>
+ </vbox>
+ <vbox align="center" id="historyDontRememberPane">
+ <hbox align="center" flex="1">
+ <spacer flex="1" class="indent"/>
+ <vbox flex="2">
+ <description>&dontrememberDescription.label;</description>
+ <separator/>
+ <description>&dontrememberActions.pre.label;<html:a
+ class="inline-link" href="#"
+ onclick="gPrivacyPane.clearPrivateDataNow(true); return false;"
+ >&dontrememberActions.clearHistory.label;</html:a>&dontrememberActions.post.label;</description>
+ </vbox>
+ <spacer flex="1" class="indent"/>
+ </hbox>
+ </vbox>
+ <vbox id="historyCustomPane">
+ <separator class="thin"/>
+ <checkbox id="privateBrowsingAutoStart" class="indent"
+ label="&privateBrowsingPermanent2.label;"
+ accesskey="&privateBrowsingPermanent2.accesskey;"
+ preference="browser.privatebrowsing.autostart"
+ oncommand="gPrivacyPane.updateAutostart()"/>
+
+ <vbox class="indent">
+ <vbox class="indent">
+ <checkbox id="rememberHistory"
+ label="&rememberHistory2.label;"
+ accesskey="&rememberHistory2.accesskey;"
+ preference="places.history.enabled"/>
+ <checkbox id="rememberForms"
+ label="&rememberSearchForm.label;"
+ accesskey="&rememberSearchForm.accesskey;"
+ preference="browser.formfill.enable"/>
+
+ <hbox id="cookiesBox">
+ <checkbox id="acceptCookies" label="&acceptCookies.label;" flex="1"
+ preference="network.cookie.cookieBehavior"
+ accesskey="&acceptCookies.accesskey;"
+ onsyncfrompreference="return gPrivacyPane.readAcceptCookies();"
+ onsynctopreference="return gPrivacyPane.writeAcceptCookies();"/>
+ <button id="cookieExceptions" oncommand="gPrivacyPane.showCookieExceptions();"
+ label="&cookieExceptions.label;" accesskey="&cookieExceptions.accesskey;"
+ preference="pref.privacy.disable_button.cookie_exceptions"/>
+ </hbox>
+
+ <hbox id="acceptThirdPartyRow" class="indent">
+ <hbox id="acceptThirdPartyBox" align="center">
+ <label id="acceptThirdPartyLabel" control="acceptThirdPartyMenu"
+ accesskey="&acceptThirdParty.pre.accesskey;">&acceptThirdParty.pre.label;</label>
+ <menulist id="acceptThirdPartyMenu" preference="network.cookie.cookieBehavior"
+ onsyncfrompreference="return gPrivacyPane.readAcceptThirdPartyCookies();"
+ onsynctopreference="return gPrivacyPane.writeAcceptThirdPartyCookies();">
+ <menupopup>
+ <menuitem label="&acceptThirdParty.always.label;" value="always"/>
+ <menuitem label="&acceptThirdParty.visited.label;" value="visited"/>
+ <menuitem label="&acceptThirdParty.never.label;" value="never"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ </hbox>
+
+ <hbox id="keepRow" class="indent">
+ <hbox id="keepBox" align="center">
+ <label id="keepUntil"
+ control="keepCookiesUntil"
+ accesskey="&keepUntil.accesskey;">&keepUntil.label;</label>
+ <menulist id="keepCookiesUntil"
+ preference="network.cookie.lifetimePolicy">
+ <menupopup>
+ <menuitem label="&expire.label;" value="0"/>
+ <menuitem label="&close.label;" value="2"/>
+ </menupopup>
+ </menulist>
+ </hbox>
+ <hbox flex="1"/>
+ <button id="showCookiesButton"
+ label="&showCookies.label;" accesskey="&showCookies.accesskey;"
+ oncommand="gPrivacyPane.showCookies();"
+ preference="pref.privacy.disable_button.view_cookies"/>
+ </hbox>
+
+ <hbox id="clearDataBox" align="center">
+ <checkbox id="alwaysClear" flex="1"
+ preference="privacy.sanitize.sanitizeOnShutdown"
+ label="&clearOnClose.label;"
+ accesskey="&clearOnClose.accesskey;"/>
+ <button id="clearDataSettings" label="&clearOnCloseSettings.label;"
+ accesskey="&clearOnCloseSettings.accesskey;"
+ oncommand="gPrivacyPane.showClearPrivateDataSettings();"/>
+ </hbox>
+ </vbox>
+ </vbox>
+ </vbox>
+ </deck>
+
+ </tabpanel>
+
+ <!-- Tracking -->
+ <tabpanel id="trackingPanel" orient="vertical">
+
+ <checkbox id="privacyDoNotTrackCheckbox"
+ label="&dntTrackingNotOkay.label2;"
+ accesskey="&dntTrackingNotOkay.accesskey;"
+ preference="privacy.donottrackheader.enabled"/>
+ <separator class="thin"/>
+ <label class="text-link" id="doNotTrackInfo"
+ href="https://www.mozilla.org/dnt"
+ value="&doNotTrackInfo.label;"/>
+
+ </tabpanel>
+
+ <!-- Location Bar -->
+ <tabpanel id="locatioBarPanel" orient="vertical">
+
+ <label id="locationBarSuggestionLabel">&locbar.suggest.label;</label>
+
+ <vbox id="tabPrefsBox" align="start" flex="1">
+ <checkbox id="historySuggestion" label="&locbar.history.label;"
+ accesskey="&locbar.history.accesskey;"
+ preference="browser.urlbar.suggest.history"/>
+ <checkbox id="bookmarkSuggestion" label="&locbar.bookmarks.label;"
+ accesskey="&locbar.bookmarks.accesskey;"
+ preference="browser.urlbar.suggest.bookmark"/>
+ <checkbox id="openpageSuggestion" label="&locbar.openpage.label;"
+ accesskey="&locbar.openpage.accesskey;"
+ preference="browser.urlbar.suggest.openpage"/>
+ </vbox>
+
+ </tabpanel>
+
+ </tabpanels>
+ </tabbox>
+ </prefpane>
+
+</overlay>
diff --git a/components/preferences/sanitize.js b/components/preferences/sanitize.js
new file mode 100644
index 0000000..15e6f58
--- /dev/null
+++ b/components/preferences/sanitize.js
@@ -0,0 +1,12 @@
+/* -*- 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/. */
+
+var gSanitizeDialog = Object.freeze({
+ onClearHistoryChanged: function () {
+ let downloadsPref = document.getElementById("privacy.clearOnShutdown.downloads");
+ let historyPref = document.getElementById("privacy.clearOnShutdown.history");
+ downloadsPref.value = historyPref.value;
+ }
+});
diff --git a/components/preferences/sanitize.xul b/components/preferences/sanitize.xul
new file mode 100644
index 0000000..829b5df
--- /dev/null
+++ b/components/preferences/sanitize.xul
@@ -0,0 +1,108 @@
+<?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/preferences/preferences.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+ <!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,help"
+ ondialoghelp="openPrefsHelp()"
+ style="width: &dialog.width2;;"
+ title="&sanitizePrefs2.title;"
+ onload="gSanitizeDialog.onClearHistoryChanged();">
+
+ <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
+ <script type="application/javascript" src="chrome://browser/content/preferences/sanitize.js"/>
+
+ <prefpane id="SanitizeDialogPane"
+ helpTopic="prefs-clear-private-data">
+
+ <preferences>
+ <preference id="privacy.clearOnShutdown.history" name="privacy.clearOnShutdown.history" type="bool"
+ onchange="return gSanitizeDialog.onClearHistoryChanged();"/>
+ <preference id="privacy.clearOnShutdown.formdata" name="privacy.clearOnShutdown.formdata" type="bool"/>
+ <preference id="privacy.clearOnShutdown.passwords" name="privacy.clearOnShutdown.passwords" type="bool"/>
+ <preference id="privacy.clearOnShutdown.downloads" name="privacy.clearOnShutdown.downloads" type="bool"/>
+ <preference id="privacy.clearOnShutdown.cookies" name="privacy.clearOnShutdown.cookies" type="bool"/>
+ <preference id="privacy.clearOnShutdown.cache" name="privacy.clearOnShutdown.cache" type="bool"/>
+ <preference id="privacy.clearOnShutdown.offlineApps" name="privacy.clearOnShutdown.offlineApps" type="bool"/>
+ <preference id="privacy.clearOnShutdown.sessions" name="privacy.clearOnShutdown.sessions" type="bool"/>
+ <preference id="privacy.clearOnShutdown.siteSettings" name="privacy.clearOnShutdown.siteSettings" type="bool"/>
+ <preference id="privacy.clearOnShutdown.connectivityData" name="privacy.clearOnShutdown.connectivityData" type="bool"/>
+ </preferences>
+
+ <description>&clearDataSettings2.label;</description>
+
+ <groupbox orient="horizontal">
+ <caption label="&historySection.label;"/>
+ <grid flex="1">
+ <columns>
+ <column style="width: &column.width2;"/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row>
+ <checkbox label="&itemHistoryAndDownloads.label;"
+ accesskey="&itemHistoryAndDownloads.accesskey;"
+ preference="privacy.clearOnShutdown.history"/>
+ <checkbox label="&itemCookies.label;"
+ accesskey="&itemCookies.accesskey;"
+ preference="privacy.clearOnShutdown.cookies"/>
+ </row>
+ <row>
+ <checkbox label="&itemActiveLogins.label;"
+ accesskey="&itemActiveLogins.accesskey;"
+ preference="privacy.clearOnShutdown.sessions"/>
+ <checkbox label="&itemCache.label;"
+ accesskey="&itemCache.accesskey;"
+ preference="privacy.clearOnShutdown.cache"/>
+ </row>
+ <row>
+ <checkbox label="&itemFormSearchHistory.label;"
+ accesskey="&itemFormSearchHistory.accesskey;"
+ preference="privacy.clearOnShutdown.formdata"/>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+ <groupbox orient="horizontal">
+ <caption label="&dataSection.label;"/>
+ <grid flex="1">
+ <columns>
+ <column style="width: &column.width2;"/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row>
+ <checkbox label="&itemPasswords.label;"
+ accesskey="&itemPasswords.accesskey;"
+ preference="privacy.clearOnShutdown.passwords"/>
+ <checkbox label="&itemOfflineApps.label;"
+ accesskey="&itemOfflineApps.accesskey;"
+ preference="privacy.clearOnShutdown.offlineApps"/>
+ </row>
+ <row>
+ <checkbox label="&itemSitePreferences.label;"
+ accesskey="&itemSitePreferences.accesskey;"
+ preference="privacy.clearOnShutdown.siteSettings"/>
+ <checkbox label="&itemConnectivityData.label;"
+ accesskey="&itemConnectivityData.accesskey;"
+ preference="privacy.clearOnShutdown.connectivityData"/>
+ </row>
+ </rows>
+ </grid>
+ </groupbox>
+ </prefpane>
+</prefwindow>
diff --git a/components/preferences/security.js b/components/preferences/security.js
new file mode 100644
index 0000000..9d5f302
--- /dev/null
+++ b/components/preferences/security.js
@@ -0,0 +1,263 @@
+/* -*- 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/. */
+
+XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
+ "resource://gre/modules/LoginHelper.jsm");
+
+Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+var gSecurityPane = {
+ _pane: null,
+
+ /**
+ * Initializes UI.
+ */
+ init: function ()
+ {
+ this._pane = document.getElementById("paneSecurity");
+ this._initMasterPasswordUI();
+ this._initHPKPUI();
+ },
+
+ // ADD-ONS
+
+ /*
+ * Preferences:
+ *
+ * xpinstall.whitelist.required
+ * - true if a site must be added to a site whitelist before extensions
+ * provided by the site may be installed from it, false if the extension
+ * may be directly installed after a confirmation dialog
+ */
+
+ /**
+ * Enables/disables the add-ons Exceptions button depending on whether
+ * or not add-on installation warnings are displayed.
+ */
+ readWarnAddonInstall: function ()
+ {
+ var warn = document.getElementById("xpinstall.whitelist.required");
+ var exceptions = document.getElementById("addonExceptions");
+
+ exceptions.disabled = !warn.value;
+
+ // don't override the preference value
+ return undefined;
+ },
+
+ /**
+ * Displays the exceptions lists for add-on installation warnings.
+ */
+ showAddonExceptions: function ()
+ {
+ var bundlePrefs = document.getElementById("bundlePreferences");
+
+ var params = this._addonParams;
+ if (!params.windowTitle || !params.introText) {
+ params.windowTitle = bundlePrefs.getString("addons_permissions_title");
+ params.introText = bundlePrefs.getString("addonspermissionstext");
+ }
+
+ document.documentElement.openWindow("Browser:Permissions",
+ "chrome://browser/content/preferences/permissions.xul",
+ "", params);
+ },
+
+ /**
+ * Parameters for the add-on install permissions dialog.
+ */
+ _addonParams:
+ {
+ blockVisible: false,
+ sessionVisible: false,
+ allowVisible: true,
+ prefilledHost: "",
+ permissionType: "install"
+ },
+
+ /**
+ * Ensures that the blocklist is enabled/disabled appropriately based on level
+ */
+ addonLevelNeedsSync: function()
+ {
+ Services.prefs.setBoolPref("extensions.blocklist.level.updated", true);
+ },
+ // called from preferences window onunload.
+ syncAddonSecurityLevel: function()
+ {
+ if (Services.prefs.getBoolPref("extensions.blocklist.level.updated") == true) {
+ Services.prefs.setBoolPref("extensions.blocklist.level.updated", false);
+ var secLevel = Services.prefs.getIntPref("extensions.blocklist.level");
+ Services.prefs.setBoolPref("extensions.blocklist.enabled",
+ !(secLevel == 99));
+ }
+ },
+
+ // PASSWORDS
+
+ /*
+ * Preferences:
+ *
+ * signon.rememberSignons
+ * - true if passwords are remembered, false otherwise
+ */
+
+ /**
+ * Enables/disables the Exceptions button used to configure sites where
+ * passwords are never saved. When browser is set to start in Private
+ * Browsing mode, the "Remember passwords" UI is useless, so we disable it.
+ */
+ readSavePasswords: function ()
+ {
+ var pref = document.getElementById("signon.rememberSignons");
+ var excepts = document.getElementById("passwordExceptions");
+
+ if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ document.getElementById("savePasswords").disabled = true;
+ excepts.disabled = true;
+ return false;
+ } else {
+ excepts.disabled = !pref.value;
+ // don't override pref value in UI
+ return undefined;
+ }
+ },
+
+ /**
+ * Displays a dialog in which the user can view and modify the list of sites
+ * where passwords are never saved.
+ */
+ showPasswordExceptions: function ()
+ {
+ let bundlePrefs = document.getElementById("bundlePreferences");
+ let params = {
+ blockVisible: true,
+ sessionVisible: false,
+ allowVisible: false,
+ hideStatusColumn: true,
+ prefilledHost: "",
+ permissionType: "login-saving",
+ windowTitle: bundlePrefs.getString("savedLoginsExceptions_title"),
+ introText: bundlePrefs.getString("savedLoginsExceptions_desc")
+ };
+
+ document.documentElement.openWindow("Toolkit:PasswordManagerExceptions",
+ "chrome://browser/content/preferences/permissions.xul",
+ null, params);
+ },
+
+ /**
+ * Initializes master password UI: the "use master password" checkbox, selects
+ * the master password button to show, and enables/disables it as necessary.
+ * The master password is controlled by various bits of NSS functionality, so
+ * the UI for it can't be controlled by the normal preference bindings.
+ */
+ _initMasterPasswordUI: function ()
+ {
+ var noMP = !LoginHelper.isMasterPasswordSet();
+
+ var button = document.getElementById("changeMasterPassword");
+ button.disabled = noMP;
+
+ var checkbox = document.getElementById("useMasterPassword");
+ checkbox.checked = !noMP;
+ },
+
+ /**
+ * Enables/disables the master password button depending on the state of the
+ * "use master password" checkbox, and prompts for master password removal if
+ * one is set.
+ */
+ updateMasterPasswordButton: function ()
+ {
+ var checkbox = document.getElementById("useMasterPassword");
+ var button = document.getElementById("changeMasterPassword");
+ button.disabled = !checkbox.checked;
+
+ // unchecking the checkbox should try to immediately remove the master
+ // password, because it's impossible to non-destructively remove the master
+ // password used to encrypt all the passwords without providing it (by
+ // design), and it would be extremely odd to pop up that dialog when the
+ // user closes the prefwindow and saves his settings
+ if (!checkbox.checked)
+ this._removeMasterPassword();
+ else
+ this.changeMasterPassword();
+
+ this._initMasterPasswordUI();
+ },
+
+ /**
+ * Displays the "remove master password" dialog to allow the user to remove
+ * the current master password. When the dialog is dismissed, master password
+ * UI is automatically updated.
+ */
+ _removeMasterPassword: function ()
+ {
+ const Cc = Components.classes, Ci = Components.interfaces;
+ var secmodDB = Cc["@mozilla.org/security/pkcs11moduledb;1"].
+ getService(Ci.nsIPKCS11ModuleDB);
+ if (secmodDB.isFIPSEnabled) {
+ var promptService = Cc["@mozilla.org/embedcomp/prompt-service;1"].
+ getService(Ci.nsIPromptService);
+ var bundle = document.getElementById("bundlePreferences");
+ promptService.alert(window,
+ bundle.getString("pw_change_failed_title"),
+ bundle.getString("pw_change2empty_in_fips_mode"));
+ }
+ else {
+ document.documentElement.openSubDialog("chrome://mozapps/content/preferences/removemp.xul",
+ "", null);
+ }
+ this._initMasterPasswordUI();
+ },
+
+ /**
+ * Displays a dialog in which the master password may be changed.
+ */
+ changeMasterPassword: function ()
+ {
+ document.documentElement.openSubDialog("chrome://mozapps/content/preferences/changemp.xul",
+ "", null);
+ this._initMasterPasswordUI();
+ },
+
+ /**
+ * Shows the sites where the user has saved passwords and the associated login
+ * information.
+ */
+ showPasswords: function ()
+ {
+ document.documentElement.openWindow("Toolkit:PasswordManager",
+ "chrome://passwordmgr/content/passwordManager.xul",
+ "", null);
+ },
+
+ _initHPKPUI: function() {
+ let checkbox = document.getElementById("enableHPKP");
+ let HPKPpref = document.getElementById("security.cert_pinning.enforcement_level");
+
+ if (HPKPpref.value == 0) {
+ checkbox.checked = false;
+ } else {
+ checkbox.checked = true;
+ }
+ },
+
+ /**
+ * Updates the HPKP enforcement level to the proper value depending on checkbox
+ * state.
+ */
+ updateHPKPPref: function() {
+ let checkbox = document.getElementById("enableHPKP");
+ let HPKPpref = document.getElementById("security.cert_pinning.enforcement_level");
+
+ if (checkbox.checked) {
+ HPKPpref.value = 2;
+ } else {
+ HPKPpref.value = 0;
+ }
+ }
+};
diff --git a/components/preferences/security.xul b/components/preferences/security.xul
new file mode 100644
index 0000000..bc16252
--- /dev/null
+++ b/components/preferences/security.xul
@@ -0,0 +1,184 @@
+<?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/. -->
+
+<!DOCTYPE overlay [
+ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+ <!ENTITY % securityDTD SYSTEM "chrome://browser/locale/preferences/security.dtd">
+ %brandDTD;
+ %securityDTD;
+]>
+
+<overlay id="SecurityPaneOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <prefpane id="paneSecurity"
+ onpaneload="gSecurityPane.init();"
+ helpTopic="prefs-security">
+
+ <preferences id="securityPreferences">
+ <!-- XXX buttons -->
+ <preference id="pref.privacy.disable_button.view_passwords"
+ name="pref.privacy.disable_button.view_passwords"
+ type="bool"/>
+ <preference id="pref.privacy.disable_button.view_passwords_exceptions"
+ name="pref.privacy.disable_button.view_passwords_exceptions"
+ type="bool"/>
+
+ <!-- Add-ons, malware, phishing -->
+ <preference id="xpinstall.whitelist.required"
+ name="xpinstall.whitelist.required"
+ type="bool"/>
+ <preference id="extensions.blocklist.level"
+ name="extensions.blocklist.level"
+ onchange="gSecurityPane.addonLevelNeedsSync();"
+ type="int"/>
+
+ <!-- Passwords -->
+ <preference id="signon.rememberSignons" name="signon.rememberSignons" type="bool"/>
+ <preference id="signon.autofillForms" name="signon.autofillForms" type="bool"/>
+
+ <!-- Security Protocols -->
+
+ <preference id="network.stricttransportsecurity.enabled"
+ name="network.stricttransportsecurity.enabled"
+ type="bool"/>
+ <preference id="security.cert_pinning.enforcement_level"
+ name="security.cert_pinning.enforcement_level"
+ type="int"/>
+
+ <!-- Opportunistic Encryption -->
+
+ <preference id="network.http.upgrade-insecure-requests"
+ name="network.http.upgrade-insecure-requests"
+ type="bool"/>
+ <preference id="network.http.altsvc.oe"
+ name="network.http.altsvc.oe"
+ type="bool"/>
+
+ <!-- XSS Filter -->
+ <!--
+ <preference id="security.xssfilter.enable" name="security.xssfilter.enable" type="bool"/>
+ -->
+
+ </preferences>
+
+ <script type="application/javascript" src="chrome://browser/content/preferences/security.js"/>
+
+ <stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences/preferences.properties"/>
+
+ <!-- addons, forgery (phishing) UI -->
+ <groupbox id="addonsSecurityGroup">
+ <caption label="&addons.label;"/>
+
+ <hbox id="addonInstallBox">
+ <checkbox id="warnAddonInstall" flex="1"
+ label="&warnAddonInstall.label;"
+ accesskey="&warnAddonInstall.accesskey;"
+ preference="xpinstall.whitelist.required"
+ onsyncfrompreference="return gSecurityPane.readWarnAddonInstall();"/>
+ <button id="addonExceptions"
+ label="&addonExceptions.label;"
+ accesskey="&addonExceptions.accesskey;"
+ oncommand="gSecurityPane.showAddonExceptions();"/>
+ </hbox>
+ <hbox id="addonSecuritySettingsBox" flex="1">
+ <vbox>
+ <label id="addonSecurity" control="addonsecurity-menu">&addonSecuritylevel;</label>
+ <menulist id="addonsecurity-menu" preference="extensions.blocklist.level" sizetopopup="always">
+ <menupopup>
+ <menuitem label="&addonSecurityLevel_Off;" value="99" />
+ <menuitem label="&addonSecurityLevel_Low;" value="3" />
+ <menuitem label="&addonSecurityLevel_High;" value="2" />
+ <menuitem label="&addonSecurityLevel_Extreme;" value="1" />
+ </menupopup>
+ </menulist>
+ </vbox>
+ </hbox>
+ </groupbox>
+
+ <!-- Passwords -->
+ <groupbox id="passwordsGroup" orient="vertical">
+ <caption label="&passwords.label;"/>
+
+ <hbox id="savePasswordsBox">
+ <checkbox id="savePasswords" flex="1"
+ label="&rememberPasswords.label;" accesskey="&rememberPasswords.accesskey;"
+ preference="signon.rememberSignons"
+ onsyncfrompreference="return gSecurityPane.readSavePasswords();"/>
+ <button id="passwordExceptions"
+ label="&passwordExceptions.label;"
+ accesskey="&passwordExceptions.accesskey;"
+ oncommand="gSecurityPane.showPasswordExceptions();"
+ preference="pref.privacy.disable_button.view_passwords_exceptions"/>
+ </hbox>
+ <checkbox id="autofillPasswords" flex="1"
+ label="&autofillPasswords.label;" accesskey="&autofillPasswords.accesskey;"
+ preference="signon.autofillForms"/>
+ <hbox id="masterPasswordBox">
+ <checkbox id="useMasterPassword" flex="1"
+ oncommand="gSecurityPane.updateMasterPasswordButton();"
+ label="&useMasterPassword.label;"
+ accesskey="&useMasterPassword.accesskey;"/>
+ <button id="changeMasterPassword"
+ label="&changeMasterPassword.label;"
+ accesskey="&changeMasterPassword.accesskey;"
+ oncommand="gSecurityPane.changeMasterPassword();"/>
+ </hbox>
+
+ <hbox id="showPasswordsBox">
+ <spacer flex="1"/>
+ <button id="showPasswords"
+ label="&savedPasswords.label;" accesskey="&savedPasswords.accesskey;"
+ oncommand="gSecurityPane.showPasswords();"
+ preference="pref.privacy.disable_button.view_passwords"/>
+ </hbox>
+ </groupbox>
+
+ <!-- Security protocols -->
+ <groupbox id="SecProtoGroup">
+ <caption label="&SecProto.label;"/>
+
+ <vbox id="SecProtoBox" align="start" flex="1">
+ <checkbox id="enableHSTS"
+ label="&enableHSTS.label;"
+ accesskey="&enableHSTS.accesskey;"
+ preference="network.stricttransportsecurity.enabled" />
+ <checkbox id="enableHPKP"
+ label="&enableHPKP.label;"
+ accesskey="&enableHPKP.accesskey;"
+ oncommand="gSecurityPane.updateHPKPPref();"/>
+ </vbox>
+ </groupbox>
+
+ <groupbox id="OpportunisticEncryption">
+ <caption label="&OpEnc.label;"/>
+ <checkbox id="enableUIROpEnc"
+ label="&enableUIROpEnc.label;"
+ preference="network.http.upgrade-insecure-requests" />
+ <checkbox id="enableAltSvcOpEnc"
+ label="&enableAltSvcOpEnc.label;"
+ preference="network.http.altsvc.oe" />
+ </groupbox>
+
+ <!-- XSS Filter -->
+ <!--
+ <groupbox id="XSSFiltGroup">
+ <caption label="&XSSFilt.label;"/>
+
+ <hbox id="XSSFiltBox">
+ <checkbox id="enableXSSFilt" flex="1"
+ label="&enableXSSFilt.label;"
+ accesskey="&enableXSSFilt.accesskey;"
+ preference="security.xssfilter.enable" />
+ </hbox>
+
+ </groupbox>
+ -->
+
+ </prefpane>
+
+</overlay>
diff --git a/components/preferences/selectBookmark.js b/components/preferences/selectBookmark.js
new file mode 100644
index 0000000..c7ce022
--- /dev/null
+++ b/components/preferences/selectBookmark.js
@@ -0,0 +1,83 @@
+//* -*- 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/. */
+
+/**
+ * SelectBookmarkDialog controls the user interface for the "Use Bookmark for
+ * Home Page" dialog.
+ *
+ * The caller (gMainPane.setHomePageToBookmark in main.js) invokes this dialog
+ * with a single argument - a reference to an object with a .urls property and
+ * a .names property. This dialog is responsible for updating the contents of
+ * the .urls property with an array of URLs to use as home pages and for
+ * updating the .names property with an array of names for those URLs before it
+ * closes.
+ */
+var SelectBookmarkDialog = {
+ init: function SBD_init() {
+ document.getElementById("bookmarks").place =
+ "place:queryType=1&folder=" + PlacesUIUtils.allBookmarksFolderId;
+
+ // Initial update of the OK button.
+ this.selectionChanged();
+ },
+
+ /**
+ * Update the disabled state of the OK button as the user changes the
+ * selection within the view.
+ */
+ selectionChanged: function SBD_selectionChanged() {
+ var accept = document.documentElement.getButton("accept");
+ var bookmarks = document.getElementById("bookmarks");
+ var disableAcceptButton = true;
+ if (bookmarks.hasSelection) {
+ if (!PlacesUtils.nodeIsSeparator(bookmarks.selectedNode))
+ disableAcceptButton = false;
+ }
+ accept.disabled = disableAcceptButton;
+ },
+
+ onItemDblClick: function SBD_onItemDblClick() {
+ var bookmarks = document.getElementById("bookmarks");
+ var selectedNode = bookmarks.selectedNode;
+ if (selectedNode && PlacesUtils.nodeIsURI(selectedNode)) {
+ /**
+ * The user has double clicked on a tree row that is a link. Take this to
+ * mean that they want that link to be their homepage, and close the dialog.
+ */
+ document.documentElement.getButton("accept").click();
+ }
+ },
+
+ /**
+ * User accepts their selection. Set all the selected URLs or the contents
+ * of the selected folder as the list of homepages.
+ */
+ accept: function SBD_accept() {
+ var bookmarks = document.getElementById("bookmarks");
+ NS_ASSERT(bookmarks.hasSelection,
+ "Should not be able to accept dialog if there is no selected URL!");
+ var urls = [];
+ var names = [];
+ var selectedNode = bookmarks.selectedNode;
+ if (PlacesUtils.nodeIsFolder(selectedNode)) {
+ var contents = PlacesUtils.getFolderContents(selectedNode.itemId).root;
+ var cc = contents.childCount;
+ for (var i = 0; i < cc; ++i) {
+ var node = contents.getChild(i);
+ if (PlacesUtils.nodeIsURI(node)) {
+ urls.push(node.uri);
+ names.push(node.title);
+ }
+ }
+ contents.containerOpen = false;
+ }
+ else {
+ urls.push(selectedNode.uri);
+ names.push(selectedNode.title);
+ }
+ window.arguments[0].urls = urls;
+ window.arguments[0].names = names;
+ }
+};
diff --git a/components/preferences/selectBookmark.xul b/components/preferences/selectBookmark.xul
new file mode 100644
index 0000000..5547534
--- /dev/null
+++ b/components/preferences/selectBookmark.xul
@@ -0,0 +1,44 @@
+<?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/places/places.css"?>
+
+<?xml-stylesheet href="chrome://global/skin/"?>
+<?xml-stylesheet href="chrome://browser/skin/places/places.css"?>
+
+<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://browser/locale/preferences/selectBookmark.dtd">
+
+<dialog id="selectBookmarkDialog"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ title="&selectBookmark.title;" style="width: 32em;"
+ persist="screenX screenY width height" screenX="24" screenY="24"
+ onload="SelectBookmarkDialog.init();"
+ ondialogaccept="SelectBookmarkDialog.accept();">
+
+ <script type="application/javascript"
+ src="chrome://browser/content/preferences/selectBookmark.js"/>
+
+ <description>&selectBookmark.label;</description>
+
+ <separator class="thin"/>
+
+ <tree id="bookmarks" flex="1" type="places"
+ style="height: 15em;"
+ hidecolumnpicker="true"
+ seltype="single"
+ ondblclick="SelectBookmarkDialog.onItemDblClick();"
+ onselect="SelectBookmarkDialog.selectionChanged();">
+ <treecols>
+ <treecol id="title" flex="1" primary="true" hideheader="true"/>
+ </treecols>
+ <treechildren id="bookmarksChildren" flex="1"/>
+ </tree>
+
+ <separator class="thin"/>
+
+</dialog>
diff --git a/components/preferences/sync.js b/components/preferences/sync.js
new file mode 100644
index 0000000..f29728d
--- /dev/null
+++ b/components/preferences/sync.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/. */
+
+Components.utils.import("resource://services-sync/main.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+const PAGE_NO_ACCOUNT = 0;
+const PAGE_HAS_ACCOUNT = 1;
+const PAGE_NEEDS_UPDATE = 2;
+
+var gSyncPane = {
+ _stringBundle: null,
+ prefArray: ["engine.bookmarks", "engine.passwords", "engine.prefs",
+ "engine.tabs", "engine.history"],
+
+ get page() {
+ return document.getElementById("weavePrefsDeck").selectedIndex;
+ },
+
+ set page(val) {
+ document.getElementById("weavePrefsDeck").selectedIndex = val;
+ },
+
+ get _usingCustomServer() {
+ return Weave.Svc.Prefs.isSet("serverURL");
+ },
+
+ needsUpdate: function () {
+ this.page = PAGE_NEEDS_UPDATE;
+ let label = document.getElementById("loginError");
+ label.value = Weave.Utils.getErrorString(Weave.Status.login);
+ label.className = "error";
+ },
+
+ init: function () {
+ // If the Service hasn't finished initializing, wait for it.
+ let xps = Components.classes["@mozilla.org/weave/service;1"]
+ .getService(Components.interfaces.nsISupports)
+ .wrappedJSObject;
+
+ if (xps.ready) {
+ this._init();
+ return;
+ }
+
+ let onUnload = function () {
+ window.removeEventListener("unload", onUnload, false);
+ try {
+ Services.obs.removeObserver(onReady, "weave:service:ready");
+ } catch (e) {}
+ };
+
+ let onReady = function () {
+ Services.obs.removeObserver(onReady, "weave:service:ready");
+ window.removeEventListener("unload", onUnload, false);
+ this._init();
+ }.bind(this);
+
+ Services.obs.addObserver(onReady, "weave:service:ready", false);
+ window.addEventListener("unload", onUnload, false);
+
+ xps.ensureLoaded();
+ },
+
+ _init: function () {
+ let topics = ["weave:service:login:error",
+ "weave:service:login:finish",
+ "weave:service:start-over",
+ "weave:service:setup-complete",
+ "weave:service:logout:finish"];
+
+ // Add the observers now and remove them on unload
+ //XXXzpao This should use Services.obs.* but Weave's Obs does nice handling
+ // of `this`. Fix in a followup. (bug 583347)
+ topics.forEach(function (topic) {
+ Weave.Svc.Obs.add(topic, this.updateWeavePrefs, this);
+ }, this);
+ window.addEventListener("unload", function() {
+ topics.forEach(function (topic) {
+ Weave.Svc.Obs.remove(topic, this.updateWeavePrefs, this);
+ }, gSyncPane);
+ }, false);
+
+ this._stringBundle =
+ Services.strings.createBundle("chrome://browser/locale/preferences/preferences.properties");
+ this.updateWeavePrefs();
+ },
+
+ updateWeavePrefs: function () {
+ if (Weave.Status.service == Weave.CLIENT_NOT_CONFIGURED ||
+ Weave.Svc.Prefs.get("firstSync", "") == "notReady") {
+ this.page = PAGE_NO_ACCOUNT;
+ } else if (Weave.Status.login == Weave.LOGIN_FAILED_INVALID_PASSPHRASE ||
+ Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED) {
+ this.needsUpdate();
+ } else {
+ this.page = PAGE_HAS_ACCOUNT;
+ document.getElementById("accountName").value = Weave.Service.identity.account;
+ document.getElementById("syncComputerName").value = Weave.Service.clientsEngine.localName;
+ document.getElementById("tosPP").hidden = this._usingCustomServer;
+ }
+ },
+
+ startOver: function (showDialog) {
+ if (showDialog) {
+ let flags = 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_1_DEFAULT;
+ let buttonChoice =
+ Services.prompt.confirmEx(window,
+ this._stringBundle.GetStringFromName("syncUnlink.title"),
+ this._stringBundle.GetStringFromName("syncUnlink.label"),
+ flags,
+ this._stringBundle.GetStringFromName("syncUnlinkConfirm.label"),
+ null, null, null, {});
+
+ // If the user selects cancel, just bail
+ if (buttonChoice == 1) {
+ return;
+ }
+ }
+
+ Weave.Service.startOver();
+ this.updateWeavePrefs();
+ },
+
+ updatePass: function () {
+ if (Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED) {
+ gSyncUtils.changePassword();
+ } else {
+ gSyncUtils.updatePassphrase();
+ }
+ },
+
+ resetPass: function () {
+ if (Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED) {
+ gSyncUtils.resetPassword();
+ } else {
+ gSyncUtils.resetPassphrase();
+ }
+ },
+
+ /**
+ * 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 (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);
+ }
+ },
+
+ openQuotaDialog: function () {
+ let win = Services.wm.getMostRecentWindow("Sync:ViewQuota");
+ if (win) {
+ win.focus();
+ } else {
+ window.openDialog("chrome://browser/content/sync/quota.xul", "",
+ "centerscreen,chrome,dialog,modal");
+ }
+ },
+
+ 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");
+ }
+ },
+
+ resetSync: function () {
+ this.openSetup("reset");
+ },
+};
+
diff --git a/components/preferences/sync.xul b/components/preferences/sync.xul
new file mode 100644
index 0000000..52f5a95
--- /dev/null
+++ b/components/preferences/sync.xul
@@ -0,0 +1,178 @@
+<?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">
+<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
+<!ENTITY % syncDTD SYSTEM "chrome://browser/locale/preferences/sync.dtd">
+%brandDTD;
+%syncBrandDTD;
+%syncDTD;
+]>
+
+<overlay id="SyncPaneOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml">
+
+ <prefpane id="paneSync"
+ helpTopic="prefs-weave"
+ onpaneload="gSyncPane.init()">
+
+ <preferences>
+<!-- <preference id="engine.addons" name="services.sync.engine.addons" type="bool"/> -->
+ <preference id="engine.bookmarks" name="services.sync.engine.bookmarks" type="bool"/>
+ <preference id="engine.history" name="services.sync.engine.history" type="bool"/>
+ <preference id="engine.tabs" name="services.sync.engine.tabs" type="bool"/>
+ <preference id="engine.prefs" name="services.sync.engine.prefs" type="bool"/>
+ <preference id="engine.passwords" name="services.sync.engine.passwords" type="bool"/>
+ </preferences>
+
+
+ <script type="application/javascript"
+ src="chrome://browser/content/preferences/sync.js"/>
+ <script type="application/javascript"
+ src="chrome://browser/content/sync/utils.js"/>
+
+
+ <deck id="weavePrefsDeck">
+ <vbox id="noAccount" align="center">
+ <spacer flex="1"/>
+ <description id="syncDesc">
+ &weaveDesc.label;
+ </description>
+ <separator/>
+ <label class="text-link"
+ onclick="event.stopPropagation(); gSyncPane.openSetup(null);"
+ value="&setupButton.label;"/>
+ <separator/>
+ <label class="text-link"
+ onclick="event.stopPropagation(); gSyncPane.openSetup('pair');"
+ value="&pairDevice.label;"/>
+ <spacer flex="3"/>
+ </vbox>
+
+ <vbox id="hasAccount">
+ <groupbox class="syncGroupBox">
+ <!-- label is set to account name -->
+ <caption id="accountCaption" align="center">
+ <image id="accountCaptionImage"/>
+ <label id="accountName" value=""/>
+ </caption>
+
+ <hbox>
+ <button type="menu"
+ label="&manageAccount.label;"
+ accesskey="&manageAccount.accesskey;">
+ <menupopup>
+ <menuitem label="&viewQuota.label;"
+ oncommand="gSyncPane.openQuotaDialog();"/>
+ <menuseparator/>
+ <menuitem label="&changePassword2.label;"
+ oncommand="gSyncUtils.changePassword();"/>
+ <menuitem label="&myRecoveryKey.label;"
+ oncommand="gSyncUtils.resetPassphrase();"/>
+ <menuseparator/>
+ <menuitem label="&resetSync2.label;"
+ oncommand="gSyncPane.resetSync();"/>
+ </menupopup>
+ </button>
+ </hbox>
+
+ <hbox>
+ <label id="syncAddDeviceLabel"
+ class="text-link"
+ onclick="gSyncPane.openAddDevice(); return false;"
+ value="&pairDevice.label;"/>
+ </hbox>
+
+ <vbox>
+ <label value="&syncMy.label;" />
+ <richlistbox id="syncEnginesList"
+ orient="vertical"
+ onselect="if (this.selectedCount) this.clearSelection();">
+<!-- <richlistitem>
+ <checkbox label="&engine.addons.label;"
+ accesskey="&engine.addons.accesskey;"
+ preference="engine.addons"/>
+ </richlistitem> -->
+ <richlistitem>
+ <checkbox label="&engine.bookmarks.label;"
+ accesskey="&engine.bookmarks.accesskey;"
+ preference="engine.bookmarks"/>
+ </richlistitem>
+ <richlistitem>
+ <checkbox label="&engine.passwords.label;"
+ accesskey="&engine.passwords.accesskey;"
+ preference="engine.passwords"/>
+ </richlistitem>
+ <richlistitem>
+ <checkbox label="&engine.prefs.label;"
+ accesskey="&engine.prefs.accesskey;"
+ preference="engine.prefs"/>
+ </richlistitem>
+ <richlistitem>
+ <checkbox label="&engine.history.label;"
+ accesskey="&engine.history.accesskey;"
+ preference="engine.history"/>
+ </richlistitem>
+ <richlistitem>
+ <checkbox label="&engine.tabs.label;"
+ accesskey="&engine.tabs.accesskey;"
+ preference="engine.tabs"/>
+ </richlistitem>
+ </richlistbox>
+ </vbox>
+ </groupbox>
+
+ <groupbox class="syncGroupBox">
+ <grid>
+ <columns>
+ <column/>
+ <column flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label value="&syncDeviceName.label;"
+ accesskey="&syncDeviceName.accesskey;"
+ control="syncComputerName"/>
+ <textbox id="syncComputerName"
+ onchange="gSyncUtils.changeName(this)"/>
+ </row>
+ </rows>
+ </grid>
+ <hbox>
+ <label class="text-link"
+ onclick="gSyncPane.startOver(true); return false;"
+ value="&unlinkDevice.label;"/>
+ </hbox>
+ </groupbox>
+ <hbox id="tosPP" pack="center">
+ <label class="text-link"
+ onclick="event.stopPropagation();gSyncUtils.openToS();"
+ value="&prefs.tosLink.label;"/>
+ <label class="text-link"
+ onclick="event.stopPropagation();gSyncUtils.openPrivacyPolicy();"
+ value="&prefs.ppLink.label;"/>
+ </hbox>
+ </vbox>
+
+ <vbox id="needsUpdate" align="center" pack="center">
+ <hbox>
+ <label id="loginError" value=""/>
+ <label class="text-link"
+ onclick="gSyncPane.updatePass(); return false;"
+ value="&updatePass.label;"/>
+ <label class="text-link"
+ onclick="gSyncPane.resetPass(); return false;"
+ value="&resetPass.label;"/>
+ </hbox>
+ <label class="text-link"
+ onclick="gSyncPane.startOver(true); return false;"
+ value="&unlinkDevice.label;"/>
+ </vbox>
+ </deck>
+ </prefpane>
+</overlay>
diff --git a/components/preferences/tabs.js b/components/preferences/tabs.js
new file mode 100644
index 0000000..b09cb60
--- /dev/null
+++ b/components/preferences/tabs.js
@@ -0,0 +1,90 @@
+# -*- 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/.
+
+var gTabsPane = {
+
+ /*
+ * Preferences:
+ *
+ * browser.link.open_newwindow
+ * - determines where pages which would open in a new window are opened:
+ * 1 opens such links in the most recent window or tab,
+ * 2 opens such links in a new window,
+ * 3 opens such links in a new tab
+ * browser.tabs.loadInBackground
+ * - true if display should switch to a new tab which has been opened from a
+ * link, false if display shouldn't switch
+ * browser.tabs.warnOnClose
+ * - true if when closing a window with multiple tabs the user is warned and
+ * allowed to cancel the action, false to just close the window
+ * browser.tabs.warnOnOpen
+ * - true if the user should be warned if he attempts to open a lot of tabs at
+ * once (e.g. a large folder of bookmarks), false otherwise
+ * browser.taskbar.previews.enable
+ * - true if tabs are to be shown in the Windows 7 taskbar
+ */
+
+ /**
+ * Initialize any platform-specific UI.
+ */
+ init: function () {
+#ifdef XP_WIN
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+ try {
+ let sysInfo = Cc["@mozilla.org/system-info;1"].
+ getService(Ci.nsIPropertyBag2);
+ let ver = parseFloat(sysInfo.getProperty("version"));
+ let showTabsInTaskbar = document.getElementById("showTabsInTaskbar");
+ showTabsInTaskbar.hidden = ver < 6.1;
+ } catch (ex) {}
+#endif
+ // Set the proper value in the newtab drop-down.
+ gTabsPane.readNewtabUrl();
+ },
+
+ /**
+ * Pale Moon: synchronize warnOnClose and warnOnCloseOtherTabs
+ */
+ syncWarnOnClose: function() {
+ var warnOnClosePref = document.getElementById("browser.tabs.warnOnClose");
+ var warnOnCloseOtherPref = document.getElementById("browser.tabs.warnOnCloseOtherTabs");
+ warnOnCloseOtherPref.value = warnOnClosePref.value;
+ },
+
+ /**
+ * Determines where a link which opens a new window will open.
+ *
+ * @returns |true| if such links should be opened in new tabs
+ */
+ readLinkTarget: function() {
+ var openNewWindow = document.getElementById("browser.link.open_newwindow");
+ return openNewWindow.value != 2;
+ },
+
+ /**
+ * Determines where a link which opens a new window will open.
+ *
+ * @returns 2 if such links should be opened in new windows,
+ * 3 if such links should be opened in new tabs
+ */
+ writeLinkTarget: function() {
+ var linkTargeting = document.getElementById("linkTargeting");
+ return linkTargeting.checked ? 3 : 2;
+ },
+
+ /**
+ * Determines the value of the New Tab display drop-down based
+ * on the value of browser.newtab.url.
+ */
+ readNewtabUrl: function() {
+ let newtabUrlChoice = document.getElementById("browser.newtab.choice");
+ newtabUrlChoice.value = gNewtabUrl.getNewtabChoice();
+ if (newtabUrlChoice.value == 0) {
+ document.getElementById("newtabPageCustom").hidden = false;
+ }
+ gNewtabUrl.newtabUrlChoiceIsSet = true;
+ }
+};
diff --git a/components/preferences/tabs.xul b/components/preferences/tabs.xul
new file mode 100644
index 0000000..64529d6
--- /dev/null
+++ b/components/preferences/tabs.xul
@@ -0,0 +1,102 @@
+<?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/.
+
+<!DOCTYPE overlay [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % tabsDTD SYSTEM "chrome://browser/locale/preferences/tabs.dtd">
+%tabsDTD;
+]>
+
+<overlay id="TabsPaneOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <prefpane id="paneTabs"
+ onpaneload="gTabsPane.init();"
+ helpTopic="prefs-tabs">
+
+ <preferences id="tabsPreferences">
+ <preference id="browser.link.open_newwindow" name="browser.link.open_newwindow" type="int"/>
+ <preference id="browser.tabs.autoHide" name="browser.tabs.autoHide" type="bool" inverted="true"/>
+ <preference id="browser.tabs.loadInBackground" name="browser.tabs.loadInBackground" type="bool" inverted="true"/>
+ <preference id="browser.tabs.warnOnClose" name="browser.tabs.warnOnClose" type="bool"
+ onchange="gTabsPane.syncWarnOnClose();"/>
+ <preference id="browser.tabs.warnOnCloseOtherTabs" name="browser.tabs.warnOnCloseOtherTabs" type="bool"/>
+ <preference id="browser.tabs.warnOnOpen" name="browser.tabs.warnOnOpen" type="bool"/>
+ <preference id="browser.sessionstore.restore_on_demand" name="browser.sessionstore.restore_on_demand" type="bool"/>
+#ifdef XP_WIN
+ <preference id="browser.taskbar.previews.enable" name="browser.taskbar.previews.enable" type="bool"/>
+#endif
+ <preference id="browser.tabs.insertRelatedAfterCurrent" name="browser.tabs.insertRelatedAfterCurrent" type="bool"/>
+ <preference id="browser.search.context.loadInBackground" name="browser.search.context.loadInBackground" type="bool" inverted="true"/>
+ <preference id="browser.tabs.closeWindowWithLastTab" name="browser.tabs.closeWindowWithLastTab" type="bool"/>
+ <preference id="browser.ctrlTab.previews" name="browser.ctrlTab.previews" type="bool"/>
+
+ <preference id="browser.newtab.url" name="browser.newtab.url" type="string"/>
+ <preference id="browser.newtab.myhome" name="browser.newtab.myhome" type="string"/>
+ <preference id="browser.newtab.choice" name="browser.newtab.choice" type="int"/>
+ </preferences>
+
+ <script type="application/javascript" src="chrome://browser/content/preferences/tabs.js"/>
+
+ <!-- XXX flex below is a hack because wrapping checkboxes don't reflow
+ properly; see bug 349098 -->
+ <vbox id="tabPrefsBox" align="start" flex="1">
+ <checkbox id="linkTargeting" label="&newWindowsAsTabs.label;"
+ accesskey="&newWindowsAsTabs.accesskey;"
+ preference="browser.link.open_newwindow"
+ onsyncfrompreference="return gTabsPane.readLinkTarget();"
+ onsynctopreference="return gTabsPane.writeLinkTarget();"/>
+ <checkbox id="warnCloseMultiple" label="&warnCloseMultipleTabs.label;"
+ accesskey="&warnCloseMultipleTabs.accesskey;"
+ preference="browser.tabs.warnOnClose"/>
+ <checkbox id="warnOpenMany" label="&warnOpenManyTabs.label;"
+ accesskey="&warnOpenManyTabs.accesskey;"
+ preference="browser.tabs.warnOnOpen"/>
+ <checkbox id="showTabBar" label="&showTabBar.label;"
+ accesskey="&showTabBar.accesskey;"
+ preference="browser.tabs.autoHide"/>
+ <checkbox id="restoreOnDemand" label="&restoreTabsOnDemand.label;"
+ accesskey="&restoreTabsOnDemand.accesskey;"
+ preference="browser.sessionstore.restore_on_demand"/>
+ <checkbox id="switchToNewTabs" label="&switchToNewTabs.label;"
+ accesskey="&switchToNewTabs.accesskey;"
+ preference="browser.tabs.loadInBackground"/>
+#ifdef XP_WIN
+ <checkbox id="showTabsInTaskbar" label="&showTabsInTaskbar.label;"
+ accesskey="&showTabsInTaskbar.accesskey;"
+ preference="browser.taskbar.previews.enable"/>
+#endif
+<!-- Pale Moon additions -->
+ <checkbox id="insertRelatedAfterCurrent" label="&insertRelatedAfterCurrent.label;"
+ preference="browser.tabs.insertRelatedAfterCurrent"/>
+ <checkbox id="contextLoadInBackground" label="&contextLoadInBackground.label;"
+ preference="browser.search.context.loadInBackground"/>
+ <checkbox id="closeWindowWithLastTab" label="&closeWindowWithLastTab.label;"
+ preference="browser.tabs.closeWindowWithLastTab"/>
+ <checkbox id="showTabPreviews" label="&showTabPreviews.label;"
+ preference="browser.ctrlTab.previews"/>
+ <hbox align="center">
+ <label value="&newtabPage.label;"/>
+ <menulist
+ id="newtabPage"
+ preference="browser.newtab.choice"
+ oncommand="gNewtabUrl.writeNewtabUrl(event.target.value);">
+ <menupopup>
+ <menuitem label="&newtabPage.custom.label;" value="0" id="newtabPageCustom" hidden="true" />
+ <menuitem label="&newtabPage.blank.label;" value="1" />
+ <menuitem label="&newtabPage.home.label;" value="2" />
+ <menuitem label="&newtabPage.myhome.label;" value="3" />
+ <menuitem label="&newtabPage.quickdial.label;" value="4" />
+ </menupopup>
+ </menulist>
+ </hbox>
+ </vbox>
+
+ </prefpane>
+
+</overlay>
diff --git a/components/privatebrowsing/content/aboutPrivateBrowsing.xhtml b/components/privatebrowsing/content/aboutPrivateBrowsing.xhtml
new file mode 100644
index 0000000..95744cf
--- /dev/null
+++ b/components/privatebrowsing/content/aboutPrivateBrowsing.xhtml
@@ -0,0 +1,156 @@
+<?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 % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
+ %browserDTD;
+#ifdef XP_MACOSX
+ <!ENTITY basePBMenu.label "&fileMenu.label;">
+#else
+ <!ENTITY basePBMenu.label "<span class='appMenuButton'>&brandShortName;</span><span class='fileMenu'>&fileMenu.label;</span>">
+#endif
+ <!ENTITY % privatebrowsingpageDTD SYSTEM "chrome://browser/locale/aboutPrivateBrowsing.dtd">
+ %privatebrowsingpageDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <link rel="stylesheet" href="chrome://global/skin/netError.css" type="text/css" media="all"/>
+ <link rel="stylesheet" href="chrome://browser/skin/aboutPrivateBrowsing.css" type="text/css" media="all"/>
+ <style type="text/css"><![CDATA[
+ body.normal .showPrivate,
+ body.private .showNormal {
+ display: none;
+ }
+ body.appMenuButtonVisible .fileMenu {
+ display: none;
+ }
+ body.appMenuButtonInvisible .appMenuButton {
+ display: none;
+ }
+ ]]></style>
+ <script type="application/javascript;version=1.7"><![CDATA[
+ const Cc = Components.classes;
+ const Ci = Components.interfaces;
+
+ Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+ if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
+ document.title = "]]>&privatebrowsingpage.title.normal;<![CDATA[";
+ setFavIcon("chrome://global/skin/icons/question-16.png");
+ } else {
+#ifndef XP_MACOSX
+ document.title = "]]>&privatebrowsingpage.title;<![CDATA[";
+#endif
+ setFavIcon("chrome://browser/skin/Privacy-16.png");
+ }
+
+ var mainWindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+
+ // Focus the location bar
+ mainWindow.focusAndSelectUrlBar();
+
+ function setFavIcon(url) {
+ var icon = document.createElement("link");
+ icon.setAttribute("rel", "icon");
+ icon.setAttribute("type", "image/png");
+ icon.setAttribute("href", url);
+ var head = document.getElementsByTagName("head")[0];
+ head.insertBefore(icon, head.firstChild);
+ }
+
+ document.addEventListener("DOMContentLoaded", function () {
+ if (!PrivateBrowsingUtils.isWindowPrivate(window)) {
+ document.body.setAttribute("class", "normal");
+ }
+
+ // Set up the help link
+ let moreInfoURL = Cc["@mozilla.org/toolkit/URLFormatterService;1"].
+ getService(Ci.nsIURLFormatter).
+ formatURLPref("app.support.baseURL");
+ let moreInfoLink = document.getElementById("moreInfoLink");
+ if (moreInfoLink)
+ moreInfoLink.setAttribute("href", moreInfoURL + "private-browsing");
+
+ // Show the correct menu structure based on whether the App Menu button is
+ // shown or not.
+ var menuBar = mainWindow.document.getElementById("toolbar-menubar");
+ var appMenuButtonIsVisible = menuBar.getAttribute("autohide") == "true";
+ document.body.classList.add(appMenuButtonIsVisible ? "appMenuButtonVisible" :
+ "appMenuButtonInvisible");
+ }, false);
+
+ function openPrivateWindow() {
+ mainWindow.OpenBrowserWindow({private: true});
+ }
+ ]]></script>
+ </head>
+
+ <body dir="&locale.dir;"
+ class="private">
+
+ <!-- PAGE CONTAINER (for styling purposes only) -->
+ <div id="errorPageContainer">
+
+ <!-- Error Title -->
+ <div id="errorTitle">
+ <h1 id="errorTitleText" class="showPrivate">&privatebrowsingpage.title;</h1>
+ <h1 id="errorTitleTextNormal" class="showNormal">&privatebrowsingpage.title.normal;</h1>
+ </div>
+
+ <!-- LONG CONTENT (the section most likely to require scrolling) -->
+ <div id="errorLongContent">
+
+ <!-- Short Description -->
+ <div id="errorShortDesc">
+ <p id="errorShortDescText" class="showPrivate">&privatebrowsingpage.perwindow.issueDesc;</p>
+ <p id="errorShortDescTextNormal" class="showNormal">&privatebrowsingpage.perwindow.issueDesc.normal;</p>
+ </div>
+
+ <!-- Long Description -->
+ <div id="errorLongDesc">
+ <p id="errorLongDescText">&privatebrowsingpage.perwindow.description;</p>
+ </div>
+
+ <!-- Start Private Browsing -->
+ <div id="startPrivateBrowsingDesc" class="showNormal">
+ <button xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="startPrivateBrowsing" label="&privatebrowsingpage.openPrivateWindow.label;"
+ accesskey="&privatebrowsingpage.openPrivateWindow.accesskey;"
+ oncommand="openPrivateWindow();"/>
+ </div>
+
+ <!-- Footer -->
+ <div id="footerDesc">
+ <p id="footerText" class="showPrivate">&privatebrowsingpage.howToStop3;</p>
+ <p id="footerTextNormal" class="showNormal">&privatebrowsingpage.howToStart3;</p>
+ </div>
+
+ <!-- More Info -->
+ <div id="moreInfo" class="showPrivate">
+ <p id="moreInfoText">
+ &privatebrowsingpage.moreInfo;
+ </p>
+ <p id="moreInfoLinkContainer">
+ <a id="moreInfoLink" target="_blank">&privatebrowsingpage.learnMore;</a>
+ </p>
+ </div>
+ </div>
+ </div>
+
+ </body>
+</html>
diff --git a/components/privatebrowsing/jar.mn b/components/privatebrowsing/jar.mn
new file mode 100644
index 0000000..75e985c
--- /dev/null
+++ b/components/privatebrowsing/jar.mn
@@ -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/.
+
+browser.jar:
+* content/browser/aboutPrivateBrowsing.xhtml (content/aboutPrivateBrowsing.xhtml)
diff --git a/components/privatebrowsing/moz.build b/components/privatebrowsing/moz.build
new file mode 100644
index 0000000..c97072b
--- /dev/null
+++ b/components/privatebrowsing/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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/components/search/content/engineManager.js b/components/search/content/engineManager.js
new file mode 100644
index 0000000..993d48b
--- /dev/null
+++ b/components/search/content/engineManager.js
@@ -0,0 +1,492 @@
+/* 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");
+Components.utils.import("resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+
+const ENGINE_FLAVOR = "text/x-moz-search-engine";
+
+const BROWSER_SUGGEST_PREF = "browser.search.suggest.enabled";
+
+var gEngineView = null;
+
+var gEngineManagerDialog = {
+ init: function engineManager_init() {
+ gEngineView = new EngineView(new EngineStore());
+
+ var suggestEnabled = Services.prefs.getBoolPref(BROWSER_SUGGEST_PREF);
+ document.getElementById("enableSuggest").checked = suggestEnabled;
+
+ var tree = document.getElementById("engineList");
+ tree.view = gEngineView;
+
+ Services.obs.addObserver(this, "browser-search-engine-modified", false);
+ },
+
+ destroy: function engineManager_destroy() {
+ // Remove the observer
+ Services.obs.removeObserver(this, "browser-search-engine-modified");
+ },
+
+ observe: function engineManager_observe(aEngine, aTopic, aVerb) {
+ if (aTopic == "browser-search-engine-modified") {
+ aEngine.QueryInterface(Ci.nsISearchEngine);
+ switch (aVerb) {
+ case "engine-added":
+ gEngineView._engineStore.addEngine(aEngine);
+ gEngineView.rowCountChanged(gEngineView.lastIndex, 1);
+ break;
+ case "engine-changed":
+ gEngineView._engineStore.reloadIcons();
+ gEngineView.invalidate();
+ break;
+ case "engine-removed":
+ case "engine-current":
+ case "engine-default":
+ // Not relevant
+ break;
+ }
+ }
+ },
+
+ onOK: function engineManager_onOK() {
+ // Set the preference
+ var newSuggestEnabled = document.getElementById("enableSuggest").checked;
+ Services.prefs.setBoolPref(BROWSER_SUGGEST_PREF, newSuggestEnabled);
+
+ // Commit the changes
+ gEngineView._engineStore.commit();
+ },
+
+ onRestoreDefaults: function engineManager_onRestoreDefaults() {
+ var num = gEngineView._engineStore.restoreDefaultEngines();
+ gEngineView.rowCountChanged(0, num);
+ gEngineView.invalidate();
+ },
+
+ showRestoreDefaults: function engineManager_showRestoreDefaults(val) {
+ document.documentElement.getButton("extra2").disabled = !val;
+ },
+
+ loadAddEngines: function engineManager_loadAddEngines() {
+ this.onOK();
+ window.opener.BrowserSearch.loadAddEngines();
+ window.close();
+ },
+
+ remove: function engineManager_remove() {
+ gEngineView._engineStore.removeEngine(gEngineView.selectedEngine);
+ var index = gEngineView.selectedIndex;
+ gEngineView.rowCountChanged(index, -1);
+ gEngineView.invalidate();
+ gEngineView.selection.select(Math.min(index, gEngineView.lastIndex));
+ gEngineView.ensureRowIsVisible(gEngineView.currentIndex);
+ document.getElementById("engineList").focus();
+ },
+
+ /**
+ * Moves the selected engine either up or down in the engine list
+ * @param aDir
+ * -1 to move the selected engine down, +1 to move it up.
+ */
+ bump: function engineManager_move(aDir) {
+ var selectedEngine = gEngineView.selectedEngine;
+ var newIndex = gEngineView.selectedIndex - aDir;
+
+ gEngineView._engineStore.moveEngine(selectedEngine, newIndex);
+
+ gEngineView.invalidate();
+ gEngineView.selection.select(newIndex);
+ gEngineView.ensureRowIsVisible(newIndex);
+ this.showRestoreDefaults(true);
+ document.getElementById("engineList").focus();
+ },
+
+ editKeyword: Task.async(function* engineManager_editKeyword() {
+ var selectedEngine = gEngineView.selectedEngine;
+ if (!selectedEngine)
+ return;
+
+ var alias = { value: selectedEngine.alias };
+ var strings = document.getElementById("engineManagerBundle");
+ var title = strings.getString("editTitle");
+ var msg = strings.getFormattedString("editMsg", [selectedEngine.name]);
+
+ while (Services.prompt.prompt(window, title, msg, alias, null, {})) {
+ var bduplicate = false;
+ var eduplicate = false;
+ var dupName = "";
+
+ if (alias.value != "") {
+ // Check for duplicates in Places keywords.
+ bduplicate = !!(yield PlacesUtils.keywords.fetch(alias.value));
+
+ // Check for duplicates in changes we haven't committed yet
+ let engines = gEngineView._engineStore.engines;
+ for each (let engine in engines) {
+ if (engine.alias == alias.value &&
+ engine.name != selectedEngine.name) {
+ eduplicate = true;
+ dupName = engine.name;
+ break;
+ }
+ }
+ }
+
+ // Notify the user if they have chosen an existing engine/bookmark keyword
+ if (eduplicate || bduplicate) {
+ var dtitle = strings.getString("duplicateTitle");
+ var bmsg = strings.getString("duplicateBookmarkMsg");
+ var emsg = strings.getFormattedString("duplicateEngineMsg", [dupName]);
+
+ Services.prompt.alert(window, dtitle, eduplicate ? emsg : bmsg);
+ } else {
+ gEngineView._engineStore.changeEngine(selectedEngine, "alias",
+ alias.value);
+ gEngineView.invalidate();
+ break;
+ }
+ }
+ }),
+
+ onSelect: function engineManager_onSelect() {
+ // Buttons only work if an engine is selected and it's not the last engine,
+ // the latter is true when the selected is first and last at the same time.
+ var lastSelected = (gEngineView.selectedIndex == gEngineView.lastIndex);
+ var firstSelected = (gEngineView.selectedIndex == 0);
+ var noSelection = (gEngineView.selectedIndex == -1);
+
+ document.getElementById("cmd_remove")
+ .setAttribute("disabled", noSelection ||
+ (firstSelected && lastSelected));
+
+ document.getElementById("cmd_moveup")
+ .setAttribute("disabled", noSelection || firstSelected);
+
+ document.getElementById("cmd_movedown")
+ .setAttribute("disabled", noSelection || lastSelected);
+
+ document.getElementById("cmd_editkeyword")
+ .setAttribute("disabled", noSelection);
+ }
+};
+
+function onDragEngineStart(event) {
+ var selectedIndex = gEngineView.selectedIndex;
+ if (selectedIndex >= 0) {
+ event.dataTransfer.setData(ENGINE_FLAVOR, selectedIndex.toString());
+ event.dataTransfer.effectAllowed = "move";
+ }
+}
+
+// "Operation" objects
+function EngineMoveOp(aEngineClone, aNewIndex) {
+ if (!aEngineClone)
+ throw new Error("bad args to new EngineMoveOp!");
+ this._engine = aEngineClone.originalEngine;
+ this._newIndex = aNewIndex;
+}
+EngineMoveOp.prototype = {
+ _engine: null,
+ _newIndex: null,
+ commit: function EMO_commit() {
+ Services.search.moveEngine(this._engine, this._newIndex);
+ }
+}
+
+function EngineRemoveOp(aEngineClone) {
+ if (!aEngineClone)
+ throw new Error("bad args to new EngineRemoveOp!");
+ this._engine = aEngineClone.originalEngine;
+}
+EngineRemoveOp.prototype = {
+ _engine: null,
+ commit: function ERO_commit() {
+ Services.search.removeEngine(this._engine);
+ }
+}
+
+function EngineUnhideOp(aEngineClone, aNewIndex) {
+ if (!aEngineClone)
+ throw new Error("bad args to new EngineUnhideOp!");
+ this._engine = aEngineClone.originalEngine;
+ this._newIndex = aNewIndex;
+}
+EngineUnhideOp.prototype = {
+ _engine: null,
+ _newIndex: null,
+ commit: function EUO_commit() {
+ this._engine.hidden = false;
+ Services.search.moveEngine(this._engine, this._newIndex);
+ }
+}
+
+function EngineChangeOp(aEngineClone, aProp, aValue) {
+ if (!aEngineClone)
+ throw new Error("bad args to new EngineChangeOp!");
+
+ this._engine = aEngineClone.originalEngine;
+ this._prop = aProp;
+ this._newValue = aValue;
+}
+EngineChangeOp.prototype = {
+ _engine: null,
+ _prop: null,
+ _newValue: null,
+ commit: function ECO_commit() {
+ this._engine[this._prop] = this._newValue;
+ }
+}
+
+function EngineStore() {
+ this._engines = Services.search.getVisibleEngines().map(this._cloneEngine);
+ this._defaultEngines = Services.search.getDefaultEngines().map(this._cloneEngine);
+
+ this._ops = [];
+
+ // check if we need to disable the restore defaults button
+ var someHidden = this._defaultEngines.some(function (e) e.hidden);
+ gEngineManagerDialog.showRestoreDefaults(someHidden);
+}
+EngineStore.prototype = {
+ _engines: null,
+ _defaultEngines: null,
+ _ops: null,
+
+ get engines() {
+ return this._engines;
+ },
+ set engines(val) {
+ this._engines = val;
+ return val;
+ },
+
+ _getIndexForEngine: function ES_getIndexForEngine(aEngine) {
+ return this._engines.indexOf(aEngine);
+ },
+
+ _getEngineByName: function ES_getEngineByName(aName) {
+ for each (var engine in this._engines)
+ if (engine.name == aName)
+ return engine;
+
+ return null;
+ },
+
+ _cloneEngine: function ES_cloneEngine(aEngine) {
+ var clonedObj={};
+ for (var i in aEngine)
+ clonedObj[i] = aEngine[i];
+ clonedObj.originalEngine = aEngine;
+ return clonedObj;
+ },
+
+ // Callback for Array's some(). A thisObj must be passed to some()
+ _isSameEngine: function ES_isSameEngine(aEngineClone) {
+ return aEngineClone.originalEngine == this.originalEngine;
+ },
+
+ commit: function ES_commit() {
+ var currentEngine = this._cloneEngine(Services.search.currentEngine);
+ for (var i = 0; i < this._ops.length; i++)
+ this._ops[i].commit();
+
+ // Restore currentEngine if it is a default engine that is still visible.
+ // Needed if the user deletes currentEngine and then restores it.
+ if (this._defaultEngines.some(this._isSameEngine, currentEngine) &&
+ !currentEngine.originalEngine.hidden)
+ Services.search.currentEngine = currentEngine.originalEngine;
+ },
+
+ addEngine: function ES_addEngine(aEngine) {
+ this._engines.push(this._cloneEngine(aEngine));
+ },
+
+ moveEngine: function ES_moveEngine(aEngine, aNewIndex) {
+ if (aNewIndex < 0 || aNewIndex > this._engines.length - 1)
+ throw new Error("ES_moveEngine: invalid aNewIndex!");
+ var index = this._getIndexForEngine(aEngine);
+ if (index == -1)
+ throw new Error("ES_moveEngine: invalid engine?");
+
+ if (index == aNewIndex)
+ return; // nothing to do
+
+ // Move the engine in our internal store
+ var removedEngine = this._engines.splice(index, 1)[0];
+ this._engines.splice(aNewIndex, 0, removedEngine);
+
+ this._ops.push(new EngineMoveOp(aEngine, aNewIndex));
+ },
+
+ removeEngine: function ES_removeEngine(aEngine) {
+ var index = this._getIndexForEngine(aEngine);
+ if (index == -1)
+ throw new Error("invalid engine?");
+
+ this._engines.splice(index, 1);
+ this._ops.push(new EngineRemoveOp(aEngine));
+ if (this._defaultEngines.some(this._isSameEngine, aEngine))
+ gEngineManagerDialog.showRestoreDefaults(true);
+ },
+
+ restoreDefaultEngines: function ES_restoreDefaultEngines() {
+ var added = 0;
+
+ for (var i = 0; i < this._defaultEngines.length; ++i) {
+ var e = this._defaultEngines[i];
+
+ // If the engine is already in the list, just move it.
+ if (this._engines.some(this._isSameEngine, e)) {
+ this.moveEngine(this._getEngineByName(e.name), i);
+ } else {
+ // Otherwise, add it back to our internal store
+ this._engines.splice(i, 0, e);
+ this._ops.push(new EngineUnhideOp(e, i));
+ added++;
+ }
+ }
+ gEngineManagerDialog.showRestoreDefaults(false);
+ return added;
+ },
+
+ changeEngine: function ES_changeEngine(aEngine, aProp, aNewValue) {
+ var index = this._getIndexForEngine(aEngine);
+ if (index == -1)
+ throw new Error("invalid engine?");
+
+ this._engines[index][aProp] = aNewValue;
+ this._ops.push(new EngineChangeOp(aEngine, aProp, aNewValue));
+ },
+
+ reloadIcons: function ES_reloadIcons() {
+ this._engines.forEach(function (e) {
+ e.uri = e.originalEngine.uri;
+ });
+ }
+}
+
+function EngineView(aEngineStore) {
+ this._engineStore = aEngineStore;
+}
+EngineView.prototype = {
+ _engineStore: null,
+ tree: null,
+
+ get lastIndex() {
+ return this.rowCount - 1;
+ },
+ get selectedIndex() {
+ var seln = this.selection;
+ if (seln.getRangeCount() > 0) {
+ var min = {};
+ seln.getRangeAt(0, min, {});
+ return min.value;
+ }
+ return -1;
+ },
+ get selectedEngine() {
+ return this._engineStore.engines[this.selectedIndex];
+ },
+
+ // Helpers
+ rowCountChanged: function (index, count) {
+ this.tree.rowCountChanged(index, count);
+ },
+
+ invalidate: function () {
+ this.tree.invalidate();
+ },
+
+ ensureRowIsVisible: function (index) {
+ this.tree.ensureRowIsVisible(index);
+ },
+
+ getSourceIndexFromDrag: function (dataTransfer) {
+ return parseInt(dataTransfer.getData(ENGINE_FLAVOR));
+ },
+
+ // nsITreeView
+ get rowCount() {
+ return this._engineStore.engines.length;
+ },
+
+ getImageSrc: function(index, column) {
+ if (column.id == "engineName" && this._engineStore.engines[index].iconURI)
+ return this._engineStore.engines[index].iconURI.spec;
+ return "";
+ },
+
+ getCellText: function(index, column) {
+ if (column.id == "engineName")
+ return this._engineStore.engines[index].name;
+ else if (column.id == "engineKeyword")
+ return this._engineStore.engines[index].alias;
+ return "";
+ },
+
+ setTree: function(tree) {
+ this.tree = tree;
+ },
+
+ canDrop: function(targetIndex, orientation, dataTransfer) {
+ var sourceIndex = this.getSourceIndexFromDrag(dataTransfer);
+ return (sourceIndex != -1 &&
+ sourceIndex != targetIndex &&
+ sourceIndex != targetIndex + orientation);
+ },
+
+ drop: function(dropIndex, orientation, dataTransfer) {
+ var sourceIndex = this.getSourceIndexFromDrag(dataTransfer);
+ var sourceEngine = this._engineStore.engines[sourceIndex];
+
+ if (dropIndex > sourceIndex) {
+ if (orientation == Ci.nsITreeView.DROP_BEFORE)
+ dropIndex--;
+ } else {
+ if (orientation == Ci.nsITreeView.DROP_AFTER)
+ dropIndex++;
+ }
+
+ this._engineStore.moveEngine(sourceEngine, dropIndex);
+ gEngineManagerDialog.showRestoreDefaults(true);
+
+ // Redraw, and adjust selection
+ this.invalidate();
+ this.selection.select(dropIndex);
+ },
+
+ selection: null,
+ getRowProperties: function(index) { return ""; },
+ getCellProperties: function(index, column) { return ""; },
+ getColumnProperties: function(column) { return ""; },
+ isContainer: function(index) { return false; },
+ isContainerOpen: function(index) { return false; },
+ isContainerEmpty: function(index) { return false; },
+ isSeparator: function(index) { return false; },
+ isSorted: function(index) { return false; },
+ getParentIndex: function(index) { return -1; },
+ hasNextSibling: function(parentIndex, index) { return false; },
+ getLevel: function(index) { return 0; },
+ getProgressMode: function(index, column) { },
+ getCellValue: function(index, column) { },
+ toggleOpenState: function(index) { },
+ cycleHeader: function(column) { },
+ selectionChanged: function() { },
+ cycleCell: function(row, column) { },
+ isEditable: function(index, column) { return false; },
+ isSelectable: function(index, column) { return false; },
+ setCellValue: function(index, column, value) { },
+ setCellText: function(index, column, value) { },
+ performAction: function(action) { },
+ performActionOnRow: function(action, index) { },
+ performActionOnCell: function(action, index, column) { }
+};
diff --git a/components/search/content/engineManager.xul b/components/search/content/engineManager.xul
new file mode 100644
index 0000000..1152ef8
--- /dev/null
+++ b/components/search/content/engineManager.xul
@@ -0,0 +1,93 @@
+<?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/"?>
+<?xml-stylesheet href="chrome://browser/skin/engineManager.css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://browser/locale/engineManager.dtd">
+
+<dialog id="engineManager"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ buttons="accept,cancel,extra2"
+ buttonlabelextra2="&restoreDefaults.label;"
+ buttonaccesskeyextra2="&restoreDefaults.accesskey;"
+ onload="gEngineManagerDialog.init();"
+ onunload="gEngineManagerDialog.destroy();"
+ ondialogaccept="gEngineManagerDialog.onOK();"
+ ondialogextra2="gEngineManagerDialog.onRestoreDefaults();"
+ title="&engineManager.title;"
+ style="&engineManager.style;"
+ persist="screenX screenY width height"
+ windowtype="Browser:SearchManager">
+
+ <script type="application/javascript"
+ src="chrome://browser/content/search/engineManager.js"/>
+
+ <commandset id="engineManagerCommandSet">
+ <command id="cmd_remove"
+ oncommand="gEngineManagerDialog.remove();"
+ disabled="true"/>
+ <command id="cmd_moveup"
+ oncommand="gEngineManagerDialog.bump(1);"
+ disabled="true"/>
+ <command id="cmd_movedown"
+ oncommand="gEngineManagerDialog.bump(-1);"
+ disabled="true"/>
+ <command id="cmd_editkeyword"
+ oncommand="gEngineManagerDialog.editKeyword().catch(Components.utils.reportError);"
+ disabled="true"/>
+ </commandset>
+
+ <keyset id="engineManagerKeyset">
+ <key id="delete" keycode="VK_DELETE" command="cmd_remove"/>
+ </keyset>
+
+ <stringbundleset id="engineManagerBundleset">
+ <stringbundle id="engineManagerBundle" src="chrome://browser/locale/engineManager.properties"/>
+ </stringbundleset>
+
+ <description>&engineManager.intro;</description>
+ <separator class="thin"/>
+ <hbox flex="1">
+ <tree id="engineList" flex="1" rows="10" hidecolumnpicker="true"
+ seltype="single" onselect="gEngineManagerDialog.onSelect();">
+ <treechildren id="engineChildren" flex="1"
+ ondragstart="onDragEngineStart(event);"/>
+ <treecols>
+ <treecol id="engineName" flex="4" label="&columnLabel.name;"/>
+ <treecol id="engineKeyword" flex="1" label="&columnLabel.keyword;"/>
+ </treecols>
+ </tree>
+ <vbox>
+ <spacer flex="1"/>
+ <button id="edit"
+ label="&edit.label;"
+ accesskey="&edit.accesskey;"
+ command="cmd_editkeyword"/>
+ <button id="up"
+ label="&up.label;"
+ accesskey="&up.accesskey;"
+ command="cmd_moveup"/>
+ <button id="down"
+ label="&dn.label;"
+ accesskey="&dn.accesskey;"
+ command="cmd_movedown"/>
+ <spacer flex="1"/>
+ <button id="remove"
+ label="&remove.label;"
+ accesskey="&remove.accesskey;"
+ command="cmd_remove"/>
+ </vbox>
+ </hbox>
+ <hbox>
+ <checkbox id="enableSuggest"
+ label="&enableSuggest.label;"
+ accesskey="&enableSuggest.accesskey;"/>
+ </hbox>
+ <hbox>
+ <label id="addEngines" class="text-link" value="&addEngine.label;"
+ onclick="if (event.button == 0) { gEngineManagerDialog.loadAddEngines(); }"/>
+ </hbox>
+</dialog>
diff --git a/components/search/content/search.xml b/components/search/content/search.xml
new file mode 100644
index 0000000..0c33b15
--- /dev/null
+++ b/components/search/content/search.xml
@@ -0,0 +1,837 @@
+<?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 % searchBarDTD SYSTEM "chrome://browser/locale/searchbar.dtd" >
+%searchBarDTD;
+<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
+%browserDTD;
+]>
+
+<bindings id="SearchBindings"
+ 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="searchbar">
+ <resources>
+ <stylesheet src="chrome://browser/content/search/searchbarBindings.css"/>
+ <stylesheet src="chrome://browser/skin/searchbar.css"/>
+ </resources>
+ <content>
+ <xul:stringbundle src="chrome://browser/locale/search.properties"
+ anonid="searchbar-stringbundle"/>
+
+ <xul:textbox class="searchbar-textbox"
+ anonid="searchbar-textbox"
+ type="autocomplete"
+ flex="1"
+ autocompletepopup="PopupAutoComplete"
+ autocompletesearch="search-autocomplete"
+ autocompletesearchparam="searchbar-history"
+ timeout="250"
+ maxrows="10"
+ completeselectedindex="true"
+ showcommentcolumn="true"
+ tabscrolling="true"
+ xbl:inherits="disabled,disableautocomplete,searchengine,src,newlines">
+ <xul:box>
+ <xul:button class="searchbar-engine-button"
+ type="menu"
+ anonid="searchbar-engine-button">
+ <xul:image class="searchbar-engine-image" xbl:inherits="src"/>
+ <xul:image class="searchbar-dropmarker-image"/>
+ <xul:menupopup class="searchbar-popup"
+ anonid="searchbar-popup">
+ <xul:menuseparator/>
+ <xul:menuitem class="open-engine-manager"
+ anonid="open-engine-manager"
+ label="&cmd_engineManager.label;"
+ oncommand="openManager(event);"/>
+ </xul:menupopup>
+ </xul:button>
+ </xul:box>
+ <xul:hbox class="search-go-container">
+ <xul:image class="search-go-button"
+ anonid="search-go-button"
+ onclick="handleSearchCommand(event);"
+ tooltiptext="&searchEndCap.label;"/>
+ </xul:hbox>
+ </xul:textbox>
+ </content>
+
+ <implementation implements="nsIObserver">
+ <constructor><![CDATA[
+ if (this.parentNode.parentNode.localName == "toolbarpaletteitem")
+ return;
+ // Make sure we rebuild the popup in onpopupshowing
+ this._needToBuildPopup = true;
+
+ var os =
+ Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ os.addObserver(this, "browser-search-engine-modified", false);
+
+ this._initialized = true;
+
+ this.searchService.init((function search_init_cb(aStatus) {
+ // Bail out if the binding has been destroyed
+ if (!this._initialized)
+ return;
+
+ if (Components.isSuccessCode(aStatus)) {
+ // Refresh the display (updating icon, etc)
+ this.updateDisplay();
+ } else {
+ Components.utils.reportError("Cannot initialize search service, bailing out: " + aStatus);
+ }
+ }).bind(this));
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ if (this._initialized) {
+ this._initialized = false;
+
+ var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+ os.removeObserver(this, "browser-search-engine-modified");
+ }
+
+ // Make sure to break the cycle from _textbox to us. Otherwise we leak
+ // the world. But make sure it's actually pointing to us.
+ if (this._textbox.mController.input == this)
+ this._textbox.mController.input = null;
+ ]]></destructor>
+
+ <field name="_stringBundle">document.getAnonymousElementByAttribute(this,
+ "anonid", "searchbar-stringbundle");</field>
+ <field name="_textbox">document.getAnonymousElementByAttribute(this,
+ "anonid", "searchbar-textbox");</field>
+ <field name="_popup">document.getAnonymousElementByAttribute(this,
+ "anonid", "searchbar-popup");</field>
+ <field name="_ss">null</field>
+ <field name="_engines">null</field>
+ <field name="FormHistory" readonly="true">
+ (Components.utils.import("resource://gre/modules/FormHistory.jsm", {})).FormHistory;
+ </field>
+
+ <property name="engines" readonly="true">
+ <getter><![CDATA[
+ if (!this._engines)
+ this._engines = this.searchService.getVisibleEngines();
+ return this._engines;
+ ]]></getter>
+ </property>
+
+ <field name="searchButton">document.getAnonymousElementByAttribute(this,
+ "anonid", "searchbar-engine-button");</field>
+
+ <property name="currentEngine">
+ <setter><![CDATA[
+ let ss = this.searchService;
+ ss.defaultEngine = ss.currentEngine = val;
+ return val;
+ ]]></setter>
+ <getter><![CDATA[
+ var currentEngine = this.searchService.currentEngine;
+ // Return a dummy engine if there is no currentEngine
+ return currentEngine || {name: "", uri: null};
+ ]]></getter>
+ </property>
+
+ <!-- textbox is used by sanitize.js to clear the undo history when
+ clearing form information. -->
+ <property name="textbox" readonly="true"
+ onget="return this._textbox;"/>
+
+ <property name="searchService" readonly="true">
+ <getter><![CDATA[
+ if (!this._ss) {
+ const nsIBSS = Components.interfaces.nsIBrowserSearchService;
+ this._ss =
+ Components.classes["@mozilla.org/browser/search-service;1"]
+ .getService(nsIBSS);
+ }
+ return this._ss;
+ ]]></getter>
+ </property>
+
+ <property name="value" onget="return this._textbox.value;"
+ onset="return this._textbox.value = val;"/>
+
+ <method name="focus">
+ <body><![CDATA[
+ this._textbox.focus();
+ ]]></body>
+ </method>
+
+ <method name="select">
+ <body><![CDATA[
+ this._textbox.select();
+ ]]></body>
+ </method>
+
+ <method name="observe">
+ <parameter name="aEngine"/>
+ <parameter name="aTopic"/>
+ <parameter name="aVerb"/>
+ <body><![CDATA[
+ if (aTopic == "browser-search-engine-modified") {
+ switch (aVerb) {
+ case "engine-removed":
+ this.offerNewEngine(aEngine);
+ break;
+ case "engine-added":
+ this.hideNewEngine(aEngine);
+ break;
+ case "engine-current":
+ // The current engine was changed. Rebuilding the menu appears to
+ // confuse its idea of whether it should be open when it's just
+ // been clicked, so we force it to close now.
+ this._popup.hidePopup();
+ break;
+ case "engine-changed":
+ // An engine was removed (or hidden) or added, or an icon was
+ // changed. Do nothing special.
+ }
+
+ // Make sure the engine list is refetched next time it's needed
+ this._engines = null;
+
+ // Rebuild the popup and update the display after any modification.
+ this.rebuildPopup();
+ this.updateDisplay();
+ }
+ ]]></body>
+ </method>
+
+ <!-- There are two seaprate lists of search engines, whose uses intersect
+ in this file. The search service (nsIBrowserSearchService and
+ nsSearchService.js) maintains a list of Engine objects which is used to
+ populate the searchbox list of available engines and to perform queries.
+ That list is accessed here via this.SearchService, and it's that sort of
+ Engine that is passed to this binding's observer as aEngine.
+
+ In addition, browser.js fills two lists of autodetected search engines
+ (browser.engines and browser.hiddenEngines) as properties of
+ mCurrentBrowser. Those lists contain unnamed JS objects of the form
+ { uri:, title:, icon: }, and that's what the searchbar uses to determine
+ whether to show any "Add <EngineName>" menu items in the drop-down.
+
+ The two types of engines are currently related by their identifying
+ titles (the Engine object's 'name'), although that may change; see bug
+ 335102. -->
+
+ <!-- If the engine that was just removed from the searchbox list was
+ autodetected on this page, move it to each browser's active list so it
+ will be offered to be added again. -->
+ <method name="offerNewEngine">
+ <parameter name="aEngine"/>
+ <body><![CDATA[
+ var allbrowsers = getBrowser().mPanelContainer.childNodes;
+ for (var tab = 0; tab < allbrowsers.length; tab++) {
+ var browser = getBrowser().getBrowserAtIndex(tab);
+ if (browser.hiddenEngines) {
+ // XXX This will need to be changed when engines are identified by
+ // URL rather than title; see bug 335102.
+ var removeTitle = aEngine.wrappedJSObject.name;
+ for (var i = 0; i < browser.hiddenEngines.length; i++) {
+ if (browser.hiddenEngines[i].title == removeTitle) {
+ if (!browser.engines)
+ browser.engines = [];
+ browser.engines.push(browser.hiddenEngines[i]);
+ browser.hiddenEngines.splice(i, 1);
+ break;
+ }
+ }
+ }
+ }
+ ]]></body>
+ </method>
+
+ <!-- If the engine that was just added to the searchbox list was
+ autodetected on this page, move it to each browser's hidden list so it is
+ no longer offered to be added. -->
+ <method name="hideNewEngine">
+ <parameter name="aEngine"/>
+ <body><![CDATA[
+ var allbrowsers = getBrowser().mPanelContainer.childNodes;
+ for (var tab = 0; tab < allbrowsers.length; tab++) {
+ var browser = getBrowser().getBrowserAtIndex(tab);
+ if (browser.engines) {
+ // XXX This will need to be changed when engines are identified by
+ // URL rather than title; see bug 335102.
+ var removeTitle = aEngine.wrappedJSObject.name;
+ for (var i = 0; i < browser.engines.length; i++) {
+ if (browser.engines[i].title == removeTitle) {
+ if (!browser.hiddenEngines)
+ browser.hiddenEngines = [];
+ browser.hiddenEngines.push(browser.engines[i]);
+ browser.engines.splice(i, 1);
+ break;
+ }
+ }
+ }
+ }
+ ]]></body>
+ </method>
+
+ <method name="setIcon">
+ <parameter name="element"/>
+ <parameter name="uri"/>
+ <body><![CDATA[
+ element.setAttribute("src", uri);
+ ]]></body>
+ </method>
+
+ <method name="updateDisplay">
+ <body><![CDATA[
+ var uri = this.currentEngine.iconURI;
+ this.setIcon(this, uri ? uri.spec : "");
+
+ var name = this.currentEngine.name;
+ var text = this._stringBundle.getFormattedString("searchtip", [name]);
+ this._textbox.placeholder = name;
+ this._textbox.label = text;
+ this._textbox.tooltipText = text;
+ ]]></body>
+ </method>
+
+ <!-- Rebuilds the dynamic portion of the popup menu (i.e., the menu items
+ for new search engines that can be added to the available list). This
+ is called each time the popup is shown.
+ -->
+ <method name="rebuildPopupDynamic">
+ <body><![CDATA[
+ // We might not have added the main popup items yet, do that first
+ // if needed.
+ if (this._needToBuildPopup)
+ this.rebuildPopup();
+
+ var popup = this._popup;
+ // Clear any addengine menuitems, including addengine-item entries and
+ // the addengine-separator. Work backward to avoid invalidating the
+ // indexes as items are removed.
+ var items = popup.childNodes;
+ for (var i = items.length - 1; i >= 0; i--) {
+ if (items[i].classList.contains("addengine-item") ||
+ items[i].classList.contains("addengine-separator"))
+ popup.removeChild(items[i]);
+ }
+
+ var addengines = getBrowser().mCurrentBrowser.engines;
+ if (addengines && addengines.length > 0) {
+ const kXULNS =
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ // Find the (first) separator in the remaining menu, or the first item
+ // if no separators are present.
+ var insertLocation = popup.firstChild;
+ while (insertLocation.nextSibling &&
+ insertLocation.localName != "menuseparator") {
+ insertLocation = insertLocation.nextSibling;
+ }
+ if (insertLocation.localName != "menuseparator")
+ insertLocation = popup.firstChild;
+
+ var separator = document.createElementNS(kXULNS, "menuseparator");
+ separator.setAttribute("class", "addengine-separator");
+ popup.insertBefore(separator, insertLocation);
+
+ // Insert the "add this engine" items.
+ for (var i = 0; i < addengines.length; i++) {
+ var menuitem = document.createElement("menuitem");
+ var engineInfo = addengines[i];
+ var labelStr =
+ this._stringBundle.getFormattedString("cmd_addFoundEngine",
+ [engineInfo.title]);
+ menuitem = document.createElementNS(kXULNS, "menuitem");
+ menuitem.setAttribute("class", "menuitem-iconic addengine-item");
+ menuitem.setAttribute("label", labelStr);
+ menuitem.setAttribute("tooltiptext", engineInfo.uri);
+ menuitem.setAttribute("uri", engineInfo.uri);
+ if (engineInfo.icon)
+ this.setIcon(menuitem, engineInfo.icon);
+ menuitem.setAttribute("title", engineInfo.title);
+ popup.insertBefore(menuitem, insertLocation);
+ }
+ }
+ ]]></body>
+ </method>
+
+ <!-- Rebuilds the list of visible search engines in the menu. Does not remove
+ or update any dynamic entries (i.e., "Add this engine" items) nor the
+ Manage Engines item. This is called by the observer when the list of
+ visible engines, or the currently selected engine, has changed.
+ -->
+ <method name="rebuildPopup">
+ <body><![CDATA[
+ var popup = this._popup;
+
+ // Clear the popup, down to the first separator
+ while (popup.firstChild && popup.firstChild.localName != "menuseparator")
+ popup.removeChild(popup.firstChild);
+
+ const kXULNS =
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ var engines = this.engines;
+ for (var i = engines.length - 1; i >= 0; --i) {
+ var menuitem = document.createElementNS(kXULNS, "menuitem");
+ var name = engines[i].name;
+ menuitem.setAttribute("label", name);
+ menuitem.setAttribute("id", name);
+ menuitem.setAttribute("class", "menuitem-iconic searchbar-engine-menuitem menuitem-with-favicon");
+ // Since this menu is rebuilt by the observer method whenever a new
+ // engine is selected, the "selected" attribute does not need to be
+ // explicitly cleared anywhere.
+ if (engines[i] == this.currentEngine)
+ menuitem.setAttribute("selected", "true");
+ var tooltip = this._stringBundle.getFormattedString("searchtip", [name]);
+ menuitem.setAttribute("tooltiptext", tooltip);
+ if (engines[i].iconURI)
+ this.setIcon(menuitem, engines[i].iconURI.spec);
+ popup.insertBefore(menuitem, popup.firstChild);
+ menuitem.engine = engines[i];
+ }
+
+ this._needToBuildPopup = false;
+ ]]></body>
+ </method>
+
+ <method name="openManager">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ var wm =
+ Components.classes["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Components.interfaces.nsIWindowMediator);
+
+ var window = wm.getMostRecentWindow("Browser:SearchManager");
+ if (window)
+ window.focus()
+ else {
+ setTimeout(function () {
+ openDialog("chrome://browser/content/search/engineManager.xul",
+ "_blank", "chrome,dialog,modal,centerscreen,resizable");
+ }, 0);
+ }
+ ]]></body>
+ </method>
+
+ <method name="selectEngine">
+ <parameter name="aEvent"/>
+ <parameter name="isNextEngine"/>
+ <body><![CDATA[
+ // Find the new index
+ var newIndex = this.engines.indexOf(this.currentEngine);
+ newIndex += isNextEngine ? 1 : -1;
+
+ if (newIndex >= 0 && newIndex < this.engines.length) {
+ this.currentEngine = this.engines[newIndex];
+ }
+
+ aEvent.preventDefault();
+ aEvent.stopPropagation();
+ ]]></body>
+ </method>
+
+ <method name="handleSearchCommand">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ var textBox = this._textbox;
+ var textValue = textBox.value;
+
+ var where = "current";
+ if (aEvent && aEvent.originalTarget.getAttribute("anonid") == "search-go-button") {
+ if (aEvent.button == 2)
+ return;
+ where = whereToOpenLink(aEvent, false, true);
+ }
+ else {
+ var newTabPref = textBox._prefBranch.getBoolPref("browser.search.openintab");
+ if ((aEvent && aEvent.altKey) ^ newTabPref)
+ where = "tab";
+ }
+
+ this.doSearch(textValue, where);
+ ]]></body>
+ </method>
+
+ <method name="doSearch">
+ <parameter name="aData"/>
+ <parameter name="aWhere"/>
+ <body><![CDATA[
+ var textBox = this._textbox;
+
+ // Save the current value in the form history
+ if (aData && !PrivateBrowsingUtils.isWindowPrivate(window)) {
+ this.FormHistory.update(
+ { op : "bump",
+ fieldname : textBox.getAttribute("autocompletesearchparam"),
+ value : aData },
+ { handleError : function(aError) {
+ Components.utils.reportError("Saving search to form history failed: " + aError.message);
+ }});
+ }
+
+ // null parameter below specifies HTML response for search
+ var submission = this.currentEngine.getSubmission(aData);
+ openUILinkIn(submission.uri.spec, aWhere, null, submission.postData);
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="command"><![CDATA[
+ const target = event.originalTarget;
+ if (target.engine) {
+ this.currentEngine = target.engine;
+ } else if (target.classList.contains("addengine-item")) {
+ var searchService =
+ Components.classes["@mozilla.org/browser/search-service;1"]
+ .getService(Components.interfaces.nsIBrowserSearchService);
+ // We only detect OpenSearch files
+ var type = Components.interfaces.nsISearchEngine.DATA_XML;
+ // Select the installed engine if the installation succeeds
+ var installCallback = {
+ onSuccess: engine => this.currentEngine = engine
+ }
+ searchService.addEngine(target.getAttribute("uri"), type,
+ target.getAttribute("src"), false,
+ installCallback);
+ }
+ else
+ return;
+
+ this.focus();
+ this.select();
+ ]]></handler>
+
+ <handler event="popupshowing" action="this.rebuildPopupDynamic();"/>
+
+ <handler event="DOMMouseScroll"
+ phase="capturing"
+ modifiers="accel"
+ action="this.selectEngine(event, (event.detail > 0));"/>
+
+ <handler event="focus">
+ <![CDATA[
+ // Speculatively connect to the current engine's search URI (and
+ // suggest URI, if different) to reduce request latency
+
+ const SUGGEST_TYPE = "application/x-suggestions+json";
+ var engine = this.currentEngine;
+ var connector =
+ Services.io.QueryInterface(Components.interfaces.nsISpeculativeConnect);
+ var searchURI = engine.getSubmission("dummy").uri;
+ let callbacks = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIWebNavigation)
+ .QueryInterface(Components.interfaces.nsILoadContext);
+ connector.speculativeConnect(searchURI, callbacks);
+
+ if (engine.supportsResponseType(SUGGEST_TYPE)) {
+ var suggestURI = engine.getSubmission("dummy", SUGGEST_TYPE).uri;
+ if (suggestURI.prePath != searchURI.prePath)
+ connector.speculativeConnect(suggestURI, callbacks);
+ }
+ ]]></handler>
+ </handlers>
+ </binding>
+
+ <binding id="searchbar-textbox"
+ extends="chrome://global/content/bindings/autocomplete.xml#autocomplete">
+ <implementation implements="nsIObserver">
+ <constructor><![CDATA[
+ const kXULNS =
+ "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ if (document.getBindingParent(this).parentNode.parentNode.localName ==
+ "toolbarpaletteitem")
+ return;
+
+ // Initialize fields
+ this._stringBundle = document.getBindingParent(this)._stringBundle;
+ this._prefBranch =
+ Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ this._suggestEnabled =
+ this._prefBranch.getBoolPref("browser.search.suggest.enabled");
+ this._clickSelectsAll =
+ this._prefBranch.getBoolPref("browser.urlbar.clickSelectsAll");
+
+ this.setAttribute("clickSelectsAll", this._clickSelectsAll);
+
+ // Add items to context menu and attach controller to handle them
+ var textBox = document.getAnonymousElementByAttribute(this,
+ "anonid", "textbox-input-box");
+ var cxmenu = document.getAnonymousElementByAttribute(textBox,
+ "anonid", "input-box-contextmenu");
+ var pasteAndSearch;
+ cxmenu.addEventListener("popupshowing", function() {
+ if (!pasteAndSearch)
+ return;
+ var controller = document.commandDispatcher.getControllerForCommand("cmd_paste");
+ var enabled = controller.isCommandEnabled("cmd_paste");
+ if (enabled)
+ pasteAndSearch.removeAttribute("disabled");
+ else
+ pasteAndSearch.setAttribute("disabled", "true");
+ }, false);
+
+ var element, label, akey;
+
+ element = document.createElementNS(kXULNS, "menuseparator");
+ cxmenu.appendChild(element);
+
+ var insertLocation = cxmenu.firstChild;
+ while (insertLocation.nextSibling &&
+ insertLocation.getAttribute("cmd") != "cmd_paste")
+ insertLocation = insertLocation.nextSibling;
+ if (insertLocation) {
+ element = document.createElementNS(kXULNS, "menuitem");
+ label = this._stringBundle.getString("cmd_pasteAndSearch");
+ element.setAttribute("label", label);
+ element.setAttribute("anonid", "paste-and-search");
+ element.setAttribute("oncommand",
+ "BrowserSearch.searchBar.select(); goDoCommand('cmd_paste'); BrowserSearch.searchBar.handleSearchCommand();");
+ cxmenu.insertBefore(element, insertLocation.nextSibling);
+ pasteAndSearch = element;
+ }
+
+ element = document.createElementNS(kXULNS, "menuitem");
+ label = this._stringBundle.getString("cmd_clearHistory");
+ akey = this._stringBundle.getString("cmd_clearHistory_accesskey");
+ element.setAttribute("label", label);
+ element.setAttribute("accesskey", akey);
+ element.setAttribute("cmd", "cmd_clearhistory");
+ cxmenu.appendChild(element);
+
+ element = document.createElementNS(kXULNS, "menuitem");
+ label = this._stringBundle.getString("cmd_showSuggestions");
+ akey = this._stringBundle.getString("cmd_showSuggestions_accesskey");
+ element.setAttribute("anonid", "toggle-suggest-item");
+ element.setAttribute("label", label);
+ element.setAttribute("accesskey", akey);
+ element.setAttribute("cmd", "cmd_togglesuggest");
+ element.setAttribute("type", "checkbox");
+ element.setAttribute("checked", this._suggestEnabled);
+ element.setAttribute("autocheck", "false");
+ this._suggestMenuItem = element;
+ cxmenu.appendChild(element);
+
+ this.controllers.appendController(this.searchbarController);
+
+ // Add observer for suggest preference
+ var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ prefs.addObserver("browser.search.suggest.enabled", this, false);
+ prefs.addObserver("browser.urlbar.clickSelectsAll", this, false);
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+ prefs.removeObserver("browser.search.suggest.enabled", this);
+ prefs.removeObserver("browser.urlbar.clickSelectsAll", this);
+
+ // Because XBL and the customize toolbar code interacts poorly,
+ // there may not be anything to remove here
+ try {
+ this.controllers.removeController(this.searchbarController);
+ } catch (ex) { }
+ ]]></destructor>
+
+ <field name="_stringBundle"/>
+ <field name="_prefBranch"/>
+ <field name="_suggestMenuItem"/>
+ <field name="_suggestEnabled"/>
+ <field name="_clickSelectsAll"/>
+
+ <!--
+ This overrides the searchParam property in autocomplete.xml. We're
+ hijacking this property as a vehicle for delivering the privacy
+ information about the window into the guts of nsSearchSuggestions.
+
+ Note that the setter is the same as the parent. We were not sure whether
+ we can override just the getter. If that proves to be the case, the setter
+ can be removed.
+ -->
+ <property name="searchParam"
+ onget="return this.getAttribute('autocompletesearchparam') +
+ (PrivateBrowsingUtils.isWindowPrivate(window) ? '|private' : '');"
+ onset="this.setAttribute('autocompletesearchparam', val); return val;"/>
+
+ <!--
+ This method overrides the autocomplete binding's openPopup (essentially
+ duplicating the logic from the autocomplete popup binding's
+ openAutocompletePopup method), modifying it so that the popup is aligned with
+ the inner textbox, but sized to not extend beyond the search bar border.
+ -->
+ <method name="openPopup">
+ <body><![CDATA[
+ var popup = this.popup;
+ if (!popup.mPopupOpen) {
+ // Initially the panel used for the searchbar (PopupAutoComplete
+ // in browser.xul) is hidden to avoid impacting startup / new
+ // window performance. The base binding's openPopup would normally
+ // call the overriden openAutocompletePopup in urlbarBindings.xml's
+ // browser-autocomplete-result-popup binding to unhide the popup,
+ // but since we're overriding openPopup we need to unhide the panel
+ // ourselves.
+ popup.hidden = false;
+
+ popup.mInput = this;
+ popup.view = this.controller.QueryInterface(Components.interfaces.nsITreeView);
+ popup.invalidate();
+
+ popup.showCommentColumn = this.showCommentColumn;
+ popup.showImageColumn = this.showImageColumn;
+
+ document.popupNode = null;
+
+ const isRTL = getComputedStyle(this, "").direction == "rtl";
+
+ var outerRect = this.getBoundingClientRect();
+ var innerRect = this.inputField.getBoundingClientRect();
+ if (isRTL) {
+ var width = innerRect.right - outerRect.left;
+ } else {
+ var width = outerRect.right - innerRect.left;
+ }
+ popup.setAttribute("width", width > 100 ? width : 100);
+
+ var yOffset = outerRect.bottom - innerRect.bottom;
+ popup.openPopup(this.inputField, "after_start", 0, yOffset, false, false);
+ }
+ ]]></body>
+ </method>
+
+ <method name="observe">
+ <parameter name="aSubject"/>
+ <parameter name="aTopic"/>
+ <parameter name="aData"/>
+ <body><![CDATA[
+ if (aTopic == "nsPref:changed") {
+ switch (aData) {
+ case "browser.search.suggest.enabled":
+ this._suggestEnabled = this._prefBranch.getBoolPref(aData);
+ this._suggestMenuItem.setAttribute("checked", this._suggestEnabled);
+ break;
+ case "browser.urlbar.clickSelectsAll":
+ this._clickSelectsAll = this._prefBranch.getBoolPref(aData);
+ this.setAttribute("clickSelectsAll", this._clickSelectsAll);
+ break;
+ }
+ }
+ ]]></body>
+ </method>
+
+ <method name="openSearch">
+ <body>
+ <![CDATA[
+ // Don't open search popup if history popup is open
+ if (!this.popupOpen) {
+ document.getBindingParent(this).searchButton.open = true;
+ return false;
+ }
+ return true;
+ ]]>
+ </body>
+ </method>
+
+ <!-- override |onTextEntered| in autocomplete.xml -->
+ <method name="onTextEntered">
+ <parameter name="aEvent"/>
+ <body><![CDATA[
+ var evt = aEvent || this.mEnterEvent;
+ document.getBindingParent(this).handleSearchCommand(evt);
+ this.mEnterEvent = null;
+ ]]></body>
+ </method>
+
+ <!-- nsIController -->
+ <field name="searchbarController" readonly="true"><![CDATA[({
+ _self: this,
+ supportsCommand: function(aCommand) {
+ return aCommand == "cmd_clearhistory" ||
+ aCommand == "cmd_togglesuggest";
+ },
+
+ isCommandEnabled: function(aCommand) {
+ return true;
+ },
+
+ doCommand: function (aCommand) {
+ switch (aCommand) {
+ case "cmd_clearhistory":
+ var param = this._self.getAttribute("autocompletesearchparam");
+
+ let searchBar = this._self.parentNode;
+
+ BrowserSearch.searchBar.FormHistory.update({ op : "remove", fieldname : param }, null);
+ this._self.value = "";
+ break;
+ case "cmd_togglesuggest":
+ // The pref observer will update _suggestEnabled and the menu
+ // checkmark.
+ this._self._prefBranch.setBoolPref("browser.search.suggest.enabled",
+ !this._self._suggestEnabled);
+ break;
+ default:
+ // do nothing with unrecognized command
+ }
+ }
+ })]]></field>
+ </implementation>
+
+ <handlers>
+ <handler event="keypress" keycode="VK_UP" modifiers="accel"
+ phase="capturing"
+ action="document.getBindingParent(this).selectEngine(event, false);"/>
+
+ <handler event="keypress" keycode="VK_DOWN" modifiers="accel"
+ phase="capturing"
+ action="document.getBindingParent(this).selectEngine(event, true);"/>
+
+ <handler event="keypress" keycode="VK_DOWN" modifiers="alt"
+ phase="capturing"
+ action="return this.openSearch();"/>
+
+ <handler event="keypress" keycode="VK_UP" modifiers="alt"
+ phase="capturing"
+ action="return this.openSearch();"/>
+
+#ifndef XP_MACOSX
+ <handler event="keypress" keycode="VK_F4"
+ phase="capturing"
+ action="return this.openSearch();"/>
+#endif
+
+ <handler event="dragover">
+ <![CDATA[
+ var types = event.dataTransfer.types;
+ if (types.contains("text/plain") || types.contains("text/x-moz-text-internal"))
+ event.preventDefault();
+ ]]>
+ </handler>
+
+ <handler event="drop">
+ <![CDATA[
+ var dataTransfer = event.dataTransfer;
+ var data = dataTransfer.getData("text/plain");
+ if (!data)
+ data = dataTransfer.getData("text/x-moz-text-internal");
+ if (data) {
+ event.preventDefault();
+ this.value = data;
+ this.onTextEntered(event);
+ }
+ ]]>
+ </handler>
+
+ </handlers>
+ </binding>
+</bindings>
diff --git a/components/search/content/searchbarBindings.css b/components/search/content/searchbarBindings.css
new file mode 100644
index 0000000..b20e215
--- /dev/null
+++ b/components/search/content/searchbarBindings.css
@@ -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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+.searchbar-textbox {
+ -moz-binding: url("chrome://browser/content/search/search.xml#searchbar-textbox");
+}
+
+.searchbar-engine-button {
+ -moz-user-focus: none;
+}
diff --git a/components/search/jar.mn b/components/search/jar.mn
new file mode 100644
index 0000000..e6c42f9
--- /dev/null
+++ b/components/search/jar.mn
@@ -0,0 +1,9 @@
+# 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/search/search.xml (content/search.xml)
+ content/browser/search/searchbarBindings.css (content/searchbarBindings.css)
+ content/browser/search/engineManager.xul (content/engineManager.xul)
+ content/browser/search/engineManager.js (content/engineManager.js)
diff --git a/components/search/moz.build b/components/search/moz.build
new file mode 100644
index 0000000..c97072b
--- /dev/null
+++ b/components/search/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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/components/sessionstore/DocumentUtils.jsm b/components/sessionstore/DocumentUtils.jsm
new file mode 100644
index 0000000..6b3f729
--- /dev/null
+++ b/components/sessionstore/DocumentUtils.jsm
@@ -0,0 +1,230 @@
+/* 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.EXPORTED_SYMBOLS = [ "DocumentUtils" ];
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource:///modules/sessionstore/XPathGenerator.jsm");
+
+this.DocumentUtils = {
+ /**
+ * Obtain form data for a DOMDocument instance.
+ *
+ * The returned object has 2 keys, "id" and "xpath". Each key holds an object
+ * which further defines form data.
+ *
+ * The "id" object maps element IDs to values. The "xpath" object maps the
+ * XPath of an element to its value.
+ *
+ * @param aDocument
+ * DOMDocument instance to obtain form data for.
+ * @return object
+ * Form data encoded in an object.
+ */
+ getFormData: function DocumentUtils_getFormData(aDocument) {
+ let formNodes = aDocument.evaluate(
+ XPathGenerator.restorableFormNodes,
+ aDocument,
+ XPathGenerator.resolveNS,
+ Ci.nsIDOMXPathResult.UNORDERED_NODE_ITERATOR_TYPE, null
+ );
+
+ let node;
+ let ret = {id: {}, xpath: {}};
+
+ // Limit the number of XPath expressions for performance reasons. See
+ // bug 477564.
+ const MAX_TRAVERSED_XPATHS = 100;
+ let generatedCount = 0;
+
+ while (node = formNodes.iterateNext()) {
+ let nId = node.id;
+ let hasDefaultValue = true;
+ let value;
+
+ // Only generate a limited number of XPath expressions for perf reasons
+ // (cf. bug 477564)
+ if (!nId && generatedCount > MAX_TRAVERSED_XPATHS) {
+ continue;
+ }
+
+ if (node instanceof Ci.nsIDOMHTMLInputElement ||
+ node instanceof Ci.nsIDOMHTMLTextAreaElement) {
+ switch (node.type) {
+ case "checkbox":
+ case "radio":
+ value = node.checked;
+ hasDefaultValue = value == node.defaultChecked;
+ break;
+ case "file":
+ value = { type: "file", fileList: node.mozGetFileNameArray() };
+ hasDefaultValue = !value.fileList.length;
+ break;
+ default: // text, textarea
+ value = node.value;
+ hasDefaultValue = value == node.defaultValue;
+ break;
+ }
+ } else if (!node.multiple) {
+ // <select>s without the multiple attribute are hard to determine the
+ // default value, so assume we don't have the default.
+ hasDefaultValue = false;
+ value = { selectedIndex: node.selectedIndex, value: node.value };
+ } else {
+ // <select>s with the multiple attribute are easier to determine the
+ // default value since each <option> has a defaultSelected
+ let options = Array.map(node.options, function(aOpt, aIx) {
+ let oSelected = aOpt.selected;
+ hasDefaultValue = hasDefaultValue && (oSelected == aOpt.defaultSelected);
+ return oSelected ? aOpt.value : -1;
+ });
+ value = options.filter(function(aIx) aIx !== -1);
+ }
+
+ // In order to reduce XPath generation (which is slow), we only save data
+ // for form fields that have been changed. (cf. bug 537289)
+ if (!hasDefaultValue) {
+ if (nId) {
+ ret.id[nId] = value;
+ } else {
+ generatedCount++;
+ ret.xpath[XPathGenerator.generate(node)] = value;
+ }
+ }
+ }
+
+ return ret;
+ },
+
+ /**
+ * Merges form data on a document from previously obtained data.
+ *
+ * This is the inverse of getFormData(). The data argument is the same object
+ * type which is returned by getFormData(): an object containing the keys
+ * "id" and "xpath" which are each objects mapping element identifiers to
+ * form values.
+ *
+ * Where the document has existing form data for an element, the value
+ * will be replaced. Where the document has a form element but no matching
+ * data in the passed object, the element is untouched.
+ *
+ * @param aDocument
+ * DOMDocument instance to which to restore form data.
+ * @param aData
+ * Object defining form data.
+ */
+ mergeFormData: function DocumentUtils_mergeFormData(aDocument, aData) {
+ if ("xpath" in aData) {
+ for each (let [xpath, value] in Iterator(aData.xpath)) {
+ let node = XPathGenerator.resolve(aDocument, xpath);
+
+ if (node) {
+ this.restoreFormValue(node, value, aDocument);
+ }
+ }
+ }
+
+ if ("id" in aData) {
+ for each (let [id, value] in Iterator(aData.id)) {
+ let node = aDocument.getElementById(id);
+
+ if (node) {
+ this.restoreFormValue(node, value, aDocument);
+ }
+ }
+ }
+ },
+
+ /**
+ * Low-level function to restore a form value to a DOMNode.
+ *
+ * If you want a higher-level interface, see mergeFormData().
+ *
+ * When the value is changed, the function will fire the appropriate DOM
+ * events.
+ *
+ * @param aNode
+ * DOMNode to set form value on.
+ * @param aValue
+ * Value to set form element to.
+ * @param aDocument [optional]
+ * DOMDocument node belongs to. If not defined, node.ownerDocument
+ * is used.
+ */
+ restoreFormValue: function DocumentUtils_restoreFormValue(aNode, aValue, aDocument) {
+ aDocument = aDocument || aNode.ownerDocument;
+
+ let eventType;
+
+ if (typeof aValue == "string" && aNode.type != "file") {
+ // Don't dispatch an input event if there is no change.
+ if (aNode.value == aValue) {
+ return;
+ }
+
+ aNode.value = aValue;
+ eventType = "input";
+ } else if (typeof aValue == "boolean") {
+ // Don't dispatch a change event for no change.
+ if (aNode.checked == aValue) {
+ return;
+ }
+
+ aNode.checked = aValue;
+ eventType = "change";
+ } else if (typeof aValue == "number") {
+ // handle select backwards compatibility, example { "#id" : index }
+ // We saved the value blindly since selects take more work to determine
+ // default values. So now we should check to avoid unnecessary events.
+ if (aNode.selectedIndex == aValue) {
+ return;
+ }
+
+ if (aValue < aNode.options.length) {
+ aNode.selectedIndex = aValue;
+ eventType = "change";
+ }
+ } else if (aValue && aValue.selectedIndex >= 0 && aValue.value) {
+ // handle select new format
+
+ // Don't dispatch a change event for no change
+ if (aNode.options[aNode.selectedIndex].value == aValue.value) {
+ return;
+ }
+
+ // find first option with matching aValue if possible
+ for (let i = 0; i < aNode.options.length; i++) {
+ if (aNode.options[i].value == aValue.value) {
+ aNode.selectedIndex = i;
+ break;
+ }
+ }
+ eventType = "change";
+ } else if (aValue && aValue.fileList && aValue.type == "file" &&
+ aNode.type == "file") {
+ aNode.mozSetFileNameArray(aValue.fileList, aValue.fileList.length);
+ eventType = "input";
+ } else if (aValue && typeof aValue.indexOf == "function" && aNode.options) {
+ Array.forEach(aNode.options, function(opt, index) {
+ // don't worry about malformed options with same values
+ opt.selected = aValue.indexOf(opt.value) > -1;
+
+ // Only fire the event here if this wasn't selected by default
+ if (!opt.defaultSelected) {
+ eventType = "change";
+ }
+ });
+ }
+
+ // Fire events for this node if applicable
+ if (eventType) {
+ let event = aDocument.createEvent("UIEvents");
+ event.initUIEvent(eventType, true, true, aDocument.defaultView, 0);
+ aNode.dispatchEvent(event);
+ }
+ }
+};
diff --git a/components/sessionstore/SessionStorage.jsm b/components/sessionstore/SessionStorage.jsm
new file mode 100644
index 0000000..64aef35
--- /dev/null
+++ b/components/sessionstore/SessionStorage.jsm
@@ -0,0 +1,165 @@
+/* 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.EXPORTED_SYMBOLS = ["SessionStorage"];
+
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
+ "resource:///modules/sessionstore/SessionStore.jsm");
+
+this.SessionStorage = {
+ /**
+ * Updates all sessionStorage "super cookies"
+ * @param aDocShell
+ * That tab's docshell (containing the sessionStorage)
+ * @param aFullData
+ * always return privacy sensitive data (use with care)
+ */
+ serialize: function ssto_serialize(aDocShell, aFullData) {
+ return DomStorage.read(aDocShell, aFullData);
+ },
+
+ /**
+ * Restores all sessionStorage "super cookies".
+ * @param aDocShell
+ * A tab's docshell (containing the sessionStorage)
+ * @param aStorageData
+ * Storage data to be restored
+ */
+ deserialize: function ssto_deserialize(aDocShell, aStorageData) {
+ DomStorage.write(aDocShell, aStorageData);
+ }
+};
+
+Object.freeze(SessionStorage);
+
+var DomStorage = {
+ /**
+ * Reads all session storage data from the given docShell.
+ * @param aDocShell
+ * A tab's docshell (containing the sessionStorage)
+ * @param aFullData
+ * Always return privacy sensitive data (use with care)
+ */
+ read: function DomStorage_read(aDocShell, aFullData) {
+ let data = {};
+ let isPinned = aDocShell.isAppTab;
+ let shistory = aDocShell.sessionHistory;
+
+ for (let i = 0; i < shistory.count; i++) {
+ let principal = History.getPrincipalForEntry(shistory, i, aDocShell);
+ if (!principal)
+ continue;
+
+ // Check if we're allowed to store sessionStorage data.
+ let isHTTPS = principal.URI && principal.URI.schemeIs("https");
+ if (aFullData || SessionStore.checkPrivacyLevel(isHTTPS, isPinned)) {
+ let origin = principal.extendedOrigin;
+
+ // Don't read a host twice.
+ if (!(origin in data)) {
+ let originData = this._readEntry(principal, aDocShell);
+ if (Object.keys(originData).length) {
+ data[origin] = originData;
+ }
+ }
+ }
+ }
+
+ return data;
+ },
+
+ /**
+ * Writes session storage data to the given tab.
+ * @param aDocShell
+ * A tab's docshell (containing the sessionStorage)
+ * @param aStorageData
+ * Storage data to be restored
+ */
+ write: function DomStorage_write(aDocShell, aStorageData) {
+ for (let [host, data] in Iterator(aStorageData)) {
+ let uri = Services.io.newURI(host, null, null);
+ let principal = Services.scriptSecurityManager.getDocShellCodebasePrincipal(uri, aDocShell);
+ let storageManager = aDocShell.QueryInterface(Components.interfaces.nsIDOMStorageManager);
+ let window = aDocShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+ .getInterface(Components.interfaces.nsIDOMWindow);
+
+ // There is no need to pass documentURI, it's only used to fill documentURI property of
+ // domstorage event, which in this case has no consumer. Prevention of events in case
+ // of missing documentURI will be solved in a followup bug to bug 600307.
+ try {
+ let storage = storageManager.createStorage(window, principal, "", aDocShell.usePrivateBrowsing);
+ } catch(e) {
+ Cu.reportError(e);
+ }
+
+ for (let [key, value] in Iterator(data)) {
+ try {
+ storage.setItem(key, value);
+ } catch (e) {
+ // throws e.g. for URIs that can't have sessionStorage
+ Cu.reportError(e);
+ }
+ }
+ }
+ },
+
+ /**
+ * Reads an entry in the session storage data contained in a tab's history.
+ * @param aURI
+ * That history entry uri
+ * @param aDocShell
+ * A tab's docshell (containing the sessionStorage)
+ */
+ _readEntry: function DomStorage_readEntry(aPrincipal, aDocShell) {
+ let hostData = {};
+ let storage;
+
+ try {
+ let storageManager = aDocShell.QueryInterface(Components.interfaces.nsIDOMStorageManager);
+ storage = storageManager.getStorage(aPrincipal);
+ } catch (e) {
+ // sessionStorage might throw if it's turned off, see bug 458954
+ }
+
+ if (storage && storage.length) {
+ for (let i = 0; i < storage.length; i++) {
+ try {
+ let key = storage.key(i);
+ hostData[key] = storage.getItem(key);
+ } catch (e) {
+ // This currently throws for secured items (cf. bug 442048).
+ }
+ }
+ }
+
+ return hostData;
+ }
+};
+
+var History = {
+ /**
+ * Returns a given history entry's URI.
+ * @param aHistory
+ * That tab's session history
+ * @param aIndex
+ * The history entry's index
+ * @param aDocShell
+ * That tab's docshell
+ */
+ getPrincipalForEntry: function History_getPrincipalForEntry(aHistory,
+ aIndex,
+ aDocShell) {
+ try {
+ return Services.scriptSecurityManager.getDocShellCodebasePrincipal(
+ aHistory.getEntryAtIndex(aIndex, false).URI, aDocShell);
+ } catch (e) {
+ // This might throw for some reason.
+ }
+ },
+};
diff --git a/components/sessionstore/SessionStore.jsm b/components/sessionstore/SessionStore.jsm
new file mode 100644
index 0000000..e19a578
--- /dev/null
+++ b/components/sessionstore/SessionStore.jsm
@@ -0,0 +1,4786 @@
+/* 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.EXPORTED_SYMBOLS = ["SessionStore"];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+
+const STATE_STOPPED = 0;
+const STATE_RUNNING = 1;
+const STATE_QUITTING = -1;
+
+const STATE_STOPPED_STR = "stopped";
+const STATE_RUNNING_STR = "running";
+
+const TAB_STATE_NEEDS_RESTORE = 1;
+const TAB_STATE_RESTORING = 2;
+
+const PRIVACY_NONE = 0;
+const PRIVACY_ENCRYPTED = 1;
+const PRIVACY_FULL = 2;
+
+const NOTIFY_WINDOWS_RESTORED = "sessionstore-windows-restored";
+const NOTIFY_BROWSER_STATE_RESTORED = "sessionstore-browser-state-restored";
+
+// Default maximum number of tabs to restore simultaneously. Controlled by
+// the browser.sessionstore.max_concurrent_tabs pref.
+const DEFAULT_MAX_CONCURRENT_TAB_RESTORES = 3;
+
+// global notifications observed
+const OBSERVING = [
+ "domwindowopened", "domwindowclosed",
+ "quit-application-requested", "quit-application-granted",
+ "browser-lastwindow-close-granted",
+ "quit-application", "browser:purge-session-history",
+ "browser:purge-domain-data"
+];
+
+// XUL Window properties to (re)store
+// Restored in restoreDimensions()
+const WINDOW_ATTRIBUTES = ["width", "height", "screenX", "screenY", "sizemode"];
+
+// Hideable window features to (re)store
+// Restored in restoreWindowFeatures()
+const WINDOW_HIDEABLE_FEATURES = [
+ "menubar", "toolbar", "locationbar", "personalbar", "statusbar", "scrollbars"
+];
+
+const MESSAGES = [
+ // The content script tells us that its form data (or that of one of its
+ // subframes) might have changed. This can be the contents or values of
+ // standard form fields or of ContentEditables.
+ "SessionStore:input",
+
+ // The content script has received a pageshow event. This happens when a
+ // page is loaded from bfcache without any network activity, i.e. when
+ // clicking the back or forward button.
+ "SessionStore:pageshow"
+];
+
+// These are tab events that we listen to.
+const TAB_EVENTS = [
+ "TabOpen", "TabClose", "TabSelect", "TabShow", "TabHide", "TabPinned",
+ "TabUnpinned"
+];
+
+#ifndef XP_WIN
+#define BROKEN_WM_Z_ORDER
+#endif
+
+Cu.import("resource://gre/modules/Services.jsm", this);
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+// debug.js adds NS_ASSERT. cf. bug 669196
+Cu.import("resource://gre/modules/debug.js", this);
+Cu.import("resource://gre/modules/osfile.jsm", this);
+Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm", this);
+Cu.import("resource://gre/modules/Promise.jsm", this);
+
+XPCOMUtils.defineLazyServiceGetter(this, "gSessionStartup",
+ "@mozilla.org/browser/sessionstartup;1", "nsISessionStartup");
+XPCOMUtils.defineLazyServiceGetter(this, "gScreenManager",
+ "@mozilla.org/gfx/screenmanager;1", "nsIScreenManager");
+
+// List of docShell capabilities to (re)store. These are automatically
+// retrieved from a given docShell if not already collected before.
+// This is made so they're automatically in sync with all nsIDocShell.allow*
+// properties.
+var gDocShellCapabilities = (function () {
+ let caps;
+
+ return docShell => {
+ if (!caps) {
+ let keys = Object.keys(docShell);
+ caps = keys.filter(k => k.startsWith("allow")).map(k => k.slice(5));
+ }
+
+ return caps;
+ };
+})();
+
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+
+#ifdef MOZ_DEVTOOLS
+XPCOMUtils.defineLazyModuleGetter(this, "ScratchpadManager",
+ "resource://devtools/client/scratchpad/scratchpad-manager.jsm");
+
+Object.defineProperty(this, "HUDService", {
+ get: function HUDService_getter() {
+ let devtools = Cu.import("resource://devtools/shared/Loader.jsm", {}).devtools;
+ return devtools.require("devtools/client/webconsole/hudservice").HUDService;
+ },
+ configurable: true,
+ enumerable: true
+});
+#endif
+
+XPCOMUtils.defineLazyModuleGetter(this, "DocumentUtils",
+ "resource:///modules/sessionstore/DocumentUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "SessionStorage",
+ "resource:///modules/sessionstore/SessionStorage.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "_SessionFile",
+ "resource:///modules/sessionstore/_SessionFile.jsm");
+
+function debug(aMsg) {
+ aMsg = ("SessionStore: " + aMsg).replace(/\S{80}/g, "$&\n");
+ Services.console.logStringMessage(aMsg);
+}
+
+this.SessionStore = {
+ get promiseInitialized() {
+ return SessionStoreInternal.promiseInitialized.promise;
+ },
+
+ get canRestoreLastSession() {
+ return SessionStoreInternal.canRestoreLastSession;
+ },
+
+ set canRestoreLastSession(val) {
+ SessionStoreInternal.canRestoreLastSession = val;
+ },
+
+ init: function ss_init(aWindow) {
+ return SessionStoreInternal.init(aWindow);
+ },
+
+ getBrowserState: function ss_getBrowserState() {
+ return SessionStoreInternal.getBrowserState();
+ },
+
+ setBrowserState: function ss_setBrowserState(aState) {
+ SessionStoreInternal.setBrowserState(aState);
+ },
+
+ getWindowState: function ss_getWindowState(aWindow) {
+ return SessionStoreInternal.getWindowState(aWindow);
+ },
+
+ setWindowState: function ss_setWindowState(aWindow, aState, aOverwrite) {
+ SessionStoreInternal.setWindowState(aWindow, aState, aOverwrite);
+ },
+
+ getTabState: function ss_getTabState(aTab) {
+ return SessionStoreInternal.getTabState(aTab);
+ },
+
+ setTabState: function ss_setTabState(aTab, aState) {
+ SessionStoreInternal.setTabState(aTab, aState);
+ },
+
+ duplicateTab: function ss_duplicateTab(aWindow, aTab, aDelta) {
+ return SessionStoreInternal.duplicateTab(aWindow, aTab, aDelta);
+ },
+
+ getClosedTabCount: function ss_getClosedTabCount(aWindow) {
+ return SessionStoreInternal.getClosedTabCount(aWindow);
+ },
+
+ getClosedTabData: function ss_getClosedTabDataAt(aWindow) {
+ return SessionStoreInternal.getClosedTabData(aWindow);
+ },
+
+ undoCloseTab: function ss_undoCloseTab(aWindow, aIndex) {
+ return SessionStoreInternal.undoCloseTab(aWindow, aIndex);
+ },
+
+ forgetClosedTab: function ss_forgetClosedTab(aWindow, aIndex) {
+ return SessionStoreInternal.forgetClosedTab(aWindow, aIndex);
+ },
+
+ getClosedWindowCount: function ss_getClosedWindowCount() {
+ return SessionStoreInternal.getClosedWindowCount();
+ },
+
+ getClosedWindowData: function ss_getClosedWindowData() {
+ return SessionStoreInternal.getClosedWindowData();
+ },
+
+ undoCloseWindow: function ss_undoCloseWindow(aIndex) {
+ return SessionStoreInternal.undoCloseWindow(aIndex);
+ },
+
+ forgetClosedWindow: function ss_forgetClosedWindow(aIndex) {
+ return SessionStoreInternal.forgetClosedWindow(aIndex);
+ },
+
+ getWindowValue: function ss_getWindowValue(aWindow, aKey) {
+ return SessionStoreInternal.getWindowValue(aWindow, aKey);
+ },
+
+ setWindowValue: function ss_setWindowValue(aWindow, aKey, aStringValue) {
+ SessionStoreInternal.setWindowValue(aWindow, aKey, aStringValue);
+ },
+
+ deleteWindowValue: function ss_deleteWindowValue(aWindow, aKey) {
+ SessionStoreInternal.deleteWindowValue(aWindow, aKey);
+ },
+
+ getTabValue: function ss_getTabValue(aTab, aKey) {
+ return SessionStoreInternal.getTabValue(aTab, aKey);
+ },
+
+ setTabValue: function ss_setTabValue(aTab, aKey, aStringValue) {
+ SessionStoreInternal.setTabValue(aTab, aKey, aStringValue);
+ },
+
+ deleteTabValue: function ss_deleteTabValue(aTab, aKey) {
+ SessionStoreInternal.deleteTabValue(aTab, aKey);
+ },
+
+ persistTabAttribute: function ss_persistTabAttribute(aName) {
+ SessionStoreInternal.persistTabAttribute(aName);
+ },
+
+ restoreLastSession: function ss_restoreLastSession() {
+ SessionStoreInternal.restoreLastSession();
+ },
+
+ checkPrivacyLevel: function ss_checkPrivacyLevel(aIsHTTPS, aUseDefaultPref) {
+ return SessionStoreInternal.checkPrivacyLevel(aIsHTTPS, aUseDefaultPref);
+ }
+};
+
+// Freeze the SessionStore object. We don't want anyone to modify it.
+Object.freeze(SessionStore);
+
+var SessionStoreInternal = {
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsIDOMEventListener,
+ Ci.nsIObserver,
+ Ci.nsISupportsWeakReference
+ ]),
+
+ // set default load state
+ _loadState: STATE_STOPPED,
+
+ // During the initial restore and setBrowserState calls tracks the number of
+ // windows yet to be restored
+ _restoreCount: -1,
+
+ // whether a setBrowserState call is in progress
+ _browserSetState: false,
+
+ // time in milliseconds (Date.now()) when the session was last written to file
+ _lastSaveTime: 0,
+
+ // time in milliseconds when the session was started (saved across sessions),
+ // defaults to now if no session was restored or timestamp doesn't exist
+ _sessionStartTime: Date.now(),
+
+ // states for all currently opened windows
+ _windows: {},
+
+ // internal states for all open windows (data we need to associate,
+ // but not write to disk)
+ _internalWindows: {},
+
+ // states for all recently closed windows
+ _closedWindows: [],
+
+ // not-"dirty" windows usually don't need to have their data updated
+ _dirtyWindows: {},
+
+ // collection of session states yet to be restored
+ _statesToRestore: {},
+
+ // counts the number of crashes since the last clean start
+ _recentCrashes: 0,
+
+ // whether the last window was closed and should be restored
+ _restoreLastWindow: false,
+
+ // number of tabs currently restoring
+ _tabsRestoringCount: 0,
+
+ // max number of tabs to restore concurrently
+ _maxConcurrentTabRestores: DEFAULT_MAX_CONCURRENT_TAB_RESTORES,
+
+ // whether restored tabs load cached versions or force a reload
+ _cacheBehavior: 0,
+
+ // The state from the previous session (after restoring pinned tabs). This
+ // state is persisted and passed through to the next session during an app
+ // restart to make the third party add-on warning not trash the deferred
+ // session
+ _lastSessionState: null,
+
+ // When starting Firefox with a single private window, this is the place
+ // where we keep the session we actually wanted to restore in case the user
+ // decides to later open a non-private window as well.
+ _deferredInitialState: null,
+
+ // A promise resolved once initialization is complete
+ _promiseInitialization: Promise.defer(),
+
+ // Whether session has been initialized
+ _sessionInitialized: false,
+
+ // True if session store is disabled by multi-process browsing.
+ // See bug 516755.
+ _disabledForMultiProcess: false,
+
+ // The original "sessionstore.resume_session_once" preference value before it
+ // was modified by saveState. saveState will set the
+ // "sessionstore.resume_session_once" to true when the
+ // the "sessionstore.resume_from_crash" preference is false (crash recovery
+ // is disabled) so that pinned tabs will be restored in the case of a
+ // crash. This variable is used to restore the original value so the
+ // previous session is not always restored when
+ // "sessionstore.resume_from_crash" is true.
+ _resume_session_once_on_shutdown: null,
+
+ /**
+ * A promise fulfilled once initialization is complete.
+ */
+ get promiseInitialized() {
+ return this._promiseInitialization;
+ },
+
+ /* ........ Public Getters .............. */
+ get canRestoreLastSession() {
+ return this._lastSessionState;
+ },
+
+ set canRestoreLastSession(val) {
+ this._lastSessionState = null;
+ },
+
+ /* ........ Global Event Handlers .............. */
+
+ /**
+ * Initialize the component
+ */
+ initService: function ssi_initService() {
+ if (this._sessionInitialized) {
+ return;
+ }
+ OBSERVING.forEach(function(aTopic) {
+ Services.obs.addObserver(this, aTopic, true);
+ }, this);
+
+ this._initPrefs();
+
+ this._disabledForMultiProcess = false;
+
+ // this pref is only read at startup, so no need to observe it
+ this._sessionhistory_max_entries =
+ this._prefBranch.getIntPref("sessionhistory.max_entries");
+
+ gSessionStartup.onceInitialized.then(
+ this.initSession.bind(this)
+ );
+ },
+
+ initSession: function ssi_initSession() {
+ let ss = gSessionStartup;
+ try {
+ if (ss.doRestore() ||
+ ss.sessionType == Ci.nsISessionStartup.DEFER_SESSION)
+ this._initialState = ss.state;
+ }
+ catch(ex) { dump(ex + "\n"); } // no state to restore, which is ok
+
+ if (this._initialState) {
+ try {
+ // If we're doing a DEFERRED session, then we want to pull pinned tabs
+ // out so they can be restored.
+ if (ss.sessionType == Ci.nsISessionStartup.DEFER_SESSION) {
+ let [iniState, remainingState] = this._prepDataForDeferredRestore(this._initialState);
+ // If we have a iniState with windows, that means that we have windows
+ // with app tabs to restore.
+ if (iniState.windows.length)
+ this._initialState = iniState;
+ else
+ this._initialState = null;
+ if (remainingState.windows.length)
+ this._lastSessionState = remainingState;
+ }
+ else {
+ // Get the last deferred session in case the user still wants to
+ // restore it
+ this._lastSessionState = this._initialState.lastSessionState;
+
+ let lastSessionCrashed =
+ this._initialState.session && this._initialState.session.state &&
+ this._initialState.session.state == STATE_RUNNING_STR;
+ if (lastSessionCrashed) {
+ this._recentCrashes = (this._initialState.session &&
+ this._initialState.session.recentCrashes || 0) + 1;
+
+ if (this._needsRestorePage(this._initialState, this._recentCrashes)) {
+ // replace the crashed session with a restore-page-only session
+ let pageData = {
+ url: "about:sessionrestore",
+ formdata: {
+ id: { "sessionData": this._initialState },
+ xpath: {}
+ }
+ };
+ this._initialState = { windows: [{ tabs: [{ entries: [pageData] }] }] };
+ }
+ }
+
+ // Load the session start time from the previous state
+ this._sessionStartTime = this._initialState.session &&
+ this._initialState.session.startTime ||
+ this._sessionStartTime;
+
+ // make sure that at least the first window doesn't have anything hidden
+ delete this._initialState.windows[0].hidden;
+ // Since nothing is hidden in the first window, it cannot be a popup
+ delete this._initialState.windows[0].isPopup;
+ // We don't want to minimize and then open a window at startup.
+ if (this._initialState.windows[0].sizemode == "minimized")
+ this._initialState.windows[0].sizemode = "normal";
+ // clear any lastSessionWindowID attributes since those don't matter
+ // during normal restore
+ this._initialState.windows.forEach(function(aWindow) {
+ delete aWindow.__lastSessionWindowID;
+ });
+ }
+ }
+ catch (ex) { debug("The session file is invalid: " + ex); }
+ }
+
+ // A Lazy getter for the sessionstore.js backup promise.
+ XPCOMUtils.defineLazyGetter(this, "_backupSessionFileOnce", function () {
+ return _SessionFile.createBackupCopy();
+ });
+
+ // at this point, we've as good as resumed the session, so we can
+ // clear the resume_session_once flag, if it's set
+ if (this._loadState != STATE_QUITTING &&
+ this._prefBranch.getBoolPref("sessionstore.resume_session_once"))
+ this._prefBranch.setBoolPref("sessionstore.resume_session_once", false);
+
+ this._initEncoding();
+
+ // Session is ready.
+ this._sessionInitialized = true;
+ this._promiseInitialization.resolve();
+ },
+
+ _initEncoding : function ssi_initEncoding() {
+ // The (UTF-8) encoder used to write to files.
+ XPCOMUtils.defineLazyGetter(this, "_writeFileEncoder", function () {
+ return new TextEncoder();
+ });
+ },
+
+ _initPrefs : function() {
+ XPCOMUtils.defineLazyGetter(this, "_prefBranch", function () {
+ return Services.prefs.getBranch("browser.");
+ });
+
+ // minimal interval between two save operations (in milliseconds)
+ XPCOMUtils.defineLazyGetter(this, "_interval", function () {
+ // used often, so caching/observing instead of fetching on-demand
+ this._prefBranch.addObserver("sessionstore.interval", this, true);
+ return this._prefBranch.getIntPref("sessionstore.interval");
+ });
+
+ // when crash recovery is disabled, session data is not written to disk
+ XPCOMUtils.defineLazyGetter(this, "_resume_from_crash", function () {
+ // get crash recovery state from prefs and allow for proper reaction to state changes
+ this._prefBranch.addObserver("sessionstore.resume_from_crash", this, true);
+ return this._prefBranch.getBoolPref("sessionstore.resume_from_crash");
+ });
+
+ this._max_tabs_undo = this._prefBranch.getIntPref("sessionstore.max_tabs_undo");
+ this._prefBranch.addObserver("sessionstore.max_tabs_undo", this, true);
+
+ this._max_windows_undo = this._prefBranch.getIntPref("sessionstore.max_windows_undo");
+ this._prefBranch.addObserver("sessionstore.max_windows_undo", this, true);
+
+ // Straight-up collect the following one-time prefs
+ this._maxConcurrentTabRestores =
+ Services.prefs.getIntPref("browser.sessionstore.max_concurrent_tabs");
+ // ensure a sane value for concurrency, ignore and set default otherwise
+ if (this._maxConcurrentTabRestores < 1 || this._maxConcurrentTabRestores > 10) {
+ this._maxConcurrentTabRestores = DEFAULT_MAX_CONCURRENT_TAB_RESTORES;
+ }
+ this._cacheBehavior =
+ Services.prefs.getIntPref("browser.sessionstore.cache_behavior");
+
+ },
+
+ _initWindow: function ssi_initWindow(aWindow) {
+ if (aWindow) {
+ this.onLoad(aWindow);
+ } else if (this._loadState == STATE_STOPPED) {
+ // If init is being called with a null window, it's possible that we
+ // just want to tell sessionstore that a session is live (as is the case
+ // with starting Firefox with -private, for example; see bug 568816),
+ // so we should mark the load state as running to make sure that
+ // things like setBrowserState calls will succeed in restoring the session.
+ this._loadState = STATE_RUNNING;
+ }
+ },
+
+ /**
+ * Start tracking a window.
+ *
+ * This function also initializes the component if it is not
+ * initialized yet.
+ */
+ init: function ssi_init(aWindow) {
+ let self = this;
+ this.initService();
+ return this._promiseInitialization.promise.then(
+ function onSuccess() {
+ self._initWindow(aWindow);
+ }
+ );
+ },
+
+ /**
+ * Called on application shutdown, after notifications:
+ * quit-application-granted, quit-application
+ */
+ _uninit: function ssi_uninit() {
+ // save all data for session resuming
+ if (this._sessionInitialized)
+ this.saveState(true);
+
+ // clear out priority queue in case it's still holding refs
+ TabRestoreQueue.reset();
+
+ // Make sure to break our cycle with the save timer
+ if (this._saveTimer) {
+ this._saveTimer.cancel();
+ this._saveTimer = null;
+ }
+ },
+
+ /**
+ * Handle notifications
+ */
+ observe: function ssi_observe(aSubject, aTopic, aData) {
+ if (this._disabledForMultiProcess)
+ return;
+
+ switch (aTopic) {
+ case "domwindowopened": // catch new windows
+ this.onOpen(aSubject);
+ break;
+ case "domwindowclosed": // catch closed windows
+ this.onClose(aSubject);
+ break;
+ case "quit-application-requested":
+ this.onQuitApplicationRequested();
+ break;
+ case "quit-application-granted":
+ this.onQuitApplicationGranted();
+ break;
+ case "browser-lastwindow-close-granted":
+ this.onLastWindowCloseGranted();
+ break;
+ case "quit-application":
+ this.onQuitApplication(aData);
+ break;
+ case "browser:purge-session-history": // catch sanitization
+ this.onPurgeSessionHistory();
+ break;
+ case "browser:purge-domain-data":
+ this.onPurgeDomainData(aData);
+ break;
+ case "nsPref:changed": // catch pref changes
+ this.onPrefChange(aData);
+ break;
+ case "timer-callback": // timer call back for delayed saving
+ this.onTimerCallback();
+ break;
+ }
+ },
+
+ /**
+ * This method handles incoming messages sent by the session store content
+ * script and thus enables communication with OOP tabs.
+ */
+ receiveMessage: function ssi_receiveMessage(aMessage) {
+ var browser = aMessage.target;
+ var win = browser.ownerDocument.defaultView;
+
+ switch (aMessage.name) {
+ case "SessionStore:pageshow":
+ this.onTabLoad(win, browser);
+ break;
+ case "SessionStore:input":
+ this.onTabInput(win, browser);
+ break;
+ default:
+ debug("received unknown message '" + aMessage.name + "'");
+ break;
+ }
+
+ this._clearRestoringWindows();
+ },
+
+ /* ........ Window Event Handlers .............. */
+
+ /**
+ * Implement nsIDOMEventListener for handling various window and tab events
+ */
+ handleEvent: function ssi_handleEvent(aEvent) {
+ if (this._disabledForMultiProcess)
+ return;
+
+ var win = aEvent.currentTarget.ownerDocument.defaultView;
+ switch (aEvent.type) {
+ case "load":
+ // If __SS_restore_data is set, then we need to restore the document
+ // (form data, scrolling, etc.). This will only happen when a tab is
+ // first restored.
+ let browser = aEvent.currentTarget;
+ if (browser.__SS_restore_data)
+ this.restoreDocument(win, browser, aEvent);
+ this.onTabLoad(win, browser);
+ break;
+ case "TabOpen":
+ this.onTabAdd(win, aEvent.originalTarget);
+ break;
+ case "TabClose":
+ // aEvent.detail determines if the tab was closed by moving to a different window
+ if (!aEvent.detail)
+ this.onTabClose(win, aEvent.originalTarget);
+ this.onTabRemove(win, aEvent.originalTarget);
+ break;
+ case "TabSelect":
+ this.onTabSelect(win);
+ break;
+ case "TabShow":
+ this.onTabShow(win, aEvent.originalTarget);
+ break;
+ case "TabHide":
+ this.onTabHide(win, aEvent.originalTarget);
+ break;
+ case "TabPinned":
+ case "TabUnpinned":
+ this.saveStateDelayed(win);
+ break;
+ }
+
+ this._clearRestoringWindows();
+ },
+
+ /**
+ * If it's the first window load since app start...
+ * - determine if we're reloading after a crash or a forced-restart
+ * - restore window state
+ * - restart downloads
+ * Set up event listeners for this window's tabs
+ * @param aWindow
+ * Window reference
+ */
+ onLoad: function ssi_onLoad(aWindow) {
+ // return if window has already been initialized
+ if (aWindow && aWindow.__SSi && this._windows[aWindow.__SSi])
+ return;
+
+ // ignore non-browser windows and windows opened while shutting down
+ if (aWindow.document.documentElement.getAttribute("windowtype") != "navigator:browser" ||
+ this._loadState == STATE_QUITTING)
+ return;
+
+ // assign it a unique identifier (timestamp)
+ aWindow.__SSi = "window" + Date.now();
+
+ // and create its data object
+ this._windows[aWindow.__SSi] = { tabs: [], selected: 0, _closedTabs: [], busy: false };
+
+ // and create its internal data object
+ this._internalWindows[aWindow.__SSi] = { hosts: {} }
+
+ let isPrivateWindow = false;
+ if (PrivateBrowsingUtils.isWindowPrivate(aWindow))
+ this._windows[aWindow.__SSi].isPrivate = isPrivateWindow = true;
+ if (!this._isWindowLoaded(aWindow))
+ this._windows[aWindow.__SSi]._restoring = true;
+ if (!aWindow.toolbar.visible)
+ this._windows[aWindow.__SSi].isPopup = true;
+
+ // perform additional initialization when the first window is loading
+ if (this._loadState == STATE_STOPPED) {
+ this._loadState = STATE_RUNNING;
+ this._lastSaveTime = Date.now();
+
+ // restore a crashed session resp. resume the last session if requested
+ if (this._initialState) {
+ if (isPrivateWindow) {
+ // We're starting with a single private window. Save the state we
+ // actually wanted to restore so that we can do it later in case
+ // the user opens another, non-private window.
+ this._deferredInitialState = gSessionStartup.state;
+ delete this._initialState;
+
+ // Nothing to restore now, notify observers things are complete.
+ Services.obs.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
+ } else {
+ // make sure that the restored tabs are first in the window
+ this._initialState._firstTabs = true;
+ this._restoreCount = this._initialState.windows ? this._initialState.windows.length : 0;
+ this.restoreWindow(aWindow, this._initialState,
+ this._isCmdLineEmpty(aWindow, this._initialState));
+ delete this._initialState;
+
+ // _loadState changed from "stopped" to "running"
+ // force a save operation so that crashes happening during startup are correctly counted
+ this.saveState(true);
+ }
+ }
+ else {
+ // Nothing to restore, notify observers things are complete.
+ Services.obs.notifyObservers(null, NOTIFY_WINDOWS_RESTORED, "");
+
+ // the next delayed save request should execute immediately
+ this._lastSaveTime -= this._interval;
+ }
+ }
+ // this window was opened by _openWindowWithState
+ else if (!this._isWindowLoaded(aWindow)) {
+ let followUp = this._statesToRestore[aWindow.__SS_restoreID].windows.length == 1;
+ this.restoreWindow(aWindow, this._statesToRestore[aWindow.__SS_restoreID], true, followUp);
+ }
+ // The user opened another, non-private window after starting up with
+ // a single private one. Let's restore the session we actually wanted to
+ // restore at startup.
+ else if (this._deferredInitialState && !isPrivateWindow &&
+ aWindow.toolbar.visible) {
+
+ this._deferredInitialState._firstTabs = true;
+ this._restoreCount = this._deferredInitialState.windows ?
+ this._deferredInitialState.windows.length : 0;
+ this.restoreWindow(aWindow, this._deferredInitialState, false);
+ this._deferredInitialState = null;
+ }
+ else if (this._restoreLastWindow && aWindow.toolbar.visible &&
+ this._closedWindows.length && !isPrivateWindow) {
+
+ // default to the most-recently closed window
+ // don't use popup windows
+ let closedWindowState = null;
+ let closedWindowIndex;
+ for (let i = 0; i < this._closedWindows.length; i++) {
+ // Take the first non-popup, point our object at it, and break out.
+ if (!this._closedWindows[i].isPopup) {
+ closedWindowState = this._closedWindows[i];
+ closedWindowIndex = i;
+ break;
+ }
+ }
+
+ if (closedWindowState) {
+ let newWindowState;
+#ifndef XP_MACOSX
+ if (!this._doResumeSession()) {
+#endif
+ // We want to split the window up into pinned tabs and unpinned tabs.
+ // Pinned tabs should be restored. If there are any remaining tabs,
+ // they should be added back to _closedWindows.
+ // We'll cheat a little bit and reuse _prepDataForDeferredRestore
+ // even though it wasn't built exactly for this.
+ let [appTabsState, normalTabsState] =
+ this._prepDataForDeferredRestore({ windows: [closedWindowState] });
+
+ // These are our pinned tabs, which we should restore
+ if (appTabsState.windows.length) {
+ newWindowState = appTabsState.windows[0];
+ delete newWindowState.__lastSessionWindowID;
+ }
+
+ // In case there were no unpinned tabs, remove the window from _closedWindows
+ if (!normalTabsState.windows.length) {
+ this._closedWindows.splice(closedWindowIndex, 1);
+ }
+ // Or update _closedWindows with the modified state
+ else {
+ delete normalTabsState.windows[0].__lastSessionWindowID;
+ this._closedWindows[closedWindowIndex] = normalTabsState.windows[0];
+ }
+#ifndef XP_MACOSX
+ }
+ else {
+ // If we're just restoring the window, make sure it gets removed from
+ // _closedWindows.
+ this._closedWindows.splice(closedWindowIndex, 1);
+ newWindowState = closedWindowState;
+ delete newWindowState.hidden;
+ }
+#endif
+ if (newWindowState) {
+ // Ensure that the window state isn't hidden
+ this._restoreCount = 1;
+ let state = { windows: [newWindowState] };
+ this.restoreWindow(aWindow, state, this._isCmdLineEmpty(aWindow, state));
+ }
+ }
+ // we actually restored the session just now.
+ this._prefBranch.setBoolPref("sessionstore.resume_session_once", false);
+ }
+ if (this._restoreLastWindow && aWindow.toolbar.visible) {
+ // always reset (if not a popup window)
+ // we don't want to restore a window directly after, for example,
+ // undoCloseWindow was executed.
+ this._restoreLastWindow = false;
+ }
+
+ var tabbrowser = aWindow.gBrowser;
+
+ // add tab change listeners to all already existing tabs
+ for (let i = 0; i < tabbrowser.tabs.length; i++) {
+ this.onTabAdd(aWindow, tabbrowser.tabs[i], true);
+ }
+ // notification of tab add/remove/selection/show/hide
+ TAB_EVENTS.forEach(function(aEvent) {
+ tabbrowser.tabContainer.addEventListener(aEvent, this, true);
+ }, this);
+ },
+
+ /**
+ * On window open
+ * @param aWindow
+ * Window reference
+ */
+ onOpen: function ssi_onOpen(aWindow) {
+ var _this = this;
+ aWindow.addEventListener("load", function(aEvent) {
+ aEvent.currentTarget.removeEventListener("load", arguments.callee, false);
+ _this.onLoad(aEvent.currentTarget);
+ }, false);
+ return;
+ },
+
+ /**
+ * On window close...
+ * - remove event listeners from tabs
+ * - save all window data
+ * @param aWindow
+ * Window reference
+ */
+ onClose: function ssi_onClose(aWindow) {
+ // this window was about to be restored - conserve its original data, if any
+ let isFullyLoaded = this._isWindowLoaded(aWindow);
+ if (!isFullyLoaded) {
+ if (!aWindow.__SSi)
+ aWindow.__SSi = "window" + Date.now();
+ this._windows[aWindow.__SSi] = this._statesToRestore[aWindow.__SS_restoreID];
+ delete this._statesToRestore[aWindow.__SS_restoreID];
+ delete aWindow.__SS_restoreID;
+ }
+
+ // ignore windows not tracked by SessionStore
+ if (!aWindow.__SSi || !this._windows[aWindow.__SSi]) {
+ return;
+ }
+
+ // notify that the session store will stop tracking this window so that
+ // extensions can store any data about this window in session store before
+ // that's not possible anymore
+ let event = aWindow.document.createEvent("Events");
+ event.initEvent("SSWindowClosing", true, false);
+ aWindow.dispatchEvent(event);
+
+ if (this.windowToFocus && this.windowToFocus == aWindow) {
+ delete this.windowToFocus;
+ }
+
+ var tabbrowser = aWindow.gBrowser;
+
+ TAB_EVENTS.forEach(function(aEvent) {
+ tabbrowser.tabContainer.removeEventListener(aEvent, this, true);
+ }, this);
+
+ // remove the progress listener for this window
+ tabbrowser.removeTabsProgressListener(gRestoreTabsProgressListener);
+
+ let winData = this._windows[aWindow.__SSi];
+ if (this._loadState == STATE_RUNNING) { // window not closed during a regular shut-down
+ // update all window data for a last time
+ this._collectWindowData(aWindow);
+
+ if (isFullyLoaded) {
+ winData.title = aWindow.content.document.title || tabbrowser.selectedTab.label;
+ winData.title = this._replaceLoadingTitle(winData.title, tabbrowser,
+ tabbrowser.selectedTab);
+ let windows = {};
+ windows[aWindow.__SSi] = winData;
+ this._updateCookies(windows);
+ }
+
+#ifndef XP_MACOSX
+ // Until we decide otherwise elsewhere, this window is part of a series
+ // of closing windows to quit.
+ winData._shouldRestore = true;
+#endif
+
+ // Save the window if it has multiple tabs or a single saveable tab and
+ // it's not private.
+ if (!winData.isPrivate && (winData.tabs.length > 1 ||
+ (winData.tabs.length == 1 && this._shouldSaveTabState(winData.tabs[0])))) {
+ // we don't want to save the busy state
+ delete winData.busy;
+
+ this._closedWindows.unshift(winData);
+ this._capClosedWindows();
+ }
+
+ // clear this window from the list
+ delete this._windows[aWindow.__SSi];
+ delete this._internalWindows[aWindow.__SSi];
+
+ // save the state without this window to disk
+ this.saveStateDelayed();
+ }
+
+ for (let i = 0; i < tabbrowser.tabs.length; i++) {
+ this.onTabRemove(aWindow, tabbrowser.tabs[i], true);
+ }
+
+ // Cache the window state until it is completely gone.
+ DyingWindowCache.set(aWindow, winData);
+
+ delete aWindow.__SSi;
+ },
+
+ /**
+ * On quit application requested
+ */
+ onQuitApplicationRequested: function ssi_onQuitApplicationRequested() {
+ // get a current snapshot of all windows
+ this._forEachBrowserWindow(function(aWindow) {
+ this._collectWindowData(aWindow);
+ });
+ // we must cache this because _getMostRecentBrowserWindow will always
+ // return null by the time quit-application occurs
+ var activeWindow = this._getMostRecentBrowserWindow();
+ if (activeWindow)
+ this.activeWindowSSiCache = activeWindow.__SSi || "";
+ this._dirtyWindows = [];
+ },
+
+ /**
+ * On quit application granted
+ */
+ onQuitApplicationGranted: function ssi_onQuitApplicationGranted() {
+ // freeze the data at what we've got (ignoring closing windows)
+ this._loadState = STATE_QUITTING;
+ },
+
+ /**
+ * On last browser window close
+ */
+ onLastWindowCloseGranted: function ssi_onLastWindowCloseGranted() {
+ // last browser window is quitting.
+ // remember to restore the last window when another browser window is opened
+ // do not account for pref(resume_session_once) at this point, as it might be
+ // set by another observer getting this notice after us
+ this._restoreLastWindow = true;
+ },
+
+ /**
+ * On quitting application
+ * @param aData
+ * String type of quitting
+ */
+ onQuitApplication: function ssi_onQuitApplication(aData) {
+ if (aData == "restart") {
+ this._prefBranch.setBoolPref("sessionstore.resume_session_once", true);
+ // The browser:purge-session-history notification fires after the
+ // quit-application notification so unregister the
+ // browser:purge-session-history notification to prevent clearing
+ // session data on disk on a restart. It is also unnecessary to
+ // perform any other sanitization processing on a restart as the
+ // browser is about to exit anyway.
+ Services.obs.removeObserver(this, "browser:purge-session-history");
+ }
+ else if (this._resume_session_once_on_shutdown != null) {
+ // if the sessionstore.resume_session_once preference was changed by
+ // saveState because crash recovery is disabled then restore the
+ // preference back to the value it was prior to that. This will prevent
+ // SessionStore from always restoring the session when crash recovery is
+ // disabled.
+ this._prefBranch.setBoolPref("sessionstore.resume_session_once",
+ this._resume_session_once_on_shutdown);
+ }
+
+ if (aData != "restart") {
+ // Throw away the previous session on shutdown
+ this._lastSessionState = null;
+ }
+
+ this._loadState = STATE_QUITTING; // just to be sure
+ this._uninit();
+ },
+
+ /**
+ * On purge of session history
+ */
+ onPurgeSessionHistory: function ssi_onPurgeSessionHistory() {
+ var _this = this;
+ _SessionFile.wipe();
+ // If the browser is shutting down, simply return after clearing the
+ // session data on disk as this notification fires after the
+ // quit-application notification so the browser is about to exit.
+ if (this._loadState == STATE_QUITTING)
+ return;
+ this._lastSessionState = null;
+ let openWindows = {};
+ this._forEachBrowserWindow(function(aWindow) {
+ Array.forEach(aWindow.gBrowser.tabs, function(aTab) {
+ delete aTab.linkedBrowser.__SS_data;
+ delete aTab.linkedBrowser.__SS_tabStillLoading;
+ delete aTab.linkedBrowser.__SS_formDataSaved;
+ delete aTab.linkedBrowser.__SS_hostSchemeData;
+ if (aTab.linkedBrowser.__SS_restoreState)
+ this._resetTabRestoringState(aTab);
+ }, this);
+ openWindows[aWindow.__SSi] = true;
+ });
+ // also clear all data about closed tabs and windows
+ for (let ix in this._windows) {
+ if (ix in openWindows) {
+ this._windows[ix]._closedTabs = [];
+ }
+ else {
+ delete this._windows[ix];
+ delete this._internalWindows[ix];
+ }
+ }
+ // also clear all data about closed windows
+ this._closedWindows = [];
+ // give the tabbrowsers a chance to clear their histories first
+ var win = this._getMostRecentBrowserWindow();
+ if (win)
+ win.setTimeout(function() { _this.saveState(true); }, 0);
+ else if (this._loadState == STATE_RUNNING)
+ this.saveState(true);
+ // Delete the private browsing backed up state, if any
+ if ("_stateBackup" in this)
+ delete this._stateBackup;
+
+ this._clearRestoringWindows();
+ },
+
+ /**
+ * On purge of domain data
+ * @param aData
+ * String domain data
+ */
+ onPurgeDomainData: function ssi_onPurgeDomainData(aData) {
+ // does a session history entry contain a url for the given domain?
+ function containsDomain(aEntry) {
+ try {
+ if (this._getURIFromString(aEntry.url).host.hasRootDomain(aData))
+ return true;
+ }
+ catch (ex) { /* url had no host at all */ }
+ return aEntry.children && aEntry.children.some(containsDomain, this);
+ }
+ // remove all closed tabs containing a reference to the given domain
+ for (let ix in this._windows) {
+ let closedTabs = this._windows[ix]._closedTabs;
+ for (let i = closedTabs.length - 1; i >= 0; i--) {
+ if (closedTabs[i].state.entries.some(containsDomain, this))
+ closedTabs.splice(i, 1);
+ }
+ }
+ // remove all open & closed tabs containing a reference to the given
+ // domain in closed windows
+ for (let ix = this._closedWindows.length - 1; ix >= 0; ix--) {
+ let closedTabs = this._closedWindows[ix]._closedTabs;
+ let openTabs = this._closedWindows[ix].tabs;
+ let openTabCount = openTabs.length;
+ for (let i = closedTabs.length - 1; i >= 0; i--)
+ if (closedTabs[i].state.entries.some(containsDomain, this))
+ closedTabs.splice(i, 1);
+ for (let j = openTabs.length - 1; j >= 0; j--) {
+ if (openTabs[j].entries.some(containsDomain, this)) {
+ openTabs.splice(j, 1);
+ if (this._closedWindows[ix].selected > j)
+ this._closedWindows[ix].selected--;
+ }
+ }
+ if (openTabs.length == 0) {
+ this._closedWindows.splice(ix, 1);
+ }
+ else if (openTabs.length != openTabCount) {
+ // Adjust the window's title if we removed an open tab
+ let selectedTab = openTabs[this._closedWindows[ix].selected - 1];
+ // some duplication from restoreHistory - make sure we get the correct title
+ let activeIndex = (selectedTab.index || selectedTab.entries.length) - 1;
+ if (activeIndex >= selectedTab.entries.length)
+ activeIndex = selectedTab.entries.length - 1;
+ this._closedWindows[ix].title = selectedTab.entries[activeIndex].title;
+ }
+ }
+ if (this._loadState == STATE_RUNNING)
+ this.saveState(true);
+
+ this._clearRestoringWindows();
+ },
+
+ /**
+ * On preference change
+ * @param aData
+ * String preference changed
+ */
+ onPrefChange: function ssi_onPrefChange(aData) {
+ switch (aData) {
+ // if the user decreases the max number of closed tabs they want
+ // preserved update our internal states to match that max
+ case "sessionstore.max_tabs_undo":
+ this._max_tabs_undo = this._prefBranch.getIntPref("sessionstore.max_tabs_undo");
+ for (let ix in this._windows) {
+ this._windows[ix]._closedTabs.splice(this._max_tabs_undo, this._windows[ix]._closedTabs.length);
+ }
+ break;
+ case "sessionstore.max_windows_undo":
+ this._max_windows_undo = this._prefBranch.getIntPref("sessionstore.max_windows_undo");
+ this._capClosedWindows();
+ break;
+ case "sessionstore.interval":
+ this._interval = this._prefBranch.getIntPref("sessionstore.interval");
+ // reset timer and save
+ if (this._saveTimer) {
+ this._saveTimer.cancel();
+ this._saveTimer = null;
+ }
+ this.saveStateDelayed(null, -1);
+ break;
+ case "sessionstore.resume_from_crash":
+ this._resume_from_crash = this._prefBranch.getBoolPref("sessionstore.resume_from_crash");
+ // restore original resume_session_once preference if set in saveState
+ if (this._resume_session_once_on_shutdown != null) {
+ this._prefBranch.setBoolPref("sessionstore.resume_session_once",
+ this._resume_session_once_on_shutdown);
+ this._resume_session_once_on_shutdown = null;
+ }
+ // either create the file with crash recovery information or remove it
+ // (when _loadState is not STATE_RUNNING, that file is used for session resuming instead)
+ if (!this._resume_from_crash)
+ _SessionFile.wipe();
+ this.saveState(true);
+ break;
+ }
+ },
+
+ /**
+ * On timer callback
+ */
+ onTimerCallback: function ssi_onTimerCallback() {
+ this._saveTimer = null;
+ this.saveState();
+ },
+
+ /**
+ * set up listeners for a new tab
+ * @param aWindow
+ * Window reference
+ * @param aTab
+ * Tab reference
+ * @param aNoNotification
+ * bool Do not save state if we're updating an existing tab
+ */
+ onTabAdd: function ssi_onTabAdd(aWindow, aTab, aNoNotification) {
+ let browser = aTab.linkedBrowser;
+ browser.addEventListener("load", this, true);
+
+ let mm = browser.messageManager;
+ MESSAGES.forEach(msg => mm.addMessageListener(msg, this));
+
+ if (!aNoNotification) {
+ this.saveStateDelayed(aWindow);
+ }
+ },
+
+ /**
+ * remove listeners for a tab
+ * @param aWindow
+ * Window reference
+ * @param aTab
+ * Tab reference
+ * @param aNoNotification
+ * bool Do not save state if we're updating an existing tab
+ */
+ onTabRemove: function ssi_onTabRemove(aWindow, aTab, aNoNotification) {
+ let browser = aTab.linkedBrowser;
+ browser.removeEventListener("load", this, true);
+
+ let mm = browser.messageManager;
+ MESSAGES.forEach(msg => mm.removeMessageListener(msg, this));
+
+ delete browser.__SS_data;
+ delete browser.__SS_tabStillLoading;
+ delete browser.__SS_formDataSaved;
+ delete browser.__SS_hostSchemeData;
+
+ // If this tab was in the middle of restoring or still needs to be restored,
+ // we need to reset that state. If the tab was restoring, we will attempt to
+ // restore the next tab.
+ let previousState = browser.__SS_restoreState;
+ if (previousState) {
+ this._resetTabRestoringState(aTab);
+ if (previousState == TAB_STATE_RESTORING)
+ this.restoreNextTab();
+ }
+
+ if (!aNoNotification) {
+ this.saveStateDelayed(aWindow);
+ }
+ },
+
+ /**
+ * When a tab closes, collect its properties
+ * @param aWindow
+ * Window reference
+ * @param aTab
+ * Tab reference
+ */
+ onTabClose: function ssi_onTabClose(aWindow, aTab) {
+ // notify the tabbrowser that the tab state will be retrieved for the last time
+ // (so that extension authors can easily set data on soon-to-be-closed tabs)
+ var event = aWindow.document.createEvent("Events");
+ event.initEvent("SSTabClosing", true, false);
+ aTab.dispatchEvent(event);
+
+ // don't update our internal state if we don't have to
+ if (this._max_tabs_undo == 0) {
+ return;
+ }
+
+ // make sure that the tab related data is up-to-date
+ var tabState = this._collectTabData(aTab);
+ this._updateTextAndScrollDataForTab(aWindow, aTab.linkedBrowser, tabState);
+
+ // store closed-tab data for undo
+ if (this._shouldSaveTabState(tabState)) {
+ let tabTitle = aTab.label;
+ let tabbrowser = aWindow.gBrowser;
+ tabTitle = this._replaceLoadingTitle(tabTitle, tabbrowser, aTab);
+
+ this._windows[aWindow.__SSi]._closedTabs.unshift({
+ state: tabState,
+ title: tabTitle,
+ image: tabbrowser.getIcon(aTab),
+ pos: aTab._tPos
+ });
+ var length = this._windows[aWindow.__SSi]._closedTabs.length;
+ if (length > this._max_tabs_undo)
+ this._windows[aWindow.__SSi]._closedTabs.splice(this._max_tabs_undo, length - this._max_tabs_undo);
+ }
+ },
+
+ /**
+ * When a tab loads, save state.
+ * @param aWindow
+ * Window reference
+ * @param aBrowser
+ * Browser reference
+ */
+ onTabLoad: function ssi_onTabLoad(aWindow, aBrowser) {
+ // react on "load" and solitary "pageshow" events (the first "pageshow"
+ // following "load" is too late for deleting the data caches)
+ // It's possible to get a load event after calling stop on a browser (when
+ // overwriting tabs). We want to return early if the tab hasn't been restored yet.
+ if (aBrowser.__SS_restoreState &&
+ aBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
+ return;
+ }
+
+ delete aBrowser.__SS_data;
+ delete aBrowser.__SS_tabStillLoading;
+ delete aBrowser.__SS_formDataSaved;
+ this.saveStateDelayed(aWindow);
+
+ },
+
+ /**
+ * Called when a browser sends the "input" notification
+ * @param aWindow
+ * Window reference
+ * @param aBrowser
+ * Browser reference
+ */
+ onTabInput: function ssi_onTabInput(aWindow, aBrowser) {
+ // deleting __SS_formDataSaved will cause us to recollect form data
+ delete aBrowser.__SS_formDataSaved;
+
+ this.saveStateDelayed(aWindow, 3000);
+ },
+
+ /**
+ * When a tab is selected, save session data
+ * @param aWindow
+ * Window reference
+ */
+ onTabSelect: function ssi_onTabSelect(aWindow) {
+ if (this._loadState == STATE_RUNNING) {
+ this._windows[aWindow.__SSi].selected = aWindow.gBrowser.tabContainer.selectedIndex;
+
+ let tab = aWindow.gBrowser.selectedTab;
+ // If __SS_restoreState is still on the browser and it is
+ // TAB_STATE_NEEDS_RESTORE, then then we haven't restored
+ // this tab yet. Explicitly call restoreTab to kick off the restore.
+ if (tab.linkedBrowser.__SS_restoreState &&
+ tab.linkedBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE)
+ this.restoreTab(tab);
+
+ }
+ },
+
+ onTabShow: function ssi_onTabShow(aWindow, aTab) {
+ // If the tab hasn't been restored yet, move it into the right bucket
+ if (aTab.linkedBrowser.__SS_restoreState &&
+ aTab.linkedBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
+ TabRestoreQueue.hiddenToVisible(aTab);
+
+ // let's kick off tab restoration again to ensure this tab gets restored
+ // with "restore_hidden_tabs" == false (now that it has become visible)
+ this.restoreNextTab();
+ }
+
+ // Default delay of 2 seconds gives enough time to catch multiple TabShow
+ // events due to changing groups in Panorama.
+ this.saveStateDelayed(aWindow);
+ },
+
+ onTabHide: function ssi_onTabHide(aWindow, aTab) {
+ // If the tab hasn't been restored yet, move it into the right bucket
+ if (aTab.linkedBrowser.__SS_restoreState &&
+ aTab.linkedBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
+ TabRestoreQueue.visibleToHidden(aTab);
+ }
+
+ // Default delay of 2 seconds gives enough time to catch multiple TabHide
+ // events due to changing groups in Panorama.
+ this.saveStateDelayed(aWindow);
+ },
+
+ /* ........ nsISessionStore API .............. */
+
+ getBrowserState: function ssi_getBrowserState() {
+ return this._toJSONString(this._getCurrentState());
+ },
+
+ setBrowserState: function ssi_setBrowserState(aState) {
+ this._handleClosedWindows();
+
+ try {
+ var state = JSON.parse(aState);
+ }
+ catch (ex) { /* invalid state object - don't restore anything */ }
+ if (!state || !state.windows)
+ throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+
+ this._browserSetState = true;
+
+ // Make sure the priority queue is emptied out
+ this._resetRestoringState();
+
+ var window = this._getMostRecentBrowserWindow();
+ if (!window) {
+ this._restoreCount = 1;
+ this._openWindowWithState(state);
+ return;
+ }
+
+ // close all other browser windows
+ this._forEachBrowserWindow(function(aWindow) {
+ if (aWindow != window) {
+ aWindow.close();
+ this.onClose(aWindow);
+ }
+ });
+
+ // make sure closed window data isn't kept
+ this._closedWindows = [];
+
+ // determine how many windows are meant to be restored
+ this._restoreCount = state.windows ? state.windows.length : 0;
+
+ // restore to the given state
+ this.restoreWindow(window, state, true);
+ },
+
+ getWindowState: function ssi_getWindowState(aWindow) {
+ if ("__SSi" in aWindow) {
+ return this._toJSONString(this._getWindowState(aWindow));
+ }
+
+ if (DyingWindowCache.has(aWindow)) {
+ let data = DyingWindowCache.get(aWindow);
+ return this._toJSONString({ windows: [data] });
+ }
+
+ throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+ },
+
+ setWindowState: function ssi_setWindowState(aWindow, aState, aOverwrite) {
+ if (!aWindow.__SSi)
+ throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+
+ this.restoreWindow(aWindow, aState, aOverwrite);
+ },
+
+ getTabState: function ssi_getTabState(aTab) {
+ if (!aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi)
+ throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+
+ var tabState = this._collectTabData(aTab);
+
+ var window = aTab.ownerDocument.defaultView;
+ this._updateTextAndScrollDataForTab(window, aTab.linkedBrowser, tabState);
+
+ return this._toJSONString(tabState);
+ },
+
+ setTabState: function ssi_setTabState(aTab, aState) {
+ var tabState = JSON.parse(aState);
+ if (!tabState.entries || !aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi)
+ throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+
+ var window = aTab.ownerDocument.defaultView;
+ this._setWindowStateBusy(window);
+ this.restoreHistoryPrecursor(window, [aTab], [tabState], 0, 0, 0);
+ },
+
+ duplicateTab: function ssi_duplicateTab(aWindow, aTab, aDelta) {
+ if (!aTab.ownerDocument || !aTab.ownerDocument.defaultView.__SSi ||
+ !aWindow.getBrowser)
+ throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+
+ var tabState = this._collectTabData(aTab, true);
+ var sourceWindow = aTab.ownerDocument.defaultView;
+ this._updateTextAndScrollDataForTab(sourceWindow, aTab.linkedBrowser, tabState, true);
+ tabState.index += aDelta;
+ tabState.index = Math.max(1, Math.min(tabState.index, tabState.entries.length));
+ tabState.pinned = false;
+
+ this._setWindowStateBusy(aWindow);
+ let newTab = aTab == aWindow.gBrowser.selectedTab ?
+ aWindow.gBrowser.addTab(null, {relatedToCurrent: true, ownerTab: aTab}) :
+ aWindow.gBrowser.addTab();
+
+ this.restoreHistoryPrecursor(aWindow, [newTab], [tabState], 0, 0, 0,
+ true /* Load this tab right away. */);
+
+ return newTab;
+ },
+
+ getClosedTabCount: function ssi_getClosedTabCount(aWindow) {
+ if ("__SSi" in aWindow) {
+ return this._windows[aWindow.__SSi]._closedTabs.length;
+ }
+
+ if (DyingWindowCache.has(aWindow)) {
+ return DyingWindowCache.get(aWindow)._closedTabs.length;
+ }
+
+ throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+ },
+
+ getClosedTabData: function ssi_getClosedTabDataAt(aWindow) {
+ if ("__SSi" in aWindow) {
+ return this._toJSONString(this._windows[aWindow.__SSi]._closedTabs);
+ }
+
+ if (DyingWindowCache.has(aWindow)) {
+ let data = DyingWindowCache.get(aWindow);
+ return this._toJSONString(data._closedTabs);
+ }
+
+ throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+ },
+
+ undoCloseTab: function ssi_undoCloseTab(aWindow, aIndex) {
+ if (!aWindow.__SSi)
+ throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+
+ var closedTabs = this._windows[aWindow.__SSi]._closedTabs;
+
+ // default to the most-recently closed tab
+ aIndex = aIndex || 0;
+ if (!(aIndex in closedTabs))
+ throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+
+ // fetch the data of closed tab, while removing it from the array
+ let closedTab = closedTabs.splice(aIndex, 1).shift();
+ let closedTabState = closedTab.state;
+
+ this._setWindowStateBusy(aWindow);
+ // create a new tab
+ let tabbrowser = aWindow.gBrowser;
+ let tab = tabbrowser.addTab();
+
+ // restore tab content
+ this.restoreHistoryPrecursor(aWindow, [tab], [closedTabState], 1, 0, 0);
+
+ // restore the tab's position
+ tabbrowser.moveTabTo(tab, closedTab.pos);
+
+ // focus the tab's content area (bug 342432)
+ tab.linkedBrowser.focus();
+
+ return tab;
+ },
+
+ forgetClosedTab: function ssi_forgetClosedTab(aWindow, aIndex) {
+ if (!aWindow.__SSi)
+ throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+
+ var closedTabs = this._windows[aWindow.__SSi]._closedTabs;
+
+ // default to the most-recently closed tab
+ aIndex = aIndex || 0;
+ if (!(aIndex in closedTabs))
+ throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+
+ // remove closed tab from the array
+ closedTabs.splice(aIndex, 1);
+ },
+
+ getClosedWindowCount: function ssi_getClosedWindowCount() {
+ return this._closedWindows.length;
+ },
+
+ getClosedWindowData: function ssi_getClosedWindowData() {
+ return this._toJSONString(this._closedWindows);
+ },
+
+ undoCloseWindow: function ssi_undoCloseWindow(aIndex) {
+ if (!(aIndex in this._closedWindows))
+ throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+
+ // reopen the window
+ let state = { windows: this._closedWindows.splice(aIndex, 1) };
+ let window = this._openWindowWithState(state);
+ this.windowToFocus = window;
+ return window;
+ },
+
+ forgetClosedWindow: function ssi_forgetClosedWindow(aIndex) {
+ // default to the most-recently closed window
+ aIndex = aIndex || 0;
+ if (!(aIndex in this._closedWindows))
+ throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+
+ // remove closed window from the array
+ this._closedWindows.splice(aIndex, 1);
+ },
+
+ getWindowValue: function ssi_getWindowValue(aWindow, aKey) {
+ if ("__SSi" in aWindow) {
+ var data = this._windows[aWindow.__SSi].extData || {};
+ return data[aKey] || "";
+ }
+
+ if (DyingWindowCache.has(aWindow)) {
+ let data = DyingWindowCache.get(aWindow).extData || {};
+ return data[aKey] || "";
+ }
+
+ throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+ },
+
+ setWindowValue: function ssi_setWindowValue(aWindow, aKey, aStringValue) {
+ if (aWindow.__SSi) {
+ if (!this._windows[aWindow.__SSi].extData) {
+ this._windows[aWindow.__SSi].extData = {};
+ }
+ this._windows[aWindow.__SSi].extData[aKey] = aStringValue;
+ this.saveStateDelayed(aWindow);
+ }
+ else {
+ throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
+ }
+ },
+
+ deleteWindowValue: function ssi_deleteWindowValue(aWindow, aKey) {
+ if (aWindow.__SSi && this._windows[aWindow.__SSi].extData &&
+ this._windows[aWindow.__SSi].extData[aKey])
+ delete this._windows[aWindow.__SSi].extData[aKey];
+ },
+
+ getTabValue: function ssi_getTabValue(aTab, aKey) {
+ let data = {};
+ if (aTab.__SS_extdata) {
+ data = aTab.__SS_extdata;
+ }
+ else if (aTab.linkedBrowser.__SS_data && aTab.linkedBrowser.__SS_data.extData) {
+ // If the tab hasn't been fully restored, get the data from the to-be-restored data
+ data = aTab.linkedBrowser.__SS_data.extData;
+ }
+ return data[aKey] || "";
+ },
+
+ setTabValue: function ssi_setTabValue(aTab, aKey, aStringValue) {
+ // If the tab hasn't been restored, then set the data there, otherwise we
+ // could lose newly added data.
+ let saveTo;
+ if (aTab.__SS_extdata) {
+ saveTo = aTab.__SS_extdata;
+ }
+ else if (aTab.linkedBrowser.__SS_data && aTab.linkedBrowser.__SS_data.extData) {
+ saveTo = aTab.linkedBrowser.__SS_data.extData;
+ }
+ else {
+ aTab.__SS_extdata = {};
+ saveTo = aTab.__SS_extdata;
+ }
+ saveTo[aKey] = aStringValue;
+ this.saveStateDelayed(aTab.ownerDocument.defaultView);
+ },
+
+ deleteTabValue: function ssi_deleteTabValue(aTab, aKey) {
+ // We want to make sure that if data is accessed early, we attempt to delete
+ // that data from __SS_data as well. Otherwise we'll throw in cases where
+ // data can be set or read.
+ let deleteFrom;
+ if (aTab.__SS_extdata) {
+ deleteFrom = aTab.__SS_extdata;
+ }
+ else if (aTab.linkedBrowser.__SS_data && aTab.linkedBrowser.__SS_data.extData) {
+ deleteFrom = aTab.linkedBrowser.__SS_data.extData;
+ }
+
+ if (deleteFrom && deleteFrom[aKey])
+ delete deleteFrom[aKey];
+ },
+
+ persistTabAttribute: function ssi_persistTabAttribute(aName) {
+ if (TabAttributes.persist(aName)) {
+ this.saveStateDelayed();
+ }
+ },
+
+ /**
+ * Restores the session state stored in _lastSessionState. This will attempt
+ * to merge data into the current session. If a window was opened at startup
+ * with pinned tab(s), then the remaining data from the previous session for
+ * that window will be opened into that winddow. Otherwise new windows will
+ * be opened.
+ */
+ restoreLastSession: function ssi_restoreLastSession() {
+ // Use the public getter since it also checks PB mode
+ if (!this.canRestoreLastSession)
+ throw (Components.returnCode = Cr.NS_ERROR_FAILURE);
+
+ // First collect each window with its id...
+ let windows = {};
+ this._forEachBrowserWindow(function(aWindow) {
+ if (aWindow.__SS_lastSessionWindowID)
+ windows[aWindow.__SS_lastSessionWindowID] = aWindow;
+ });
+
+ let lastSessionState = this._lastSessionState;
+
+ // This shouldn't ever be the case...
+ if (!lastSessionState.windows.length)
+ throw (Components.returnCode = Cr.NS_ERROR_UNEXPECTED);
+
+ // We're technically doing a restore, so set things up so we send the
+ // notification when we're done. We want to send "sessionstore-browser-state-restored".
+ this._restoreCount = lastSessionState.windows.length;
+ this._browserSetState = true;
+
+ // We want to re-use the last opened window instead of opening a new one in
+ // the case where it's "empty" and not associated with a window in the session.
+ // We will do more processing via _prepWindowToRestoreInto if we need to use
+ // the lastWindow.
+ let lastWindow = this._getMostRecentBrowserWindow();
+ let canUseLastWindow = lastWindow &&
+ !lastWindow.__SS_lastSessionWindowID;
+
+ // Restore into windows or open new ones as needed.
+ for (let i = 0; i < lastSessionState.windows.length; i++) {
+ let winState = lastSessionState.windows[i];
+ let lastSessionWindowID = winState.__lastSessionWindowID;
+ // delete lastSessionWindowID so we don't add that to the window again
+ delete winState.__lastSessionWindowID;
+
+ // See if we can use an open window. First try one that is associated with
+ // the state we're trying to restore and then fallback to the last selected
+ // window.
+ let windowToUse = windows[lastSessionWindowID];
+ if (!windowToUse && canUseLastWindow) {
+ windowToUse = lastWindow;
+ canUseLastWindow = false;
+ }
+
+ let [canUseWindow, canOverwriteTabs] = this._prepWindowToRestoreInto(windowToUse);
+
+ // If there's a window already open that we can restore into, use that
+ if (canUseWindow) {
+ // Since we're not overwriting existing tabs, we want to merge _closedTabs,
+ // putting existing ones first. Then make sure we're respecting the max pref.
+ if (winState._closedTabs && winState._closedTabs.length) {
+ let curWinState = this._windows[windowToUse.__SSi];
+ curWinState._closedTabs = curWinState._closedTabs.concat(winState._closedTabs);
+ curWinState._closedTabs.splice(this._prefBranch.getIntPref("sessionstore.max_tabs_undo"), curWinState._closedTabs.length);
+ }
+
+ // Restore into that window - pretend it's a followup since we'll already
+ // have a focused window.
+ //XXXzpao This is going to merge extData together (taking what was in
+ // winState over what is in the window already. The hack we have
+ // in _preWindowToRestoreInto will prevent most (all?) Panorama
+ // weirdness but we will still merge other extData.
+ // Bug 588217 should make this go away by merging the group data.
+ this.restoreWindow(windowToUse, { windows: [winState] }, canOverwriteTabs, true);
+ }
+ else {
+ this._openWindowWithState({ windows: [winState] });
+ }
+ }
+
+ // Merge closed windows from this session with ones from last session
+ if (lastSessionState._closedWindows) {
+ this._closedWindows = this._closedWindows.concat(lastSessionState._closedWindows);
+ this._capClosedWindows();
+ }
+
+#ifdef MOZ_DEVTOOLS
+ // Scratchpad
+ if (lastSessionState.scratchpads) {
+ ScratchpadManager.restoreSession(lastSessionState.scratchpads);
+ }
+
+ // The Browser Console
+ if (lastSessionState.browserConsole) {
+ HUDService.restoreBrowserConsoleSession();
+ }
+#endif
+
+ // Set data that persists between sessions
+ this._recentCrashes = lastSessionState.session &&
+ lastSessionState.session.recentCrashes || 0;
+ this._sessionStartTime = lastSessionState.session &&
+ lastSessionState.session.startTime ||
+ this._sessionStartTime;
+
+ this._lastSessionState = null;
+ },
+
+ /**
+ * See if aWindow is usable for use when restoring a previous session via
+ * restoreLastSession. If usable, prepare it for use.
+ *
+ * @param aWindow
+ * the window to inspect & prepare
+ * @returns [canUseWindow, canOverwriteTabs]
+ * canUseWindow: can the window be used to restore into
+ * canOverwriteTabs: all of the current tabs are home pages and we
+ * can overwrite them
+ */
+ _prepWindowToRestoreInto: function ssi_prepWindowToRestoreInto(aWindow) {
+ if (!aWindow)
+ return [false, false];
+
+ let event = aWindow.document.createEvent("Events");
+ event.initEvent("SSRestoreIntoWindow", true, true);
+
+ // Check if we can use the window.
+ if (!aWindow.dispatchEvent(event))
+ return [false, false];
+
+ // We might be able to overwrite the existing tabs instead of just adding
+ // the previous session's tabs to the end. This will be set if possible.
+ let canOverwriteTabs = false;
+
+ // Look at the open tabs in comparison to home pages. If all the tabs are
+ // home pages then we'll end up overwriting all of them. Otherwise we'll
+ // just close the tabs that match home pages. Tabs with the about:blank
+ // URI will always be overwritten.
+ let homePages = ["about:blank"];
+ let removableTabs = [];
+ let tabbrowser = aWindow.gBrowser;
+ let normalTabsLen = tabbrowser.tabs.length - tabbrowser._numPinnedTabs;
+ let startupPref = this._prefBranch.getIntPref("startup.page");
+ if (startupPref == 1)
+ homePages = homePages.concat(aWindow.gHomeButton.getHomePage().split("|"));
+
+ for (let i = tabbrowser._numPinnedTabs; i < tabbrowser.tabs.length; i++) {
+ let tab = tabbrowser.tabs[i];
+ if (homePages.indexOf(tab.linkedBrowser.currentURI.spec) != -1) {
+ removableTabs.push(tab);
+ }
+ }
+
+ if (tabbrowser.tabs.length == removableTabs.length) {
+ canOverwriteTabs = true;
+ }
+ else {
+ // If we're not overwriting all of the tabs, then close the home tabs.
+ for (let i = removableTabs.length - 1; i >= 0; i--) {
+ tabbrowser.removeTab(removableTabs.pop(), { animate: false });
+ }
+ }
+
+ return [true, canOverwriteTabs];
+ },
+
+ /* ........ Saving Functionality .............. */
+
+ /**
+ * Store all session data for a window
+ * @param aWindow
+ * Window reference
+ */
+ _saveWindowHistory: function ssi_saveWindowHistory(aWindow) {
+ var tabbrowser = aWindow.gBrowser;
+ var tabs = tabbrowser.tabs;
+ var tabsData = this._windows[aWindow.__SSi].tabs = [];
+
+ for (var i = 0; i < tabs.length; i++)
+ tabsData.push(this._collectTabData(tabs[i]));
+
+ this._windows[aWindow.__SSi].selected = tabbrowser.mTabBox.selectedIndex + 1;
+ },
+
+ /**
+ * Collect data related to a single tab
+ * @param aTab
+ * tabbrowser tab
+ * @param aFullData
+ * always return privacy sensitive data (use with care)
+ * @returns object
+ */
+ _collectTabData: function ssi_collectTabData(aTab, aFullData) {
+ var tabData = { entries: [], lastAccessed: aTab.lastAccessed };
+ var browser = aTab.linkedBrowser;
+
+ if (!browser || !browser.currentURI)
+ // can happen when calling this function right after .addTab()
+ return tabData;
+ else if (browser.__SS_data && browser.__SS_tabStillLoading) {
+ // use the data to be restored when the tab hasn't been completely loaded
+ tabData = browser.__SS_data;
+ if (aTab.pinned)
+ tabData.pinned = true;
+ else
+ delete tabData.pinned;
+ tabData.hidden = aTab.hidden;
+
+ // If __SS_extdata is set then we'll use that since it might be newer.
+ if (aTab.__SS_extdata)
+ tabData.extData = aTab.__SS_extdata;
+ // If it exists but is empty then a key was likely deleted. In that case just
+ // delete extData.
+ if (tabData.extData && !Object.keys(tabData.extData).length)
+ delete tabData.extData;
+ return tabData;
+ }
+
+ var history = null;
+ try {
+ history = browser.sessionHistory;
+ }
+ catch (ex) { } // this could happen if we catch a tab during (de)initialization
+
+ // Limit number of back/forward button history entries to save
+ let oldest, newest;
+ let maxSerializeBack = this._prefBranch.getIntPref("sessionstore.max_serialize_back");
+ if (maxSerializeBack >= 0) {
+ oldest = Math.max(0, history.index - maxSerializeBack);
+ } else { // History.getEntryAtIndex(0, ...) is the oldest.
+ oldest = 0;
+ }
+ let maxSerializeFwd = this._prefBranch.getIntPref("sessionstore.max_serialize_forward");
+ if (maxSerializeFwd >= 0) {
+ newest = Math.min(history.count - 1, history.index + maxSerializeFwd);
+ } else { // History.getEntryAtIndex(history.count - 1, ...) is the newest.
+ newest = history.count - 1;
+ }
+
+ // XXXzeniko anchor navigation doesn't reset __SS_data, so we could reuse
+ // data even when we shouldn't (e.g. Back, different anchor)
+ // Warning: this is required to save form data and scrolling position!
+ if (history && browser.__SS_data &&
+ browser.__SS_data.entries[history.index] &&
+ browser.__SS_data.entries[history.index].url == browser.currentURI.spec &&
+ history.index < this._sessionhistory_max_entries - 1 && !aFullData) {
+ try {
+ tabData.entries = browser.__SS_data.entries.slice(oldest, newest + 1);
+ }
+ catch (ex) {
+ // No errors are expected above, but we use try-catch to keep sessionstore.js safe
+ NS_ASSERT(false, "SessionStore failed to slice history from browser.__SS_data");
+ }
+
+ // Set the one-based index of the currently active tab, ensuring it isn't out of bounds
+ tabData.index = Math.min(history.index - oldest + 1, tabData.entries.length);
+ }
+ else if (history && history.count > 0) {
+ browser.__SS_hostSchemeData = [];
+ try {
+ for (var j = oldest; j <= newest; j++) {
+ let entry = this._serializeHistoryEntry(history.getEntryAtIndex(j, false),
+ aFullData, aTab.pinned, browser.__SS_hostSchemeData);
+ tabData.entries.push(entry);
+ }
+ }
+ catch (ex) {
+ // In some cases, getEntryAtIndex will throw. This seems to be due to
+ // history.count being higher than it should be. By doing this in a
+ // try-catch, we'll update history to where it breaks, assert for
+ // non-release builds, and still save sessionstore.js.
+ NS_ASSERT(false, "SessionStore failed gathering complete history " +
+ "for the focused window/tab. See bug 669196.");
+ }
+
+ // Set the one-based index of the currently active tab, ensuring it isn't out of bounds
+ tabData.index = Math.min(history.index - oldest + 1, tabData.entries.length);
+
+ // make sure not to cache privacy sensitive data which shouldn't get out
+ if (!aFullData)
+ browser.__SS_data = tabData;
+ }
+ else if (browser.currentURI.spec != "about:blank" ||
+ browser.contentDocument.body.hasChildNodes()) {
+ tabData.entries[0] = { url: browser.currentURI.spec };
+ tabData.index = 1;
+ }
+
+ // If there is a userTypedValue set, then either the user has typed something
+ // in the URL bar, or a new tab was opened with a URI to load. userTypedClear
+ // is used to indicate whether the tab was in some sort of loading state with
+ // userTypedValue.
+ if (browser.userTypedValue) {
+ tabData.userTypedValue = browser.userTypedValue;
+ // We always used to keep track of the loading state as an integer, where
+ // '0' indicated the user had typed since the last load (or no load was
+ // ongoing), and any positive value indicated we had started a load since
+ // the last time the user typed in the URL bar. Mimic this to keep the
+ // session store representation in sync, even though we now represent this
+ // more explicitly:
+ tabData.userTypedClear = browser.didStartLoadSinceLastUserTyping() ? 1 : 0;
+ } else {
+ delete tabData.userTypedValue;
+ delete tabData.userTypedClear;
+ }
+
+ if (aTab.pinned)
+ tabData.pinned = true;
+ else
+ delete tabData.pinned;
+ tabData.hidden = aTab.hidden;
+
+ var disallow = [];
+ for (let cap of gDocShellCapabilities(browser.docShell))
+ if (!browser.docShell["allow" + cap])
+ disallow.push(cap);
+ if (disallow.length > 0)
+ tabData.disallow = disallow.join(",");
+ else if (tabData.disallow)
+ delete tabData.disallow;
+
+ // Save tab attributes.
+ tabData.attributes = TabAttributes.get(aTab);
+
+ // Store the tab icon.
+ let tabbrowser = aTab.ownerDocument.defaultView.gBrowser;
+ tabData.image = tabbrowser.getIcon(aTab);
+
+ if (aTab.__SS_extdata)
+ tabData.extData = aTab.__SS_extdata;
+ else if (tabData.extData)
+ delete tabData.extData;
+
+ if (history && browser.docShell instanceof Ci.nsIDocShell) {
+ let storageData = SessionStorage.serialize(browser.docShell, aFullData)
+ if (Object.keys(storageData).length)
+ tabData.storage = storageData;
+ }
+
+ return tabData;
+ },
+
+ /**
+ * Get an object that is a serialized representation of a History entry
+ * Used for data storage
+ * @param aEntry
+ * nsISHEntry instance
+ * @param aFullData
+ * always return privacy sensitive data (use with care)
+ * @param aIsPinned
+ * the tab is pinned and should be treated differently for privacy
+ * @param aHostSchemeData
+ * an array of objects with host & scheme keys
+ * @returns object
+ */
+ _serializeHistoryEntry:
+ function ssi_serializeHistoryEntry(aEntry, aFullData, aIsPinned, aHostSchemeData) {
+ var entry = { url: aEntry.URI.spec };
+
+ try {
+ // throwing is expensive, we know that about: pages will throw
+ if (entry.url.indexOf("about:") != 0)
+ aHostSchemeData.push({ host: aEntry.URI.host, scheme: aEntry.URI.scheme });
+ }
+ catch (ex) {
+ // We just won't attempt to get cookies for this entry.
+ }
+
+ if (aEntry.title && aEntry.title != entry.url) {
+ entry.title = aEntry.title;
+ }
+ if (aEntry.isSubFrame) {
+ entry.subframe = true;
+ }
+ if (!(aEntry instanceof Ci.nsISHEntry)) {
+ return entry;
+ }
+
+ var cacheKey = aEntry.cacheKey;
+ if (cacheKey && cacheKey instanceof Ci.nsISupportsPRUint32 &&
+ cacheKey.data != 0) {
+ // XXXbz would be better to have cache keys implement
+ // nsISerializable or something.
+ entry.cacheKey = cacheKey.data;
+ }
+ entry.ID = aEntry.ID;
+ entry.docshellID = aEntry.docshellID;
+
+ if (aEntry.referrerURI)
+ entry.referrer = aEntry.referrerURI.spec;
+
+ if (aEntry.srcdocData)
+ entry.srcdocData = aEntry.srcdocData;
+
+ if (aEntry.isSrcdocEntry)
+ entry.isSrcdocEntry = aEntry.isSrcdocEntry;
+
+ if (aEntry.contentType)
+ entry.contentType = aEntry.contentType;
+
+ var x = {}, y = {};
+ aEntry.getScrollPosition(x, y);
+ if (x.value != 0 || y.value != 0)
+ entry.scroll = x.value + "," + y.value;
+
+ try {
+ var prefPostdata = this._prefBranch.getIntPref("sessionstore.postdata");
+ if (aEntry.postData && (aFullData || prefPostdata &&
+ this.checkPrivacyLevel(aEntry.URI.schemeIs("https"), aIsPinned))) {
+ aEntry.postData.QueryInterface(Ci.nsISeekableStream).
+ seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
+ var stream = Cc["@mozilla.org/binaryinputstream;1"].
+ createInstance(Ci.nsIBinaryInputStream);
+ stream.setInputStream(aEntry.postData);
+ var postBytes = stream.readByteArray(stream.available());
+ var postdata = String.fromCharCode.apply(null, postBytes);
+ if (aFullData || prefPostdata == -1 ||
+ postdata.replace(/^(Content-.*\r\n)+(\r\n)*/, "").length <=
+ prefPostdata) {
+ // We can stop doing base64 encoding once our serialization into JSON
+ // is guaranteed to handle all chars in strings, including embedded
+ // nulls.
+ entry.postdata_b64 = btoa(postdata);
+ }
+ }
+ }
+ catch (ex) { debug(ex); } // POSTDATA is tricky - especially since some extensions don't get it right
+
+ if (aEntry.triggeringPrincipal) {
+ // Not catching anything specific here, just possible errors
+ // from writeCompoundObject and the like.
+ try {
+ var binaryStream = Cc["@mozilla.org/binaryoutputstream;1"].
+ createInstance(Ci.nsIObjectOutputStream);
+ var pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
+ pipe.init(false, false, 0, 0xffffffff, null);
+ binaryStream.setOutputStream(pipe.outputStream);
+ binaryStream.writeCompoundObject(aEntry.triggeringPrincipal, Ci.nsIPrincipal, true);
+ binaryStream.close();
+
+ // Now we want to read the data from the pipe's input end and encode it.
+ var scriptableStream = Cc["@mozilla.org/binaryinputstream;1"].
+ createInstance(Ci.nsIBinaryInputStream);
+ scriptableStream.setInputStream(pipe.inputStream);
+ var triggeringPrincipalBytes =
+ scriptableStream.readByteArray(scriptableStream.available());
+ // We can stop doing base64 encoding once our serialization into JSON
+ // is guaranteed to handle all chars in strings, including embedded
+ // nulls.
+ entry.triggeringPrincipal_b64 = btoa(String.fromCharCode.apply(null, triggeringPrincipalBytes));
+ }
+ catch (ex) { debug(ex); }
+ }
+
+ entry.docIdentifier = aEntry.BFCacheEntry.ID;
+
+ if (aEntry.stateData != null) {
+ entry.structuredCloneState = aEntry.stateData.getDataAsBase64();
+ entry.structuredCloneVersion = aEntry.stateData.formatVersion;
+ }
+
+ if (!(aEntry instanceof Ci.nsISHContainer)) {
+ return entry;
+ }
+
+ if (aEntry.childCount > 0) {
+ let children = [];
+ for (var i = 0; i < aEntry.childCount; i++) {
+ var child = aEntry.GetChildAt(i);
+
+ if (child) {
+ // don't try to restore framesets containing wyciwyg URLs (cf. bug 424689 and bug 450595)
+ if (child.URI.schemeIs("wyciwyg")) {
+ children = [];
+ break;
+ }
+
+ children.push(this._serializeHistoryEntry(child, aFullData,
+ aIsPinned, aHostSchemeData));
+ }
+ }
+
+ if (children.length)
+ entry.children = children;
+ }
+
+ return entry;
+ },
+
+ /**
+ * go through all tabs and store the current scroll positions
+ * and innerHTML content of WYSIWYG editors
+ * @param aWindow
+ * Window reference
+ */
+ _updateTextAndScrollData: function ssi_updateTextAndScrollData(aWindow) {
+ var browsers = aWindow.gBrowser.browsers;
+ this._windows[aWindow.__SSi].tabs.forEach(function (tabData, i) {
+ try {
+ this._updateTextAndScrollDataForTab(aWindow, browsers[i], tabData);
+ }
+ catch (ex) { debug(ex); } // get as much data as possible, ignore failures (might succeed the next time)
+ }, this);
+ },
+
+ /**
+ * go through all frames and store the current scroll positions
+ * and innerHTML content of WYSIWYG editors
+ * @param aWindow
+ * Window reference
+ * @param aBrowser
+ * single browser reference
+ * @param aTabData
+ * tabData object to add the information to
+ * @param aFullData
+ * always return privacy sensitive data (use with care)
+ */
+ _updateTextAndScrollDataForTab:
+ function ssi_updateTextAndScrollDataForTab(aWindow, aBrowser, aTabData, aFullData) {
+ // we shouldn't update data for incompletely initialized tabs
+ if (aBrowser.__SS_data && aBrowser.__SS_tabStillLoading)
+ return;
+
+ var tabIndex = (aTabData.index || aTabData.entries.length) - 1;
+ // entry data needn't exist for tabs just initialized with an incomplete session state
+ if (!aTabData.entries[tabIndex])
+ return;
+
+ let selectedPageStyle = aBrowser.markupDocumentViewer.authorStyleDisabled ? "_nostyle" :
+ this._getSelectedPageStyle(aBrowser.contentWindow);
+ if (selectedPageStyle)
+ aTabData.pageStyle = selectedPageStyle;
+ else if (aTabData.pageStyle)
+ delete aTabData.pageStyle;
+
+ this._updateTextAndScrollDataForFrame(aWindow, aBrowser.contentWindow,
+ aTabData.entries[tabIndex],
+ !aBrowser.__SS_formDataSaved, aFullData,
+ !!aTabData.pinned);
+ aBrowser.__SS_formDataSaved = true;
+ if (aBrowser.currentURI.spec == "about:config")
+ aTabData.entries[tabIndex].formdata = {
+ id: {
+ "textbox": aBrowser.contentDocument.getElementById("textbox").value
+ },
+ xpath: {}
+ };
+ },
+
+ /**
+ * go through all subframes and store all form data, the current
+ * scroll positions and innerHTML content of WYSIWYG editors
+ * @param aWindow
+ * Window reference
+ * @param aContent
+ * frame reference
+ * @param aData
+ * part of a tabData object to add the information to
+ * @param aUpdateFormData
+ * update all form data for this tab
+ * @param aFullData
+ * always return privacy sensitive data (use with care)
+ * @param aIsPinned
+ * the tab is pinned and should be treated differently for privacy
+ */
+ _updateTextAndScrollDataForFrame:
+ function ssi_updateTextAndScrollDataForFrame(aWindow, aContent, aData,
+ aUpdateFormData, aFullData, aIsPinned) {
+ for (var i = 0; i < aContent.frames.length; i++) {
+ if (aData.children && aData.children[i])
+ this._updateTextAndScrollDataForFrame(aWindow, aContent.frames[i],
+ aData.children[i], aUpdateFormData,
+ aFullData, aIsPinned);
+ }
+ var isHTTPS = this._getURIFromString((aContent.parent || aContent).
+ document.location.href).schemeIs("https");
+ let isAboutSR = aContent.top.document.location.href == "about:sessionrestore";
+ if (aFullData || this.checkPrivacyLevel(isHTTPS, aIsPinned) || isAboutSR) {
+ if (aFullData || aUpdateFormData) {
+ let formData = DocumentUtils.getFormData(aContent.document);
+
+ // We want to avoid saving data for about:sessionrestore as a string.
+ // Since it's stored in the form as stringified JSON, stringifying further
+ // causes an explosion of escape characters. cf. bug 467409
+ if (formData && isAboutSR) {
+ formData.id["sessionData"] = JSON.parse(formData.id["sessionData"]);
+ }
+
+ if (Object.keys(formData.id).length ||
+ Object.keys(formData.xpath).length) {
+ aData.formdata = formData;
+ } else if (aData.formdata) {
+ delete aData.formdata;
+ }
+ }
+
+ // designMode is undefined e.g. for XUL documents (as about:config)
+ if ((aContent.document.designMode || "") == "on" && aContent.document.body)
+ aData.innerHTML = aContent.document.body.innerHTML;
+ }
+
+ // get scroll position from nsIDOMWindowUtils, since it allows avoiding a
+ // flush of layout
+ let domWindowUtils = aContent.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ let scrollX = {}, scrollY = {};
+ domWindowUtils.getScrollXY(false, scrollX, scrollY);
+ aData.scroll = scrollX.value + "," + scrollY.value;
+ },
+
+ /**
+ * determine the title of the currently enabled style sheet (if any)
+ * and recurse through the frameset if necessary
+ * @param aContent is a frame reference
+ * @returns the title style sheet determined to be enabled (empty string if none)
+ */
+ _getSelectedPageStyle: function ssi_getSelectedPageStyle(aContent) {
+ const forScreen = /(?:^|,)\s*(?:all|screen)\s*(?:,|$)/i;
+ for (let i = 0; i < aContent.document.styleSheets.length; i++) {
+ let ss = aContent.document.styleSheets[i];
+ let media = ss.media.mediaText;
+ if (!ss.disabled && ss.title && (!media || forScreen.test(media)))
+ return ss.title
+ }
+ for (let i = 0; i < aContent.frames.length; i++) {
+ let selectedPageStyle = this._getSelectedPageStyle(aContent.frames[i]);
+ if (selectedPageStyle)
+ return selectedPageStyle;
+ }
+ return "";
+ },
+
+ /**
+ * extract the base domain from a history entry and its children
+ * @param aEntry
+ * the history entry, serialized
+ * @param aHosts
+ * the hash that will be used to store hosts eg, { hostname: true }
+ * @param aCheckPrivacy
+ * should we check the privacy level for https
+ * @param aIsPinned
+ * is the entry we're evaluating for a pinned tab; used only if
+ * aCheckPrivacy
+ */
+ _extractHostsForCookiesFromEntry:
+ function ssi_extractHostsForCookiesFromEntry(aEntry, aHosts, aCheckPrivacy, aIsPinned) {
+
+ let host = aEntry._host,
+ scheme = aEntry._scheme;
+
+ // If host & scheme aren't defined, then we are likely here in the startup
+ // process via _splitCookiesFromWindow. In that case, we'll turn aEntry.url
+ // into an nsIURI and get host/scheme from that. This will throw for about:
+ // urls in which case we don't need to do anything.
+ if (!host && !scheme) {
+ try {
+ let uri = this._getURIFromString(aEntry.url);
+ host = uri.host;
+ scheme = uri.scheme;
+ this._extractHostsForCookiesFromHostScheme(host, scheme, aHosts, aCheckPrivacy, aIsPinned);
+ }
+ catch(ex) { }
+ }
+
+ if (aEntry.children) {
+ aEntry.children.forEach(function(entry) {
+ this._extractHostsForCookiesFromEntry(entry, aHosts, aCheckPrivacy, aIsPinned);
+ }, this);
+ }
+ },
+
+ /**
+ * extract the base domain from a host & scheme
+ * @param aHost
+ * the host of a uri (usually via nsIURI.host)
+ * @param aScheme
+ * the scheme of a uri (usually via nsIURI.scheme)
+ * @param aHosts
+ * the hash that will be used to store hosts eg, { hostname: true }
+ * @param aCheckPrivacy
+ * should we check the privacy level for https
+ * @param aIsPinned
+ * is the entry we're evaluating for a pinned tab; used only if
+ * aCheckPrivacy
+ */
+ _extractHostsForCookiesFromHostScheme:
+ function ssi_extractHostsForCookiesFromHostScheme(aHost, aScheme, aHosts, aCheckPrivacy, aIsPinned) {
+ // host and scheme may not be set (for about: urls for example), in which
+ // case testing scheme will be sufficient.
+ if (/https?/.test(aScheme) && !aHosts[aHost] &&
+ (!aCheckPrivacy ||
+ this.checkPrivacyLevel(aScheme == "https", aIsPinned))) {
+ // By setting this to true or false, we can determine when looking at
+ // the host in _updateCookies if we should check for privacy.
+ aHosts[aHost] = aIsPinned;
+ }
+ else if (aScheme == "file") {
+ aHosts[aHost] = true;
+ }
+ },
+
+ /**
+ * store all hosts for a URL
+ * @param aWindow
+ * Window reference
+ */
+ _updateCookieHosts: function ssi_updateCookieHosts(aWindow) {
+ var hosts = this._internalWindows[aWindow.__SSi].hosts = {};
+
+ // Since _updateCookiesHosts is only ever called for open windows during a
+ // session, we can call into _extractHostsForCookiesFromHostScheme directly
+ // using data that is attached to each browser.
+ for (let i = 0; i < aWindow.gBrowser.tabs.length; i++) {
+ let tab = aWindow.gBrowser.tabs[i];
+ let hostSchemeData = tab.linkedBrowser.__SS_hostSchemeData || [];
+ for (let j = 0; j < hostSchemeData.length; j++) {
+ this._extractHostsForCookiesFromHostScheme(hostSchemeData[j].host,
+ hostSchemeData[j].scheme,
+ hosts, true, tab.pinned);
+ }
+ }
+ },
+
+ /**
+ * Serialize cookie data
+ * @param aWindows
+ * JS object containing window data references
+ * { id: winData, etc. }
+ */
+ _updateCookies: function ssi_updateCookies(aWindows) {
+ function addCookieToHash(aHash, aHost, aPath, aName, aCookie) {
+ // lazily build up a 3-dimensional hash, with
+ // aHost, aPath, and aName as keys
+ if (!aHash[aHost])
+ aHash[aHost] = {};
+ if (!aHash[aHost][aPath])
+ aHash[aHost][aPath] = {};
+ aHash[aHost][aPath][aName] = aCookie;
+ }
+
+ var jscookies = {};
+ var _this = this;
+ // MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision
+ var MAX_EXPIRY = Math.pow(2, 62);
+
+ for (let [id, window] in Iterator(aWindows)) {
+ window.cookies = [];
+ let internalWindow = this._internalWindows[id];
+ if (!internalWindow.hosts)
+ return;
+ for (var [host, isPinned] in Iterator(internalWindow.hosts)) {
+ let list;
+ try {
+ list = Services.cookies.getCookiesFromHost(host, {});
+ }
+ catch (ex) {
+ debug("getCookiesFromHost failed. Host: " + host);
+ }
+ while (list && list.hasMoreElements()) {
+ var cookie = list.getNext().QueryInterface(Ci.nsICookie2);
+ // window._hosts will only have hosts with the right privacy rules,
+ // so there is no need to do anything special with this call to
+ // checkPrivacyLevel.
+ if (cookie.isSession && _this.checkPrivacyLevel(cookie.isSecure, isPinned)) {
+ // use the cookie's host, path, and name as keys into a hash,
+ // to make sure we serialize each cookie only once
+ if (!(cookie.host in jscookies &&
+ cookie.path in jscookies[cookie.host] &&
+ cookie.name in jscookies[cookie.host][cookie.path])) {
+ var jscookie = { "host": cookie.host, "value": cookie.value };
+ // only add attributes with non-default values (saving a few bits)
+ if (cookie.path) jscookie.path = cookie.path;
+ if (cookie.name) jscookie.name = cookie.name;
+ if (cookie.isSecure) jscookie.secure = true;
+ if (cookie.isHttpOnly) jscookie.httponly = true;
+ if (cookie.expiry < MAX_EXPIRY) jscookie.expiry = cookie.expiry;
+
+ addCookieToHash(jscookies, cookie.host, cookie.path, cookie.name, jscookie);
+ }
+ window.cookies.push(jscookies[cookie.host][cookie.path][cookie.name]);
+ }
+ }
+ }
+
+ // don't include empty cookie sections
+ if (!window.cookies.length)
+ delete window.cookies;
+ }
+ },
+
+ /**
+ * Store window dimensions, visibility, sidebar
+ * @param aWindow
+ * Window reference
+ */
+ _updateWindowFeatures: function ssi_updateWindowFeatures(aWindow) {
+ var winData = this._windows[aWindow.__SSi];
+
+ WINDOW_ATTRIBUTES.forEach(function(aAttr) {
+ winData[aAttr] = this._getWindowDimension(aWindow, aAttr);
+ }, this);
+
+ var hidden = WINDOW_HIDEABLE_FEATURES.filter(function(aItem) {
+ return aWindow[aItem] && !aWindow[aItem].visible;
+ });
+ if (hidden.length != 0)
+ winData.hidden = hidden.join(",");
+ else if (winData.hidden)
+ delete winData.hidden;
+
+ var sidebar = aWindow.document.getElementById("sidebar-box").getAttribute("sidebarcommand");
+ if (sidebar)
+ winData.sidebar = sidebar;
+ else if (winData.sidebar)
+ delete winData.sidebar;
+ },
+
+ /**
+ * gather session data as object
+ * @param aUpdateAll
+ * Bool update all windows
+ * @param aPinnedOnly
+ * Bool collect pinned tabs only
+ * @returns object
+ */
+ _getCurrentState: function ssi_getCurrentState(aUpdateAll, aPinnedOnly) {
+ this._handleClosedWindows();
+
+ var activeWindow = this._getMostRecentBrowserWindow();
+
+ if (this._loadState == STATE_RUNNING) {
+ // update the data for all windows with activities since the last save operation
+ this._forEachBrowserWindow(function(aWindow) {
+ if (!this._isWindowLoaded(aWindow)) // window data is still in _statesToRestore
+ return;
+ if (aUpdateAll || this._dirtyWindows[aWindow.__SSi] || aWindow == activeWindow) {
+ this._collectWindowData(aWindow);
+ }
+ else { // always update the window features (whose change alone never triggers a save operation)
+ this._updateWindowFeatures(aWindow);
+ }
+ });
+ this._dirtyWindows = [];
+ }
+
+ // collect the data for all windows
+ var total = [], windows = {}, ids = [];
+ var nonPopupCount = 0;
+ var ix;
+ for (ix in this._windows) {
+ if (this._windows[ix]._restoring) // window data is still in _statesToRestore
+ continue;
+ total.push(this._windows[ix]);
+ ids.push(ix);
+ windows[ix] = this._windows[ix];
+ if (!this._windows[ix].isPopup)
+ nonPopupCount++;
+ }
+ this._updateCookies(windows);
+
+ // collect the data for all windows yet to be restored
+ for (ix in this._statesToRestore) {
+ for each (let winData in this._statesToRestore[ix].windows) {
+ total.push(winData);
+ if (!winData.isPopup)
+ nonPopupCount++;
+ }
+ }
+
+ // shallow copy this._closedWindows to preserve current state
+ let lastClosedWindowsCopy = this._closedWindows.slice();
+
+#ifndef XP_MACOSX
+ // If no non-popup browser window remains open, return the state of the last
+ // closed window(s). We only want to do this when we're actually "ending"
+ // the session.
+ //XXXzpao We should do this for _restoreLastWindow == true, but that has
+ // its own check for popups. c.f. bug 597619
+ if (nonPopupCount == 0 && lastClosedWindowsCopy.length > 0 &&
+ this._loadState == STATE_QUITTING) {
+ // prepend the last non-popup browser window, so that if the user loads more tabs
+ // at startup we don't accidentally add them to a popup window
+ do {
+ total.unshift(lastClosedWindowsCopy.shift())
+ } while (total[0].isPopup && lastClosedWindowsCopy.length > 0)
+ }
+#endif
+
+ if (aPinnedOnly) {
+ // perform a deep copy so that existing session variables are not changed.
+ total = JSON.parse(this._toJSONString(total));
+ total = total.filter(function (win) {
+ win.tabs = win.tabs.filter(function (tab) tab.pinned);
+ // remove closed tabs
+ win._closedTabs = [];
+ // correct selected tab index if it was stripped out
+ if (win.selected > win.tabs.length)
+ win.selected = 1;
+ return win.tabs.length > 0;
+ });
+ if (total.length == 0)
+ return null;
+
+ lastClosedWindowsCopy = [];
+ }
+
+ if (activeWindow) {
+ this.activeWindowSSiCache = activeWindow.__SSi || "";
+ }
+ ix = ids.indexOf(this.activeWindowSSiCache);
+ // We don't want to restore focus to a minimized window or a window which had all its
+ // tabs stripped out (doesn't exist).
+ if (ix != -1 && total[ix] && total[ix].sizemode == "minimized")
+ ix = -1;
+
+ let session = {
+ state: this._loadState == STATE_RUNNING ? STATE_RUNNING_STR : STATE_STOPPED_STR,
+ lastUpdate: Date.now(),
+ startTime: this._sessionStartTime,
+ recentCrashes: this._recentCrashes
+ };
+
+ var scratchpads = null;
+ var browserConsole = null;
+#ifdef MOZ_DEVTOOLS
+ // Scratchpad
+ // get open Scratchpad window states too
+ scratchpads = ScratchpadManager.getSessionState();
+
+ // The Browser Console
+ browserConsole = HUDService.getBrowserConsoleSessionState();
+#endif
+
+ return {
+ windows: total,
+ selectedWindow: ix + 1,
+ _closedWindows: lastClosedWindowsCopy,
+#ifdef MOZ_DEVTOOLS
+ session: session,
+ scratchpads: scratchpads,
+ browserConsole: browserConsole
+#else
+ session: session
+#endif
+ };
+ },
+
+ /**
+ * serialize session data for a window
+ * @param aWindow
+ * Window reference
+ * @returns string
+ */
+ _getWindowState: function ssi_getWindowState(aWindow) {
+ if (!this._isWindowLoaded(aWindow))
+ return this._statesToRestore[aWindow.__SS_restoreID];
+
+ if (this._loadState == STATE_RUNNING) {
+ this._collectWindowData(aWindow);
+ }
+
+ var winData = this._windows[aWindow.__SSi];
+ let windows = {};
+ windows[aWindow.__SSi] = winData;
+ this._updateCookies(windows);
+
+ return { windows: [winData] };
+ },
+
+ _collectWindowData: function ssi_collectWindowData(aWindow) {
+ if (!this._isWindowLoaded(aWindow))
+ return;
+
+ // update the internal state data for this window
+ this._saveWindowHistory(aWindow);
+ this._updateTextAndScrollData(aWindow);
+ this._updateCookieHosts(aWindow);
+ this._updateWindowFeatures(aWindow);
+
+ // Make sure we keep __SS_lastSessionWindowID around for cases like entering
+ // or leaving PB mode.
+ if (aWindow.__SS_lastSessionWindowID)
+ this._windows[aWindow.__SSi].__lastSessionWindowID =
+ aWindow.__SS_lastSessionWindowID;
+
+ this._dirtyWindows[aWindow.__SSi] = false;
+ },
+
+ /* ........ Restoring Functionality .............. */
+
+ /**
+ * restore features to a single window
+ * @param aWindow
+ * Window reference
+ * @param aState
+ * JS object or its eval'able source
+ * @param aOverwriteTabs
+ * bool overwrite existing tabs w/ new ones
+ * @param aFollowUp
+ * bool this isn't the restoration of the first window
+ */
+ restoreWindow: function ssi_restoreWindow(aWindow, aState, aOverwriteTabs, aFollowUp) {
+ if (!aFollowUp) {
+ this.windowToFocus = aWindow;
+ }
+ // initialize window if necessary
+ if (aWindow && (!aWindow.__SSi || !this._windows[aWindow.__SSi]))
+ this.onLoad(aWindow);
+
+ try {
+ var root = typeof aState == "string" ? JSON.parse(aState) : aState;
+ if (!root.windows[0]) {
+ this._sendRestoreCompletedNotifications();
+ return; // nothing to restore
+ }
+ }
+ catch (ex) { // invalid state object - don't restore anything
+ debug(ex);
+ this._sendRestoreCompletedNotifications();
+ return;
+ }
+
+ // We're not returning from this before we end up calling restoreHistoryPrecursor
+ // for this window, so make sure we send the SSWindowStateBusy event.
+ this._setWindowStateBusy(aWindow);
+
+ if (root._closedWindows)
+ this._closedWindows = root._closedWindows;
+
+ var winData;
+ if (!root.selectedWindow || root.selectedWindow > root.windows.length) {
+ root.selectedWindow = 0;
+ }
+
+ // open new windows for all further window entries of a multi-window session
+ // (unless they don't contain any tab data)
+ for (var w = 1; w < root.windows.length; w++) {
+ winData = root.windows[w];
+ if (winData && winData.tabs && winData.tabs[0]) {
+ var window = this._openWindowWithState({ windows: [winData] });
+ if (w == root.selectedWindow - 1) {
+ this.windowToFocus = window;
+ }
+ }
+ }
+ winData = root.windows[0];
+ if (!winData.tabs) {
+ winData.tabs = [];
+ }
+ // don't restore a single blank tab when we've had an external
+ // URL passed in for loading at startup (cf. bug 357419)
+ else if (root._firstTabs && !aOverwriteTabs && winData.tabs.length == 1 &&
+ (!winData.tabs[0].entries || winData.tabs[0].entries.length == 0)) {
+ winData.tabs = [];
+ }
+
+ var tabbrowser = aWindow.gBrowser;
+ var openTabCount = aOverwriteTabs ? tabbrowser.browsers.length : -1;
+ var newTabCount = winData.tabs.length;
+ var tabs = [];
+
+ // disable smooth scrolling while adding, moving, removing and selecting tabs
+ var tabstrip = tabbrowser.tabContainer.mTabstrip;
+ var smoothScroll = tabstrip.smoothScroll;
+ tabstrip.smoothScroll = false;
+
+ // unpin all tabs to ensure they are not reordered in the next loop
+ if (aOverwriteTabs) {
+ for (let t = tabbrowser._numPinnedTabs - 1; t > -1; t--)
+ tabbrowser.unpinTab(tabbrowser.tabs[t]);
+ }
+
+ // make sure that the selected tab won't be closed in order to
+ // prevent unnecessary flickering
+ if (aOverwriteTabs && tabbrowser.selectedTab._tPos >= newTabCount)
+ tabbrowser.moveTabTo(tabbrowser.selectedTab, newTabCount - 1);
+
+ let numVisibleTabs = 0;
+
+ for (var t = 0; t < newTabCount; t++) {
+ tabs.push(t < openTabCount ?
+ tabbrowser.tabs[t] :
+ tabbrowser.addTab("about:blank",
+ {skipAnimation: true,
+ skipBackgroundNotify: true}));
+ // when resuming at startup: add additionally requested pages to the end
+ if (!aOverwriteTabs && root._firstTabs) {
+ tabbrowser.moveTabTo(tabs[t], t);
+ }
+
+ if (winData.tabs[t].pinned)
+ tabbrowser.pinTab(tabs[t]);
+
+ if (winData.tabs[t].hidden) {
+ tabbrowser.hideTab(tabs[t]);
+ }
+ else {
+ tabbrowser.showTab(tabs[t]);
+ numVisibleTabs++;
+ }
+ }
+
+ // if all tabs to be restored are hidden, make the first one visible
+ if (!numVisibleTabs && winData.tabs.length) {
+ winData.tabs[0].hidden = false;
+ tabbrowser.showTab(tabs[0]);
+ }
+
+ // If overwriting tabs, we want to reset each tab's "restoring" state. Since
+ // we're overwriting those tabs, they should no longer be restoring. The
+ // tabs will be rebuilt and marked if they need to be restored after loading
+ // state (in restoreHistoryPrecursor).
+ if (aOverwriteTabs) {
+ for (let i = 0; i < tabbrowser.tabs.length; i++) {
+ if (tabbrowser.browsers[i].__SS_restoreState)
+ this._resetTabRestoringState(tabbrowser.tabs[i]);
+ }
+ }
+
+ // We want to set up a counter on the window that indicates how many tabs
+ // in this window are unrestored. This will be used in restoreNextTab to
+ // determine if gRestoreTabsProgressListener should be removed from the window.
+ // If we aren't overwriting existing tabs, then we want to add to the existing
+ // count in case there are still tabs restoring.
+ if (!aWindow.__SS_tabsToRestore)
+ aWindow.__SS_tabsToRestore = 0;
+ if (aOverwriteTabs)
+ aWindow.__SS_tabsToRestore = newTabCount;
+ else
+ aWindow.__SS_tabsToRestore += newTabCount;
+
+ // We want to correlate the window with data from the last session, so
+ // assign another id if we have one. Otherwise clear so we don't do
+ // anything with it.
+ delete aWindow.__SS_lastSessionWindowID;
+ if (winData.__lastSessionWindowID)
+ aWindow.__SS_lastSessionWindowID = winData.__lastSessionWindowID;
+
+ // when overwriting tabs, remove all superflous ones
+ if (aOverwriteTabs && newTabCount < openTabCount) {
+ Array.slice(tabbrowser.tabs, newTabCount, openTabCount)
+ .forEach(tabbrowser.removeTab, tabbrowser);
+ }
+
+ if (aOverwriteTabs) {
+ this.restoreWindowFeatures(aWindow, winData);
+ delete this._windows[aWindow.__SSi].extData;
+ }
+ if (winData.cookies) {
+ this.restoreCookies(winData.cookies);
+ }
+ if (winData.extData) {
+ if (!this._windows[aWindow.__SSi].extData) {
+ this._windows[aWindow.__SSi].extData = {};
+ }
+ for (var key in winData.extData) {
+ this._windows[aWindow.__SSi].extData[key] = winData.extData[key];
+ }
+ }
+ if (aOverwriteTabs || root._firstTabs) {
+ this._windows[aWindow.__SSi]._closedTabs = winData._closedTabs || [];
+ }
+
+ this.restoreHistoryPrecursor(aWindow, tabs, winData.tabs,
+ (aOverwriteTabs ? (parseInt(winData.selected) || 1) : 0), 0, 0);
+
+#ifdef MOZ_DEVTOOLS
+ if (aState.scratchpads) {
+ ScratchpadManager.restoreSession(aState.scratchpads);
+ }
+
+ // The Browser Console
+ if (aState.browserConsole) {
+ HUDService.restoreBrowserConsoleSession();
+ }
+
+#endif
+ // set smoothScroll back to the original value
+ tabstrip.smoothScroll = smoothScroll;
+
+ this._sendRestoreCompletedNotifications();
+ },
+
+ /**
+ * Sets the tabs restoring order with the following priority:
+ * Selected tab, pinned tabs, optimized visible tabs, other visible tabs and
+ * hidden tabs.
+ * @param aTabBrowser
+ * Tab browser object
+ * @param aTabs
+ * Array of tab references
+ * @param aTabData
+ * Array of tab data
+ * @param aSelectedTab
+ * Index of selected tab (1 is first tab, 0 no selected tab)
+ */
+ _setTabsRestoringOrder : function ssi__setTabsRestoringOrder(
+ aTabBrowser, aTabs, aTabData, aSelectedTab) {
+
+ // Store the selected tab. Need to substract one to get the index in aTabs.
+ let selectedTab;
+ if (aSelectedTab > 0 && aTabs[aSelectedTab - 1]) {
+ selectedTab = aTabs[aSelectedTab - 1];
+ }
+
+ // Store the pinned tabs and hidden tabs.
+ let pinnedTabs = [];
+ let pinnedTabsData = [];
+ let hiddenTabs = [];
+ let hiddenTabsData = [];
+ if (aTabs.length > 1) {
+ for (let t = aTabs.length - 1; t >= 0; t--) {
+ if (aTabData[t].pinned) {
+ pinnedTabs.unshift(aTabs.splice(t, 1)[0]);
+ pinnedTabsData.unshift(aTabData.splice(t, 1)[0]);
+ } else if (aTabData[t].hidden) {
+ hiddenTabs.unshift(aTabs.splice(t, 1)[0]);
+ hiddenTabsData.unshift(aTabData.splice(t, 1)[0]);
+ }
+ }
+ }
+
+ // Optimize the visible tabs only if there is a selected tab.
+ if (selectedTab) {
+ let selectedTabIndex = aTabs.indexOf(selectedTab);
+ if (selectedTabIndex > 0) {
+ let scrollSize = aTabBrowser.tabContainer.mTabstrip.scrollClientSize;
+ let tabWidth = aTabs[0].getBoundingClientRect().width;
+ let maxVisibleTabs = Math.ceil(scrollSize / tabWidth);
+ if (maxVisibleTabs < aTabs.length) {
+ let firstVisibleTab = 0;
+ let nonVisibleTabsCount = aTabs.length - maxVisibleTabs;
+ if (nonVisibleTabsCount >= selectedTabIndex) {
+ // Selected tab is leftmost since we scroll to it when possible.
+ firstVisibleTab = selectedTabIndex;
+ } else {
+ // Selected tab is rightmost or no more room to scroll right.
+ firstVisibleTab = nonVisibleTabsCount;
+ }
+ aTabs = aTabs.splice(firstVisibleTab, maxVisibleTabs).concat(aTabs);
+ aTabData =
+ aTabData.splice(firstVisibleTab, maxVisibleTabs).concat(aTabData);
+ }
+ }
+ }
+
+ // Merge the stored tabs in order.
+ aTabs = pinnedTabs.concat(aTabs, hiddenTabs);
+ aTabData = pinnedTabsData.concat(aTabData, hiddenTabsData);
+
+ // Load the selected tab to the first position and select it.
+ if (selectedTab) {
+ let selectedTabIndex = aTabs.indexOf(selectedTab);
+ if (selectedTabIndex > 0) {
+ aTabs = aTabs.splice(selectedTabIndex, 1).concat(aTabs);
+ aTabData = aTabData.splice(selectedTabIndex, 1).concat(aTabData);
+ }
+ aTabBrowser.selectedTab = selectedTab;
+ }
+
+ return [aTabs, aTabData];
+ },
+
+ /**
+ * Manage history restoration for a window
+ * @param aWindow
+ * Window to restore the tabs into
+ * @param aTabs
+ * Array of tab references
+ * @param aTabData
+ * Array of tab data
+ * @param aSelectTab
+ * Index of selected tab
+ * @param aIx
+ * Index of the next tab to check readyness for
+ * @param aCount
+ * Counter for number of times delaying b/c browser or history aren't ready
+ * @param aRestoreImmediately
+ * Flag to indicate whether the given set of tabs aTabs should be
+ * restored/loaded immediately even if restore_on_demand = true
+ */
+ restoreHistoryPrecursor:
+ function ssi_restoreHistoryPrecursor(aWindow, aTabs, aTabData, aSelectTab,
+ aIx, aCount, aRestoreImmediately = false) {
+ var tabbrowser = aWindow.gBrowser;
+
+ // make sure that all browsers and their histories are available
+ // - if one's not, resume this check in 100ms (repeat at most 10 times)
+ for (var t = aIx; t < aTabs.length; t++) {
+ try {
+ if (!tabbrowser.getBrowserForTab(aTabs[t]).webNavigation.sessionHistory) {
+ throw new Error();
+ }
+ }
+ catch (ex) { // in case browser or history aren't ready yet
+ if (aCount < 10) {
+ var restoreHistoryFunc = function(self) {
+ self.restoreHistoryPrecursor(aWindow, aTabs, aTabData, aSelectTab,
+ aIx, aCount + 1, aRestoreImmediately);
+ }
+ aWindow.setTimeout(restoreHistoryFunc, 100, this);
+ return;
+ }
+ }
+ }
+
+ if (!this._isWindowLoaded(aWindow)) {
+ // from now on, the data will come from the actual window
+ delete this._statesToRestore[aWindow.__SS_restoreID];
+ delete aWindow.__SS_restoreID;
+ delete this._windows[aWindow.__SSi]._restoring;
+
+ // It's important to set the window state to dirty so that
+ // we collect their data for the first time when saving state.
+ this._dirtyWindows[aWindow.__SSi] = true;
+ }
+
+ if (aTabs.length == 0) {
+ // this is normally done in restoreHistory() but as we're returning early
+ // here we need to take care of it.
+ this._setWindowStateReady(aWindow);
+ return;
+ }
+
+ // Sets the tabs restoring order.
+ [aTabs, aTabData] =
+ this._setTabsRestoringOrder(tabbrowser, aTabs, aTabData, aSelectTab);
+
+ // Prepare the tabs so that they can be properly restored. We'll pin/unpin
+ // and show/hide tabs as necessary. We'll also set the labels, user typed
+ // value, and attach a copy of the tab's data in case we close it before
+ // it's been restored.
+ for (t = 0; t < aTabs.length; t++) {
+ let tab = aTabs[t];
+ let browser = tabbrowser.getBrowserForTab(tab);
+ let tabData = aTabData[t];
+
+ if (tabData.pinned)
+ tabbrowser.pinTab(tab);
+ else
+ tabbrowser.unpinTab(tab);
+
+ if (tabData.hidden)
+ tabbrowser.hideTab(tab);
+ else
+ tabbrowser.showTab(tab);
+
+ if ("attributes" in tabData) {
+ // Ensure that we persist tab attributes restored from previous sessions.
+ Object.keys(tabData.attributes).forEach(a => TabAttributes.persist(a));
+ }
+
+ browser.__SS_tabStillLoading = true;
+
+ // keep the data around to prevent dataloss in case
+ // a tab gets closed before it's been properly restored
+ browser.__SS_data = tabData;
+ browser.__SS_restoreState = TAB_STATE_NEEDS_RESTORE;
+ browser.setAttribute("pending", "true");
+ tab.setAttribute("pending", "true");
+
+ // Make sure that set/getTabValue will set/read the correct data by
+ // wiping out any current value in tab.__SS_extdata.
+ delete tab.__SS_extdata;
+
+ if (!tabData.entries || tabData.entries.length == 0) {
+ // make sure to blank out this tab's content
+ // (just purging the tab's history won't be enough)
+ browser.contentDocument.location = "about:blank";
+ continue;
+ }
+
+ browser.stop(); // in case about:blank isn't done yet
+
+ // wall-paper fix for bug 439675: make sure that the URL to be loaded
+ // is always visible in the address bar
+ let activeIndex = (tabData.index || tabData.entries.length) - 1;
+ let activePageData = tabData.entries[activeIndex] || null;
+ let uri = activePageData ? activePageData.url || null : null;
+ browser.userTypedValue = uri;
+
+ // Also make sure currentURI is set so that switch-to-tab works before
+ // the tab is restored. We'll reset this to about:blank when we try to
+ // restore the tab to ensure that docshell doeesn't get confused.
+ if (uri)
+ browser.docShell.setCurrentURI(this._getURIFromString(uri));
+
+ // If the page has a title, set it.
+ if (activePageData) {
+ if (activePageData.title) {
+ tab.label = activePageData.title;
+ tab.crop = "end";
+ } else if (activePageData.url != "about:blank") {
+ tab.label = activePageData.url;
+ tab.crop = "center";
+ }
+ }
+ }
+
+ // helper hashes for ensuring unique frame IDs and unique document
+ // identifiers.
+ var idMap = { used: {} };
+ var docIdentMap = {};
+ this.restoreHistory(aWindow, aTabs, aTabData, idMap, docIdentMap,
+ aRestoreImmediately);
+ },
+
+ /**
+ * Restore history for a window
+ * @param aWindow
+ * Window reference
+ * @param aTabs
+ * Array of tab references
+ * @param aTabData
+ * Array of tab data
+ * @param aIdMap
+ * Hash for ensuring unique frame IDs
+ * @param aRestoreImmediately
+ * Flag to indicate whether the given set of tabs aTabs should be
+ * restored/loaded immediately even if restore_on_demand = true
+ */
+ restoreHistory:
+ function ssi_restoreHistory(aWindow, aTabs, aTabData, aIdMap, aDocIdentMap,
+ aRestoreImmediately) {
+ var _this = this;
+ // if the tab got removed before being completely restored, then skip it
+ while (aTabs.length > 0 && !(this._canRestoreTabHistory(aTabs[0]))) {
+ aTabs.shift();
+ aTabData.shift();
+ }
+ if (aTabs.length == 0) {
+ // At this point we're essentially ready for consumers to read/write data
+ // via the sessionstore API so we'll send the SSWindowStateReady event.
+ this._setWindowStateReady(aWindow);
+ return; // no more tabs to restore
+ }
+
+ var tab = aTabs.shift();
+ var tabData = aTabData.shift();
+
+ var browser = aWindow.gBrowser.getBrowserForTab(tab);
+ var history = browser.webNavigation.sessionHistory;
+
+ if (history.count > 0) {
+ history.PurgeHistory(history.count);
+ }
+ history.QueryInterface(Ci.nsISHistoryInternal);
+
+ browser.__SS_shistoryListener = new SessionStoreSHistoryListener(tab);
+ history.addSHistoryListener(browser.__SS_shistoryListener);
+
+ if (!tabData.entries) {
+ tabData.entries = [];
+ }
+ if (tabData.extData) {
+ tab.__SS_extdata = {};
+ for (let key in tabData.extData)
+ tab.__SS_extdata[key] = tabData.extData[key];
+ }
+ else
+ delete tab.__SS_extdata;
+
+ for (var i = 0; i < tabData.entries.length; i++) {
+ //XXXzpao Wallpaper patch for bug 514751
+ if (!tabData.entries[i].url)
+ continue;
+ history.addEntry(this._deserializeHistoryEntry(tabData.entries[i],
+ aIdMap, aDocIdentMap), true);
+ }
+
+ // make sure to reset the capabilities and attributes, in case this tab gets reused
+ let disallow = new Set(tabData.disallow && tabData.disallow.split(","));
+ for (let cap of gDocShellCapabilities(browser.docShell))
+ browser.docShell["allow" + cap] = !disallow.has(cap);
+
+ // Restore tab attributes.
+ if ("attributes" in tabData) {
+ TabAttributes.set(tab, tabData.attributes);
+ }
+
+ // Restore the tab icon.
+ if ("image" in tabData) {
+ // Using null as the loadingPrincipal because serializing
+ // the principal would be overkill. Within SetIcon we
+ // default to the systemPrincipal if aLoadingPrincipal is
+ // null which will allow the favicon to load.
+ aWindow.gBrowser.setIcon(tab, tabData.image, null);
+ }
+
+ if (tabData.storage && browser.docShell instanceof Ci.nsIDocShell)
+ SessionStorage.deserialize(browser.docShell, tabData.storage);
+
+ // notify the tabbrowser that the tab chrome has been restored
+ var event = aWindow.document.createEvent("Events");
+ event.initEvent("SSTabRestoring", true, false);
+ tab.dispatchEvent(event);
+
+ // Restore the history in the next tab
+ aWindow.setTimeout(function(){
+ _this.restoreHistory(aWindow, aTabs, aTabData, aIdMap, aDocIdentMap,
+ aRestoreImmediately);
+ }, 0);
+
+ // This could cause us to ignore max_concurrent_tabs pref a bit, but
+ // it ensures each window will have its selected tab loaded.
+ if (aRestoreImmediately || aWindow.gBrowser.selectedBrowser == browser) {
+ this.restoreTab(tab);
+ }
+ else {
+ TabRestoreQueue.add(tab);
+ this.restoreNextTab();
+ }
+ },
+
+ /**
+ * Restores the specified tab. If the tab can't be restored (eg, no history or
+ * calling gotoIndex fails), then state changes will be rolled back.
+ * This method will check if gTabsProgressListener is attached to the tab's
+ * window, ensuring that we don't get caught without one.
+ * This method removes the session history listener right before starting to
+ * attempt a load. This will prevent cases of "stuck" listeners.
+ * If this method returns false, then it is up to the caller to decide what to
+ * do. In the common case (restoreNextTab), we will want to then attempt to
+ * restore the next tab. In the other case (selecting the tab, reloading the
+ * tab), the caller doesn't actually want to do anything if no page is loaded.
+ *
+ * @param aTab
+ * the tab to restore
+ *
+ * @returns true/false indicating whether or not a load actually happened
+ */
+ restoreTab: function ssi_restoreTab(aTab) {
+ let window = aTab.ownerDocument.defaultView;
+ let browser = aTab.linkedBrowser;
+ let tabData = browser.__SS_data;
+
+ // There are cases within where we haven't actually started a load. In that
+ // that case we'll reset state changes we made and return false to the caller
+ // can handle appropriately.
+ let didStartLoad = false;
+
+ // Make sure that the tabs progress listener is attached to this window
+ this._ensureTabsProgressListener(window);
+
+ // Make sure that this tab is removed from the priority queue.
+ TabRestoreQueue.remove(aTab);
+
+ // Increase our internal count.
+ this._tabsRestoringCount++;
+
+ // Set this tab's state to restoring
+ browser.__SS_restoreState = TAB_STATE_RESTORING;
+ browser.removeAttribute("pending");
+ aTab.removeAttribute("pending");
+
+ // Remove the history listener, since we no longer need it once we start restoring
+ this._removeSHistoryListener(aTab);
+
+ let activeIndex = (tabData.index || tabData.entries.length) - 1;
+ if (activeIndex >= tabData.entries.length)
+ activeIndex = tabData.entries.length - 1;
+ // Reset currentURI. This creates a new session history entry with a new
+ // doc identifier, so we need to explicitly save and restore the old doc
+ // identifier (corresponding to the SHEntry at activeIndex) below.
+ browser.webNavigation.setCurrentURI(this._getURIFromString("about:blank"));
+ // Attach data that will be restored on "load" event, after tab is restored.
+ if (activeIndex > -1) {
+ // restore those aspects of the currently active documents which are not
+ // preserved in the plain history entries (mainly scroll state and text data)
+ browser.__SS_restore_data = tabData.entries[activeIndex] || {};
+ browser.__SS_restore_pageStyle = tabData.pageStyle || "";
+ browser.__SS_restore_tab = aTab;
+ didStartLoad = true;
+ try {
+ // In order to work around certain issues in session history, we need to
+ // force session history to update its internal index and call reload
+ // instead of gotoIndex. See bug 597315.
+ browser.webNavigation.sessionHistory.getEntryAtIndex(activeIndex, true);
+ browser.webNavigation.sessionHistory.reloadCurrentEntry();
+ // If the user prefers it, bypass cache and always load from the network.
+ let flags = Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
+ switch (this._cacheBehavior) {
+ case 2: // hard refresh
+ flags = Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_PROXY |
+ Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
+ browser.webNavigation.reload(flags);
+ break;
+ case 1: // soft refresh
+ browser.webNavigation.reload(flags);
+ break;
+ default: // 0 or other: use cache, so do nothing.
+ break;
+ }
+ }
+ catch (ex) {
+ // ignore page load errors
+ aTab.removeAttribute("busy");
+ didStartLoad = false;
+ }
+ }
+
+ // Handle userTypedValue. Setting userTypedValue seems to update gURLbar
+ // as needed. Calling loadURI will cancel form filling in restoreDocument
+ if (tabData.userTypedValue) {
+ browser.userTypedValue = tabData.userTypedValue;
+ if (tabData.userTypedClear) {
+ // Make it so that we'll enter restoreDocument on page load. We will
+ // fire SSTabRestored from there. We don't have any form data to restore
+ // so we can just set the URL to null.
+ browser.__SS_restore_data = { url: null };
+ browser.__SS_restore_tab = aTab;
+ if (didStartLoad)
+ browser.stop();
+ didStartLoad = true;
+ browser.loadURIWithFlags(tabData.userTypedValue,
+ Ci.nsIWebNavigation.LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP);
+ }
+ }
+
+ // If we didn't start a load, then we won't reset this tab through the usual
+ // channel (via the progress listener), so reset the tab ourselves. We will
+ // also send SSTabRestored since this tab has technically been restored.
+ if (!didStartLoad) {
+ this._sendTabRestoredNotification(aTab);
+ this._resetTabRestoringState(aTab);
+ }
+
+ return didStartLoad;
+ },
+
+ /**
+ * This _attempts_ to restore the next available tab. If the restore fails,
+ * then we will attempt the next one.
+ * There are conditions where this won't do anything:
+ * if we're in the process of quitting
+ * if there are no tabs to restore
+ * if we have already reached the limit for number of tabs to restore
+ */
+ restoreNextTab: function ssi_restoreNextTab() {
+ // If we call in here while quitting, we don't actually want to do anything
+ if (this._loadState == STATE_QUITTING)
+ return;
+
+ // Don't exceed the maximum number of concurrent tab restores.
+ if (this._tabsRestoringCount >= this._maxConcurrentTabRestores)
+ return;
+
+ let tab = TabRestoreQueue.shift();
+ if (tab) {
+ let didStartLoad = this.restoreTab(tab);
+ // If we don't start a load in the restored tab (eg, no entries) then we
+ // want to attempt to restore the next tab.
+ if (!didStartLoad)
+ this.restoreNextTab();
+ }
+ },
+
+ /**
+ * expands serialized history data into a session-history-entry instance
+ * @param aEntry
+ * Object containing serialized history data for a URL
+ * @param aIdMap
+ * Hash for ensuring unique frame IDs
+ * @returns nsISHEntry
+ */
+ _deserializeHistoryEntry:
+ function ssi_deserializeHistoryEntry(aEntry, aIdMap, aDocIdentMap) {
+
+ var shEntry = Cc["@mozilla.org/browser/session-history-entry;1"].
+ createInstance(Ci.nsISHEntry);
+
+ shEntry.setURI(this._getURIFromString(aEntry.url));
+ shEntry.setTitle(aEntry.title || aEntry.url);
+ if (aEntry.subframe)
+ shEntry.setIsSubFrame(aEntry.subframe || false);
+ shEntry.loadType = Ci.nsIDocShellLoadInfo.loadHistory;
+ if (aEntry.contentType)
+ shEntry.contentType = aEntry.contentType;
+ if (aEntry.referrer)
+ shEntry.referrerURI = this._getURIFromString(aEntry.referrer);
+ if (aEntry.isSrcdocEntry)
+ shEntry.srcdocData = aEntry.srcdocData;
+
+ if (aEntry.cacheKey) {
+ var cacheKey = Cc["@mozilla.org/supports-PRUint32;1"].
+ createInstance(Ci.nsISupportsPRUint32);
+ cacheKey.data = aEntry.cacheKey;
+ shEntry.cacheKey = cacheKey;
+ }
+
+ if (aEntry.ID) {
+ // get a new unique ID for this frame (since the one from the last
+ // start might already be in use)
+ var id = aIdMap[aEntry.ID] || 0;
+ if (!id) {
+ for (id = Date.now(); id in aIdMap.used; id++);
+ aIdMap[aEntry.ID] = id;
+ aIdMap.used[id] = true;
+ }
+ shEntry.ID = id;
+ }
+
+ if (aEntry.docshellID)
+ shEntry.docshellID = aEntry.docshellID;
+
+ if (aEntry.structuredCloneState && aEntry.structuredCloneVersion) {
+ shEntry.stateData =
+ Cc["@mozilla.org/docshell/structured-clone-container;1"].
+ createInstance(Ci.nsIStructuredCloneContainer);
+
+ shEntry.stateData.initFromBase64(aEntry.structuredCloneState,
+ aEntry.structuredCloneVersion);
+ }
+
+ if (aEntry.scroll) {
+ var scrollPos = (aEntry.scroll || "0,0").split(",");
+ scrollPos = [parseInt(scrollPos[0]) || 0, parseInt(scrollPos[1]) || 0];
+ shEntry.setScrollPosition(scrollPos[0], scrollPos[1]);
+ }
+
+ if (aEntry.postdata_b64) {
+ var postdata = atob(aEntry.postdata_b64);
+ var stream = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+ stream.setData(postdata, postdata.length);
+ shEntry.postData = stream;
+ }
+
+ let childDocIdents = {};
+ if (aEntry.docIdentifier) {
+ // If we have a serialized document identifier, try to find an SHEntry
+ // which matches that doc identifier and adopt that SHEntry's
+ // BFCacheEntry. If we don't find a match, insert shEntry as the match
+ // for the document identifier.
+ let matchingEntry = aDocIdentMap[aEntry.docIdentifier];
+ if (!matchingEntry) {
+ matchingEntry = {shEntry: shEntry, childDocIdents: childDocIdents};
+ aDocIdentMap[aEntry.docIdentifier] = matchingEntry;
+ }
+ else {
+ shEntry.adoptBFCacheEntry(matchingEntry.shEntry);
+ childDocIdents = matchingEntry.childDocIdents;
+ }
+ }
+
+ // The field aEntry.owner_b64 got renamed to aEntry.triggeringPricipal_b64 in
+ // Bug 1286472. To remain backward compatible we still have to support that
+ // field for a few cycles before we can remove it within Bug 1289785.
+ if (aEntry.owner_b64) {
+ aEntry.triggeringPrincipal_b64 = aEntry.owner_b64;
+ delete aEntry.owner_b64;
+ }
+
+ if (aEntry.triggeringPrincipal_b64) {
+ var triggeringPrincipalInput = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+ var binaryData = atob(aEntry.triggeringPrincipal_b64);
+ triggeringPrincipalInput.setData(binaryData, binaryData.length);
+ var binaryStream = Cc["@mozilla.org/binaryinputstream;1"].
+ createInstance(Ci.nsIObjectInputStream);
+ binaryStream.setInputStream(triggeringPrincipalInput);
+ try { // Catch possible deserialization exceptions
+ shEntry.triggeringPrincipal = binaryStream.readObject(true);
+ } catch (ex) { debug(ex); }
+ }
+
+ if (aEntry.children && shEntry instanceof Ci.nsISHContainer) {
+ for (var i = 0; i < aEntry.children.length; i++) {
+ //XXXzpao Wallpaper patch for bug 514751
+ if (!aEntry.children[i].url)
+ continue;
+
+ // We're getting sessionrestore.js files with a cycle in the
+ // doc-identifier graph, likely due to bug 698656. (That is, we have
+ // an entry where doc identifier A is an ancestor of doc identifier B,
+ // and another entry where doc identifier B is an ancestor of A.)
+ //
+ // If we were to respect these doc identifiers, we'd create a cycle in
+ // the SHEntries themselves, which causes the docshell to loop forever
+ // when it looks for the root SHEntry.
+ //
+ // So as a hack to fix this, we restrict the scope of a doc identifier
+ // to be a node's siblings and cousins, and pass childDocIdents, not
+ // aDocIdents, to _deserializeHistoryEntry. That is, we say that two
+ // SHEntries with the same doc identifier have the same document iff
+ // they have the same parent or their parents have the same document.
+
+ shEntry.AddChild(this._deserializeHistoryEntry(aEntry.children[i], aIdMap,
+ childDocIdents), i);
+ }
+ }
+
+ return shEntry;
+ },
+
+ /**
+ * Restore properties to a loaded document
+ */
+ restoreDocument: function ssi_restoreDocument(aWindow, aBrowser, aEvent) {
+ // wait for the top frame to be loaded completely
+ if (!aEvent || !aEvent.originalTarget || !aEvent.originalTarget.defaultView ||
+ aEvent.originalTarget.defaultView != aEvent.originalTarget.defaultView.top) {
+ return;
+ }
+
+ // always call this before injecting content into a document!
+ function hasExpectedURL(aDocument, aURL)
+ !aURL || aURL.replace(/#.*/, "") == aDocument.location.href.replace(/#.*/, "");
+
+ let selectedPageStyle = aBrowser.__SS_restore_pageStyle;
+ function restoreTextDataAndScrolling(aContent, aData, aPrefix) {
+ if (aData.formdata && hasExpectedURL(aContent.document, aData.url)) {
+ let formdata = aData.formdata;
+
+ // handle backwards compatibility
+ // this is a migration from pre-firefox 15. cf. bug 742051
+ if (!("xpath" in formdata || "id" in formdata)) {
+ formdata = { xpath: {}, id: {} };
+
+ for each (let [key, value] in Iterator(aData.formdata)) {
+ if (key.charAt(0) == "#") {
+ formdata.id[key.slice(1)] = value;
+ } else {
+ formdata.xpath[key] = value;
+ }
+ }
+ }
+
+ // for about:sessionrestore we saved the field as JSON to avoid
+ // nested instances causing humongous sessionstore.js files.
+ // cf. bug 467409
+ if (aData.url == "about:sessionrestore" &&
+ "sessionData" in formdata.id &&
+ typeof formdata.id["sessionData"] == "object") {
+ formdata.id["sessionData"] =
+ JSON.stringify(formdata.id["sessionData"]);
+ }
+
+ // update the formdata
+ aData.formdata = formdata;
+ // merge the formdata
+ DocumentUtils.mergeFormData(aContent.document, formdata);
+ }
+
+ if (aData.innerHTML) {
+ aWindow.setTimeout(function() {
+ if (aContent.document.designMode == "on" &&
+ hasExpectedURL(aContent.document, aData.url) &&
+ aContent.document.body) {
+ aContent.document.body.innerHTML = aData.innerHTML;
+ }
+ }, 0);
+ }
+ var match;
+ if (aData.scroll && (match = /(\d+),(\d+)/.exec(aData.scroll)) != null) {
+ aContent.scrollTo(match[1], match[2]);
+ }
+ Array.forEach(aContent.document.styleSheets, function(aSS) {
+ aSS.disabled = aSS.title && aSS.title != selectedPageStyle;
+ });
+ for (var i = 0; i < aContent.frames.length; i++) {
+ if (aData.children && aData.children[i] &&
+ hasExpectedURL(aContent.document, aData.url)) {
+ restoreTextDataAndScrolling(aContent.frames[i], aData.children[i], aPrefix + i + "|");
+ }
+ }
+ }
+
+ // don't restore text data and scrolling state if the user has navigated
+ // away before the loading completed (except for in-page navigation)
+ if (hasExpectedURL(aEvent.originalTarget, aBrowser.__SS_restore_data.url)) {
+ var content = aEvent.originalTarget.defaultView;
+ restoreTextDataAndScrolling(content, aBrowser.__SS_restore_data, "");
+ aBrowser.markupDocumentViewer.authorStyleDisabled = selectedPageStyle == "_nostyle";
+ }
+
+ // notify the tabbrowser that this document has been completely restored
+ this._sendTabRestoredNotification(aBrowser.__SS_restore_tab);
+
+ delete aBrowser.__SS_restore_data;
+ delete aBrowser.__SS_restore_pageStyle;
+ delete aBrowser.__SS_restore_tab;
+ },
+
+ /**
+ * Restore visibility and dimension features to a window
+ * @param aWindow
+ * Window reference
+ * @param aWinData
+ * Object containing session data for the window
+ */
+ restoreWindowFeatures: function ssi_restoreWindowFeatures(aWindow, aWinData) {
+ var hidden = (aWinData.hidden)?aWinData.hidden.split(","):[];
+ WINDOW_HIDEABLE_FEATURES.forEach(function(aItem) {
+ aWindow[aItem].visible = hidden.indexOf(aItem) == -1;
+ });
+
+ if (aWinData.isPopup) {
+ this._windows[aWindow.__SSi].isPopup = true;
+ if (aWindow.gURLBar) {
+ aWindow.gURLBar.readOnly = true;
+ aWindow.gURLBar.setAttribute("enablehistory", "false");
+ }
+ }
+ else {
+ delete this._windows[aWindow.__SSi].isPopup;
+ if (aWindow.gURLBar) {
+ aWindow.gURLBar.readOnly = false;
+ aWindow.gURLBar.setAttribute("enablehistory", "true");
+ }
+ }
+
+ var _this = this;
+ aWindow.setTimeout(function() {
+ _this.restoreDimensions.apply(_this, [aWindow,
+ +aWinData.width || 0,
+ +aWinData.height || 0,
+ "screenX" in aWinData ? +aWinData.screenX : NaN,
+ "screenY" in aWinData ? +aWinData.screenY : NaN,
+ aWinData.sizemode || "", aWinData.sidebar || ""]);
+ }, 0);
+ },
+
+ /**
+ * Restore a window's dimensions
+ * @param aWidth
+ * Window width
+ * @param aHeight
+ * Window height
+ * @param aLeft
+ * Window left
+ * @param aTop
+ * Window top
+ * @param aSizeMode
+ * Window size mode (eg: maximized)
+ * @param aSidebar
+ * Sidebar command
+ */
+ restoreDimensions: function ssi_restoreDimensions(aWindow, aWidth, aHeight, aLeft, aTop, aSizeMode, aSidebar) {
+ var win = aWindow;
+ var _this = this;
+ function win_(aName) { return _this._getWindowDimension(win, aName); }
+
+ // Find available space on the screen where this window is being placed
+ let screen = gScreenManager.screenForRect(aLeft, aTop, aWidth, aHeight);
+ if (screen && !this._prefBranch.getBoolPref("sessionstore.exactPos")) {
+ let screenLeft = {}, screenTop = {}, screenWidth = {}, screenHeight = {};
+ screen.GetAvailRectDisplayPix(screenLeft, screenTop, screenWidth, screenHeight);
+
+ // Screen X/Y are based on the origin of the screen's desktop-pixel coordinate space
+ let screenLeftCss = screenLeft.value;
+ let screenTopCss = screenTop.value;
+
+ // Convert the screen's device pixel dimensions to CSS px dimensions
+ screen.GetAvailRect(screenLeft, screenTop, screenWidth, screenHeight);
+ let cssToDevScale = screen.defaultCSSScaleFactor;
+ let screenRightCss = screenLeftCss + screenWidth.value / cssToDevScale;
+ let screenBottomCss = screenTopCss + screenHeight.value / cssToDevScale;
+
+ // Pull the window within the screen's bounds.
+ // First, ensure the left edge is on-screen
+ if (aLeft < screenLeftCss) {
+ aLeft = screenLeftCss;
+ }
+ // Then check the resulting right edge, and reduce it if necessary.
+ let right = aLeft + aWidth;
+ if (right > screenRightCss) {
+ right = screenRightCss;
+ // See if we can move the left edge leftwards to maintain width.
+ if (aLeft > screenLeftCss) {
+ aLeft = Math.max(right - aWidth, screenLeftCss);
+ }
+ }
+ // Finally, update aWidth to account for the adjusted left and right edges.
+ aWidth = right - aLeft;
+
+ // Do the same in the vertical dimension.
+ // First, ensure the top edge is on-screen
+ if (aTop < screenTopCss) {
+ aTop = screenTopCss;
+ }
+ // Then check the resulting right edge, and reduce it if necessary.
+ let bottom = aTop + aHeight;
+ if (bottom > screenBottomCss) {
+ bottom = screenBottomCss;
+ // See if we can move the top edge upwards to maintain height.
+ if (aTop > screenTopCss) {
+ aTop = Math.max(bottom - aHeight, screenTopCss);
+ }
+ }
+ // Finally, update aHeight to account for the adjusted top and bottom edges.
+ aHeight = bottom - aTop;
+ }
+
+ // Only modify those aspects which aren't correct yet
+ if (!isNaN(aLeft) && !isNaN(aTop) && (aLeft != win_("screenX") || aTop != win_("screenY"))) {
+ aWindow.moveTo(aLeft, aTop);
+ }
+ if (aWidth && aHeight && (aWidth != win_("width") || aHeight != win_("height"))) {
+ // Don't resize the window if it's currently maximized and we would
+ // maximize it again shortly after.
+ if (aSizeMode != "maximized" || win_("sizemode") != "maximized") {
+ aWindow.resizeTo(aWidth, aHeight);
+ }
+ }
+
+ // Restore window state
+ if (aSizeMode && win_("sizemode") != aSizeMode)
+ {
+ switch (aSizeMode)
+ {
+ case "maximized":
+ aWindow.maximize();
+ break;
+ case "minimized":
+ aWindow.minimize();
+ break;
+ case "normal":
+ aWindow.restore();
+ break;
+ }
+ }
+ var sidebar = aWindow.document.getElementById("sidebar-box");
+ if (sidebar.getAttribute("sidebarcommand") != aSidebar) {
+ aWindow.toggleSidebar(aSidebar);
+ }
+ // since resizing/moving a window brings it to the foreground,
+ // we might want to re-focus the last focused window
+ if (this.windowToFocus) {
+ this.windowToFocus.focus();
+ }
+ },
+
+ /**
+ * Restores cookies
+ * @param aCookies
+ * Array of cookie objects
+ */
+ restoreCookies: function ssi_restoreCookies(aCookies) {
+ // MAX_EXPIRY should be 2^63-1, but JavaScript can't handle that precision
+ var MAX_EXPIRY = Math.pow(2, 62);
+ for (let i = 0; i < aCookies.length; i++) {
+ var cookie = aCookies[i];
+ try {
+ Services.cookies.add(cookie.host, cookie.path || "", cookie.name || "",
+ cookie.value, !!cookie.secure, !!cookie.httponly, true,
+ "expiry" in cookie ? cookie.expiry : MAX_EXPIRY, {});
+ }
+ catch (ex) { Cu.reportError(ex); } // don't let a single cookie stop recovering
+ }
+ },
+
+ /* ........ Disk Access .............. */
+
+ /**
+ * save state delayed by N ms
+ * marks window as dirty (i.e. data update can't be skipped)
+ * @param aWindow
+ * Window reference
+ * @param aDelay
+ * Milliseconds to delay
+ */
+ saveStateDelayed: function ssi_saveStateDelayed(aWindow, aDelay) {
+ if (aWindow) {
+ this._dirtyWindows[aWindow.__SSi] = true;
+ }
+
+ if (!this._saveTimer) {
+ // interval until the next disk operation is allowed
+ var minimalDelay = this._lastSaveTime + this._interval - Date.now();
+
+ // if we have to wait, set a timer, otherwise saveState directly
+ aDelay = Math.max(minimalDelay, aDelay || 2000);
+ if (aDelay > 0) {
+ this._saveTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this._saveTimer.init(this, aDelay, Ci.nsITimer.TYPE_ONE_SHOT);
+ }
+ else {
+ this.saveState();
+ }
+ }
+ },
+
+ /**
+ * save state to disk
+ * @param aUpdateAll
+ * Bool update all windows
+ */
+ saveState: function ssi_saveState(aUpdateAll) {
+ // If crash recovery is disabled, we only want to resume with pinned tabs
+ // if we crash.
+ let pinnedOnly = this._loadState == STATE_RUNNING && !this._resume_from_crash;
+
+ var oState = this._getCurrentState(aUpdateAll, pinnedOnly);
+ if (!oState) {
+ return;
+ }
+
+ // Forget about private windows.
+ for (let i = oState.windows.length - 1; i >= 0; i--) {
+ if (oState.windows[i].isPrivate) {
+ oState.windows.splice(i, 1);
+ if (oState.selectedWindow >= i) {
+ oState.selectedWindow--;
+ }
+ }
+ }
+
+ for (let i = oState._closedWindows.length - 1; i >= 0; i--) {
+ if (oState._closedWindows[i].isPrivate) {
+ oState._closedWindows.splice(i, 1);
+ }
+ }
+
+#ifndef XP_MACOSX
+ // We want to restore closed windows that are marked with _shouldRestore.
+ // We're doing this here because we want to control this only when saving
+ // the file.
+ while (oState._closedWindows.length) {
+ let i = oState._closedWindows.length - 1;
+ if (oState._closedWindows[i]._shouldRestore) {
+ delete oState._closedWindows[i]._shouldRestore;
+ oState.windows.unshift(oState._closedWindows.pop());
+ }
+ else {
+ // We only need to go until we hit !needsRestore since we're going in reverse
+ break;
+ }
+ }
+#endif
+
+ if (pinnedOnly) {
+ // Save original resume_session_once preference for when quiting browser,
+ // otherwise session will be restored next time browser starts and we
+ // only want it to be restored in the case of a crash.
+ if (this._resume_session_once_on_shutdown == null) {
+ this._resume_session_once_on_shutdown =
+ this._prefBranch.getBoolPref("sessionstore.resume_session_once");
+ this._prefBranch.setBoolPref("sessionstore.resume_session_once", true);
+ // flush the preference file so preference will be saved in case of a crash
+ Services.prefs.savePrefFile(null);
+ }
+ }
+
+ // Persist the last session if we deferred restoring it
+ if (this._lastSessionState)
+ oState.lastSessionState = this._lastSessionState;
+
+ // Make sure that we keep the previous session if we started with a single
+ // private window and no non-private windows have been opened, yet.
+ if (this._deferredInitialState) {
+ oState.windows = this._deferredInitialState.windows || [];
+ }
+
+ this._saveStateObject(oState);
+ },
+
+ /**
+ * write a state object to disk
+ */
+ _saveStateObject: function ssi_saveStateObject(aStateObj) {
+ let data = this._toJSONString(aStateObj);
+
+ let stateString = this._createSupportsString(data);
+ Services.obs.notifyObservers(stateString, "sessionstore-state-write", "");
+ data = stateString.data;
+
+ // Don't touch the file if an observer has deleted all state data.
+ if (!data) {
+ return;
+ }
+
+ let promise;
+ // If "sessionstore.resume_from_crash" is true, attempt to backup the
+ // session file first, before writing to it.
+ if (this._resume_from_crash) {
+ // Note that we do not have race conditions here as _SessionFile
+ // guarantees that any I/O operation is completed before proceeding to
+ // the next I/O operation.
+ // Note backup happens only once, on initial save.
+ promise = this._backupSessionFileOnce;
+ } else {
+ promise = Promise.resolve();
+ }
+
+ // Attempt to write to the session file (potentially, depending on
+ // "sessionstore.resume_from_crash" preference, after successful backup).
+ promise = promise.then(function onSuccess() {
+ // Write (atomically) to a session file, using a tmp file.
+ return _SessionFile.write(data);
+ });
+
+ // Once the session file is successfully updated, save the time stamp of the
+ // last save and notify the observers.
+ promise = promise.then(() => {
+ this._lastSaveTime = Date.now();
+ Services.obs.notifyObservers(null, "sessionstore-state-write-complete",
+ "");
+ });
+ },
+
+ /* ........ Auxiliary Functions .............. */
+
+ // Wrap a string as a nsISupports
+ _createSupportsString: function ssi_createSupportsString(aData) {
+ let string = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(Ci.nsISupportsString);
+ string.data = aData;
+ return string;
+ },
+
+ /**
+ * call a callback for all currently opened browser windows
+ * (might miss the most recent one)
+ * @param aFunc
+ * Callback each window is passed to
+ */
+ _forEachBrowserWindow: function ssi_forEachBrowserWindow(aFunc) {
+ var windowsEnum = Services.wm.getEnumerator("navigator:browser");
+
+ while (windowsEnum.hasMoreElements()) {
+ var window = windowsEnum.getNext();
+ if (window.__SSi && !window.closed) {
+ aFunc.call(this, window);
+ }
+ }
+ },
+
+ /**
+ * Returns most recent window
+ * @returns Window reference
+ */
+ _getMostRecentBrowserWindow: function ssi_getMostRecentBrowserWindow() {
+ var win = Services.wm.getMostRecentWindow("navigator:browser");
+ if (!win)
+ return null;
+ if (!win.closed)
+ return win;
+
+#ifdef BROKEN_WM_Z_ORDER
+ win = null;
+ var windowsEnum = Services.wm.getEnumerator("navigator:browser");
+ // this is oldest to newest, so this gets a bit ugly
+ while (windowsEnum.hasMoreElements()) {
+ let nextWin = windowsEnum.getNext();
+ if (!nextWin.closed)
+ win = nextWin;
+ }
+ return win;
+#else
+ var windowsEnum =
+ Services.wm.getZOrderDOMWindowEnumerator("navigator:browser", true);
+ while (windowsEnum.hasMoreElements()) {
+ win = windowsEnum.getNext();
+ if (!win.closed)
+ return win;
+ }
+ return null;
+#endif
+ },
+
+ /**
+ * Calls onClose for windows that are determined to be closed but aren't
+ * destroyed yet, which would otherwise cause getBrowserState and
+ * setBrowserState to treat them as open windows.
+ */
+ _handleClosedWindows: function ssi_handleClosedWindows() {
+ var windowsEnum = Services.wm.getEnumerator("navigator:browser");
+
+ while (windowsEnum.hasMoreElements()) {
+ var window = windowsEnum.getNext();
+ if (window.closed) {
+ this.onClose(window);
+ }
+ }
+ },
+
+ /**
+ * open a new browser window for a given session state
+ * called when restoring a multi-window session
+ * @param aState
+ * Object containing session data
+ */
+ _openWindowWithState: function ssi_openWindowWithState(aState) {
+ var argString = Cc["@mozilla.org/supports-string;1"].
+ createInstance(Ci.nsISupportsString);
+ argString.data = "";
+
+ // Build feature string
+ let features = "chrome,dialog=no,macsuppressanimation,all";
+ let winState = aState.windows[0];
+ WINDOW_ATTRIBUTES.forEach(function(aFeature) {
+ // Use !isNaN as an easy way to ignore sizemode and check for numbers
+ if (aFeature in winState && !isNaN(winState[aFeature]))
+ features += "," + aFeature + "=" + winState[aFeature];
+ });
+
+ if (winState.isPrivate) {
+ features += ",private";
+ }
+
+ var window =
+ Services.ww.openWindow(null, this._prefBranch.getCharPref("chromeURL"),
+ "_blank", features, argString);
+
+ do {
+ var ID = "window" + Math.random();
+ } while (ID in this._statesToRestore);
+ this._statesToRestore[(window.__SS_restoreID = ID)] = aState;
+
+ return window;
+ },
+
+ /**
+ * Whether or not to resume session, if not recovering from a crash.
+ * @returns bool
+ */
+ _doResumeSession: function ssi_doResumeSession() {
+ return this._prefBranch.getIntPref("startup.page") == 3 ||
+ this._prefBranch.getBoolPref("sessionstore.resume_session_once");
+ },
+
+ /**
+ * whether the user wants to load any other page at startup
+ * (except the homepage) - needed for determining whether to overwrite the current tabs
+ * C.f.: nsBrowserContentHandler's defaultArgs implementation.
+ * @returns bool
+ */
+ _isCmdLineEmpty: function ssi_isCmdLineEmpty(aWindow, aState) {
+ var pinnedOnly = aState.windows &&
+ aState.windows.every(function (win)
+ win.tabs.every(function (tab) tab.pinned));
+
+ let hasFirstArgument = aWindow.arguments && aWindow.arguments[0];
+ if (!pinnedOnly) {
+ let defaultArgs = Cc["@mozilla.org/browser/clh;1"].
+ getService(Ci.nsIBrowserHandler).defaultArgs;
+ if (aWindow.arguments &&
+ aWindow.arguments[0] &&
+ aWindow.arguments[0] == defaultArgs)
+ hasFirstArgument = false;
+ }
+
+ return !hasFirstArgument;
+ },
+
+ /**
+ * don't save sensitive data if the user doesn't want to
+ * (distinguishes between encrypted and non-encrypted sites)
+ * @param aIsHTTPS
+ * Bool is encrypted
+ * @param aUseDefaultPref
+ * don't do normal check for deferred
+ * @returns bool
+ */
+ checkPrivacyLevel: function ssi_checkPrivacyLevel(aIsHTTPS, aUseDefaultPref) {
+ let pref = "sessionstore.privacy_level";
+ // If we're in the process of quitting and we're not autoresuming the session
+ // then we should treat it as a deferred session. We have a different privacy
+ // pref for that case.
+ if (!aUseDefaultPref && this._loadState == STATE_QUITTING && !this._doResumeSession())
+ pref = "sessionstore.privacy_level_deferred";
+ return this._prefBranch.getIntPref(pref) < (aIsHTTPS ? PRIVACY_ENCRYPTED : PRIVACY_FULL);
+ },
+
+ /**
+ * on popup windows, the XULWindow's attributes seem not to be set correctly
+ * we use thus JSDOMWindow attributes for sizemode and normal window attributes
+ * (and hope for reasonable values when maximized/minimized - since then
+ * outerWidth/outerHeight aren't the dimensions of the restored window)
+ * @param aWindow
+ * Window reference
+ * @param aAttribute
+ * String sizemode | width | height | other window attribute
+ * @returns string
+ */
+ _getWindowDimension: function ssi_getWindowDimension(aWindow, aAttribute) {
+ if (aAttribute == "sizemode") {
+ switch (aWindow.windowState) {
+ case aWindow.STATE_FULLSCREEN:
+ case aWindow.STATE_MAXIMIZED:
+ return "maximized";
+ case aWindow.STATE_MINIMIZED:
+ return "minimized";
+ default:
+ return "normal";
+ }
+ }
+
+ var dimension;
+ switch (aAttribute) {
+ case "width":
+ dimension = aWindow.outerWidth;
+ break;
+ case "height":
+ dimension = aWindow.outerHeight;
+ break;
+ default:
+ dimension = aAttribute in aWindow ? aWindow[aAttribute] : "";
+ break;
+ }
+
+ if (aWindow.windowState == aWindow.STATE_NORMAL) {
+ return dimension;
+ }
+ return aWindow.document.documentElement.getAttribute(aAttribute) || dimension;
+ },
+
+ /**
+ * Get nsIURI from string
+ * @param string
+ * @returns nsIURI
+ */
+ _getURIFromString: function ssi_getURIFromString(aString) {
+ return Services.io.newURI(aString, null, null);
+ },
+
+ /**
+ * @param aState is a session state
+ * @param aRecentCrashes is the number of consecutive crashes
+ * @returns whether a restore page will be needed for the session state
+ */
+ _needsRestorePage: function ssi_needsRestorePage(aState, aRecentCrashes) {
+ const SIX_HOURS_IN_MS = 6 * 60 * 60 * 1000;
+
+ // don't display the page when there's nothing to restore
+ let winData = aState.windows || null;
+ if (!winData || winData.length == 0)
+ return false;
+
+ // don't wrap a single about:sessionrestore page
+ if (winData.length == 1 && winData[0].tabs &&
+ winData[0].tabs.length == 1 && winData[0].tabs[0].entries &&
+ winData[0].tabs[0].entries.length == 1 &&
+ winData[0].tabs[0].entries[0].url == "about:sessionrestore")
+ return false;
+
+ // don't automatically restore in Safe Mode
+ if (Services.appinfo.inSafeMode)
+ return true;
+
+ let max_resumed_crashes =
+ this._prefBranch.getIntPref("sessionstore.max_resumed_crashes");
+ let sessionAge = aState.session && aState.session.lastUpdate &&
+ (Date.now() - aState.session.lastUpdate);
+
+ return max_resumed_crashes != -1 &&
+ (aRecentCrashes > max_resumed_crashes ||
+ sessionAge && sessionAge >= SIX_HOURS_IN_MS);
+ },
+
+ /**
+ * Determine if the tab state we're passed is something we should save. This
+ * is used when closing a tab or closing a window with a single tab
+ *
+ * @param aTabState
+ * The current tab state
+ * @returns boolean
+ */
+ _shouldSaveTabState: function ssi_shouldSaveTabState(aTabState) {
+ // If the tab has only a transient about: history entry, no other
+ // session history, and no userTypedValue, then we don't actually want to
+ // store this tab's data.
+ return aTabState.entries.length &&
+ !(aTabState.entries.length == 1 &&
+ (aTabState.entries[0].url == "about:blank" ||
+ aTabState.entries[0].url == "about:newtab") &&
+ !aTabState.userTypedValue);
+ },
+
+ /**
+ * Determine if we can restore history into this tab.
+ * This will be false when a tab has been removed (usually between
+ * restoreHistoryPrecursor && restoreHistory) or if the tab is still marked
+ * as loading.
+ *
+ * @param aTab
+ * @returns boolean
+ */
+ _canRestoreTabHistory: function ssi_canRestoreTabHistory(aTab) {
+ return aTab.parentNode && aTab.linkedBrowser &&
+ aTab.linkedBrowser.__SS_tabStillLoading;
+ },
+
+ /**
+ * This is going to take a state as provided at startup (via
+ * nsISessionStartup.state) and split it into 2 parts. The first part
+ * (defaultState) will be a state that should still be restored at startup,
+ * while the second part (state) is a state that should be saved for later.
+ * defaultState will be comprised of windows with only pinned tabs, extracted
+ * from state. It will contain the cookies that go along with the history
+ * entries in those tabs. It will also contain window position information.
+ *
+ * defaultState will be restored at startup. state will be placed into
+ * this._lastSessionState and will be kept in case the user explicitly wants
+ * to restore the previous session (publicly exposed as restoreLastSession).
+ *
+ * @param state
+ * The state, presumably from nsISessionStartup.state
+ * @returns [defaultState, state]
+ */
+ _prepDataForDeferredRestore: function ssi_prepDataForDeferredRestore(state) {
+ // Make sure that we don't modify the global state as provided by
+ // nsSessionStartup.state. Converting the object to a JSON string and
+ // parsing it again is the easiest way to do that, although not the most
+ // efficient one. Deferred sessions that don't have automatic session
+ // restore enabled tend to be a lot smaller though so that this shouldn't
+ // be a big perf hit.
+ state = JSON.parse(JSON.stringify(state));
+
+ let defaultState = { windows: [], selectedWindow: 1 };
+
+ state.selectedWindow = state.selectedWindow || 1;
+
+ // Look at each window, remove pinned tabs, adjust selectedindex,
+ // remove window if necessary.
+ for (let wIndex = 0; wIndex < state.windows.length;) {
+ let window = state.windows[wIndex];
+ window.selected = window.selected || 1;
+ // We're going to put the state of the window into this object
+ let pinnedWindowState = { tabs: [], cookies: []};
+ for (let tIndex = 0; tIndex < window.tabs.length;) {
+ if (window.tabs[tIndex].pinned) {
+ // Adjust window.selected
+ if (tIndex + 1 < window.selected)
+ window.selected -= 1;
+ else if (tIndex + 1 == window.selected)
+ pinnedWindowState.selected = pinnedWindowState.tabs.length + 2;
+ // + 2 because the tab isn't actually in the array yet
+
+ // Now add the pinned tab to our window
+ pinnedWindowState.tabs =
+ pinnedWindowState.tabs.concat(window.tabs.splice(tIndex, 1));
+ // We don't want to increment tIndex here.
+ continue;
+ }
+ tIndex++;
+ }
+
+ // At this point the window in the state object has been modified (or not)
+ // We want to build the rest of this new window object if we have pinnedTabs.
+ if (pinnedWindowState.tabs.length) {
+ // First get the other attributes off the window
+ WINDOW_ATTRIBUTES.forEach(function(attr) {
+ if (attr in window) {
+ pinnedWindowState[attr] = window[attr];
+ delete window[attr];
+ }
+ });
+ // We're just copying position data into the pinned window.
+ // Not copying over:
+ // - _closedTabs
+ // - extData
+ // - isPopup
+ // - hidden
+
+ // Assign a unique ID to correlate the window to be opened with the
+ // remaining data
+ window.__lastSessionWindowID = pinnedWindowState.__lastSessionWindowID
+ = "" + Date.now() + Math.random();
+
+ // Extract the cookies that belong with each pinned tab
+ this._splitCookiesFromWindow(window, pinnedWindowState);
+
+ // Actually add this window to our defaultState
+ defaultState.windows.push(pinnedWindowState);
+ // Remove the window from the state if it doesn't have any tabs
+ if (!window.tabs.length) {
+ if (wIndex + 1 <= state.selectedWindow)
+ state.selectedWindow -= 1;
+ else if (wIndex + 1 == state.selectedWindow)
+ defaultState.selectedIndex = defaultState.windows.length + 1;
+
+ state.windows.splice(wIndex, 1);
+ // We don't want to increment wIndex here.
+ continue;
+ }
+
+
+ }
+ wIndex++;
+ }
+
+ return [defaultState, state];
+ },
+
+ /**
+ * Splits out the cookies from aWinState into aTargetWinState based on the
+ * tabs that are in aTargetWinState.
+ * This alters the state of aWinState and aTargetWinState.
+ */
+ _splitCookiesFromWindow:
+ function ssi_splitCookiesFromWindow(aWinState, aTargetWinState) {
+ if (!aWinState.cookies || !aWinState.cookies.length)
+ return;
+
+ // Get the hosts for history entries in aTargetWinState
+ let cookieHosts = {};
+ aTargetWinState.tabs.forEach(function(tab) {
+ tab.entries.forEach(function(entry) {
+ this._extractHostsForCookiesFromEntry(entry, cookieHosts, false);
+ }, this);
+ }, this);
+
+ // By creating a regex we reduce overhead and there is only one loop pass
+ // through either array (cookieHosts and aWinState.cookies).
+ let hosts = Object.keys(cookieHosts).join("|").replace("\\.", "\\.", "g");
+ // If we don't actually have any hosts, then we don't want to do anything.
+ if (!hosts.length)
+ return;
+ let cookieRegex = new RegExp(".*(" + hosts + ")");
+ for (let cIndex = 0; cIndex < aWinState.cookies.length;) {
+ if (cookieRegex.test(aWinState.cookies[cIndex].host)) {
+ aTargetWinState.cookies =
+ aTargetWinState.cookies.concat(aWinState.cookies.splice(cIndex, 1));
+ continue;
+ }
+ cIndex++;
+ }
+ },
+
+ /**
+ * Converts a JavaScript object into a JSON string
+ * (see http://www.json.org/ for more information).
+ *
+ * The inverse operation consists of JSON.parse(JSON_string).
+ *
+ * @param aJSObject is the object to be converted
+ * @returns the object's JSON representation
+ */
+ _toJSONString: function ssi_toJSONString(aJSObject) {
+ return JSON.stringify(aJSObject);
+ },
+
+ _sendRestoreCompletedNotifications: function ssi_sendRestoreCompletedNotifications() {
+ // not all windows restored, yet
+ if (this._restoreCount > 1) {
+ this._restoreCount--;
+ return;
+ }
+
+ // observers were already notified
+ if (this._restoreCount == -1)
+ return;
+
+ // This was the last window restored at startup, notify observers.
+ Services.obs.notifyObservers(null,
+ this._browserSetState ? NOTIFY_BROWSER_STATE_RESTORED : NOTIFY_WINDOWS_RESTORED,
+ "");
+
+ this._browserSetState = false;
+ this._restoreCount = -1;
+ },
+
+ /**
+ * Set the given window's busy state
+ * @param aWindow the window
+ * @param aValue the window's busy state
+ */
+ _setWindowStateBusyValue:
+ function ssi_changeWindowStateBusyValue(aWindow, aValue) {
+
+ this._windows[aWindow.__SSi].busy = aValue;
+
+ // Keep the to-be-restored state in sync because that is returned by
+ // getWindowState() as long as the window isn't loaded, yet.
+ if (!this._isWindowLoaded(aWindow)) {
+ let stateToRestore = this._statesToRestore[aWindow.__SS_restoreID].windows[0];
+ stateToRestore.busy = aValue;
+ }
+ },
+
+ /**
+ * Set the given window's state to 'not busy'.
+ * @param aWindow the window
+ */
+ _setWindowStateReady: function ssi_setWindowStateReady(aWindow) {
+ this._setWindowStateBusyValue(aWindow, false);
+ this._sendWindowStateEvent(aWindow, "Ready");
+ },
+
+ /**
+ * Set the given window's state to 'busy'.
+ * @param aWindow the window
+ */
+ _setWindowStateBusy: function ssi_setWindowStateBusy(aWindow) {
+ this._setWindowStateBusyValue(aWindow, true);
+ this._sendWindowStateEvent(aWindow, "Busy");
+ },
+
+ /**
+ * Dispatch an SSWindowState_____ event for the given window.
+ * @param aWindow the window
+ * @param aType the type of event, SSWindowState will be prepended to this string
+ */
+ _sendWindowStateEvent: function ssi_sendWindowStateEvent(aWindow, aType) {
+ let event = aWindow.document.createEvent("Events");
+ event.initEvent("SSWindowState" + aType, true, false);
+ aWindow.dispatchEvent(event);
+ },
+
+ /**
+ * Dispatch the SSTabRestored event for the given tab.
+ * @param aTab the which has been restored
+ */
+ _sendTabRestoredNotification: function ssi_sendTabRestoredNotification(aTab) {
+ let event = aTab.ownerDocument.createEvent("Events");
+ event.initEvent("SSTabRestored", true, false);
+ aTab.dispatchEvent(event);
+ },
+
+ /**
+ * @param aWindow
+ * Window reference
+ * @returns whether this window's data is still cached in _statesToRestore
+ * because it's not fully loaded yet
+ */
+ _isWindowLoaded: function ssi_isWindowLoaded(aWindow) {
+ return !aWindow.__SS_restoreID;
+ },
+
+ /**
+ * Replace "Loading..." with the tab label (with minimal side-effects)
+ * @param aString is the string the title is stored in
+ * @param aTabbrowser is a tabbrowser object, containing aTab
+ * @param aTab is the tab whose title we're updating & using
+ *
+ * @returns aString that has been updated with the new title
+ */
+ _replaceLoadingTitle : function ssi_replaceLoadingTitle(aString, aTabbrowser, aTab) {
+ if (aString == aTabbrowser.mStringBundle.getString("tabs.connecting")) {
+ aTabbrowser.setTabTitle(aTab);
+ [aString, aTab.label] = [aTab.label, aString];
+ }
+ return aString;
+ },
+
+ /**
+ * Resize this._closedWindows to the value of the pref, except in the case
+ * where we don't have any non-popup windows on Windows and Linux. Then we must
+ * resize such that we have at least one non-popup window.
+ */
+ _capClosedWindows : function ssi_capClosedWindows() {
+ if (this._closedWindows.length <= this._max_windows_undo)
+ return;
+ let spliceTo = this._max_windows_undo;
+#ifndef XP_MACOSX
+ let normalWindowIndex = 0;
+ // try to find a non-popup window in this._closedWindows
+ while (normalWindowIndex < this._closedWindows.length &&
+ !!this._closedWindows[normalWindowIndex].isPopup)
+ normalWindowIndex++;
+ if (normalWindowIndex >= this._max_windows_undo)
+ spliceTo = normalWindowIndex + 1;
+#endif
+ this._closedWindows.splice(spliceTo, this._closedWindows.length);
+ },
+
+ _clearRestoringWindows: function ssi_clearRestoringWindows() {
+ for (let i = 0; i < this._closedWindows.length; i++) {
+ delete this._closedWindows[i]._shouldRestore;
+ }
+ },
+
+ /**
+ * Reset state to prepare for a new session state to be restored.
+ */
+ _resetRestoringState: function ssi_initRestoringState() {
+ TabRestoreQueue.reset();
+ this._tabsRestoringCount = 0;
+ },
+
+ /**
+ * Reset the restoring state for a particular tab. This will be called when
+ * removing a tab or when a tab needs to be reset (it's being overwritten).
+ *
+ * @param aTab
+ * The tab that will be "reset"
+ */
+ _resetTabRestoringState: function ssi_resetTabRestoringState(aTab) {
+ let window = aTab.ownerDocument.defaultView;
+ let browser = aTab.linkedBrowser;
+
+ // Keep the tab's previous state for later in this method
+ let previousState = browser.__SS_restoreState;
+
+ // The browser is no longer in any sort of restoring state.
+ delete browser.__SS_restoreState;
+
+ aTab.removeAttribute("pending");
+ browser.removeAttribute("pending");
+
+ // We want to decrement window.__SS_tabsToRestore here so that we always
+ // decrement it AFTER a tab is done restoring or when a tab gets "reset".
+ window.__SS_tabsToRestore--;
+
+ // Remove the progress listener if we should.
+ this._removeTabsProgressListener(window);
+
+ if (previousState == TAB_STATE_RESTORING) {
+ if (this._tabsRestoringCount)
+ this._tabsRestoringCount--;
+ }
+ else if (previousState == TAB_STATE_NEEDS_RESTORE) {
+ // Make sure the session history listener is removed. This is normally
+ // done in restoreTab, but this tab is being removed before that gets called.
+ this._removeSHistoryListener(aTab);
+
+ // Make sure that the tab is removed from the list of tabs to restore.
+ // Again, this is normally done in restoreTab, but that isn't being called
+ // for this tab.
+ TabRestoreQueue.remove(aTab);
+ }
+ },
+
+ /**
+ * Add the tabs progress listener to the window if it isn't already
+ *
+ * @param aWindow
+ * The window to add our progress listener to
+ */
+ _ensureTabsProgressListener: function ssi_ensureTabsProgressListener(aWindow) {
+ let tabbrowser = aWindow.gBrowser;
+ if (tabbrowser.mTabsProgressListeners.indexOf(gRestoreTabsProgressListener) == -1)
+ tabbrowser.addTabsProgressListener(gRestoreTabsProgressListener);
+ },
+
+ /**
+ * Attempt to remove the tabs progress listener from the window.
+ *
+ * @param aWindow
+ * The window from which to remove our progress listener from
+ */
+ _removeTabsProgressListener: function ssi_removeTabsProgressListener(aWindow) {
+ // If there are no tabs left to restore (or restoring) in this window, then
+ // we can safely remove the progress listener from this window.
+ if (!aWindow.__SS_tabsToRestore)
+ aWindow.gBrowser.removeTabsProgressListener(gRestoreTabsProgressListener);
+ },
+
+ /**
+ * Remove the session history listener from the tab's browser if there is one.
+ *
+ * @param aTab
+ * The tab who's browser to remove the listener
+ */
+ _removeSHistoryListener: function ssi_removeSHistoryListener(aTab) {
+ let browser = aTab.linkedBrowser;
+ if (browser.__SS_shistoryListener) {
+ browser.webNavigation.sessionHistory.
+ removeSHistoryListener(browser.__SS_shistoryListener);
+ delete browser.__SS_shistoryListener;
+ }
+ }
+};
+
+/**
+ * Priority queue that keeps track of a list of tabs to restore and returns
+ * the tab we should restore next, based on priority rules. We decide between
+ * pinned, visible and hidden tabs in that and FIFO order. Hidden tabs are only
+ * restored with restore_hidden_tabs=true.
+ */
+var TabRestoreQueue = {
+ // The separate buckets used to store tabs.
+ tabs: {priority: [], visible: [], hidden: []},
+
+ // Preferences used by the TabRestoreQueue to determine which tabs
+ // are restored automatically and which tabs will be on-demand.
+ prefs: {
+ // Lazy getter that returns whether tabs are restored on demand.
+ get restoreOnDemand() {
+ let updateValue = () => {
+ let value = Services.prefs.getBoolPref(PREF);
+ let definition = {value: value, configurable: true};
+ Object.defineProperty(this, "restoreOnDemand", definition);
+ return value;
+ }
+
+ const PREF = "browser.sessionstore.restore_on_demand";
+ Services.prefs.addObserver(PREF, updateValue, false);
+ return updateValue();
+ },
+
+ // Lazy getter that returns whether pinned tabs are restored on demand.
+ get restorePinnedTabsOnDemand() {
+ let updateValue = () => {
+ let value = Services.prefs.getBoolPref(PREF);
+ let definition = {value: value, configurable: true};
+ Object.defineProperty(this, "restorePinnedTabsOnDemand", definition);
+ return value;
+ }
+
+ const PREF = "browser.sessionstore.restore_pinned_tabs_on_demand";
+ Services.prefs.addObserver(PREF, updateValue, false);
+ return updateValue();
+ },
+
+ // Lazy getter that returns whether we should restore hidden tabs.
+ get restoreHiddenTabs() {
+ let updateValue = () => {
+ let value = Services.prefs.getBoolPref(PREF);
+ let definition = {value: value, configurable: true};
+ Object.defineProperty(this, "restoreHiddenTabs", definition);
+ return value;
+ }
+
+ const PREF = "browser.sessionstore.restore_hidden_tabs";
+ Services.prefs.addObserver(PREF, updateValue, false);
+ return updateValue();
+ }
+ },
+
+ // Resets the queue and removes all tabs.
+ reset: function () {
+ this.tabs = {priority: [], visible: [], hidden: []};
+ },
+
+ // Adds a tab to the queue and determines its priority bucket.
+ add: function (tab) {
+ let {priority, hidden, visible} = this.tabs;
+
+ if (tab.pinned) {
+ priority.push(tab);
+ } else if (tab.hidden) {
+ hidden.push(tab);
+ } else {
+ visible.push(tab);
+ }
+ },
+
+ // Removes a given tab from the queue, if it's in there.
+ remove: function (tab) {
+ let {priority, hidden, visible} = this.tabs;
+
+ // We'll always check priority first since we don't
+ // have an indicator if a tab will be there or not.
+ let set = priority;
+ let index = set.indexOf(tab);
+
+ if (index == -1) {
+ set = tab.hidden ? hidden : visible;
+ index = set.indexOf(tab);
+ }
+
+ if (index > -1) {
+ set.splice(index, 1);
+ }
+ },
+
+ // Returns and removes the tab with the highest priority.
+ shift: function () {
+ let set;
+ let {priority, hidden, visible} = this.tabs;
+
+ let {restoreOnDemand, restorePinnedTabsOnDemand} = this.prefs;
+ let restorePinned = !(restoreOnDemand && restorePinnedTabsOnDemand);
+ if (restorePinned && priority.length) {
+ set = priority;
+ } else if (!restoreOnDemand) {
+ if (visible.length) {
+ set = visible;
+ } else if (this.prefs.restoreHiddenTabs && hidden.length) {
+ set = hidden;
+ }
+ }
+
+ return set && set.shift();
+ },
+
+ // Moves a given tab from the 'hidden' to the 'visible' bucket.
+ hiddenToVisible: function (tab) {
+ let {hidden, visible} = this.tabs;
+ let index = hidden.indexOf(tab);
+
+ if (index > -1) {
+ hidden.splice(index, 1);
+ visible.push(tab);
+ } else {
+ throw new Error("restore queue: hidden tab not found");
+ }
+ },
+
+ // Moves a given tab from the 'visible' to the 'hidden' bucket.
+ visibleToHidden: function (tab) {
+ let {visible, hidden} = this.tabs;
+ let index = visible.indexOf(tab);
+
+ if (index > -1) {
+ visible.splice(index, 1);
+ hidden.push(tab);
+ } else {
+ throw new Error("restore queue: visible tab not found");
+ }
+ }
+};
+
+// A map storing a closed window's state data until it goes aways (is GC'ed).
+// This ensures that API clients can still read (but not write) states of
+// windows they still hold a reference to but we don't.
+var DyingWindowCache = {
+ _data: new WeakMap(),
+
+ has: function (window) {
+ return this._data.has(window);
+ },
+
+ get: function (window) {
+ return this._data.get(window);
+ },
+
+ set: function (window, data) {
+ this._data.set(window, data);
+ },
+
+ remove: function (window) {
+ this._data.delete(window);
+ }
+};
+
+// A set of tab attributes to persist. We will read a given list of tab
+// attributes when collecting tab data and will re-set those attributes when
+// the given tab data is restored to a new tab.
+var TabAttributes = {
+ _attrs: new Set(),
+
+ // We never want to directly read or write those attributes.
+ // 'image' should not be accessed directly but handled by using the
+ // gBrowser.getIcon()/setIcon() methods.
+ // 'pending' is used internal by sessionstore and managed accordingly.
+ // 'skipbackgroundnotify' is used internal by tabbrowser.xml.
+ _skipAttrs: new Set(["image", "pending", "skipbackgroundnotify"]),
+
+ persist: function (name) {
+ if (this._attrs.has(name) || this._skipAttrs.has(name)) {
+ return false;
+ }
+
+ this._attrs.add(name);
+ return true;
+ },
+
+ get: function (tab) {
+ let data = {};
+
+ for (let name of this._attrs) {
+ if (tab.hasAttribute(name)) {
+ data[name] = tab.getAttribute(name);
+ }
+ }
+
+ return data;
+ },
+
+ set: function (tab, data = {}) {
+ // Clear attributes.
+ for (let name of this._attrs) {
+ tab.removeAttribute(name);
+ }
+
+ // Set attributes.
+ for (let name in data) {
+ tab.setAttribute(name, data[name]);
+ }
+ }
+};
+
+// This is used to help meter the number of restoring tabs. This is the control
+// point for telling the next tab to restore. It gets attached to each gBrowser
+// via gBrowser.addTabsProgressListener
+var gRestoreTabsProgressListener = {
+ onStateChange: function(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
+ // Ignore state changes on browsers that we've already restored and state
+ // changes that aren't applicable.
+ if (aBrowser.__SS_restoreState &&
+ aBrowser.__SS_restoreState == TAB_STATE_RESTORING &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) {
+ // We need to reset the tab before starting the next restore.
+ let win = aBrowser.ownerDocument.defaultView;
+ let tab = win.gBrowser.getTabForBrowser(aBrowser);
+ SessionStoreInternal._resetTabRestoringState(tab);
+ SessionStoreInternal.restoreNextTab();
+ }
+ }
+};
+
+// A SessionStoreSHistoryListener will be attached to each browser before it is
+// restored. We need to catch reloads that occur before the tab is restored
+// because otherwise, docShell will reload an old URI (usually about:blank).
+function SessionStoreSHistoryListener(aTab) {
+ this.tab = aTab;
+}
+SessionStoreSHistoryListener.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsISHistoryListener,
+ Ci.nsISupportsWeakReference
+ ]),
+ browser: null,
+ OnHistoryNewEntry: function(aNewURI) { },
+ OnHistoryGoBack: function(aBackURI) { return true; },
+ OnHistoryGoForward: function(aForwardURI) { return true; },
+ OnHistoryGotoIndex: function(aIndex, aGotoURI) { return true; },
+ OnHistoryPurge: function(aNumEntries) { return true; },
+ OnHistoryReload: function(aReloadURI, aReloadFlags) {
+ // On reload, we want to make sure that session history loads the right
+ // URI. In order to do that, we will juet call restoreTab. That will remove
+ // the history listener and load the right URI.
+ SessionStoreInternal.restoreTab(this.tab);
+ // Returning false will stop the load that docshell is attempting.
+ return false;
+ }
+}
+
+// See toolkit/forgetaboutsite/ForgetAboutSite.jsm
+String.prototype.hasRootDomain = function hasRootDomain(aDomain) {
+ let index = this.indexOf(aDomain);
+ if (index == -1)
+ return false;
+
+ if (this == aDomain)
+ return true;
+
+ let prevChar = this[index - 1];
+ return (index == (this.length - aDomain.length)) &&
+ (prevChar == "." || prevChar == "/");
+}
diff --git a/components/sessionstore/XPathGenerator.jsm b/components/sessionstore/XPathGenerator.jsm
new file mode 100644
index 0000000..83ff2b8
--- /dev/null
+++ b/components/sessionstore/XPathGenerator.jsm
@@ -0,0 +1,97 @@
+/* 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.EXPORTED_SYMBOLS = ["XPathGenerator"];
+
+this.XPathGenerator = {
+ // these two hashes should be kept in sync
+ namespaceURIs: { "xhtml": "http://www.w3.org/1999/xhtml" },
+ namespacePrefixes: { "http://www.w3.org/1999/xhtml": "xhtml" },
+
+ /**
+ * Generates an approximate XPath query to an (X)HTML node
+ */
+ generate: function sss_xph_generate(aNode) {
+ // have we reached the document node already?
+ if (!aNode.parentNode)
+ return "";
+
+ // Access localName, namespaceURI just once per node since it's expensive.
+ let nNamespaceURI = aNode.namespaceURI;
+ let nLocalName = aNode.localName;
+
+ let prefix = this.namespacePrefixes[nNamespaceURI] || null;
+ let tag = (prefix ? prefix + ":" : "") + this.escapeName(nLocalName);
+
+ // stop once we've found a tag with an ID
+ if (aNode.id)
+ return "//" + tag + "[@id=" + this.quoteArgument(aNode.id) + "]";
+
+ // count the number of previous sibling nodes of the same tag
+ // (and possible also the same name)
+ let count = 0;
+ let nName = aNode.name || null;
+ for (let n = aNode; (n = n.previousSibling); )
+ if (n.localName == nLocalName && n.namespaceURI == nNamespaceURI &&
+ (!nName || n.name == nName))
+ count++;
+
+ // recurse until hitting either the document node or an ID'd node
+ return this.generate(aNode.parentNode) + "/" + tag +
+ (nName ? "[@name=" + this.quoteArgument(nName) + "]" : "") +
+ (count ? "[" + (count + 1) + "]" : "");
+ },
+
+ /**
+ * Resolves an XPath query generated by XPathGenerator.generate
+ */
+ resolve: function sss_xph_resolve(aDocument, aQuery) {
+ let xptype = Components.interfaces.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE;
+ return aDocument.evaluate(aQuery, aDocument, this.resolveNS, xptype, null).singleNodeValue;
+ },
+
+ /**
+ * Namespace resolver for the above XPath resolver
+ */
+ resolveNS: function sss_xph_resolveNS(aPrefix) {
+ return XPathGenerator.namespaceURIs[aPrefix] || null;
+ },
+
+ /**
+ * @returns valid XPath for the given node (usually just the local name itself)
+ */
+ escapeName: function sss_xph_escapeName(aName) {
+ // we can't just use the node's local name, if it contains
+ // special characters (cf. bug 485482)
+ return /^\w+$/.test(aName) ? aName :
+ "*[local-name()=" + this.quoteArgument(aName) + "]";
+ },
+
+ /**
+ * @returns a properly quoted string to insert into an XPath query
+ */
+ quoteArgument: function sss_xph_quoteArgument(aArg) {
+ return !/'/.test(aArg) ? "'" + aArg + "'" :
+ !/"/.test(aArg) ? '"' + aArg + '"' :
+ "concat('" + aArg.replace(/'+/g, "',\"$&\",'") + "')";
+ },
+
+ /**
+ * @returns an XPath query to all savable form field nodes
+ */
+ get restorableFormNodes() {
+ // for a comprehensive list of all available <INPUT> types see
+ // http://mxr.mozilla.org/mozilla-central/search?string=kInputTypeTable
+ let ignoreTypes = ["password", "hidden", "button", "image", "submit", "reset"];
+ // XXXzeniko work-around until lower-case has been implemented (bug 398389)
+ let toLowerCase = '"ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz"';
+ let ignore = "not(translate(@type, " + toLowerCase + ")='" +
+ ignoreTypes.join("' or translate(@type, " + toLowerCase + ")='") + "')";
+ let formNodesXPath = "//textarea|//select|//xhtml:textarea|//xhtml:select|" +
+ "//input[" + ignore + "]|//xhtml:input[" + ignore + "]";
+
+ delete this.restorableFormNodes;
+ return (this.restorableFormNodes = formNodesXPath);
+ }
+};
diff --git a/components/sessionstore/_SessionFile.jsm b/components/sessionstore/_SessionFile.jsm
new file mode 100644
index 0000000..62b4d16
--- /dev/null
+++ b/components/sessionstore/_SessionFile.jsm
@@ -0,0 +1,314 @@
+/* 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";
+
+this.EXPORTED_SYMBOLS = ["_SessionFile"];
+
+/**
+ * Implementation of all the disk I/O required by the session store.
+ * This is a private API, meant to be used only by the session store.
+ * It will change. Do not use it for any other purpose.
+ *
+ * Note that this module implicitly depends on one of two things:
+ * 1. either the asynchronous file I/O system enqueues its requests
+ * and never attempts to simultaneously execute two I/O requests on
+ * the files used by this module from two distinct threads; or
+ * 2. the clients of this API are well-behaved and do not place
+ * concurrent requests to the files used by this module.
+ *
+ * Otherwise, we could encounter bugs, especially under Windows,
+ * e.g. if a request attempts to write sessionstore.js while
+ * another attempts to copy that file.
+ *
+ * This implementation uses OS.File, which guarantees property 1.
+ */
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+ "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+ "resource://gre/modules/Task.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "console",
+ "resource://gre/modules/Console.jsm");
+
+// An encoder to UTF-8.
+XPCOMUtils.defineLazyGetter(this, "gEncoder", function () {
+ return new TextEncoder();
+});
+// A decoder.
+XPCOMUtils.defineLazyGetter(this, "gDecoder", function () {
+ return new TextDecoder();
+});
+
+this._SessionFile = {
+ /**
+ * A promise fulfilled once initialization (either synchronous or
+ * asynchronous) is complete.
+ */
+ promiseInitialized: function SessionFile_initialized() {
+ return SessionFileInternal.promiseInitialized;
+ },
+ /**
+ * Read the contents of the session file, asynchronously.
+ */
+ read: function SessionFile_read() {
+ return SessionFileInternal.read();
+ },
+ /**
+ * Read the contents of the session file, synchronously.
+ */
+ syncRead: function SessionFile_syncRead() {
+ return SessionFileInternal.syncRead();
+ },
+ /**
+ * Write the contents of the session file, asynchronously.
+ */
+ write: function SessionFile_write(aData) {
+ return SessionFileInternal.write(aData);
+ },
+ /**
+ * Create a backup copy, asynchronously.
+ */
+ createBackupCopy: function SessionFile_createBackupCopy() {
+ return SessionFileInternal.createBackupCopy();
+ },
+ /**
+ * Wipe the contents of the session file, asynchronously.
+ */
+ wipe: function SessionFile_wipe() {
+ return SessionFileInternal.wipe();
+ }
+};
+
+Object.freeze(_SessionFile);
+
+/**
+ * Utilities for dealing with promises and Task.jsm
+ */
+const TaskUtils = {
+ /**
+ * Add logging to a promise.
+ *
+ * @param {Promise} promise
+ * @return {Promise} A promise behaving as |promise|, but with additional
+ * logging in case of uncaught error.
+ */
+ captureErrors: function captureErrors(promise) {
+ return promise.then(
+ null,
+ function onError(reason) {
+ console.error("Uncaught asynchronous error:", reason);
+ throw reason;
+ }
+ );
+ },
+ /**
+ * Spawn a new Task from a generator.
+ *
+ * This function behaves as |Task.spawn|, with the exception that it
+ * adds logging in case of uncaught error. For more information, see
+ * the documentation of |Task.jsm|.
+ *
+ * @param {generator} gen Some generator.
+ * @return {Promise} A promise built from |gen|, with the same semantics
+ * as |Task.spawn(gen)|.
+ */
+ spawn: function spawn(gen) {
+ return this.captureErrors(Task.spawn(gen));
+ }
+};
+
+var SessionFileInternal = {
+ /**
+ * A promise fulfilled once initialization is complete
+ */
+ promiseInitialized: Promise.defer(),
+
+ /**
+ * The path to sessionstore.js
+ */
+ path: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js"),
+
+ /**
+ * The path to sessionstore.bak
+ */
+ backupPath: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.bak"),
+
+ /**
+ * Utility function to safely read a file synchronously.
+ * @param aPath
+ * A path to read the file from.
+ * @returns string if successful, undefined otherwise.
+ */
+ readAuxSync: function ssfi_readAuxSync(aPath) {
+ let text;
+ try {
+ let file = new FileUtils.File(aPath);
+ let chan = NetUtil.newChannel({
+ uri: NetUtil.newURI(file),
+ loadUsingSystemPrincipal: true
+ });
+ let stream = chan.open();
+ text = NetUtil.readInputStreamToString(stream, stream.available(),
+ {charset: "utf-8"});
+ } catch (e if e.result == Components.results.NS_ERROR_FILE_NOT_FOUND) {
+ // Ignore exceptions about non-existent files.
+ } catch (ex) {
+ // Any other error.
+ console.error("Uncaught error:", ex);
+ } finally {
+ return text;
+ }
+ },
+
+ /**
+ * Read the sessionstore file synchronously.
+ *
+ * This function is meant to serve as a fallback in case of race
+ * between a synchronous usage of the API and asynchronous
+ * initialization.
+ *
+ * In case if sessionstore.js file does not exist or is corrupted (something
+ * happened between backup and write), attempt to read the sessionstore.bak
+ * instead.
+ */
+ syncRead: function ssfi_syncRead() {
+ // First read the sessionstore.js.
+ let text = this.readAuxSync(this.path);
+ if (typeof text === "undefined") {
+ // If sessionstore.js does not exist or is corrupted, read sessionstore.bak.
+ text = this.readAuxSync(this.backupPath);
+ }
+ return text || "";
+ },
+
+ /**
+ * Utility function to safely read a file asynchronously.
+ * @param aPath
+ * A path to read the file from.
+ * @param aReadOptions
+ * Read operation options.
+ * |outExecutionDuration| option will be reused and can be
+ * incrementally updated by the worker process.
+ * @returns string if successful, undefined otherwise.
+ */
+ readAux: function ssfi_readAux(aPath, aReadOptions) {
+ let self = this;
+ return TaskUtils.spawn(function () {
+ let text;
+ try {
+ let bytes = yield OS.File.read(aPath, undefined, aReadOptions);
+ text = gDecoder.decode(bytes);
+ } catch (ex if self._isNoSuchFile(ex)) {
+ // Ignore exceptions about non-existent files.
+ } catch (ex) {
+ // Any other error.
+ console.error("Uncaught error - with the file: " + self.path, ex);
+ }
+ throw new Task.Result(text);
+ });
+ },
+
+ /**
+ * Read the sessionstore file asynchronously.
+ *
+ * In case sessionstore.js file does not exist or is corrupted (something
+ * happened between backup and write), attempt to read the sessionstore.bak
+ * instead.
+ */
+ read: function ssfi_read() {
+ let self = this;
+ return TaskUtils.spawn(function task() {
+ // Specify |outExecutionDuration| option to hold the combined duration of
+ // the asynchronous reads off the main thread (of both sessionstore.js and
+ // sessionstore.bak, if necessary). If sessionstore.js does not exist or
+ // is corrupted, |outExecutionDuration| will register the time it took to
+ // attempt to read the file. It will then be subsequently incremented by
+ // the read time of sessionsore.bak.
+ let readOptions = {
+ outExecutionDuration: null
+ };
+ // First read the sessionstore.js.
+ let text = yield self.readAux(self.path, readOptions);
+ if (typeof text === "undefined") {
+ // If sessionstore.js does not exist or is corrupted, read the
+ // sessionstore.bak.
+ text = yield self.readAux(self.backupPath, readOptions);
+ }
+ // Return either the content of the sessionstore.bak if it was read
+ // successfully or an empty string otherwise.
+ throw new Task.Result(text || "");
+ });
+ },
+
+ write: function ssfi_write(aData) {
+ let refObj = {};
+ let self = this;
+ return TaskUtils.spawn(function task() {
+ let bytes = gEncoder.encode(aData);
+
+ try {
+ let promise = OS.File.writeAtomic(self.path, bytes, {tmpPath: self.path + ".tmp"});
+ yield promise;
+ } catch (ex) {
+ console.error("Could not write session state file: " + self.path, ex);
+ }
+ });
+ },
+
+ createBackupCopy: function ssfi_createBackupCopy() {
+ let backupCopyOptions = {
+ outExecutionDuration: null
+ };
+ let self = this;
+ return TaskUtils.spawn(function task() {
+ try {
+ yield OS.File.move(self.path, self.backupPath, backupCopyOptions);
+ } catch (ex if self._isNoSuchFile(ex)) {
+ // Ignore exceptions about non-existent files.
+ } catch (ex) {
+ console.error("Could not backup session state file: " + self.path, ex);
+ throw ex;
+ }
+ });
+ },
+
+ wipe: function ssfi_wipe() {
+ let self = this;
+ return TaskUtils.spawn(function task() {
+ try {
+ yield OS.File.remove(self.path);
+ } catch (ex if self._isNoSuchFile(ex)) {
+ // Ignore exceptions about non-existent files.
+ } catch (ex) {
+ console.error("Could not remove session state file: " + self.path, ex);
+ throw ex;
+ }
+
+ try {
+ yield OS.File.remove(self.backupPath);
+ } catch (ex if self._isNoSuchFile(ex)) {
+ // Ignore exceptions about non-existent files.
+ } catch (ex) {
+ console.error("Could not remove session state backup file: " + self.path, ex);
+ throw ex;
+ }
+ });
+ },
+
+ _isNoSuchFile: function ssfi_isNoSuchFile(aReason) {
+ return aReason instanceof OS.File.Error && aReason.becauseNoSuchFile;
+ }
+};
diff --git a/components/sessionstore/content/aboutSessionRestore.js b/components/sessionstore/content/aboutSessionRestore.js
new file mode 100644
index 0000000..2b6f9ea
--- /dev/null
+++ b/components/sessionstore/content/aboutSessionRestore.js
@@ -0,0 +1,320 @@
+/* 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 Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+var gStateObject;
+var gTreeData;
+
+// Page initialization
+
+window.onload = function() {
+ // the crashed session state is kept inside a textbox so that SessionStore picks it up
+ // (for when the tab is closed or the session crashes right again)
+ var sessionData = document.getElementById("sessionData");
+ if (!sessionData.value) {
+ document.getElementById("errorTryAgain").disabled = true;
+ return;
+ }
+
+ // remove unneeded braces (added for compatibility with Firefox 2.0 and 3.0)
+ if (sessionData.value.charAt(0) == '(')
+ sessionData.value = sessionData.value.slice(1, -1);
+ try {
+ gStateObject = JSON.parse(sessionData.value);
+ }
+ catch (exJSON) {
+ var s = new Cu.Sandbox("about:blank", {sandboxName: 'aboutSessionRestore'});
+ gStateObject = Cu.evalInSandbox("(" + sessionData.value + ")", s);
+ // If we couldn't parse the string with JSON.parse originally, make sure
+ // that the value in the textbox will be parsable.
+ sessionData.value = JSON.stringify(gStateObject);
+ }
+
+ // make sure the data is tracked to be restored in case of a subsequent crash
+ var event = document.createEvent("UIEvents");
+ event.initUIEvent("input", true, true, window, 0);
+ sessionData.dispatchEvent(event);
+
+ initTreeView();
+
+ document.getElementById("errorTryAgain").focus();
+};
+
+function initTreeView() {
+ var tabList = document.getElementById("tabList");
+ var winLabel = tabList.getAttribute("_window_label");
+
+ gTreeData = [];
+ gStateObject.windows.forEach(function(aWinData, aIx) {
+ var winState = {
+ label: winLabel.replace("%S", (aIx + 1)),
+ open: true,
+ checked: true,
+ ix: aIx
+ };
+ winState.tabs = aWinData.tabs.map(function(aTabData) {
+ var entry = aTabData.entries[aTabData.index - 1] || { url: "about:blank" };
+ var iconURL = aTabData.attributes && aTabData.attributes.image || null;
+ // don't initiate a connection just to fetch a favicon (see bug 462863)
+ if (/^https?:/.test(iconURL))
+ iconURL = "moz-anno:favicon:" + iconURL;
+ return {
+ label: entry.title || entry.url,
+ checked: true,
+ src: iconURL,
+ parent: winState
+ };
+ });
+ gTreeData.push(winState);
+ for (let tab of winState.tabs)
+ gTreeData.push(tab);
+ }, this);
+
+ tabList.view = treeView;
+ tabList.view.selection.select(0);
+}
+
+// User actions
+
+function restoreSession() {
+ document.getElementById("errorTryAgain").disabled = true;
+
+ // remove all unselected tabs from the state before restoring it
+ var ix = gStateObject.windows.length - 1;
+ for (var t = gTreeData.length - 1; t >= 0; t--) {
+ if (treeView.isContainer(t)) {
+ if (gTreeData[t].checked === 0)
+ // this window will be restored partially
+ gStateObject.windows[ix].tabs =
+ gStateObject.windows[ix].tabs.filter(function(aTabData, aIx)
+ gTreeData[t].tabs[aIx].checked);
+ else if (!gTreeData[t].checked)
+ // this window won't be restored at all
+ gStateObject.windows.splice(ix, 1);
+ ix--;
+ }
+ }
+ var stateString = JSON.stringify(gStateObject);
+
+ var ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
+ var top = getBrowserWindow();
+
+ // if there's only this page open, reuse the window for restoring the session
+ if (top.gBrowser.tabs.length == 1) {
+ ss.setWindowState(top, stateString, true);
+ return;
+ }
+
+ // restore the session into a new window and close the current tab
+ var newWindow = top.openDialog(top.location, "_blank", "chrome,dialog=no,all");
+ newWindow.addEventListener("load", function() {
+ newWindow.removeEventListener("load", arguments.callee, true);
+ ss.setWindowState(newWindow, stateString, true);
+
+ var tabbrowser = top.gBrowser;
+ var tabIndex = tabbrowser.getBrowserIndexForDocument(document);
+ tabbrowser.removeTab(tabbrowser.tabs[tabIndex]);
+ }, true);
+}
+
+function startNewSession() {
+ var prefBranch = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+ if (prefBranch.getIntPref("browser.startup.page") == 0)
+ getBrowserWindow().gBrowser.loadURI("about:logopage");
+ else
+ getBrowserWindow().BrowserHome();
+}
+
+function onListClick(aEvent) {
+ // don't react to right-clicks
+ if (aEvent.button == 2)
+ return;
+
+ if (!treeView.treeBox) {
+ return;
+ }
+ var cell = treeView.treeBox.getCellAt(aEvent.clientX, aEvent.clientY);
+ if (cell.col) {
+ // Restore this specific tab in the same window for middle/double/accel clicking
+ // on a tab's title.
+#ifdef XP_MACOSX
+ let accelKey = aEvent.metaKey;
+#else
+ let accelKey = aEvent.ctrlKey;
+#endif
+ if ((aEvent.button == 1 || aEvent.button == 0 && aEvent.detail == 2 || accelKey) &&
+ cell.col.id == "title" &&
+ !treeView.isContainer(cell.row)) {
+ restoreSingleTab(cell.row, aEvent.shiftKey);
+ aEvent.stopPropagation();
+ }
+ else if (cell.col.id == "restore")
+ toggleRowChecked(cell.row);
+ }
+}
+
+function onListKeyDown(aEvent) {
+ switch (aEvent.keyCode)
+ {
+ case KeyEvent.DOM_VK_SPACE:
+ toggleRowChecked(document.getElementById("tabList").currentIndex);
+ break;
+ case KeyEvent.DOM_VK_RETURN:
+ var ix = document.getElementById("tabList").currentIndex;
+ if (aEvent.ctrlKey && !treeView.isContainer(ix))
+ restoreSingleTab(ix, aEvent.shiftKey);
+ break;
+ case KeyEvent.DOM_VK_UP:
+ case KeyEvent.DOM_VK_DOWN:
+ case KeyEvent.DOM_VK_PAGE_UP:
+ case KeyEvent.DOM_VK_PAGE_DOWN:
+ case KeyEvent.DOM_VK_HOME:
+ case KeyEvent.DOM_VK_END:
+ aEvent.preventDefault(); // else the page scrolls unwantedly
+ break;
+ }
+}
+
+// Helper functions
+
+function getBrowserWindow() {
+ return window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem).rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
+}
+
+function toggleRowChecked(aIx) {
+ var item = gTreeData[aIx];
+ item.checked = !item.checked;
+ treeView.treeBox.invalidateRow(aIx);
+
+ function isChecked(aItem) aItem.checked;
+
+ if (treeView.isContainer(aIx)) {
+ // (un)check all tabs of this window as well
+ for (let tab of item.tabs) {
+ tab.checked = item.checked;
+ treeView.treeBox.invalidateRow(gTreeData.indexOf(tab));
+ }
+ }
+ else {
+ // update the window's checkmark as well (0 means "partially checked")
+ item.parent.checked = item.parent.tabs.every(isChecked) ? true :
+ item.parent.tabs.some(isChecked) ? 0 : false;
+ treeView.treeBox.invalidateRow(gTreeData.indexOf(item.parent));
+ }
+
+ document.getElementById("errorTryAgain").disabled = !gTreeData.some(isChecked);
+}
+
+function restoreSingleTab(aIx, aShifted) {
+ var tabbrowser = getBrowserWindow().gBrowser;
+ var newTab = tabbrowser.addTab();
+ var item = gTreeData[aIx];
+
+ var ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
+ var tabState = gStateObject.windows[item.parent.ix]
+ .tabs[aIx - gTreeData.indexOf(item.parent) - 1];
+ // ensure tab would be visible on the tabstrip.
+ tabState.hidden = false;
+ ss.setTabState(newTab, JSON.stringify(tabState));
+
+ // respect the preference as to whether to select the tab (the Shift key inverses)
+ var prefBranch = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+ if (prefBranch.getBoolPref("browser.tabs.loadInBackground") != !aShifted)
+ tabbrowser.selectedTab = newTab;
+}
+
+// Tree controller
+
+var treeView = {
+ treeBox: null,
+ selection: null,
+
+ get rowCount() { return gTreeData.length; },
+ setTree: function(treeBox) { this.treeBox = treeBox; },
+ getCellText: function(idx, column) { return gTreeData[idx].label; },
+ isContainer: function(idx) { return "open" in gTreeData[idx]; },
+ getCellValue: function(idx, column){ return gTreeData[idx].checked; },
+ isContainerOpen: function(idx) { return gTreeData[idx].open; },
+ isContainerEmpty: function(idx) { return false; },
+ isSeparator: function(idx) { return false; },
+ isSorted: function() { return false; },
+ isEditable: function(idx, column) { return false; },
+ canDrop: function(idx, orientation, dt) { return false; },
+ getLevel: function(idx) { return this.isContainer(idx) ? 0 : 1; },
+
+ getParentIndex: function(idx) {
+ if (!this.isContainer(idx))
+ for (var t = idx - 1; t >= 0 ; t--)
+ if (this.isContainer(t))
+ return t;
+ return -1;
+ },
+
+ hasNextSibling: function(idx, after) {
+ var thisLevel = this.getLevel(idx);
+ for (var t = after + 1; t < gTreeData.length; t++)
+ if (this.getLevel(t) <= thisLevel)
+ return this.getLevel(t) == thisLevel;
+ return false;
+ },
+
+ toggleOpenState: function(idx) {
+ if (!this.isContainer(idx))
+ return;
+ var item = gTreeData[idx];
+ if (item.open) {
+ // remove this window's tab rows from the view
+ var thisLevel = this.getLevel(idx);
+ for (var t = idx + 1; t < gTreeData.length && this.getLevel(t) > thisLevel; t++);
+ var deletecount = t - idx - 1;
+ gTreeData.splice(idx + 1, deletecount);
+ this.treeBox.rowCountChanged(idx + 1, -deletecount);
+ }
+ else {
+ // add this window's tab rows to the view
+ var toinsert = gTreeData[idx].tabs;
+ for (var i = 0; i < toinsert.length; i++)
+ gTreeData.splice(idx + i + 1, 0, toinsert[i]);
+ this.treeBox.rowCountChanged(idx + 1, toinsert.length);
+ }
+ item.open = !item.open;
+ this.treeBox.invalidateRow(idx);
+ },
+
+ getCellProperties: function(idx, column) {
+ if (column.id == "restore" && this.isContainer(idx) && gTreeData[idx].checked === 0)
+ return "partial";
+ if (column.id == "title")
+ return this.getImageSrc(idx, column) ? "icon" : "noicon";
+
+ return "";
+ },
+
+ getRowProperties: function(idx) {
+ var winState = gTreeData[idx].parent || gTreeData[idx];
+ if (winState.ix % 2 != 0)
+ return "alternate";
+
+ return "";
+ },
+
+ getImageSrc: function(idx, column) {
+ if (column.id == "title")
+ return gTreeData[idx].src || null;
+ return null;
+ },
+
+ getProgressMode : function(idx, column) { },
+ cycleHeader: function(column) { },
+ cycleCell: function(idx, column) { },
+ selectionChanged: function() { },
+ performAction: function(action) { },
+ performActionOnCell: function(action, index, column) { },
+ getColumnProperties: function(column) { return ""; }
+};
diff --git a/components/sessionstore/content/aboutSessionRestore.xhtml b/components/sessionstore/content/aboutSessionRestore.xhtml
new file mode 100644
index 0000000..6b22250
--- /dev/null
+++ b/components/sessionstore/content/aboutSessionRestore.xhtml
@@ -0,0 +1,94 @@
+<?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 % restorepageDTD SYSTEM "chrome://browser/locale/aboutSessionRestore.dtd">
+ %restorepageDTD;
+]>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>&restorepage.tabtitle;</title>
+ <link rel="stylesheet" href="chrome://global/skin/netError.css" type="text/css" media="all"/>
+ <link rel="stylesheet" href="chrome://browser/skin/aboutSessionRestore.css" type="text/css" media="all"/>
+ <link rel="icon" type="image/png" href="chrome://global/skin/icons/warning-16.png"/>
+
+ <script type="application/javascript;version=1.8" src="chrome://browser/content/aboutSessionRestore.js"/>
+ </head>
+
+ <body dir="&locale.dir;">
+
+ <!-- PAGE CONTAINER (for styling purposes only) -->
+ <div id="errorPageContainer">
+
+ <!-- Error Title -->
+ <div id="errorTitle">
+ <h1 id="errorTitleText">&restorepage.errorTitle;</h1>
+ </div>
+
+ <!-- LONG CONTENT (the section most likely to require scrolling) -->
+ <div id="errorLongContent">
+
+ <!-- Short Description -->
+ <div id="errorShortDesc">
+ <p id="errorShortDescText">&restorepage.problemDesc;</p>
+ </div>
+
+ <!-- Long Description (Note: See netError.dtd for used XHTML tags) -->
+ <div id="errorLongDesc">
+ <p>&restorepage.tryThis;</p>
+ <ul>
+ <li>&restorepage.restoreSome;</li>
+ <li>&restorepage.startNew;</li>
+ </ul>
+ </div>
+
+ <!-- Short Description -->
+ <div id="errorTrailerDesc">
+ <tree xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ id="tabList" flex="1" seltype="single" hidecolumnpicker="true"
+ onclick="onListClick(event);" onkeydown="onListKeyDown(event);"
+ _window_label="&restorepage.windowLabel;">
+ <treecols>
+ <treecol cycler="true" id="restore" type="checkbox" label="&restorepage.restoreHeader;"/>
+ <splitter class="tree-splitter"/>
+ <treecol primary="true" id="title" label="&restorepage.listHeader;" flex="1"/>
+ </treecols>
+ <treechildren flex="1"/>
+ </tree>
+ </div>
+ </div>
+
+ <!-- Buttons -->
+ <hbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" id="buttons">
+#ifdef XP_UNIX
+ <button id="errorCancel" label="&restorepage.closeButton;"
+ accesskey="&restorepage.close.access;"
+ oncommand="startNewSession();"/>
+ <button id="errorTryAgain" label="&restorepage.tryagainButton;"
+ accesskey="&restorepage.restore.access;"
+ oncommand="restoreSession();"/>
+#else
+ <button id="errorTryAgain" label="&restorepage.tryagainButton;"
+ accesskey="&restorepage.restore.access;"
+ oncommand="restoreSession();"/>
+ <button id="errorCancel" label="&restorepage.closeButton;"
+ accesskey="&restorepage.close.access;"
+ oncommand="startNewSession();"/>
+#endif
+ </hbox>
+ <!-- holds the session data for when the tab is closed -->
+ <input type="text" id="sessionData" style="display: none;"/>
+ </div>
+
+ </body>
+</html>
diff --git a/components/sessionstore/content/content-sessionStore.js b/components/sessionstore/content/content-sessionStore.js
new file mode 100644
index 0000000..e3e956e
--- /dev/null
+++ b/components/sessionstore/content/content-sessionStore.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/. */
+
+function debug(msg) {
+ Services.console.logStringMessage("SessionStoreContent: " + msg);
+}
+
+/**
+ * Listens for and handles content events that we need for the
+ * session store service to be notified of state changes in content.
+ */
+var EventListener = {
+
+ DOM_EVENTS: [
+ "pageshow", "change", "input"
+ ],
+
+ init: function () {
+ this.DOM_EVENTS.forEach(e => addEventListener(e, this, true));
+ },
+
+ handleEvent: function (event) {
+ switch (event.type) {
+ case "pageshow":
+ if (event.persisted)
+ sendAsyncMessage("SessionStore:pageshow");
+ break;
+ case "input":
+ case "change":
+ sendAsyncMessage("SessionStore:input");
+ break;
+ default:
+ debug("received unknown event '" + event.type + "'");
+ break;
+ }
+ }
+};
+
+EventListener.init();
diff --git a/components/sessionstore/jar.mn b/components/sessionstore/jar.mn
new file mode 100644
index 0000000..825b00f
--- /dev/null
+++ b/components/sessionstore/jar.mn
@@ -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/.
+
+browser.jar:
+* content/browser/aboutSessionRestore.xhtml (content/aboutSessionRestore.xhtml)
+* content/browser/aboutSessionRestore.js (content/aboutSessionRestore.js)
+ content/browser/content-sessionStore.js (content/content-sessionStore.js)
diff --git a/components/sessionstore/moz.build b/components/sessionstore/moz.build
new file mode 100644
index 0000000..84278da
--- /dev/null
+++ b/components/sessionstore/moz.build
@@ -0,0 +1,29 @@
+# -*- 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/.
+
+JAR_MANIFESTS += ['jar.mn']
+
+XPIDL_SOURCES += [
+ 'nsISessionStartup.idl',
+ 'nsISessionStore.idl',
+]
+
+XPIDL_MODULE = 'sessionstore'
+
+EXTRA_COMPONENTS += [
+ 'nsSessionStartup.js',
+ 'nsSessionStore.js',
+ 'nsSessionStore.manifest',
+]
+
+EXTRA_JS_MODULES.sessionstore = [
+ '_SessionFile.jsm',
+ 'DocumentUtils.jsm',
+ 'SessionStorage.jsm',
+ 'XPathGenerator.jsm',
+]
+
+EXTRA_PP_JS_MODULES.sessionstore += ['SessionStore.jsm'] \ No newline at end of file
diff --git a/components/sessionstore/nsISessionStartup.idl b/components/sessionstore/nsISessionStartup.idl
new file mode 100644
index 0000000..a8e786d
--- /dev/null
+++ b/components/sessionstore/nsISessionStartup.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+/**
+ * nsISessionStore keeps track of the current browsing state - i.e.
+ * tab history, cookies, scroll state, form data, POSTDATA and window features
+ * - and allows to restore everything into one window.
+ */
+
+[scriptable, uuid(51f4b9f0-f3d2-11e2-bb62-2c24dd830245)]
+interface nsISessionStartup: nsISupports
+{
+ /**
+ * Return a promise that is resolved once initialization
+ * is complete.
+ */
+ readonly attribute jsval onceInitialized;
+
+ // Get session state
+ readonly attribute jsval state;
+
+ /**
+ * Determines whether there is a pending session restore and makes sure that
+ * we're initialized before returning. If we're not yet this will read the
+ * session file synchronously.
+ */
+ boolean doRestore();
+
+ /**
+ * Returns whether we will restore a session that ends up replacing the
+ * homepage. The browser uses this to not start loading the homepage if
+ * we're going to stop its load anyway shortly after.
+ *
+ * This is meant to be an optimization for the average case that loading the
+ * session file finishes before we may want to start loading the default
+ * homepage. Should this be called before the session file has been read it
+ * will just return false.
+ */
+ readonly attribute bool willOverrideHomepage;
+
+ /**
+ * What type of session we're restoring.
+ * NO_SESSION There is no data available from the previous session
+ * RECOVER_SESSION The last session crashed. It will either be restored or
+ * about:sessionrestore will be shown.
+ * RESUME_SESSION The previous session should be restored at startup
+ * DEFER_SESSION The previous session is fine, but it shouldn't be restored
+ * without explicit action (with the exception of pinned tabs)
+ */
+ const unsigned long NO_SESSION = 0;
+ const unsigned long RECOVER_SESSION = 1;
+ const unsigned long RESUME_SESSION = 2;
+ const unsigned long DEFER_SESSION = 3;
+
+ readonly attribute unsigned long sessionType;
+};
diff --git a/components/sessionstore/nsISessionStore.idl b/components/sessionstore/nsISessionStore.idl
new file mode 100644
index 0000000..0490772
--- /dev/null
+++ b/components/sessionstore/nsISessionStore.idl
@@ -0,0 +1,206 @@
+/* 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/. */
+
+#include "nsISupports.idl"
+
+interface nsIDOMWindow;
+interface nsIDOMNode;
+
+/**
+ * nsISessionStore keeps track of the current browsing state - i.e.
+ * tab history, cookies, scroll state, form data, POSTDATA and window features
+ * - and allows to restore everything into one browser window.
+ *
+ * The nsISessionStore API operates mostly on browser windows and the tabbrowser
+ * tabs contained in them:
+ *
+ * * "Browser windows" are those DOM windows having loaded
+ * chrome://browser/content/browser.xul . From overlays you can just pass the
+ * global |window| object to the API, though (or |top| from a sidebar).
+ * From elsewhere you can get browser windows through the nsIWindowMediator
+ * by looking for "navigator:browser" windows.
+ *
+ * * "Tabbrowser tabs" are all the child nodes of a browser window's
+ * |gBrowser.tabContainer| such as e.g. |gBrowser.selectedTab|.
+ */
+
+[scriptable, uuid(43ec216b-f002-4424-bfc5-fc555c87dbc4)]
+interface nsISessionStore : nsISupports
+{
+ /**
+ * Initialize the service
+ */
+ jsval init(in nsIDOMWindow aWindow);
+
+ /**
+ * Is it possible to restore the previous session. Will always be false when
+ * in Private Browsing mode.
+ */
+ attribute boolean canRestoreLastSession;
+
+ /**
+ * Restore the previous session if possible. This will not overwrite the
+ * current session. Instead the previous session will be merged into the
+ * current session. Current windows will be reused if they were windows that
+ * pinned tabs were previously restored into. New windows will be opened as
+ * needed.
+ *
+ * Note: This will throw if there is no previous state to restore. Check with
+ * canRestoreLastSession first to avoid thrown errors.
+ */
+ void restoreLastSession();
+
+ /**
+ * Get the current browsing state.
+ * @returns a JSON string representing the session state.
+ */
+ AString getBrowserState();
+
+ /**
+ * Set the browsing state.
+ * This will immediately restore the state of the whole application to the state
+ * passed in, *replacing* the current session.
+ *
+ * @param aState is a JSON string representing the session state.
+ */
+ void setBrowserState(in AString aState);
+
+ /**
+ * @param aWindow is the browser window whose state is to be returned.
+ *
+ * @returns a JSON string representing a session state with only one window.
+ */
+ AString getWindowState(in nsIDOMWindow aWindow);
+
+ /**
+ * @param aWindow is the browser window whose state is to be set.
+ * @param aState is a JSON string representing a session state.
+ * @param aOverwrite boolean overwrite existing tabs
+ */
+ void setWindowState(in nsIDOMWindow aWindow, in AString aState, in boolean aOverwrite);
+
+ /**
+ * @param aTab is the tabbrowser tab whose state is to be returned.
+ *
+ * @returns a JSON string representing the state of the tab
+ * (note: doesn't contain cookies - if you need them, use getWindowState instead).
+ */
+ AString getTabState(in nsIDOMNode aTab);
+
+ /**
+ * @param aTab is the tabbrowser tab whose state is to be set.
+ * @param aState is a JSON string representing a session state.
+ */
+ void setTabState(in nsIDOMNode aTab, in AString aState);
+
+ /**
+ * Duplicates a given tab as thoroughly as possible.
+ *
+ * @param aWindow is the browser window into which the tab will be duplicated.
+ * @param aTab is the tabbrowser tab to duplicate (can be from a different window).
+ * @param aDelta is the offset to the history entry to load in the duplicated tab.
+ * @returns a reference to the newly created tab.
+ */
+ nsIDOMNode duplicateTab(in nsIDOMWindow aWindow, in nsIDOMNode aTab,
+ [optional] in long aDelta);
+
+ /**
+ * Get the number of restore-able tabs for a browser window
+ */
+ unsigned long getClosedTabCount(in nsIDOMWindow aWindow);
+
+ /**
+ * Get closed tab data
+ *
+ * @param aWindow is the browser window for which to get closed tab data
+ * @returns a JSON string representing the list of closed tabs.
+ */
+ AString getClosedTabData(in nsIDOMWindow aWindow);
+
+ /**
+ * @param aWindow is the browser window to reopen a closed tab in.
+ * @param aIndex is the index of the tab to be restored (FIFO ordered).
+ * @returns a reference to the reopened tab.
+ */
+ nsIDOMNode undoCloseTab(in nsIDOMWindow aWindow, in unsigned long aIndex);
+
+ /**
+ * @param aWindow is the browser window associated with the closed tab.
+ * @param aIndex is the index of the closed tab to be removed (FIFO ordered).
+ */
+ nsIDOMNode forgetClosedTab(in nsIDOMWindow aWindow, in unsigned long aIndex);
+
+ /**
+ * Get the number of restore-able windows
+ */
+ unsigned long getClosedWindowCount();
+
+ /**
+ * Get closed windows data
+ *
+ * @returns a JSON string representing the list of closed windows.
+ */
+ AString getClosedWindowData();
+
+ /**
+ * @param aIndex is the index of the windows to be restored (FIFO ordered).
+ * @returns the nsIDOMWindow object of the reopened window
+ */
+ nsIDOMWindow undoCloseWindow(in unsigned long aIndex);
+
+ /**
+ * @param aIndex is the index of the closed window to be removed (FIFO ordered).
+ *
+ * @throws NS_ERROR_INVALID_ARG
+ * when aIndex does not map to a closed window
+ */
+ nsIDOMNode forgetClosedWindow(in unsigned long aIndex);
+
+ /**
+ * @param aWindow is the window to get the value for.
+ * @param aKey is the value's name.
+ *
+ * @returns A string value or an empty string if none is set.
+ */
+ AString getWindowValue(in nsIDOMWindow aWindow, in AString aKey);
+
+ /**
+ * @param aWindow is the browser window to set the value for.
+ * @param aKey is the value's name.
+ * @param aStringValue is the value itself (use JSON.stringify/parse before setting JS objects).
+ */
+ void setWindowValue(in nsIDOMWindow aWindow, in AString aKey, in AString aStringValue);
+
+ /**
+ * @param aWindow is the browser window to get the value for.
+ * @param aKey is the value's name.
+ */
+ void deleteWindowValue(in nsIDOMWindow aWindow, in AString aKey);
+
+ /**
+ * @param aTab is the tabbrowser tab to get the value for.
+ * @param aKey is the value's name.
+ *
+ * @returns A string value or an empty string if none is set.
+ */
+ AString getTabValue(in nsIDOMNode aTab, in AString aKey);
+
+ /**
+ * @param aTab is the tabbrowser tab to set the value for.
+ * @param aKey is the value's name.
+ * @param aStringValue is the value itself (use JSON.stringify/parse before setting JS objects).
+ */
+ void setTabValue(in nsIDOMNode aTab, in AString aKey, in AString aStringValue);
+
+ /**
+ * @param aTab is the tabbrowser tab to get the value for.
+ * @param aKey is the value's name.
+ */
+ void deleteTabValue(in nsIDOMNode aTab, in AString aKey);
+
+ /**
+ * @param aName is the name of the attribute to save/restore for all tabbrowser tabs.
+ */
+ void persistTabAttribute(in AString aName);
+};
diff --git a/components/sessionstore/nsSessionStartup.js b/components/sessionstore/nsSessionStartup.js
new file mode 100644
index 0000000..04037c1
--- /dev/null
+++ b/components/sessionstore/nsSessionStartup.js
@@ -0,0 +1,296 @@
+/* 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/. */
+
+/**
+ * Session Storage and Restoration
+ *
+ * Overview
+ * This service reads user's session file at startup, and makes a determination
+ * as to whether the session should be restored. It will restore the session
+ * under the circumstances described below. If the auto-start Private Browsing
+ * mode is active, however, the session is never restored.
+ *
+ * Crash Detection
+ * The session file stores a session.state property, that
+ * indicates whether the browser is currently running. When the browser shuts
+ * down, the field is changed to "stopped". At startup, this field is read, and
+ * if its value is "running", then it's assumed that the browser had previously
+ * crashed, or at the very least that something bad happened, and that we should
+ * restore the session.
+ *
+ * Forced Restarts
+ * In the event that a restart is required due to application update or extension
+ * installation, set the browser.sessionstore.resume_session_once pref to true,
+ * and the session will be restored the next time the browser starts.
+ *
+ * Always Resume
+ * This service will always resume the session if the integer pref
+ * browser.startup.page is set to 3.
+ */
+
+/* :::::::: Constants and Helpers ::::::::::::::: */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "_SessionFile",
+ "resource:///modules/sessionstore/_SessionFile.jsm");
+
+const STATE_RUNNING_STR = "running";
+
+function debug(aMsg) {
+ aMsg = ("SessionStartup: " + aMsg).replace(/\S{80}/g, "$&\n");
+ Services.console.logStringMessage(aMsg);
+}
+
+var gOnceInitializedDeferred = Promise.defer();
+
+/* :::::::: The Service ::::::::::::::: */
+
+function SessionStartup() {
+}
+
+SessionStartup.prototype = {
+
+ // the state to restore at startup
+ _initialState: null,
+ _sessionType: Ci.nsISessionStartup.NO_SESSION,
+ _initialized: false,
+
+/* ........ Global Event Handlers .............. */
+
+ /**
+ * Initialize the component
+ */
+ init: function sss_init() {
+ // do not need to initialize anything in auto-started private browsing sessions
+ if (PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ this._initialized = true;
+ gOnceInitializedDeferred.resolve();
+ return;
+ }
+
+ if (Services.prefs.getBoolPref("browser.sessionstore.resume_session_once") ||
+ Services.prefs.getIntPref("browser.startup.page") == 3) {
+ this._ensureInitialized();
+ } else {
+ _SessionFile.read().then(
+ this._onSessionFileRead.bind(this)
+ );
+ }
+ },
+
+ // Wrap a string as a nsISupports
+ _createSupportsString: function ssfi_createSupportsString(aData) {
+ let string = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(Ci.nsISupportsString);
+ string.data = aData;
+ return string;
+ },
+
+ _onSessionFileRead: function sss_onSessionFileRead(aStateString) {
+ if (this._initialized) {
+ // Initialization is complete, nothing else to do
+ return;
+ }
+ try {
+ this._initialized = true;
+
+ // Let observers modify the state before it is used
+ let supportsStateString = this._createSupportsString(aStateString);
+ Services.obs.notifyObservers(supportsStateString, "sessionstore-state-read", "");
+ aStateString = supportsStateString.data;
+
+ // No valid session found.
+ if (!aStateString) {
+ this._sessionType = Ci.nsISessionStartup.NO_SESSION;
+ return;
+ }
+
+ // parse the session state into a JS object
+ // remove unneeded braces (added for compatibility with Firefox 2.0 and 3.0)
+ if (aStateString.charAt(0) == '(')
+ aStateString = aStateString.slice(1, -1);
+ let corruptFile = false;
+ try {
+ this._initialState = JSON.parse(aStateString);
+ }
+ catch (ex) {
+ debug("The session file contained un-parse-able JSON: " + ex);
+ // This is not valid JSON, but this might still be valid JavaScript,
+ // as used in FF2/FF3, so we need to eval.
+ // evalInSandbox will throw if aStateString is not parse-able.
+ try {
+ var s = new Cu.Sandbox("about:blank", {sandboxName: 'nsSessionStartup'});
+ this._initialState = Cu.evalInSandbox("(" + aStateString + ")", s);
+ } catch(ex) {
+ debug("The session file contained un-eval-able JSON: " + ex);
+ corruptFile = true;
+ }
+ }
+ let doResumeSessionOnce = Services.prefs.getBoolPref("browser.sessionstore.resume_session_once");
+ let doResumeSession = doResumeSessionOnce ||
+ Services.prefs.getIntPref("browser.startup.page") == 3;
+
+ // If this is a normal restore then throw away any previous session
+ if (!doResumeSessionOnce)
+ delete this._initialState.lastSessionState;
+
+ let resumeFromCrash = Services.prefs.getBoolPref("browser.sessionstore.resume_from_crash");
+ let lastSessionCrashed =
+ this._initialState && this._initialState.session &&
+ this._initialState.session.state &&
+ this._initialState.session.state == STATE_RUNNING_STR;
+
+ // set the startup type
+ if (lastSessionCrashed && resumeFromCrash)
+ this._sessionType = Ci.nsISessionStartup.RECOVER_SESSION;
+ else if (!lastSessionCrashed && doResumeSession)
+ this._sessionType = Ci.nsISessionStartup.RESUME_SESSION;
+ else if (this._initialState)
+ this._sessionType = Ci.nsISessionStartup.DEFER_SESSION;
+ else
+ this._initialState = null; // reset the state
+
+ Services.obs.addObserver(this, "sessionstore-windows-restored", true);
+
+ if (this._sessionType != Ci.nsISessionStartup.NO_SESSION)
+ Services.obs.addObserver(this, "browser:purge-session-history", true);
+
+ } finally {
+ // We're ready. Notify everyone else.
+ Services.obs.notifyObservers(null, "sessionstore-state-finalized", "");
+ gOnceInitializedDeferred.resolve();
+ }
+ },
+
+ /**
+ * Handle notifications
+ */
+ observe: function sss_observe(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "app-startup":
+ Services.obs.addObserver(this, "final-ui-startup", true);
+ Services.obs.addObserver(this, "quit-application", true);
+ break;
+ case "final-ui-startup":
+ Services.obs.removeObserver(this, "final-ui-startup");
+ Services.obs.removeObserver(this, "quit-application");
+ this.init();
+ break;
+ case "quit-application":
+ // no reason for initializing at this point (cf. bug 409115)
+ Services.obs.removeObserver(this, "final-ui-startup");
+ Services.obs.removeObserver(this, "quit-application");
+ if (this._sessionType != Ci.nsISessionStartup.NO_SESSION)
+ Services.obs.removeObserver(this, "browser:purge-session-history");
+ break;
+ case "sessionstore-windows-restored":
+ Services.obs.removeObserver(this, "sessionstore-windows-restored");
+ // free _initialState after nsSessionStore is done with it
+ this._initialState = null;
+ break;
+ case "browser:purge-session-history":
+ Services.obs.removeObserver(this, "browser:purge-session-history");
+ // reset all state on sanitization
+ this._sessionType = Ci.nsISessionStartup.NO_SESSION;
+ break;
+ }
+ },
+
+/* ........ Public API ................*/
+
+ get onceInitialized() {
+ return gOnceInitializedDeferred.promise;
+ },
+
+ /**
+ * Get the session state as a jsval
+ */
+ get state() {
+ this._ensureInitialized();
+ return this._initialState;
+ },
+
+ /**
+ * Determines whether there is a pending session restore and makes sure that
+ * we're initialized before returning. If we're not yet this will read the
+ * session file synchronously.
+ * @returns bool
+ */
+ doRestore: function sss_doRestore() {
+ this._ensureInitialized();
+ return this._willRestore();
+ },
+
+ /**
+ * Determines whether there is a pending session restore.
+ * @returns bool
+ */
+ _willRestore: function () {
+ return this._sessionType == Ci.nsISessionStartup.RECOVER_SESSION ||
+ this._sessionType == Ci.nsISessionStartup.RESUME_SESSION;
+ },
+
+ /**
+ * Returns whether we will restore a session that ends up replacing the
+ * homepage. The browser uses this to not start loading the homepage if
+ * we're going to stop its load anyway shortly after.
+ *
+ * This is meant to be an optimization for the average case that loading the
+ * session file finishes before we may want to start loading the default
+ * homepage. Should this be called before the session file has been read it
+ * will just return false.
+ *
+ * @returns bool
+ */
+ get willOverrideHomepage() {
+ if (this._initialState && this._willRestore()) {
+ let windows = this._initialState.windows || null;
+ // If there are valid windows with not only pinned tabs, signal that we
+ // will override the default homepage by restoring a session.
+ return windows && windows.some(w => w.tabs.some(t => !t.pinned));
+ }
+ return false;
+ },
+
+ /**
+ * Get the type of pending session store, if any.
+ */
+ get sessionType() {
+ this._ensureInitialized();
+ return this._sessionType;
+ },
+
+ // Ensure that initialization is complete.
+ // If initialization is not complete yet, fall back to a synchronous
+ // initialization and kill ongoing asynchronous initialization
+ _ensureInitialized: function sss__ensureInitialized() {
+ try {
+ if (this._initialized) {
+ // Initialization is complete, nothing else to do
+ return;
+ }
+ let contents = _SessionFile.syncRead();
+ this._onSessionFileRead(contents);
+ } catch(ex) {
+ debug("ensureInitialized: could not read session " + ex + ", " + ex.stack);
+ throw ex;
+ }
+ },
+
+ /* ........ QueryInterface .............. */
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference,
+ Ci.nsISessionStartup]),
+ classID: Components.ID("{ec7a6c20-e081-11da-8ad9-0800200c9a66}")
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SessionStartup]);
diff --git a/components/sessionstore/nsSessionStore.js b/components/sessionstore/nsSessionStore.js
new file mode 100644
index 0000000..38713d5
--- /dev/null
+++ b/components/sessionstore/nsSessionStore.js
@@ -0,0 +1,37 @@
+/* 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/. */
+
+/**
+ * Session Storage and Restoration
+ *
+ * Overview
+ * This service keeps track of a user's session, storing the various bits
+ * required to return the browser to its current state. The relevant data is
+ * stored in memory, and is periodically saved to disk in a file in the
+ * profile directory. The service is started at first window load, in
+ * delayedStartup, and will restore the session from the data received from
+ * the nsSessionStartup service.
+ */
+
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource:///modules/sessionstore/SessionStore.jsm");
+
+function SessionStoreService() {}
+
+// The SessionStore module's object is frozen. We need to modify our prototype
+// and add some properties so let's just copy the SessionStore object.
+Object.keys(SessionStore).forEach(function (aName) {
+ let desc = Object.getOwnPropertyDescriptor(SessionStore, aName);
+ Object.defineProperty(SessionStoreService.prototype, aName, desc);
+});
+
+SessionStoreService.prototype.classID =
+ Components.ID("{5280606b-2510-4fe0-97ef-9b5a22eafe6b}");
+SessionStoreService.prototype.QueryInterface =
+ XPCOMUtils.generateQI([Ci.nsISessionStore]);
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SessionStoreService]);
diff --git a/components/sessionstore/nsSessionStore.manifest b/components/sessionstore/nsSessionStore.manifest
new file mode 100644
index 0000000..b136b41
--- /dev/null
+++ b/components/sessionstore/nsSessionStore.manifest
@@ -0,0 +1,18 @@
+# WebappRT doesn't need these instructions, and they don't necessarily work
+# with it, but it does use a GRE directory that the GRE shares with Firefox,
+# so in order to prevent the instructions from being processed for WebappRT,
+# we need to restrict them to the applications that depend on them, i.e.:
+#
+# b2g: {3c2e2abc-06d4-11e1-ac3b-374f68613e61}
+# browser: {8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
+# mobile/android: {aa3c5121-dab2-40e2-81ca-7ea25febc110}
+# mobile/xul: {a23983c0-fd0e-11dc-95ff-0800200c9a66}
+#
+# In theory we should do this for all these instructions, but in practice it is
+# sufficient to do it for the app-startup one, and the file is simpler that way.
+
+component {5280606b-2510-4fe0-97ef-9b5a22eafe6b} nsSessionStore.js
+contract @mozilla.org/browser/sessionstore;1 {5280606b-2510-4fe0-97ef-9b5a22eafe6b}
+component {ec7a6c20-e081-11da-8ad9-0800200c9a66} nsSessionStartup.js
+contract @mozilla.org/browser/sessionstartup;1 {ec7a6c20-e081-11da-8ad9-0800200c9a66}
+category app-startup nsSessionStartup service,@mozilla.org/browser/sessionstartup;1 application={3c2e2abc-06d4-11e1-ac3b-374f68613e61} application={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4} application={aa3c5121-dab2-40e2-81ca-7ea25febc110} application={a23983c0-fd0e-11dc-95ff-0800200c9a66}
diff --git a/components/shell/ShellService.jsm b/components/shell/ShellService.jsm
new file mode 100644
index 0000000..74632b6
--- /dev/null
+++ b/components/shell/ShellService.jsm
@@ -0,0 +1,114 @@
+/* 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";
+
+this.EXPORTED_SYMBOLS = ["ShellService"];
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
+ "resource://gre/modules/WindowsRegistry.jsm");
+
+/**
+ * Internal functionality to save and restore the docShell.allow* properties.
+ */
+var ShellServiceInternal = {
+ /**
+ * Used to determine whether or not to offer "Set as desktop background"
+ * functionality. Even if shell service is available it is not
+ * guaranteed that it is able to set the background for every desktop
+ * which is especially true for Linux with its many different desktop
+ * environments.
+ */
+ get canSetDesktopBackground() {
+ if (AppConstants.platform == "win" ||
+ AppConstants.platform == "macosx") {
+ return true;
+ }
+
+ if (AppConstants.platform == "linux") {
+ if (this.shellService) {
+ let linuxShellService = this.shellService
+ .QueryInterface(Ci.nsIGNOMEShellService);
+ return linuxShellService.canSetDesktopBackground;
+ }
+ }
+
+ return false;
+ },
+
+ /**
+ * Used to determine whether or not to show a "Set Default Browser"
+ * query dialog. This attribute is true if the application is starting
+ * up and "browser.shell.checkDefaultBrowser" is true, otherwise it
+ * is false.
+ */
+ _checkedThisSession: false,
+ get shouldCheckDefaultBrowser() {
+ // If we've already checked, the browser has been started and this is a
+ // new window open, and we don't want to check again.
+ if (this._checkedThisSession) {
+ return false;
+ }
+
+ if (!Services.prefs.getBoolPref("browser.shell.checkDefaultBrowser")) {
+ return false;
+ }
+
+ if (AppConstants.platform == "win") {
+ let optOutValue = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ "Software\\Mozilla\\PaleMoon",
+ "DefaultBrowserOptOut");
+ WindowsRegistry.removeRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ "Software\\Mozilla\\PaleMoon",
+ "DefaultBrowserOptOut");
+ if (optOutValue == "True") {
+ Services.prefs.setBoolPref("browser.shell.checkDefaultBrowser", false);
+ return false;
+ }
+ }
+
+ return true;
+ },
+
+ set shouldCheckDefaultBrowser(shouldCheck) {
+ Services.prefs.setBoolPref("browser.shell.checkDefaultBrowser", !!shouldCheck);
+ },
+
+ isDefaultBrowser(startupCheck, forAllTypes) {
+ // If this is the first browser window, maintain internal state that we've
+ // checked this session (so that subsequent window opens don't show the
+ // default browser dialog).
+ if (startupCheck) {
+ this._checkedThisSession = true;
+ }
+ if (this.shellService) {
+ return this.shellService.isDefaultBrowser(startupCheck, forAllTypes);
+ }
+ return false;
+ }
+};
+
+XPCOMUtils.defineLazyServiceGetter(ShellServiceInternal, "shellService",
+ "@mozilla.org/browser/shell-service;1", Ci.nsIShellService);
+
+/**
+ * The external API exported by this module.
+ */
+this.ShellService = new Proxy(ShellServiceInternal, {
+ get(target, name) {
+ if (name in target) {
+ return target[name];
+ }
+ if (target.shellService) {
+ return target.shellService[name];
+ }
+ Services.console.logStringMessage(`${name} not found in ShellService: ${target.shellService}`);
+ return undefined;
+ }
+});
diff --git a/components/shell/content/setDesktopBackground.js b/components/shell/content/setDesktopBackground.js
new file mode 100644
index 0000000..53cc70d
--- /dev/null
+++ b/components/shell/content/setDesktopBackground.js
@@ -0,0 +1,214 @@
+/* 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/AppConstants.jsm");
+
+var Ci = Components.interfaces;
+
+var gSetBackground = {
+ _position : AppConstants.platform == "macosx" ? "STRETCH" : "",
+ _backgroundColor : AppConstants.platform != "macosx" ? 0 : undefined,
+ _screenWidth : 0,
+ _screenHeight : 0,
+ _image : null,
+ _canvas : null,
+
+ get _shell()
+ {
+ return Components.classes["@mozilla.org/browser/shell-service;1"]
+ .getService(Ci.nsIShellService);
+ },
+
+ load: function ()
+ {
+ this._canvas = document.getElementById("screen");
+ this._screenWidth = screen.width;
+ this._screenHeight = screen.height;
+ if (AppConstants.platform == "macosx") {
+ document.documentElement.getButton("accept").hidden = true;
+ }
+ if (this._screenWidth / this._screenHeight >= 1.6)
+ document.getElementById("monitor").setAttribute("aspectratio", "16:10");
+
+ if (AppConstants.platform == "win") {
+ // Hide fill + fit options if < Win7 since they don't work.
+ var version = Components.classes["@mozilla.org/system-info;1"]
+ .getService(Ci.nsIPropertyBag2)
+ .getProperty("version");
+ var isWindows7OrHigher = (parseFloat(version) >= 6.1);
+ if (!isWindows7OrHigher) {
+ document.getElementById("fillPosition").hidden = true;
+ document.getElementById("fitPosition").hidden = true;
+ }
+ }
+
+ // make sure that the correct dimensions will be used
+ setTimeout(function(self) {
+ self.init(window.arguments[0]);
+ }, 0, this);
+ },
+
+ init: function (aImage)
+ {
+ this._image = aImage;
+
+ // set the size of the coordinate space
+ this._canvas.width = this._canvas.clientWidth;
+ this._canvas.height = this._canvas.clientHeight;
+
+ var ctx = this._canvas.getContext("2d");
+ ctx.scale(this._canvas.clientWidth / this._screenWidth, this._canvas.clientHeight / this._screenHeight);
+
+ if (AppConstants.platform != "macosx") {
+ this._initColor();
+ } else {
+ // Make sure to reset the button state in case the user has already
+ // set an image as their desktop background.
+ var setDesktopBackground = document.getElementById("setDesktopBackground");
+ setDesktopBackground.hidden = false;
+ var bundle = document.getElementById("backgroundBundle");
+ setDesktopBackground.label = bundle.getString("DesktopBackgroundSet");
+ setDesktopBackground.disabled = false;
+
+ document.getElementById("showDesktopPreferences").hidden = true;
+ }
+ this.updatePosition();
+ },
+
+ setDesktopBackground: function ()
+ {
+ if (AppConstants.platform != "macosx") {
+ document.persist("menuPosition", "value");
+ this._shell.desktopBackgroundColor = this._hexStringToLong(this._backgroundColor);
+ } else {
+ Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Ci.nsIObserverService)
+ .addObserver(this, "shell:desktop-background-changed", false);
+
+ var bundle = document.getElementById("backgroundBundle");
+ var setDesktopBackground = document.getElementById("setDesktopBackground");
+ setDesktopBackground.disabled = true;
+ setDesktopBackground.label = bundle.getString("DesktopBackgroundDownloading");
+ }
+ this._shell.setDesktopBackground(this._image,
+ Ci.nsIShellService["BACKGROUND_" + this._position]);
+ },
+
+ updatePosition: function ()
+ {
+ var ctx = this._canvas.getContext("2d");
+ ctx.clearRect(0, 0, this._screenWidth, this._screenHeight);
+
+ if (AppConstants.platform != "macosx") {
+ this._position = document.getElementById("menuPosition").value;
+ }
+
+ switch (this._position) {
+ case "TILE":
+ ctx.save();
+ ctx.fillStyle = ctx.createPattern(this._image, "repeat");
+ ctx.fillRect(0, 0, this._screenWidth, this._screenHeight);
+ ctx.restore();
+ break;
+ case "STRETCH":
+ ctx.drawImage(this._image, 0, 0, this._screenWidth, this._screenHeight);
+ break;
+ case "CENTER": {
+ let x = (this._screenWidth - this._image.naturalWidth) / 2;
+ let y = (this._screenHeight - this._image.naturalHeight) / 2;
+ ctx.drawImage(this._image, x, y);
+ break;
+ }
+ case "FILL": {
+ // Try maxing width first, overflow height.
+ let widthRatio = this._screenWidth / this._image.naturalWidth;
+ let width = this._image.naturalWidth * widthRatio;
+ let height = this._image.naturalHeight * widthRatio;
+ if (height < this._screenHeight) {
+ // Height less than screen, max height and overflow width.
+ let heightRatio = this._screenHeight / this._image.naturalHeight;
+ width = this._image.naturalWidth * heightRatio;
+ height = this._image.naturalHeight * heightRatio;
+ }
+ let x = (this._screenWidth - width) / 2;
+ let y = (this._screenHeight - height) / 2;
+ ctx.drawImage(this._image, x, y, width, height);
+ break;
+ }
+ case "FIT": {
+ // Try maxing width first, top and bottom borders.
+ let widthRatio = this._screenWidth / this._image.naturalWidth;
+ let width = this._image.naturalWidth * widthRatio;
+ let height = this._image.naturalHeight * widthRatio;
+ let x = 0;
+ let y = (this._screenHeight - height) / 2;
+ if (height > this._screenHeight) {
+ // Height overflow, maximise height, side borders.
+ let heightRatio = this._screenHeight / this._image.naturalHeight;
+ width = this._image.naturalWidth * heightRatio;
+ height = this._image.naturalHeight * heightRatio;
+ x = (this._screenWidth - width) / 2;
+ y = 0;
+ }
+ ctx.drawImage(this._image, x, y, width, height);
+ break;
+ }
+ }
+ }
+};
+
+if (AppConstants.platform != "macosx") {
+ gSetBackground["_initColor"] = function ()
+ {
+ var color = this._shell.desktopBackgroundColor;
+
+ const rMask = 4294901760;
+ const gMask = 65280;
+ const bMask = 255;
+ var r = (color & rMask) >> 16;
+ var g = (color & gMask) >> 8;
+ var b = (color & bMask);
+ this.updateColor(this._rgbToHex(r, g, b));
+
+ var colorpicker = document.getElementById("desktopColor");
+ colorpicker.color = this._backgroundColor;
+ };
+
+ gSetBackground["updateColor"] = function (aColor)
+ {
+ this._backgroundColor = aColor;
+ this._canvas.style.backgroundColor = aColor;
+ };
+
+ // Converts a color string in the format "#RRGGBB" to an integer.
+ gSetBackground["_hexStringToLong"] = function (aString)
+ {
+ return parseInt(aString.substring(1, 3), 16) << 16 |
+ parseInt(aString.substring(3, 5), 16) << 8 |
+ parseInt(aString.substring(5, 7), 16);
+ };
+
+ gSetBackground["_rgbToHex"] = function (aR, aG, aB)
+ {
+ return "#" + [aR, aG, aB].map(aInt => aInt.toString(16).replace(/^(.)$/, "0$1"))
+ .join("").toUpperCase();
+ };
+} else {
+ gSetBackground["observe"] = function (aSubject, aTopic, aData)
+ {
+ if (aTopic == "shell:desktop-background-changed") {
+ document.getElementById("setDesktopBackground").hidden = true;
+ document.getElementById("showDesktopPreferences").hidden = false;
+
+ Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Ci.nsIObserverService)
+ .removeObserver(this, "shell:desktop-background-changed");
+ }
+ };
+
+ gSetBackground["showDesktopPrefs"] = function()
+ {
+ this._shell.openApplication(Ci.nsIMacShellService.APPLICATION_DESKTOP);
+ };
+}
diff --git a/components/shell/content/setDesktopBackground.xul b/components/shell/content/setDesktopBackground.xul
new file mode 100644
index 0000000..d7d4079
--- /dev/null
+++ b/components/shell/content/setDesktopBackground.xul
@@ -0,0 +1,84 @@
+<?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/skin/setDesktopBackground.css" type="text/css"?>
+
+<!DOCTYPE dialog SYSTEM "chrome://browser/locale/setDesktopBackground.dtd">
+
+#ifdef XP_MACOSX
+<?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?>
+#endif
+
+<dialog xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ windowtype="Shell:SetDesktopBackground"
+#ifndef XP_MACOSX
+ buttons="accept,cancel"
+#else
+ buttons="accept"
+#endif
+ buttonlabelaccept="&setDesktopBackground.title;"
+ onload="gSetBackground.load();"
+ ondialogaccept="gSetBackground.setDesktopBackground();"
+ title="&setDesktopBackground.title;"
+ style="width: 30em;">
+
+ <stringbundle id="backgroundBundle"
+ src="chrome://browser/locale/shellservice.properties"/>
+ <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
+ <script type="application/javascript" src="chrome://browser/content/setDesktopBackground.js"/>
+ <script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
+
+#ifndef XP_MACOSX
+ <hbox align="center">
+ <label value="&position.label;"/>
+ <menulist id="menuPosition"
+ label="&position.label;"
+ oncommand="gSetBackground.updatePosition();">
+ <menupopup>
+ <menuitem label="&center.label;" value="CENTER"/>
+ <menuitem label="&tile.label;" value="TILE"/>
+ <menuitem label="&stretch.label;" value="STRETCH"/>
+ <menuitem label="&fill.label;" value="FILL" id="fillPosition"/>
+ <menuitem label="&fit.label;" value="FIT" id="fitPosition"/>
+ </menupopup>
+ </menulist>
+ <spacer flex="1"/>
+ <label value="&color.label;"/>
+ <colorpicker id="desktopColor"
+ type="button"
+ onchange="gSetBackground.updateColor(this.color);"/>
+ </hbox>
+#endif
+ <groupbox align="center">
+ <caption label="&preview.label;"/>
+ <stack>
+ <!-- if width and height are not present, they default to 300x150 and stretch the stack -->
+ <html:canvas id="screen" width="1" height="1"/>
+ <image id="monitor"/>
+ </stack>
+ </groupbox>
+
+#ifdef XP_MACOSX
+ <separator/>
+
+ <hbox align="right">
+ <button id="setDesktopBackground"
+ label="&setDesktopBackground.title;"
+ oncommand="gSetBackground.setDesktopBackground();"/>
+ <button id="showDesktopPreferences"
+ label="&openDesktopPrefs.label;"
+ oncommand="gSetBackground.showDesktopPrefs();"
+ hidden="true"/>
+ </hbox>
+#endif
+
+#ifdef XP_MACOSX
+#include ../../../base/content/browserMountPoints.inc
+#endif
+
+</dialog>
diff --git a/components/shell/jar.mn b/components/shell/jar.mn
new file mode 100644
index 0000000..0864e1b
--- /dev/null
+++ b/components/shell/jar.mn
@@ -0,0 +1,7 @@
+# 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/setDesktopBackground.xul (content/setDesktopBackground.xul)
+ content/browser/setDesktopBackground.js (content/setDesktopBackground.js)
diff --git a/components/shell/moz.build b/components/shell/moz.build
new file mode 100644
index 0000000..16bffd7
--- /dev/null
+++ b/components/shell/moz.build
@@ -0,0 +1,40 @@
+# -*- Mode: python; 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/.
+
+JAR_MANIFESTS += ['jar.mn']
+
+XPIDL_SOURCES += ['nsIShellService.idl']
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ XPIDL_SOURCES += ['nsIWindowsShellService.idl']
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ XPIDL_SOURCES += ['nsIMacShellService.idl']
+elif 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ XPIDL_SOURCES += ['nsIGNOMEShellService.idl']
+
+XPIDL_MODULE = 'shellservice'
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+ SOURCES += ['nsWindowsShellService.cpp']
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+ SOURCES += ['nsMacShellService.cpp']
+elif 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
+ SOURCES += ['nsGNOMEShellService.cpp']
+
+if SOURCES:
+ FINAL_LIBRARY = 'browsercomps'
+
+EXTRA_COMPONENTS += [
+ 'nsSetDefaultBrowser.js',
+ 'nsSetDefaultBrowser.manifest',
+]
+
+EXTRA_JS_MODULES += ['ShellService.jsm']
+
+for var in ('MOZ_APP_NAME', 'MOZ_APP_VERSION'):
+ DEFINES[var] = '"%s"' % CONFIG[var]
+
+CXXFLAGS += CONFIG['TK_CFLAGS']
diff --git a/components/shell/nsGNOMEShellService.cpp b/components/shell/nsGNOMEShellService.cpp
new file mode 100644
index 0000000..9bc5f59
--- /dev/null
+++ b/components/shell/nsGNOMEShellService.cpp
@@ -0,0 +1,637 @@
+/* -*- 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/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsCOMPtr.h"
+#include "nsGNOMEShellService.h"
+#include "nsShellService.h"
+#include "nsIServiceManager.h"
+#include "nsIFile.h"
+#include "nsIProperties.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIPrefService.h"
+#include "prenv.h"
+#include "nsStringAPI.h"
+#include "nsIGConfService.h"
+#include "nsIGIOService.h"
+#include "nsIGSettingsService.h"
+#include "nsIStringBundle.h"
+#include "nsIOutputStream.h"
+#include "nsIProcess.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIDOMHTMLImageElement.h"
+#include "nsIImageLoadingContent.h"
+#include "imgIRequest.h"
+#include "imgIContainer.h"
+#include "mozilla/Sprintf.h"
+#if defined(MOZ_WIDGET_GTK)
+#include "nsIImageToPixbuf.h"
+#endif
+#include "nsXULAppAPI.h"
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#include <gdk-pixbuf/gdk-pixbuf.h>
+#include <limits.h>
+#include <stdlib.h>
+
+using namespace mozilla;
+
+struct ProtocolAssociation
+{
+ const char *name;
+ bool essential;
+};
+
+struct MimeTypeAssociation
+{
+ const char *mimeType;
+ const char *extensions;
+};
+
+static const ProtocolAssociation appProtocols[] = {
+ { "http", true },
+ { "https", true },
+ { "ftp", false },
+ { "chrome", false }
+};
+
+static const MimeTypeAssociation appTypes[] = {
+ { "text/html", "htm html shtml" },
+ { "application/xhtml+xml", "xhtml xht" }
+};
+
+// GConf registry key constants
+#define DG_BACKGROUND "/desktop/gnome/background"
+
+static const char kDesktopImageKey[] = DG_BACKGROUND "/picture_filename";
+static const char kDesktopOptionsKey[] = DG_BACKGROUND "/picture_options";
+static const char kDesktopDrawBGKey[] = DG_BACKGROUND "/draw_background";
+static const char kDesktopColorKey[] = DG_BACKGROUND "/primary_color";
+
+static const char kDesktopBGSchema[] = "org.gnome.desktop.background";
+static const char kDesktopImageGSKey[] = "picture-uri";
+static const char kDesktopOptionGSKey[] = "picture-options";
+static const char kDesktopDrawBGGSKey[] = "draw-background";
+static const char kDesktopColorGSKey[] = "primary-color";
+
+nsresult
+nsGNOMEShellService::Init()
+{
+ nsresult rv;
+
+ // GConf, GSettings or GIO _must_ be available, or we do not allow
+ // CreateInstance to succeed.
+
+ nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID);
+ nsCOMPtr<nsIGIOService> giovfs =
+ do_GetService(NS_GIOSERVICE_CONTRACTID);
+ nsCOMPtr<nsIGSettingsService> gsettings =
+ do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
+
+ if (!gconf && !giovfs && !gsettings)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ // Check G_BROKEN_FILENAMES. If it's set, then filenames in glib use
+ // the locale encoding. If it's not set, they use UTF-8.
+ mUseLocaleFilenames = PR_GetEnv("G_BROKEN_FILENAMES") != nullptr;
+
+ if (GetAppPathFromLauncher())
+ return NS_OK;
+
+ nsCOMPtr<nsIProperties> dirSvc
+ (do_GetService("@mozilla.org/file/directory_service;1"));
+ NS_ENSURE_TRUE(dirSvc, NS_ERROR_NOT_AVAILABLE);
+
+ nsCOMPtr<nsIFile> appPath;
+ rv = dirSvc->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile),
+ getter_AddRefs(appPath));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return appPath->GetNativePath(mAppPath);
+}
+
+NS_IMPL_ISUPPORTS(nsGNOMEShellService, nsIGNOMEShellService, nsIShellService)
+
+bool
+nsGNOMEShellService::GetAppPathFromLauncher()
+{
+ gchar *tmp;
+
+ const char *launcher = PR_GetEnv("MOZ_APP_LAUNCHER");
+ if (!launcher)
+ return false;
+
+ if (g_path_is_absolute(launcher)) {
+ mAppPath = launcher;
+ tmp = g_path_get_basename(launcher);
+ gchar *fullpath = g_find_program_in_path(tmp);
+ if (fullpath && mAppPath.Equals(fullpath))
+ mAppIsInPath = true;
+ g_free(fullpath);
+ } else {
+ tmp = g_find_program_in_path(launcher);
+ if (!tmp)
+ return false;
+ mAppPath = tmp;
+ mAppIsInPath = true;
+ }
+
+ g_free(tmp);
+ return true;
+}
+
+bool
+nsGNOMEShellService::KeyMatchesAppName(const char *aKeyValue) const
+{
+
+ gchar *commandPath;
+ if (mUseLocaleFilenames) {
+ gchar *nativePath = g_filename_from_utf8(aKeyValue, -1,
+ nullptr, nullptr, nullptr);
+ if (!nativePath) {
+ NS_ERROR("Error converting path to filesystem encoding");
+ return false;
+ }
+
+ commandPath = g_find_program_in_path(nativePath);
+ g_free(nativePath);
+ } else {
+ commandPath = g_find_program_in_path(aKeyValue);
+ }
+
+ if (!commandPath)
+ return false;
+
+ bool matches = mAppPath.Equals(commandPath);
+ g_free(commandPath);
+ return matches;
+}
+
+bool
+nsGNOMEShellService::CheckHandlerMatchesAppName(const nsACString &handler) const
+{
+ gint argc;
+ gchar **argv;
+ nsAutoCString command(handler);
+
+ // The string will be something of the form: [/path/to/]browser "%s"
+ // We want to remove all of the parameters and get just the binary name.
+
+ if (g_shell_parse_argv(command.get(), &argc, &argv, nullptr) && argc > 0) {
+ command.Assign(argv[0]);
+ g_strfreev(argv);
+ }
+
+ if (!KeyMatchesAppName(command.get()))
+ return false; // the handler is set to another app
+
+ return true;
+}
+
+NS_IMETHODIMP
+nsGNOMEShellService::IsDefaultBrowser(bool aStartupCheck,
+ bool aForAllTypes,
+ bool* aIsDefaultBrowser)
+{
+ *aIsDefaultBrowser = false;
+
+ nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID);
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+
+ bool enabled;
+ nsAutoCString handler;
+ nsCOMPtr<nsIGIOMimeApp> gioApp;
+
+ for (unsigned int i = 0; i < ArrayLength(appProtocols); ++i) {
+ if (!appProtocols[i].essential)
+ continue;
+
+ if (gconf) {
+ handler.Truncate();
+ gconf->GetAppForProtocol(nsDependentCString(appProtocols[i].name),
+ &enabled, handler);
+
+ if (!CheckHandlerMatchesAppName(handler) || !enabled)
+ return NS_OK; // the handler is disabled or set to another app
+ }
+
+ if (giovfs) {
+ handler.Truncate();
+ giovfs->GetAppForURIScheme(nsDependentCString(appProtocols[i].name),
+ getter_AddRefs(gioApp));
+ if (!gioApp)
+ return NS_OK;
+
+ gioApp->GetCommand(handler);
+
+ if (!CheckHandlerMatchesAppName(handler))
+ return NS_OK; // the handler is set to another app
+ }
+ }
+
+ *aIsDefaultBrowser = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGNOMEShellService::SetDefaultBrowser(bool aClaimAllTypes,
+ bool aForAllUsers)
+{
+#ifdef DEBUG
+ if (aForAllUsers)
+ NS_WARNING("Setting the default browser for all users is not yet supported");
+#endif
+
+ nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID);
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ if (gconf) {
+ nsAutoCString appKeyValue;
+ if (mAppIsInPath) {
+ // mAppPath is in the users path, so use only the basename as the launcher
+ gchar *tmp = g_path_get_basename(mAppPath.get());
+ appKeyValue = tmp;
+ g_free(tmp);
+ } else {
+ appKeyValue = mAppPath;
+ }
+
+ appKeyValue.AppendLiteral(" %s");
+
+ for (unsigned int i = 0; i < ArrayLength(appProtocols); ++i) {
+ if (appProtocols[i].essential || aClaimAllTypes) {
+ gconf->SetAppForProtocol(nsDependentCString(appProtocols[i].name),
+ appKeyValue);
+ }
+ }
+ }
+
+ if (giovfs) {
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStringBundle> brandBundle;
+ rv = bundleService->CreateBundle(BRAND_PROPERTIES, getter_AddRefs(brandBundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString brandShortName;
+ brandBundle->GetStringFromName(u"brandShortName",
+ getter_Copies(brandShortName));
+
+ // use brandShortName as the application id.
+ NS_ConvertUTF16toUTF8 id(brandShortName);
+ nsCOMPtr<nsIGIOMimeApp> appInfo;
+ rv = giovfs->CreateAppFromCommand(mAppPath,
+ id,
+ getter_AddRefs(appInfo));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // set handler for the protocols
+ for (unsigned int i = 0; i < ArrayLength(appProtocols); ++i) {
+ if (appProtocols[i].essential || aClaimAllTypes) {
+ appInfo->SetAsDefaultForURIScheme(nsDependentCString(appProtocols[i].name));
+ }
+ }
+
+ // set handler for .html and xhtml files and MIME types:
+ if (aClaimAllTypes) {
+ // Add mime types for html, xhtml extension and set app to just created appinfo.
+ for (unsigned int i = 0; i < ArrayLength(appTypes); ++i) {
+ appInfo->SetAsDefaultForMimeType(nsDependentCString(appTypes[i].mimeType));
+ appInfo->SetAsDefaultForFileExtensions(nsDependentCString(appTypes[i].extensions));
+ }
+ }
+ }
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs) {
+ (void) prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true);
+ // Reset the number of times the dialog should be shown
+ // before it is silenced.
+ (void) prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT, 0);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGNOMEShellService::GetCanSetDesktopBackground(bool* aResult)
+{
+ // setting desktop background is currently only supported
+ // for Gnome or desktops using the same GSettings and GConf keys
+ const char* gnomeSession = getenv("GNOME_DESKTOP_SESSION_ID");
+ if (gnomeSession) {
+ *aResult = true;
+ } else {
+ *aResult = false;
+ }
+
+ return NS_OK;
+}
+
+static nsresult
+WriteImage(const nsCString& aPath, imgIContainer* aImage)
+{
+#if !defined(MOZ_WIDGET_GTK)
+ return NS_ERROR_NOT_AVAILABLE;
+#else
+ nsCOMPtr<nsIImageToPixbuf> imgToPixbuf =
+ do_GetService("@mozilla.org/widget/image-to-gdk-pixbuf;1");
+ if (!imgToPixbuf)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ GdkPixbuf* pixbuf = imgToPixbuf->ConvertImageToPixbuf(aImage);
+ if (!pixbuf)
+ return NS_ERROR_NOT_AVAILABLE;
+
+ gboolean res = gdk_pixbuf_save(pixbuf, aPath.get(), "png", nullptr, nullptr);
+
+ g_object_unref(pixbuf);
+ return res ? NS_OK : NS_ERROR_FAILURE;
+#endif
+}
+
+NS_IMETHODIMP
+nsGNOMEShellService::SetDesktopBackground(nsIDOMElement* aElement,
+ int32_t aPosition)
+{
+ nsresult rv;
+ nsCOMPtr<nsIImageLoadingContent> imageContent = do_QueryInterface(aElement, &rv);
+ if (!imageContent) return rv;
+
+ // get the image container
+ nsCOMPtr<imgIRequest> request;
+ rv = imageContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+ getter_AddRefs(request));
+ if (!request) return rv;
+ nsCOMPtr<imgIContainer> container;
+ rv = request->GetImage(getter_AddRefs(container));
+ if (!container) return rv;
+
+ // Set desktop wallpaper filling style
+ nsAutoCString options;
+ if (aPosition == BACKGROUND_TILE)
+ options.AssignLiteral("wallpaper");
+ else if (aPosition == BACKGROUND_STRETCH)
+ options.AssignLiteral("stretched");
+ else if (aPosition == BACKGROUND_FILL)
+ options.AssignLiteral("zoom");
+ else if (aPosition == BACKGROUND_FIT)
+ options.AssignLiteral("scaled");
+ else
+ options.AssignLiteral("centered");
+
+ // Write the background file to the home directory.
+ nsAutoCString filePath(PR_GetEnv("HOME"));
+
+ // get the product brand name from localized strings
+ nsString brandName;
+ nsCID bundleCID = NS_STRINGBUNDLESERVICE_CID;
+ nsCOMPtr<nsIStringBundleService> bundleService(do_GetService(bundleCID));
+ if (bundleService) {
+ nsCOMPtr<nsIStringBundle> brandBundle;
+ rv = bundleService->CreateBundle(BRAND_PROPERTIES,
+ getter_AddRefs(brandBundle));
+ if (NS_SUCCEEDED(rv) && brandBundle) {
+ rv = brandBundle->GetStringFromName(u"brandShortName",
+ getter_Copies(brandName));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ // build the file name
+ filePath.Append('/');
+ filePath.Append(NS_ConvertUTF16toUTF8(brandName));
+ filePath.AppendLiteral("_wallpaper.png");
+
+ // write the image to a file in the home dir
+ rv = WriteImage(filePath, container);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Try GSettings first. If we don't have GSettings or the right schema, fall back
+ // to using GConf instead. Note that if GSettings works ok, the changes get
+ // mirrored to GConf by the gsettings->gconf bridge in gnome-settings-daemon
+ nsCOMPtr<nsIGSettingsService> gsettings =
+ do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
+ if (gsettings) {
+ nsCOMPtr<nsIGSettingsCollection> background_settings;
+ gsettings->GetCollectionForSchema(
+ NS_LITERAL_CSTRING(kDesktopBGSchema), getter_AddRefs(background_settings));
+ if (background_settings) {
+ gchar *file_uri = g_filename_to_uri(filePath.get(), nullptr, nullptr);
+ if (!file_uri)
+ return NS_ERROR_FAILURE;
+
+ background_settings->SetString(NS_LITERAL_CSTRING(kDesktopOptionGSKey),
+ options);
+
+ background_settings->SetString(NS_LITERAL_CSTRING(kDesktopImageGSKey),
+ nsDependentCString(file_uri));
+ g_free(file_uri);
+ background_settings->SetBoolean(NS_LITERAL_CSTRING(kDesktopDrawBGGSKey),
+ true);
+ return rv;
+ }
+ }
+
+ // if the file was written successfully, set it as the system wallpaper
+ nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID);
+
+ if (gconf) {
+ gconf->SetString(NS_LITERAL_CSTRING(kDesktopOptionsKey), options);
+
+ // Set the image to an empty string first to force a refresh
+ // (since we could be writing a new image on top of an existing
+ // PaleMoon_wallpaper.png and nautilus doesn't monitor the file for changes)
+ gconf->SetString(NS_LITERAL_CSTRING(kDesktopImageKey),
+ EmptyCString());
+
+ gconf->SetString(NS_LITERAL_CSTRING(kDesktopImageKey), filePath);
+ gconf->SetBool(NS_LITERAL_CSTRING(kDesktopDrawBGKey), true);
+ }
+
+ return rv;
+}
+
+#define COLOR_16_TO_8_BIT(_c) ((_c) >> 8)
+#define COLOR_8_TO_16_BIT(_c) ((_c) << 8 | (_c))
+
+NS_IMETHODIMP
+nsGNOMEShellService::GetDesktopBackgroundColor(uint32_t *aColor)
+{
+ nsCOMPtr<nsIGSettingsService> gsettings =
+ do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
+ nsCOMPtr<nsIGSettingsCollection> background_settings;
+ nsAutoCString background;
+
+ if (gsettings) {
+ gsettings->GetCollectionForSchema(
+ NS_LITERAL_CSTRING(kDesktopBGSchema), getter_AddRefs(background_settings));
+ if (background_settings) {
+ background_settings->GetString(NS_LITERAL_CSTRING(kDesktopColorGSKey),
+ background);
+ }
+ }
+
+ if (!background_settings) {
+ nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID);
+ if (gconf)
+ gconf->GetString(NS_LITERAL_CSTRING(kDesktopColorKey), background);
+ }
+
+ if (background.IsEmpty()) {
+ *aColor = 0;
+ return NS_OK;
+ }
+
+ GdkColor color;
+ gboolean success = gdk_color_parse(background.get(), &color);
+
+ NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
+
+ *aColor = COLOR_16_TO_8_BIT(color.red) << 16 |
+ COLOR_16_TO_8_BIT(color.green) << 8 |
+ COLOR_16_TO_8_BIT(color.blue);
+ return NS_OK;
+}
+
+static void
+ColorToCString(uint32_t aColor, nsCString& aResult)
+{
+ // The #rrrrggggbbbb format is used to match gdk_color_to_string()
+ char *buf = aResult.BeginWriting(13);
+ if (!buf)
+ return;
+
+ uint16_t red = COLOR_8_TO_16_BIT((aColor >> 16) & 0xff);
+ uint16_t green = COLOR_8_TO_16_BIT((aColor >> 8) & 0xff);
+ uint16_t blue = COLOR_8_TO_16_BIT(aColor & 0xff);
+
+ snprintf(buf, 14, "#%04x%04x%04x", red, green, blue);
+}
+
+NS_IMETHODIMP
+nsGNOMEShellService::SetDesktopBackgroundColor(uint32_t aColor)
+{
+ NS_ASSERTION(aColor <= 0xffffff, "aColor has extra bits");
+ nsAutoCString colorString;
+ ColorToCString(aColor, colorString);
+
+ nsCOMPtr<nsIGSettingsService> gsettings =
+ do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
+ if (gsettings) {
+ nsCOMPtr<nsIGSettingsCollection> background_settings;
+ gsettings->GetCollectionForSchema(
+ NS_LITERAL_CSTRING(kDesktopBGSchema), getter_AddRefs(background_settings));
+ if (background_settings) {
+ background_settings->SetString(NS_LITERAL_CSTRING(kDesktopColorGSKey),
+ colorString);
+ return NS_OK;
+ }
+ }
+
+ nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID);
+
+ if (gconf) {
+ gconf->SetString(NS_LITERAL_CSTRING(kDesktopColorKey), colorString);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsGNOMEShellService::OpenApplication(int32_t aApplication)
+{
+ nsAutoCString scheme;
+ if (aApplication == APPLICATION_MAIL)
+ scheme.AssignLiteral("mailto");
+ else if (aApplication == APPLICATION_NEWS)
+ scheme.AssignLiteral("news");
+ else
+ return NS_ERROR_NOT_AVAILABLE;
+
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ if (giovfs) {
+ nsCOMPtr<nsIGIOMimeApp> gioApp;
+ giovfs->GetAppForURIScheme(scheme, getter_AddRefs(gioApp));
+ if (gioApp)
+ return gioApp->Launch(EmptyCString());
+ }
+
+ nsCOMPtr<nsIGConfService> gconf = do_GetService(NS_GCONFSERVICE_CONTRACTID);
+ if (!gconf)
+ return NS_ERROR_FAILURE;
+
+ bool enabled;
+ nsAutoCString appCommand;
+ gconf->GetAppForProtocol(scheme, &enabled, appCommand);
+
+ if (!enabled)
+ return NS_ERROR_FAILURE;
+
+ // XXX we don't currently handle launching a terminal window.
+ // If the handler requires a terminal, bail.
+ bool requiresTerminal;
+ gconf->HandlerRequiresTerminal(scheme, &requiresTerminal);
+ if (requiresTerminal)
+ return NS_ERROR_FAILURE;
+
+ // Perform shell argument expansion
+ int argc;
+ char **argv;
+ if (!g_shell_parse_argv(appCommand.get(), &argc, &argv, nullptr))
+ return NS_ERROR_FAILURE;
+
+ char **newArgv = new char*[argc + 1];
+ int newArgc = 0;
+
+ // Run through the list of arguments. Copy all of them to the new
+ // argv except for %s, which we skip.
+ for (int i = 0; i < argc; ++i) {
+ if (strcmp(argv[i], "%s") != 0)
+ newArgv[newArgc++] = argv[i];
+ }
+
+ newArgv[newArgc] = nullptr;
+
+ gboolean err = g_spawn_async(nullptr, newArgv, nullptr, G_SPAWN_SEARCH_PATH,
+ nullptr, nullptr, nullptr, nullptr);
+
+ g_strfreev(argv);
+ delete[] newArgv;
+
+ return err ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsGNOMEShellService::OpenApplicationWithURI(nsIFile* aApplication, const nsACString& aURI)
+{
+ nsresult rv;
+ nsCOMPtr<nsIProcess> process =
+ do_CreateInstance("@mozilla.org/process/util;1", &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = process->Init(aApplication);
+ if (NS_FAILED(rv))
+ return rv;
+
+ const nsCString spec(aURI);
+ const char* specStr = spec.get();
+ return process->Run(false, &specStr, 1);
+}
+
+NS_IMETHODIMP
+nsGNOMEShellService::GetDefaultFeedReader(nsIFile** _retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/components/shell/nsGNOMEShellService.h b/components/shell/nsGNOMEShellService.h
new file mode 100644
index 0000000..a7b0038
--- /dev/null
+++ b/components/shell/nsGNOMEShellService.h
@@ -0,0 +1,36 @@
+/* -*- 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/. */
+
+#ifndef nsgnomeshellservice_h____
+#define nsgnomeshellservice_h____
+
+#include "nsIGNOMEShellService.h"
+#include "nsStringAPI.h"
+#include "mozilla/Attributes.h"
+
+class nsGNOMEShellService final : public nsIGNOMEShellService
+{
+public:
+ nsGNOMEShellService() : mAppIsInPath(false) { }
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISHELLSERVICE
+ NS_DECL_NSIGNOMESHELLSERVICE
+
+ nsresult Init();
+
+private:
+ ~nsGNOMEShellService() {}
+
+ bool KeyMatchesAppName(const char *aKeyValue) const;
+ bool CheckHandlerMatchesAppName(const nsACString& handler) const;
+
+ bool GetAppPathFromLauncher();
+ bool mUseLocaleFilenames;
+ nsCString mAppPath;
+ bool mAppIsInPath;
+};
+
+#endif // nsgnomeshellservice_h____
diff --git a/components/shell/nsIGNOMEShellService.idl b/components/shell/nsIGNOMEShellService.idl
new file mode 100644
index 0000000..842ce5e
--- /dev/null
+++ b/components/shell/nsIGNOMEShellService.idl
@@ -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/. */
+
+#include "nsIShellService.idl"
+
+[scriptable, uuid(2ce5c803-edcd-443d-98eb-ceba86d02d13)]
+interface nsIGNOMEShellService : nsIShellService
+{
+ /**
+ * Used to determine whether or not to offer "Set as desktop background"
+ * functionality. Even if shell service is available it is not
+ * guaranteed that it is able to set the background for every desktop
+ * which is especially true for Linux with its many different desktop
+ * environments.
+ */
+ readonly attribute boolean canSetDesktopBackground;
+};
+
diff --git a/components/shell/nsIMacShellService.idl b/components/shell/nsIMacShellService.idl
new file mode 100644
index 0000000..6a532bb
--- /dev/null
+++ b/components/shell/nsIMacShellService.idl
@@ -0,0 +1,15 @@
+/* -*- 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/. */
+
+#include "nsIShellService.idl"
+
+[scriptable, uuid(387fdc80-0077-4b60-a0d9-d9e80a83ba64)]
+interface nsIMacShellService : nsIShellService
+{
+ const long APPLICATION_KEYCHAIN_ACCESS = 2;
+ const long APPLICATION_NETWORK = 3;
+ const long APPLICATION_DESKTOP = 4;
+};
+
diff --git a/components/shell/nsIShellService.idl b/components/shell/nsIShellService.idl
new file mode 100644
index 0000000..3e7e94b
--- /dev/null
+++ b/components/shell/nsIShellService.idl
@@ -0,0 +1,95 @@
+/* -*- 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/. */
+
+#include "nsISupports.idl"
+
+interface nsIDOMElement;
+interface nsIFile;
+
+[scriptable, uuid(2d1a95e4-5bd8-4eeb-b0a8-c1455fd2a357)]
+interface nsIShellService : nsISupports
+{
+ /**
+ * Determines whether or not Firefox is the "Default Browser."
+ * This is simply whether or not Firefox is registered to handle
+ * http links.
+ *
+ * @param aStartupCheck true if this is the check being performed
+ * by the first browser window at startup,
+ * false otherwise.
+ * @param aForAllTypes true if the check should be made for HTTP and HTML.
+ * false if the check should be made for HTTP only.
+ * This parameter may be ignored on some platforms.
+ */
+ boolean isDefaultBrowser(in boolean aStartupCheck,
+ [optional] in boolean aForAllTypes);
+
+ /**
+ * Registers Firefox as the "Default Browser."
+ *
+ * @param aClaimAllTypes Register Firefox as the handler for
+ * additional protocols (ftp, chrome etc)
+ * and web documents (.html, .xhtml etc).
+ * @param aForAllUsers Whether or not Firefox should attempt
+ * to become the default browser for all
+ * users on a multi-user system.
+ */
+ void setDefaultBrowser(in boolean aClaimAllTypes, in boolean aForAllUsers);
+
+ /**
+ * Flags for positioning/sizing of the Desktop Background image.
+ */
+ const long BACKGROUND_TILE = 1;
+ const long BACKGROUND_STRETCH = 2;
+ const long BACKGROUND_CENTER = 3;
+ const long BACKGROUND_FILL = 4;
+ const long BACKGROUND_FIT = 5;
+
+ /**
+ * Sets the desktop background image using either the HTML <IMG>
+ * element supplied or the background image of the element supplied.
+ *
+ * @param aImageElement Either a HTML <IMG> element or an element with
+ * a background image from which to source the
+ * background image.
+ * @param aPosition How to place the image on the desktop
+ */
+ void setDesktopBackground(in nsIDOMElement aElement, in long aPosition);
+
+ /**
+ * Constants identifying applications that can be opened with
+ * openApplication.
+ */
+ const long APPLICATION_MAIL = 0;
+ const long APPLICATION_NEWS = 1;
+
+ /**
+ * Opens the application specified. If more than one application of the
+ * given type is available on the system, the default or "preferred"
+ * application is used.
+ */
+ void openApplication(in long aApplication);
+
+ /**
+ * The desktop background color, visible when no background image is
+ * used, or if the background image is centered and does not fill the
+ * entire screen. A rgb value, where (r << 16 | g << 8 | b)
+ */
+ attribute unsigned long desktopBackgroundColor;
+
+ /**
+ * Opens an application with a specific URI to load.
+ * @param application
+ * The application file (or bundle directory, on OS X)
+ * @param uri
+ * The uri to be loaded by the application
+ */
+ void openApplicationWithURI(in nsIFile aApplication, in ACString aURI);
+
+ /**
+ * The default system handler for web feeds
+ */
+ readonly attribute nsIFile defaultFeedReader;
+};
diff --git a/components/shell/nsIWindowsShellService.idl b/components/shell/nsIWindowsShellService.idl
new file mode 100644
index 0000000..57ed370
--- /dev/null
+++ b/components/shell/nsIWindowsShellService.idl
@@ -0,0 +1,17 @@
+/* -*- 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/. */
+
+#include "nsIShellService.idl"
+
+[scriptable, uuid(f8a26b94-49e5-4441-8fbc-315e0b4f22ef)]
+interface nsIWindowsShellService : nsIShellService
+{
+ /**
+ * Provides the shell service an opportunity to do some Win7+ shortcut
+ * maintenance needed on initial startup of the browser.
+ */
+ void shortcutMaintenance();
+};
+
diff --git a/components/shell/nsMacShellService.cpp b/components/shell/nsMacShellService.cpp
new file mode 100644
index 0000000..d8d6403
--- /dev/null
+++ b/components/shell/nsMacShellService.cpp
@@ -0,0 +1,434 @@
+/* -*- 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/. */
+
+#include "nsDirectoryServiceDefs.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMHTMLImageElement.h"
+#include "nsIImageLoadingContent.h"
+#include "nsIDocument.h"
+#include "nsIContent.h"
+#include "nsILocalFileMac.h"
+#include "nsIObserverService.h"
+#include "nsIPrefService.h"
+#include "nsIServiceManager.h"
+#include "nsIStringBundle.h"
+#include "nsIURL.h"
+#include "nsIWebBrowserPersist.h"
+#include "nsMacShellService.h"
+#include "nsIProperties.h"
+#include "nsServiceManagerUtils.h"
+#include "nsShellService.h"
+#include "nsStringAPI.h"
+#include "nsIDocShell.h"
+#include "nsILoadContext.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <ApplicationServices/ApplicationServices.h>
+
+#define NETWORK_PREFPANE NS_LITERAL_CSTRING("/System/Library/PreferencePanes/Network.prefPane")
+#define DESKTOP_PREFPANE NS_LITERAL_CSTRING("/System/Library/PreferencePanes/DesktopScreenEffectsPref.prefPane")
+
+#define SAFARI_BUNDLE_IDENTIFIER "com.apple.Safari"
+
+NS_IMPL_ISUPPORTS(nsMacShellService, nsIMacShellService, nsIShellService, nsIWebProgressListener)
+
+NS_IMETHODIMP
+nsMacShellService::IsDefaultBrowser(bool aStartupCheck,
+ bool aForAllTypes,
+ bool* aIsDefaultBrowser)
+{
+ *aIsDefaultBrowser = false;
+
+ CFStringRef firefoxID = ::CFBundleGetIdentifier(::CFBundleGetMainBundle());
+ if (!firefoxID) {
+ // CFBundleGetIdentifier is expected to return nullptr only if the specified
+ // bundle doesn't have a bundle identifier in its plist. In this case, that
+ // means a failure, since our bundle does have an identifier.
+ return NS_ERROR_FAILURE;
+ }
+
+ // Get the default http handler's bundle ID (or nullptr if it has not been
+ // explicitly set)
+ CFStringRef defaultBrowserID = ::LSCopyDefaultHandlerForURLScheme(CFSTR("http"));
+ if (defaultBrowserID) {
+ *aIsDefaultBrowser = ::CFStringCompare(firefoxID, defaultBrowserID, 0) == kCFCompareEqualTo;
+ ::CFRelease(defaultBrowserID);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacShellService::SetDefaultBrowser(bool aClaimAllTypes, bool aForAllUsers)
+{
+ // Note: We don't support aForAllUsers on Mac OS X.
+
+ CFStringRef firefoxID = ::CFBundleGetIdentifier(::CFBundleGetMainBundle());
+ if (!firefoxID) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (::LSSetDefaultHandlerForURLScheme(CFSTR("http"), firefoxID) != noErr) {
+ return NS_ERROR_FAILURE;
+ }
+ if (::LSSetDefaultHandlerForURLScheme(CFSTR("https"), firefoxID) != noErr) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aClaimAllTypes) {
+ if (::LSSetDefaultHandlerForURLScheme(CFSTR("ftp"), firefoxID) != noErr) {
+ return NS_ERROR_FAILURE;
+ }
+ if (::LSSetDefaultRoleHandlerForContentType(kUTTypeHTML, kLSRolesAll, firefoxID) != noErr) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs) {
+ (void) prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true);
+ // Reset the number of times the dialog should be shown
+ // before it is silenced.
+ (void) prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT, 0);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacShellService::SetDesktopBackground(nsIDOMElement* aElement,
+ int32_t aPosition)
+{
+ // Note: We don't support aPosition on OS X.
+
+ // Get the image URI:
+ nsresult rv;
+ nsCOMPtr<nsIImageLoadingContent> imageContent = do_QueryInterface(aElement,
+ &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIURI> imageURI;
+ rv = imageContent->GetCurrentURI(getter_AddRefs(imageURI));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We need the referer URI for nsIWebBrowserPersist::saveURI
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aElement, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIURI *docURI = content->OwnerDoc()->GetDocumentURI();
+ if (!docURI)
+ return NS_ERROR_FAILURE;
+
+ // Get the desired image file name
+ nsCOMPtr<nsIURL> imageURL(do_QueryInterface(imageURI));
+ if (!imageURL) {
+ // XXXmano (bug 300293): Non-URL images (e.g. the data: protocol) are not
+ // yet supported. What filename should we take here?
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ nsAutoCString fileName;
+ imageURL->GetFileName(fileName);
+ nsCOMPtr<nsIProperties> fileLocator
+ (do_GetService("@mozilla.org/file/directory_service;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the current user's "Pictures" folder (That's ~/Pictures):
+ fileLocator->Get(NS_OSX_PICTURE_DOCUMENTS_DIR, NS_GET_IID(nsIFile),
+ getter_AddRefs(mBackgroundFile));
+ if (!mBackgroundFile)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ nsAutoString fileNameUnicode;
+ CopyUTF8toUTF16(fileName, fileNameUnicode);
+
+ // and add the imgage file name itself:
+ mBackgroundFile->Append(fileNameUnicode);
+
+ // Download the image; the desktop background will be set in OnStateChange()
+ nsCOMPtr<nsIWebBrowserPersist> wbp
+ (do_CreateInstance("@mozilla.org/embedding/browser/nsWebBrowserPersist;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t flags = nsIWebBrowserPersist::PERSIST_FLAGS_NO_CONVERSION |
+ nsIWebBrowserPersist::PERSIST_FLAGS_REPLACE_EXISTING_FILES |
+ nsIWebBrowserPersist::PERSIST_FLAGS_FROM_CACHE;
+
+ wbp->SetPersistFlags(flags);
+ wbp->SetProgressListener(this);
+
+ nsCOMPtr<nsILoadContext> loadContext;
+ nsCOMPtr<nsISupports> container = content->OwnerDoc()->GetContainer();
+ nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(container);
+ if (docShell) {
+ loadContext = do_QueryInterface(docShell);
+ }
+
+ return wbp->SaveURI(imageURI, nullptr,
+ docURI, content->OwnerDoc()->GetReferrerPolicy(),
+ nullptr, nullptr,
+ mBackgroundFile, loadContext);
+}
+
+NS_IMETHODIMP
+nsMacShellService::OnProgressChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ int32_t aCurSelfProgress,
+ int32_t aMaxSelfProgress,
+ int32_t aCurTotalProgress,
+ int32_t aMaxTotalProgress)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacShellService::OnLocationChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsIURI* aLocation,
+ uint32_t aFlags)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacShellService::OnStatusChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ nsresult aStatus,
+ const char16_t* aMessage)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacShellService::OnSecurityChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aState)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacShellService::OnStateChange(nsIWebProgress* aWebProgress,
+ nsIRequest* aRequest,
+ uint32_t aStateFlags,
+ nsresult aStatus)
+{
+ if (aStateFlags & STATE_STOP) {
+ nsCOMPtr<nsIObserverService> os(do_GetService("@mozilla.org/observer-service;1"));
+ if (os)
+ os->NotifyObservers(nullptr, "shell:desktop-background-changed", nullptr);
+
+ bool exists = false;
+ mBackgroundFile->Exists(&exists);
+ if (!exists)
+ return NS_OK;
+
+ nsAutoCString nativePath;
+ mBackgroundFile->GetNativePath(nativePath);
+
+ AEDesc tAEDesc = { typeNull, nil };
+ OSErr err = noErr;
+ AliasHandle aliasHandle = nil;
+ FSRef pictureRef;
+ OSStatus status;
+
+ // Convert the path into a FSRef
+ status = ::FSPathMakeRef((const UInt8*)nativePath.get(), &pictureRef,
+ nullptr);
+ if (status == noErr) {
+ err = ::FSNewAlias(nil, &pictureRef, &aliasHandle);
+ if (err == noErr && aliasHandle == nil)
+ err = paramErr;
+
+ if (err == noErr) {
+ // We need the descriptor (based on the picture file reference)
+ // for the 'Set Desktop Picture' apple event.
+ char handleState = ::HGetState((Handle)aliasHandle);
+ ::HLock((Handle)aliasHandle);
+ err = ::AECreateDesc(typeAlias, *aliasHandle,
+ GetHandleSize((Handle)aliasHandle), &tAEDesc);
+ // unlock the alias handler
+ ::HSetState((Handle)aliasHandle, handleState);
+ ::DisposeHandle((Handle)aliasHandle);
+ }
+ if (err == noErr) {
+ AppleEvent tAppleEvent;
+ OSType sig = 'MACS';
+ AEBuildError tAEBuildError;
+ // Create a 'Set Desktop Pictue' Apple Event
+ err = ::AEBuildAppleEvent(kAECoreSuite, kAESetData, typeApplSignature,
+ &sig, sizeof(OSType), kAutoGenerateReturnID,
+ kAnyTransactionID, &tAppleEvent, &tAEBuildError,
+ "'----':'obj '{want:type (prop),form:prop" \
+ ",seld:type('dpic'),from:'null'()},data:(@)",
+ &tAEDesc);
+ if (err == noErr) {
+ AppleEvent reply = { typeNull, nil };
+ // Sent the event we built, the reply event isn't necessary
+ err = ::AESend(&tAppleEvent, &reply, kAENoReply, kAENormalPriority,
+ kNoTimeOut, nil, nil);
+ ::AEDisposeDesc(&tAppleEvent);
+ }
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacShellService::OpenApplication(int32_t aApplication)
+{
+ nsresult rv = NS_OK;
+ CFURLRef appURL = nil;
+ OSStatus err = noErr;
+
+ switch (aApplication) {
+ case nsIShellService::APPLICATION_MAIL:
+ {
+ CFURLRef tempURL = ::CFURLCreateWithString(kCFAllocatorDefault,
+ CFSTR("mailto:"), nullptr);
+ err = ::LSGetApplicationForURL(tempURL, kLSRolesAll, nullptr, &appURL);
+ ::CFRelease(tempURL);
+ }
+ break;
+ case nsIShellService::APPLICATION_NEWS:
+ {
+ CFURLRef tempURL = ::CFURLCreateWithString(kCFAllocatorDefault,
+ CFSTR("news:"), nullptr);
+ err = ::LSGetApplicationForURL(tempURL, kLSRolesAll, nullptr, &appURL);
+ ::CFRelease(tempURL);
+ }
+ break;
+ case nsIMacShellService::APPLICATION_KEYCHAIN_ACCESS:
+ err = ::LSGetApplicationForInfo('APPL', 'kcmr', nullptr, kLSRolesAll,
+ nullptr, &appURL);
+ break;
+ case nsIMacShellService::APPLICATION_NETWORK:
+ {
+ nsCOMPtr<nsIFile> lf;
+ rv = NS_NewNativeLocalFile(NETWORK_PREFPANE, true, getter_AddRefs(lf));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool exists;
+ lf->Exists(&exists);
+ if (!exists)
+ return NS_ERROR_FILE_NOT_FOUND;
+ return lf->Launch();
+ }
+ case nsIMacShellService::APPLICATION_DESKTOP:
+ {
+ nsCOMPtr<nsIFile> lf;
+ rv = NS_NewNativeLocalFile(DESKTOP_PREFPANE, true, getter_AddRefs(lf));
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool exists;
+ lf->Exists(&exists);
+ if (!exists)
+ return NS_ERROR_FILE_NOT_FOUND;
+ return lf->Launch();
+ }
+ }
+
+ if (appURL && err == noErr) {
+ err = ::LSOpenCFURLRef(appURL, nullptr);
+ rv = err != noErr ? NS_ERROR_FAILURE : NS_OK;
+
+ ::CFRelease(appURL);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsMacShellService::GetDesktopBackgroundColor(uint32_t *aColor)
+{
+ // This method and |SetDesktopBackgroundColor| has no meaning on Mac OS X.
+ // The mac desktop preferences UI uses pictures for the few solid colors it
+ // supports.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMacShellService::SetDesktopBackgroundColor(uint32_t aColor)
+{
+ // This method and |GetDesktopBackgroundColor| has no meaning on Mac OS X.
+ // The mac desktop preferences UI uses pictures for the few solid colors it
+ // supports.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsMacShellService::OpenApplicationWithURI(nsIFile* aApplication, const nsACString& aURI)
+{
+ nsCOMPtr<nsILocalFileMac> lfm(do_QueryInterface(aApplication));
+ CFURLRef appURL;
+ nsresult rv = lfm->GetCFURL(&appURL);
+ if (NS_FAILED(rv))
+ return rv;
+
+ const nsCString spec(aURI);
+ const UInt8* uriString = (const UInt8*)spec.get();
+ CFURLRef uri = ::CFURLCreateWithBytes(nullptr, uriString, aURI.Length(),
+ kCFStringEncodingUTF8, nullptr);
+ if (!uri)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ CFArrayRef uris = ::CFArrayCreate(nullptr, (const void**)&uri, 1, nullptr);
+ if (!uris) {
+ ::CFRelease(uri);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ LSLaunchURLSpec launchSpec;
+ launchSpec.appURL = appURL;
+ launchSpec.itemURLs = uris;
+ launchSpec.passThruParams = nullptr;
+ launchSpec.launchFlags = kLSLaunchDefaults;
+ launchSpec.asyncRefCon = nullptr;
+
+ OSErr err = ::LSOpenFromURLSpec(&launchSpec, nullptr);
+
+ ::CFRelease(uris);
+ ::CFRelease(uri);
+
+ return err != noErr ? NS_ERROR_FAILURE : NS_OK;
+}
+
+NS_IMETHODIMP
+nsMacShellService::GetDefaultFeedReader(nsIFile** _retval)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+ *_retval = nullptr;
+
+ CFStringRef defaultHandlerID = ::LSCopyDefaultHandlerForURLScheme(CFSTR("feed"));
+ if (!defaultHandlerID) {
+ defaultHandlerID = ::CFStringCreateWithCString(kCFAllocatorDefault,
+ SAFARI_BUNDLE_IDENTIFIER,
+ kCFStringEncodingASCII);
+ }
+
+ CFURLRef defaultHandlerURL = nullptr;
+ OSStatus status = ::LSFindApplicationForInfo(kLSUnknownCreator,
+ defaultHandlerID,
+ nullptr, // inName
+ nullptr, // outAppRef
+ &defaultHandlerURL);
+
+ if (status == noErr && defaultHandlerURL) {
+ nsCOMPtr<nsILocalFileMac> defaultReader =
+ do_CreateInstance("@mozilla.org/file/local;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = defaultReader->InitWithCFURL(defaultHandlerURL);
+ if (NS_SUCCEEDED(rv)) {
+ NS_ADDREF(*_retval = defaultReader);
+ rv = NS_OK;
+ }
+ }
+
+ ::CFRelease(defaultHandlerURL);
+ }
+
+ ::CFRelease(defaultHandlerID);
+
+ return rv;
+}
diff --git a/components/shell/nsMacShellService.h b/components/shell/nsMacShellService.h
new file mode 100644
index 0000000..db95278
--- /dev/null
+++ b/components/shell/nsMacShellService.h
@@ -0,0 +1,32 @@
+/* -*- 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/. */
+
+#ifndef nsmacshellservice_h____
+#define nsmacshellservice_h____
+
+#include "nsIMacShellService.h"
+#include "nsIWebProgressListener.h"
+#include "nsIFile.h"
+#include "nsCOMPtr.h"
+
+class nsMacShellService : public nsIMacShellService,
+ public nsIWebProgressListener
+{
+public:
+ nsMacShellService() {};
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISHELLSERVICE
+ NS_DECL_NSIMACSHELLSERVICE
+ NS_DECL_NSIWEBPROGRESSLISTENER
+
+protected:
+ virtual ~nsMacShellService() {};
+
+private:
+ nsCOMPtr<nsIFile> mBackgroundFile;
+};
+
+#endif // nsmacshellservice_h____
diff --git a/components/shell/nsSetDefaultBrowser.js b/components/shell/nsSetDefaultBrowser.js
new file mode 100644
index 0000000..c7a78c5
--- /dev/null
+++ b/components/shell/nsSetDefaultBrowser.js
@@ -0,0 +1,30 @@
+/* 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/. */
+
+/*
+ * --setDefaultBrowser commandline handler
+ * Makes the current executable the "default browser".
+ */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+Components.utils.import("resource:///modules/ShellService.jsm");
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function nsSetDefaultBrowser() {}
+
+nsSetDefaultBrowser.prototype = {
+ handle: function nsSetDefault_handle(aCmdline) {
+ if (aCmdline.handleFlag("setDefaultBrowser", false)) {
+ ShellService.setDefaultBrowser(true, true);
+ }
+ },
+
+ helpInfo: " --setDefaultBrowser Set this app as the default browser.\n",
+
+ classID: Components.ID("{F57899D0-4E2C-4ac6-9E29-50C736103B0C}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler]),
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsSetDefaultBrowser]);
diff --git a/components/shell/nsSetDefaultBrowser.manifest b/components/shell/nsSetDefaultBrowser.manifest
new file mode 100644
index 0000000..bf3c0f0
--- /dev/null
+++ b/components/shell/nsSetDefaultBrowser.manifest
@@ -0,0 +1,3 @@
+component {F57899D0-4E2C-4ac6-9E29-50C736103B0C} nsSetDefaultBrowser.js
+contract @mozilla.org/browser/default-browser-clh;1 {F57899D0-4E2C-4ac6-9E29-50C736103B0C}
+category command-line-handler m-setdefaultbrowser @mozilla.org/browser/default-browser-clh;1
diff --git a/components/shell/nsShellService.h b/components/shell/nsShellService.h
new file mode 100644
index 0000000..516a842
--- /dev/null
+++ b/components/shell/nsShellService.h
@@ -0,0 +1,12 @@
+/* -*- 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/. */
+
+#define PREF_CHECKDEFAULTBROWSER "browser.shell.checkDefaultBrowser"
+#define PREF_SKIPDEFAULTBROWSERCHECK "browser.shell.skipDefaultBrowserCheck"
+#define PREF_DEFAULTBROWSERCHECKCOUNT "browser.shell.defaultBrowserCheckCount"
+
+#define SHELLSERVICE_PROPERTIES "chrome://browser/locale/shellservice.properties"
+#define BRAND_PROPERTIES "chrome://branding/locale/brand.properties"
+
diff --git a/components/shell/nsWindowsShellService.cpp b/components/shell/nsWindowsShellService.cpp
new file mode 100644
index 0000000..c4039b9
--- /dev/null
+++ b/components/shell/nsWindowsShellService.cpp
@@ -0,0 +1,1277 @@
+/* -*- 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/. */
+
+#include "nsWindowsShellService.h"
+
+#include "imgIContainer.h"
+#include "imgIRequest.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/RefPtr.h"
+#include "nsIDOMElement.h"
+#include "nsIDOMHTMLImageElement.h"
+#include "nsIImageLoadingContent.h"
+#include "nsIPrefService.h"
+#include "nsIPrefLocalizedString.h"
+#include "nsIServiceManager.h"
+#include "nsIStringBundle.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsShellService.h"
+#include "nsIProcess.h"
+#include "nsICategoryManager.h"
+#include "nsBrowserCompsCID.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIWindowsRegKey.h"
+#include "nsUnicharUtils.h"
+#include "nsIWinTaskbar.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIURLFormatter.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "mozilla/WindowsVersion.h"
+
+#include "windows.h"
+#include "shellapi.h"
+
+#ifdef _WIN32_WINNT
+#undef _WIN32_WINNT
+#endif
+#define _WIN32_WINNT 0x0600
+#define INITGUID
+#undef NTDDI_VERSION
+#define NTDDI_VERSION NTDDI_WIN8
+// Needed for access to IApplicationActivationManager
+#include <shlobj.h>
+
+#include <mbstring.h>
+#include <shlwapi.h>
+
+#include <lm.h>
+#undef ACCESS_READ
+
+#ifndef MAX_BUF
+#define MAX_BUF 4096
+#endif
+
+#define REG_SUCCEEDED(val) \
+ (val == ERROR_SUCCESS)
+
+#define REG_FAILED(val) \
+ (val != ERROR_SUCCESS)
+
+#define NS_TASKBAR_CONTRACTID "@mozilla.org/windows-taskbar;1"
+
+using mozilla::IsWin8OrLater;
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+NS_IMPL_ISUPPORTS(nsWindowsShellService, nsIWindowsShellService, nsIShellService)
+
+static nsresult
+OpenKeyForReading(HKEY aKeyRoot, const nsAString& aKeyName, HKEY* aKey)
+{
+ const nsString &flatName = PromiseFlatString(aKeyName);
+
+ DWORD res = ::RegOpenKeyExW(aKeyRoot, flatName.get(), 0, KEY_READ, aKey);
+ switch (res) {
+ case ERROR_SUCCESS:
+ break;
+ case ERROR_ACCESS_DENIED:
+ return NS_ERROR_FILE_ACCESS_DENIED;
+ case ERROR_FILE_NOT_FOUND:
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Default Browser Registry Settings
+//
+// The setting of these values are made by an external binary since writing
+// these values may require elevation.
+//
+// - File Extension Mappings
+// -----------------------
+// The following file extensions:
+// .htm .html .shtml .xht .xhtml
+// are mapped like so:
+//
+// HKCU\SOFTWARE\Classes\.<ext>\ (default) REG_SZ PaleMoonHTML
+//
+// as aliases to the class:
+//
+// HKCU\SOFTWARE\Classes\PaleMoonHTML\
+// DefaultIcon (default) REG_SZ <apppath>,1
+// shell\open\command (default) REG_SZ <apppath> -osint -url "%1"
+// shell\open\ddeexec (default) REG_SZ <empty string>
+//
+// - Windows Vista and above Protocol Handler
+//
+// HKCU\SOFTWARE\Classes\PaleMoonURL\ (default) REG_SZ <appname> URL
+// EditFlags REG_DWORD 2
+// FriendlyTypeName REG_SZ <appname> URL
+// DefaultIcon (default) REG_SZ <apppath>,1
+// shell\open\command (default) REG_SZ <apppath> -osint -url "%1"
+// shell\open\ddeexec (default) REG_SZ <empty string>
+//
+// - Protocol Mappings
+// -----------------
+// The following protocols:
+// HTTP, HTTPS, FTP
+// are mapped like so:
+//
+// HKCU\SOFTWARE\Classes\<protocol>\
+// DefaultIcon (default) REG_SZ <apppath>,1
+// shell\open\command (default) REG_SZ <apppath> -osint -url "%1"
+// shell\open\ddeexec (default) REG_SZ <empty string>
+//
+// - Windows Start Menu (XP SP1 and newer)
+// -------------------------------------------------
+// The following keys are set to make PaleMoon appear in the Start Menu as the
+// browser:
+//
+// HKCU\SOFTWARE\Clients\StartMenuInternet\PaleMoon.EXE\
+// (default) REG_SZ <appname>
+// DefaultIcon (default) REG_SZ <apppath>,0
+// InstallInfo HideIconsCommand REG_SZ <uninstpath> /HideShortcuts
+// InstallInfo IconsVisible REG_DWORD 1
+// InstallInfo ReinstallCommand REG_SZ <uninstpath> /SetAsDefaultAppGlobal
+// InstallInfo ShowIconsCommand REG_SZ <uninstpath> /ShowShortcuts
+// shell\open\command (default) REG_SZ <apppath>
+// shell\properties (default) REG_SZ <appname> &Options
+// shell\properties\command (default) REG_SZ <apppath> -preferences
+// shell\safemode (default) REG_SZ <appname> &Safe Mode
+// shell\safemode\command (default) REG_SZ <apppath> -safe-mode
+//
+
+// The values checked are all default values so the value name is not needed.
+typedef struct {
+ const char* keyName;
+ const char* valueData;
+ const char* oldValueData;
+} SETTING;
+
+#define APP_REG_NAME L"Pale Moon"
+#define VAL_FILE_ICON "%APPPATH%,1"
+#define VAL_OPEN "\"%APPPATH%\" -osint -url \"%1\""
+#define OLD_VAL_OPEN "\"%APPPATH%\" -requestPending -osint -url \"%1\""
+#define DI "\\DefaultIcon"
+#define SOC "\\shell\\open\\command"
+#define SOD "\\shell\\open\\ddeexec"
+// Used for updating the FTP protocol handler's shell open command under HKCU.
+#define FTP_SOC L"Software\\Classes\\ftp\\shell\\open\\command"
+
+#define MAKE_KEY_NAME1(PREFIX, MID) \
+ PREFIX MID
+
+// The DefaultIcon registry key value should never be used when checking if
+// PaleMoon is the default browser for file handlers since other applications
+// (e.g. MS Office) may modify the DefaultIcon registry key value to add Icon
+// Handlers. see http://msdn2.microsoft.com/en-us/library/aa969357.aspx for
+// more info. The FTP protocol is not checked so advanced users can set the FTP
+// handler to another application and still have PaleMoon check if it is the
+// default HTTP and HTTPS handler.
+// *** Do not add additional checks here unless you skip them when aForAllTypes
+// is false below***.
+static SETTING gSettings[] = {
+ // File Handler Class
+ // ***keep this as the first entry because when aForAllTypes is not set below
+ // it will skip over this check.***
+ { MAKE_KEY_NAME1("PaleMoonHTML", SOC), VAL_OPEN, OLD_VAL_OPEN },
+
+ // Protocol Handler Class - for Vista and above
+ { MAKE_KEY_NAME1("PaleMoonURL", SOC), VAL_OPEN, OLD_VAL_OPEN },
+
+ // Protocol Handlers
+ { MAKE_KEY_NAME1("HTTP", DI), VAL_FILE_ICON },
+ { MAKE_KEY_NAME1("HTTP", SOC), VAL_OPEN, OLD_VAL_OPEN },
+ { MAKE_KEY_NAME1("HTTPS", DI), VAL_FILE_ICON },
+ { MAKE_KEY_NAME1("HTTPS", SOC), VAL_OPEN, OLD_VAL_OPEN }
+};
+
+// The settings to disable DDE are separate from the default browser settings
+// since they are only checked when PaleMoon is the default browser and if they
+// are incorrect they are fixed without notifying the user.
+static SETTING gDDESettings[] = {
+ // File Handler Class
+ { MAKE_KEY_NAME1("Software\\Classes\\PaleMoonHTML", SOD) },
+
+ // Protocol Handler Class - for Vista and above
+ { MAKE_KEY_NAME1("Software\\Classes\\PaleMoonURL", SOD) },
+
+ // Protocol Handlers
+ { MAKE_KEY_NAME1("Software\\Classes\\FTP", SOD) },
+ { MAKE_KEY_NAME1("Software\\Classes\\HTTP", SOD) },
+ { MAKE_KEY_NAME1("Software\\Classes\\HTTPS", SOD) }
+};
+
+nsresult
+GetHelperPath(nsAutoString& aPath)
+{
+ nsresult rv;
+ nsCOMPtr<nsIProperties> directoryService =
+ do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> appHelper;
+ rv = directoryService->Get(XRE_EXECUTABLE_FILE,
+ NS_GET_IID(nsIFile),
+ getter_AddRefs(appHelper));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = appHelper->SetNativeLeafName(NS_LITERAL_CSTRING("uninstall"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = appHelper->AppendNative(NS_LITERAL_CSTRING("helper.exe"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = appHelper->GetPath(aPath);
+
+ aPath.Insert(L'"', 0);
+ aPath.Append(L'"');
+ return rv;
+}
+
+nsresult
+LaunchHelper(nsAutoString& aPath)
+{
+ STARTUPINFOW si = {sizeof(si), 0};
+ PROCESS_INFORMATION pi = {0};
+
+ if (!CreateProcessW(nullptr, (LPWSTR)aPath.get(), nullptr, nullptr, FALSE,
+ 0, nullptr, nullptr, &si, &pi)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowsShellService::ShortcutMaintenance()
+{
+ nsresult rv;
+
+ // XXX App ids were updated to a constant install path hash,
+ // XXX this code can be removed after a few upgrade cycles.
+
+ // Launch helper.exe so it can update the application user model ids on
+ // shortcuts in the user's taskbar and start menu. This keeps older pinned
+ // shortcuts grouped correctly after major updates. Note, we also do this
+ // through the upgrade installer script, however, this is the only place we
+ // have a chance to trap links created by users who do control the install/
+ // update process of the browser.
+
+ nsCOMPtr<nsIWinTaskbar> taskbarInfo =
+ do_GetService(NS_TASKBAR_CONTRACTID);
+ if (!taskbarInfo) // If we haven't built with win7 sdk features, this fails.
+ return NS_OK;
+
+ // Avoid if this isn't Win7+
+ bool isSupported = false;
+ taskbarInfo->GetAvailable(&isSupported);
+ if (!isSupported)
+ return NS_OK;
+
+ nsAutoString appId;
+ if (NS_FAILED(taskbarInfo->GetDefaultGroupId(appId)))
+ return NS_ERROR_UNEXPECTED;
+
+ NS_NAMED_LITERAL_CSTRING(prefName, "browser.taskbar.lastgroupid");
+ nsCOMPtr<nsIPrefBranch> prefs =
+ do_GetService(NS_PREFSERVICE_CONTRACTID);
+ if (!prefs)
+ return NS_ERROR_UNEXPECTED;
+
+ nsCOMPtr<nsISupportsString> prefString;
+ rv = prefs->GetComplexValue(prefName.get(),
+ NS_GET_IID(nsISupportsString),
+ getter_AddRefs(prefString));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoString version;
+ prefString->GetData(version);
+ if (!version.IsEmpty() && version.Equals(appId)) {
+ // We're all good, get out of here.
+ return NS_OK;
+ }
+ }
+ // Update the version in prefs
+ prefString =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ prefString->SetData(appId);
+ rv = prefs->SetComplexValue(prefName.get(),
+ NS_GET_IID(nsISupportsString),
+ prefString);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Couldn't set last user model id!");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsAutoString appHelperPath;
+ if (NS_FAILED(GetHelperPath(appHelperPath)))
+ return NS_ERROR_UNEXPECTED;
+
+ appHelperPath.AppendLiteral(" /UpdateShortcutAppUserModelIds");
+
+ return LaunchHelper(appHelperPath);
+}
+
+static bool
+IsAARDefault(const RefPtr<IApplicationAssociationRegistration>& pAAR,
+ LPCWSTR aClassName)
+{
+ // Make sure the Prog ID matches what we have
+ LPWSTR registeredApp;
+ bool isProtocol = *aClassName != L'.';
+ ASSOCIATIONTYPE queryType = isProtocol ? AT_URLPROTOCOL : AT_FILEEXTENSION;
+ HRESULT hr = pAAR->QueryCurrentDefault(aClassName, queryType, AL_EFFECTIVE,
+ &registeredApp);
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ LPCWSTR progID = isProtocol ? L"PaleMoonURL" : L"PaleMoonHTML";
+ bool isDefault = !wcsicmp(registeredApp, progID);
+ CoTaskMemFree(registeredApp);
+
+ return isDefault;
+}
+
+static void
+IsDefaultBrowserWin8(bool aCheckAllTypes, bool* aIsDefaultBrowser)
+{
+ RefPtr<IApplicationAssociationRegistration> pAAR;
+ HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationRegistration,
+ nullptr,
+ CLSCTX_INPROC,
+ IID_IApplicationAssociationRegistration,
+ getter_AddRefs(pAAR));
+ if (FAILED(hr)) {
+ return;
+ }
+
+ bool res = IsAARDefault(pAAR, L"http");
+ if (*aIsDefaultBrowser) {
+ *aIsDefaultBrowser = res;
+ }
+ res = IsAARDefault(pAAR, L".html");
+ if (*aIsDefaultBrowser && aCheckAllTypes) {
+ *aIsDefaultBrowser = res;
+ }
+}
+
+/*
+ * Query's the AAR for the default status.
+ * This only checks for PaleMoonURL and if aCheckAllTypes is set, then
+ * it also checks for PaleMoonHTML. Note that those ProgIDs are shared
+ * by all PaleMoon browsers.
+*/
+bool
+nsWindowsShellService::IsDefaultBrowserVista(bool aCheckAllTypes,
+ bool* aIsDefaultBrowser)
+{
+ RefPtr<IApplicationAssociationRegistration> pAAR;
+ HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationRegistration,
+ nullptr,
+ CLSCTX_INPROC,
+ IID_IApplicationAssociationRegistration,
+ getter_AddRefs(pAAR));
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ if (aCheckAllTypes) {
+ BOOL res;
+ hr = pAAR->QueryAppIsDefaultAll(AL_EFFECTIVE,
+ APP_REG_NAME,
+ &res);
+ *aIsDefaultBrowser = res;
+ } else if (!IsWin8OrLater()) {
+ *aIsDefaultBrowser = IsAARDefault(pAAR, L"http");
+ }
+
+ return true;
+}
+
+NS_IMETHODIMP
+nsWindowsShellService::IsDefaultBrowser(bool aStartupCheck,
+ bool aForAllTypes,
+ bool* aIsDefaultBrowser)
+{
+ // Assume we're the default unless one of the several checks below tell us
+ // otherwise.
+ *aIsDefaultBrowser = true;
+
+ wchar_t exePath[MAX_BUF];
+ if (!::GetModuleFileNameW(0, exePath, MAX_BUF))
+ return NS_ERROR_FAILURE;
+
+ // Convert the path to a long path since GetModuleFileNameW returns the path
+ // that was used to launch PaleMoon which is not necessarily a long path.
+ if (!::GetLongPathNameW(exePath, exePath, MAX_BUF))
+ return NS_ERROR_FAILURE;
+
+ nsAutoString appLongPath(exePath);
+
+ HKEY theKey;
+ DWORD res;
+ nsresult rv;
+ wchar_t currValue[MAX_BUF];
+
+ SETTING* settings = gSettings;
+ if (!aForAllTypes && IsWin8OrLater()) {
+ // Skip over the file handler check
+ settings++;
+ }
+
+ SETTING* end = gSettings + sizeof(gSettings) / sizeof(SETTING);
+
+ for (; settings < end; ++settings) {
+ NS_ConvertUTF8toUTF16 keyName(settings->keyName);
+ NS_ConvertUTF8toUTF16 valueData(settings->valueData);
+ int32_t offset = valueData.Find("%APPPATH%");
+ valueData.Replace(offset, 9, appLongPath);
+
+ rv = OpenKeyForReading(HKEY_CLASSES_ROOT, keyName, &theKey);
+ if (NS_FAILED(rv)) {
+ *aIsDefaultBrowser = false;
+ return NS_OK;
+ }
+
+ ::ZeroMemory(currValue, sizeof(currValue));
+ DWORD len = sizeof currValue;
+ res = ::RegQueryValueExW(theKey, L"", nullptr, nullptr,
+ (LPBYTE)currValue, &len);
+ // Close the key that was opened.
+ ::RegCloseKey(theKey);
+ if (REG_FAILED(res) ||
+ _wcsicmp(valueData.get(), currValue)) {
+ // Key wasn't set or was set to something other than our registry entry.
+ NS_ConvertUTF8toUTF16 oldValueData(settings->oldValueData);
+ offset = oldValueData.Find("%APPPATH%");
+ oldValueData.Replace(offset, 9, appLongPath);
+ // The current registry value doesn't match the current or the old format.
+ if (_wcsicmp(oldValueData.get(), currValue)) {
+ *aIsDefaultBrowser = false;
+ return NS_OK;
+ }
+
+ res = ::RegOpenKeyExW(HKEY_CLASSES_ROOT, PromiseFlatString(keyName).get(),
+ 0, KEY_SET_VALUE, &theKey);
+ if (REG_FAILED(res)) {
+ // If updating the open command fails try to update it using the helper
+ // application when setting PaleMoon as the default browser.
+ *aIsDefaultBrowser = false;
+ return NS_OK;
+ }
+
+ const nsString &flatValue = PromiseFlatString(valueData);
+ res = ::RegSetValueExW(theKey, L"", 0, REG_SZ,
+ (const BYTE *) flatValue.get(),
+ (flatValue.Length() + 1) * sizeof(char16_t));
+ // Close the key that was created.
+ ::RegCloseKey(theKey);
+ if (REG_FAILED(res)) {
+ // If updating the open command fails try to update it using the helper
+ // application when setting PaleMoon as the default browser.
+ *aIsDefaultBrowser = false;
+ return NS_OK;
+ }
+ }
+ }
+
+ // Only check if PaleMoon is the default browser on Vista and above if the
+ // previous checks show that PaleMoon is the default browser.
+ if (*aIsDefaultBrowser) {
+ IsDefaultBrowserVista(aForAllTypes, aIsDefaultBrowser);
+ if (IsWin8OrLater()) {
+ IsDefaultBrowserWin8(aForAllTypes, aIsDefaultBrowser);
+ }
+ }
+
+ // To handle the case where DDE isn't disabled due for a user because there
+ // account didn't perform a PaleMoon update this will check if PaleMoon is the
+ // default browser and if dde is disabled for each handler
+ // and if it isn't disable it. When PaleMoon is not the default browser the
+ // helper application will disable dde for each handler.
+ if (*aIsDefaultBrowser && aForAllTypes) {
+ // Check ftp settings
+
+ end = gDDESettings + sizeof(gDDESettings) / sizeof(SETTING);
+
+ for (settings = gDDESettings; settings < end; ++settings) {
+ NS_ConvertUTF8toUTF16 keyName(settings->keyName);
+
+ rv = OpenKeyForReading(HKEY_CURRENT_USER, keyName, &theKey);
+ if (NS_FAILED(rv)) {
+ ::RegCloseKey(theKey);
+ // If disabling DDE fails try to disable it using the helper
+ // application when setting PaleMoon as the default browser.
+ *aIsDefaultBrowser = false;
+ return NS_OK;
+ }
+
+ ::ZeroMemory(currValue, sizeof(currValue));
+ DWORD len = sizeof currValue;
+ res = ::RegQueryValueExW(theKey, L"", nullptr, nullptr,
+ (LPBYTE)currValue, &len);
+ // Close the key that was opened.
+ ::RegCloseKey(theKey);
+ if (REG_FAILED(res) || char16_t('\0') != *currValue) {
+ // Key wasn't set or was set to something other than our registry entry.
+ // Delete the key along with all of its childrean and then recreate it.
+ const nsString &flatName = PromiseFlatString(keyName);
+ ::SHDeleteKeyW(HKEY_CURRENT_USER, flatName.get());
+ res = ::RegCreateKeyExW(HKEY_CURRENT_USER, flatName.get(), 0, nullptr,
+ REG_OPTION_NON_VOLATILE, KEY_SET_VALUE,
+ nullptr, &theKey, nullptr);
+ if (REG_FAILED(res)) {
+ // If disabling DDE fails try to disable it using the helper
+ // application when setting PaleMoon as the default browser.
+ *aIsDefaultBrowser = false;
+ return NS_OK;
+ }
+
+ res = ::RegSetValueExW(theKey, L"", 0, REG_SZ, (const BYTE *) L"",
+ sizeof(char16_t));
+ // Close the key that was created.
+ ::RegCloseKey(theKey);
+ if (REG_FAILED(res)) {
+ // If disabling DDE fails try to disable it using the helper
+ // application when setting PaleMoon as the default browser.
+ *aIsDefaultBrowser = false;
+ return NS_OK;
+ }
+ }
+ }
+
+ // Update the FTP protocol handler's shell open command if it is the old
+ // format.
+ res = ::RegOpenKeyExW(HKEY_CURRENT_USER, FTP_SOC, 0, KEY_ALL_ACCESS,
+ &theKey);
+ // Don't update the FTP protocol handler's shell open command when opening
+ // its registry key fails under HKCU since it most likely doesn't exist.
+ if (NS_FAILED(rv)) {
+ return NS_OK;
+ }
+
+ NS_ConvertUTF8toUTF16 oldValueOpen(OLD_VAL_OPEN);
+ int32_t offset = oldValueOpen.Find("%APPPATH%");
+ oldValueOpen.Replace(offset, 9, appLongPath);
+
+ ::ZeroMemory(currValue, sizeof(currValue));
+ DWORD len = sizeof currValue;
+ res = ::RegQueryValueExW(theKey, L"", nullptr, nullptr, (LPBYTE)currValue,
+ &len);
+
+ // Don't update the FTP protocol handler's shell open command when the
+ // current registry value doesn't exist or matches the old format.
+ if (REG_FAILED(res) ||
+ _wcsicmp(oldValueOpen.get(), currValue)) {
+ ::RegCloseKey(theKey);
+ return NS_OK;
+ }
+
+ NS_ConvertUTF8toUTF16 valueData(VAL_OPEN);
+ valueData.Replace(offset, 9, appLongPath);
+ const nsString &flatValue = PromiseFlatString(valueData);
+ res = ::RegSetValueExW(theKey, L"", 0, REG_SZ,
+ (const BYTE *) flatValue.get(),
+ (flatValue.Length() + 1) * sizeof(char16_t));
+ // Close the key that was created.
+ ::RegCloseKey(theKey);
+ // If updating the FTP protocol handlers shell open command fails try to
+ // update it using the helper application when setting PaleMoon as the
+ // default browser.
+ if (REG_FAILED(res)) {
+ *aIsDefaultBrowser = false;
+ }
+ }
+
+ return NS_OK;
+}
+
+static nsresult
+DynSHOpenWithDialog(HWND hwndParent, const OPENASINFO *poainfo)
+{
+ // shell32.dll is in the knownDLLs list so will always be loaded from the
+ // system32 directory.
+ static const wchar_t kSehllLibraryName[] = L"shell32.dll";
+ HMODULE shellDLL = ::LoadLibraryW(kSehllLibraryName);
+ if (!shellDLL) {
+ return NS_ERROR_FAILURE;
+ }
+
+ decltype(SHOpenWithDialog)* SHOpenWithDialogFn =
+ (decltype(SHOpenWithDialog)*) GetProcAddress(shellDLL, "SHOpenWithDialog");
+
+ if (!SHOpenWithDialogFn) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+ HRESULT hr = SHOpenWithDialogFn(hwndParent, poainfo);
+ if (SUCCEEDED(hr) || (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED))) {
+ rv = NS_OK;
+ } else {
+ rv = NS_ERROR_FAILURE;
+ }
+ FreeLibrary(shellDLL);
+ return rv;
+}
+
+nsresult
+nsWindowsShellService::LaunchControlPanelDefaultsSelectionUI()
+{
+ IApplicationAssociationRegistrationUI* pAARUI;
+ HRESULT hr = CoCreateInstance(CLSID_ApplicationAssociationRegistrationUI,
+ NULL,
+ CLSCTX_INPROC,
+ IID_IApplicationAssociationRegistrationUI,
+ (void**)&pAARUI);
+ if (SUCCEEDED(hr)) {
+ hr = pAARUI->LaunchAdvancedAssociationUI(APP_REG_NAME);
+ pAARUI->Release();
+ }
+ return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult
+nsWindowsShellService::LaunchControlPanelDefaultPrograms()
+{
+ // Build the path control.exe path safely
+ WCHAR controlEXEPath[MAX_PATH + 1] = { '\0' };
+ if (!GetSystemDirectoryW(controlEXEPath, MAX_PATH)) {
+ return NS_ERROR_FAILURE;
+ }
+ LPCWSTR controlEXE = L"control.exe";
+ if (wcslen(controlEXEPath) + wcslen(controlEXE) >= MAX_PATH) {
+ return NS_ERROR_FAILURE;
+ }
+ if (!PathAppendW(controlEXEPath, controlEXE)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ WCHAR params[] = L"control.exe /name Microsoft.DefaultPrograms /page "
+ "pageDefaultProgram\\pageAdvancedSettings?pszAppName=" APP_REG_NAME;
+ STARTUPINFOW si = {sizeof(si), 0};
+ si.dwFlags = STARTF_USESHOWWINDOW;
+ si.wShowWindow = SW_SHOWDEFAULT;
+ PROCESS_INFORMATION pi = {0};
+ if (!CreateProcessW(controlEXEPath, params, nullptr, nullptr, FALSE,
+ 0, nullptr, nullptr, &si, &pi)) {
+ return NS_ERROR_FAILURE;
+ }
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+
+ return NS_OK;
+}
+
+static bool
+IsWindowsLogonConnected()
+{
+ WCHAR userName[UNLEN + 1];
+ DWORD size = ArrayLength(userName);
+ if (!GetUserNameW(userName, &size)) {
+ return false;
+ }
+
+ LPUSER_INFO_24 info;
+ if (NetUserGetInfo(nullptr, userName, 24, (LPBYTE *)&info)
+ != NERR_Success) {
+ return false;
+ }
+ bool connected = info->usri24_internet_identity;
+ NetApiBufferFree(info);
+
+ return connected;
+}
+
+static bool
+SettingsAppBelievesConnected()
+{
+ nsresult rv;
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ NS_LITERAL_STRING("SOFTWARE\\Microsoft\\Windows\\Shell\\Associations"),
+ nsIWindowsRegKey::ACCESS_READ);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ uint32_t value;
+ rv = regKey->ReadIntValue(NS_LITERAL_STRING("IsConnectedAtLogon"), &value);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ return !!value;
+}
+
+nsresult
+nsWindowsShellService::LaunchModernSettingsDialogDefaultApps()
+{
+ if (!IsWindowsBuildOrLater(14965) &&
+ !IsWindowsLogonConnected() && SettingsAppBelievesConnected()) {
+ // Use the classic Control Panel to work around a bug of older
+ // builds of Windows 10.
+ return LaunchControlPanelDefaultPrograms();
+ }
+
+ IApplicationActivationManager* pActivator;
+ HRESULT hr = CoCreateInstance(CLSID_ApplicationActivationManager,
+ nullptr,
+ CLSCTX_INPROC,
+ IID_IApplicationActivationManager,
+ (void**)&pActivator);
+
+ if (SUCCEEDED(hr)) {
+ DWORD pid;
+ hr = pActivator->ActivateApplication(
+ L"windows.immersivecontrolpanel_cw5n1h2txyewy"
+ L"!microsoft.windows.immersivecontrolpanel",
+ L"page=SettingsPageAppsDefaults", AO_NONE, &pid);
+ if (SUCCEEDED(hr)) {
+ // Do not check error because we could at least open
+ // the "Default apps" setting.
+ pActivator->ActivateApplication(
+ L"windows.immersivecontrolpanel_cw5n1h2txyewy"
+ L"!microsoft.windows.immersivecontrolpanel",
+ L"page=SettingsPageAppsDefaults"
+ L"&target=SystemSettings_DefaultApps_Browser", AO_NONE, &pid);
+ }
+ pActivator->Release();
+ return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsWindowsShellService::InvokeHTTPOpenAsVerb()
+{
+ nsCOMPtr<nsIURLFormatter> formatter(
+ do_GetService("@mozilla.org/toolkit/URLFormatterService;1"));
+ if (!formatter) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsString urlStr;
+ nsresult rv = formatter->FormatURLPref(
+ NS_LITERAL_STRING("app.support.baseURL"), urlStr);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!StringBeginsWith(urlStr, NS_LITERAL_STRING("https://"))) {
+ return NS_ERROR_FAILURE;
+ }
+ urlStr.AppendLiteral("win10-default-browser");
+
+ SHELLEXECUTEINFOW seinfo = { sizeof(SHELLEXECUTEINFOW) };
+ seinfo.lpVerb = L"openas";
+ seinfo.lpFile = urlStr.get();
+ seinfo.nShow = SW_SHOWNORMAL;
+ if (!ShellExecuteExW(&seinfo)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsWindowsShellService::LaunchHTTPHandlerPane()
+{
+ OPENASINFO info;
+ info.pcszFile = L"http";
+ info.pcszClass = nullptr;
+ info.oaifInFlags = OAIF_FORCE_REGISTRATION |
+ OAIF_URL_PROTOCOL |
+ OAIF_REGISTER_EXT;
+ return DynSHOpenWithDialog(nullptr, &info);
+}
+
+NS_IMETHODIMP
+nsWindowsShellService::SetDefaultBrowser(bool aClaimAllTypes, bool aForAllUsers)
+{
+ nsAutoString appHelperPath;
+ if (NS_FAILED(GetHelperPath(appHelperPath)))
+ return NS_ERROR_FAILURE;
+
+ if (aForAllUsers) {
+ appHelperPath.AppendLiteral(" /SetAsDefaultAppGlobal");
+ } else {
+ appHelperPath.AppendLiteral(" /SetAsDefaultAppUser");
+ }
+
+ nsresult rv = LaunchHelper(appHelperPath);
+ if (NS_SUCCEEDED(rv) && IsWin8OrLater()) {
+ if (aClaimAllTypes) {
+ if (IsWin10OrLater()) {
+ rv = LaunchModernSettingsDialogDefaultApps();
+ } else {
+ rv = LaunchControlPanelDefaultsSelectionUI();
+ }
+ // The above call should never really fail, but just in case
+ // fall back to showing the HTTP association screen only.
+ if (NS_FAILED(rv)) {
+ if (IsWin10OrLater()) {
+ rv = InvokeHTTPOpenAsVerb();
+ } else {
+ rv = LaunchHTTPHandlerPane();
+ }
+ }
+ } else {
+ // Windows 10 blocks attempts to load the
+ // HTTP Handler association dialog.
+ if (IsWin10OrLater()) {
+ rv = LaunchModernSettingsDialogDefaultApps();
+ } else {
+ rv = LaunchHTTPHandlerPane();
+ }
+
+ // The above call should never really fail, but just in case
+ // fall back to showing control panel for all defaults
+ if (NS_FAILED(rv)) {
+ rv = LaunchControlPanelDefaultsSelectionUI();
+ }
+ }
+ }
+
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ if (prefs) {
+ (void) prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true);
+ // Reset the number of times the dialog should be shown
+ // before it is silenced.
+ (void) prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT, 0);
+ }
+
+ return rv;
+}
+
+static nsresult
+WriteBitmap(nsIFile* aFile, imgIContainer* aImage)
+{
+ nsresult rv;
+
+ RefPtr<SourceSurface> surface =
+ aImage->GetFrame(imgIContainer::FRAME_FIRST,
+ imgIContainer::FLAG_SYNC_DECODE);
+ NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
+
+ // For either of the following formats we want to set the biBitCount member
+ // of the BITMAPINFOHEADER struct to 32, below. For that value the bitmap
+ // format defines that the A8/X8 WORDs in the bitmap byte stream be ignored
+ // for the BI_RGB value we use for the biCompression member.
+ MOZ_ASSERT(surface->GetFormat() == SurfaceFormat::B8G8R8A8 ||
+ surface->GetFormat() == SurfaceFormat::B8G8R8X8);
+
+ RefPtr<DataSourceSurface> dataSurface = surface->GetDataSurface();
+ NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
+
+ int32_t width = dataSurface->GetSize().width;
+ int32_t height = dataSurface->GetSize().height;
+ int32_t bytesPerPixel = 4 * sizeof(uint8_t);
+ uint32_t bytesPerRow = bytesPerPixel * width;
+
+ // initialize these bitmap structs which we will later
+ // serialize directly to the head of the bitmap file
+ BITMAPINFOHEADER bmi;
+ bmi.biSize = sizeof(BITMAPINFOHEADER);
+ bmi.biWidth = width;
+ bmi.biHeight = height;
+ bmi.biPlanes = 1;
+ bmi.biBitCount = (WORD)bytesPerPixel*8;
+ bmi.biCompression = BI_RGB;
+ bmi.biSizeImage = bytesPerRow * height;
+ bmi.biXPelsPerMeter = 0;
+ bmi.biYPelsPerMeter = 0;
+ bmi.biClrUsed = 0;
+ bmi.biClrImportant = 0;
+
+ BITMAPFILEHEADER bf;
+ bf.bfType = 0x4D42; // 'BM'
+ bf.bfReserved1 = 0;
+ bf.bfReserved2 = 0;
+ bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
+ bf.bfSize = bf.bfOffBits + bmi.biSizeImage;
+
+ // get a file output stream
+ nsCOMPtr<nsIOutputStream> stream;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(stream), aFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ DataSourceSurface::MappedSurface map;
+ if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // write the bitmap headers and rgb pixel data to the file
+ rv = NS_ERROR_FAILURE;
+ if (stream) {
+ uint32_t written;
+ stream->Write((const char*)&bf, sizeof(BITMAPFILEHEADER), &written);
+ if (written == sizeof(BITMAPFILEHEADER)) {
+ stream->Write((const char*)&bmi, sizeof(BITMAPINFOHEADER), &written);
+ if (written == sizeof(BITMAPINFOHEADER)) {
+ // write out the image data backwards because the desktop won't
+ // show bitmaps with negative heights for top-to-bottom
+ uint32_t i = map.mStride * height;
+ do {
+ i -= map.mStride;
+ stream->Write(((const char*)map.mData) + i, bytesPerRow, &written);
+ if (written == bytesPerRow) {
+ rv = NS_OK;
+ } else {
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ } while (i != 0);
+ }
+ }
+
+ stream->Close();
+ }
+
+ dataSurface->Unmap();
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsWindowsShellService::SetDesktopBackground(nsIDOMElement* aElement,
+ int32_t aPosition)
+{
+ nsresult rv;
+
+ nsCOMPtr<imgIContainer> container;
+ nsCOMPtr<nsIDOMHTMLImageElement> imgElement(do_QueryInterface(aElement));
+ if (!imgElement) {
+ // XXX write background loading stuff!
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ else {
+ nsCOMPtr<nsIImageLoadingContent> imageContent =
+ do_QueryInterface(aElement, &rv);
+ if (!imageContent)
+ return rv;
+
+ // get the image container
+ nsCOMPtr<imgIRequest> request;
+ rv = imageContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+ getter_AddRefs(request));
+ if (!request)
+ return rv;
+ rv = request->GetImage(getter_AddRefs(container));
+ if (!container)
+ return NS_ERROR_FAILURE;
+ }
+
+ // get the file name from localized strings
+ nsCOMPtr<nsIStringBundleService>
+ bundleService(do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStringBundle> shellBundle;
+ rv = bundleService->CreateBundle(SHELLSERVICE_PROPERTIES,
+ getter_AddRefs(shellBundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // e.g. "Desktop Background.bmp"
+ nsString fileLeafName;
+ rv = shellBundle->GetStringFromName
+ (u"desktopBackgroundLeafNameWin",
+ getter_Copies(fileLeafName));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // get the profile root directory
+ nsCOMPtr<nsIFile> file;
+ rv = NS_GetSpecialDirectory(NS_APP_APPLICATION_REGISTRY_DIR,
+ getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // eventually, the path is "%APPDATA%\Mozilla\PaleMoon\Desktop Background.bmp"
+ rv = file->Append(fileLeafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString path;
+ rv = file->GetPath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // write the bitmap to a file in the profile directory
+ rv = WriteBitmap(file, container);
+
+ // if the file was written successfully, set it as the system wallpaper
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = regKey->Create(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ NS_LITERAL_STRING("Control Panel\\Desktop"),
+ nsIWindowsRegKey::ACCESS_SET_VALUE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString tile;
+ nsAutoString style;
+ switch (aPosition) {
+ case BACKGROUND_TILE:
+ style.Assign('0');
+ tile.Assign('1');
+ break;
+ case BACKGROUND_CENTER:
+ style.Assign('0');
+ tile.Assign('0');
+ break;
+ case BACKGROUND_STRETCH:
+ style.Assign('2');
+ tile.Assign('0');
+ break;
+ case BACKGROUND_FILL:
+ style.AssignLiteral("10");
+ tile.Assign('0');
+ break;
+ case BACKGROUND_FIT:
+ style.Assign('6');
+ tile.Assign('0');
+ break;
+ }
+
+ rv = regKey->WriteStringValue(NS_LITERAL_STRING("TileWallpaper"), tile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = regKey->WriteStringValue(NS_LITERAL_STRING("WallpaperStyle"), style);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = regKey->Close();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ::SystemParametersInfoW(SPI_SETDESKWALLPAPER, 0, (PVOID)path.get(),
+ SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsWindowsShellService::OpenApplication(int32_t aApplication)
+{
+ nsAutoString application;
+ switch (aApplication) {
+ case nsIShellService::APPLICATION_MAIL:
+ application.AssignLiteral("Mail");
+ break;
+ case nsIShellService::APPLICATION_NEWS:
+ application.AssignLiteral("News");
+ break;
+ }
+
+ // The Default Client section of the Windows Registry looks like this:
+ //
+ // Clients\aClient\
+ // e.g. aClient = "Mail"...
+ // \Mail\(default) = Client Subkey Name
+ // \Client Subkey Name
+ // \Client Subkey Name\shell\open\command\
+ // \Client Subkey Name\shell\open\command\(default) = path to exe
+ //
+
+ // Find the default application for this class.
+ HKEY theKey;
+ nsresult rv = OpenKeyForReading(HKEY_CLASSES_ROOT, application, &theKey);
+ if (NS_FAILED(rv))
+ return rv;
+
+ wchar_t buf[MAX_BUF];
+ DWORD type, len = sizeof buf;
+ DWORD res = ::RegQueryValueExW(theKey, EmptyString().get(), 0,
+ &type, (LPBYTE)&buf, &len);
+
+ if (REG_FAILED(res) || !*buf)
+ return NS_OK;
+
+ // Close the key we opened.
+ ::RegCloseKey(theKey);
+
+ // Find the "open" command
+ application.Append('\\');
+ application.Append(buf);
+ application.AppendLiteral("\\shell\\open\\command");
+
+ rv = OpenKeyForReading(HKEY_CLASSES_ROOT, application, &theKey);
+ if (NS_FAILED(rv))
+ return rv;
+
+ ::ZeroMemory(buf, sizeof(buf));
+ len = sizeof buf;
+ res = ::RegQueryValueExW(theKey, EmptyString().get(), 0,
+ &type, (LPBYTE)&buf, &len);
+ if (REG_FAILED(res) || !*buf)
+ return NS_ERROR_FAILURE;
+
+ // Close the key we opened.
+ ::RegCloseKey(theKey);
+
+ // Look for any embedded environment variables and substitute their
+ // values, as |::CreateProcessW| is unable to do this.
+ nsAutoString path(buf);
+ int32_t end = path.Length();
+ int32_t cursor = 0, temp = 0;
+ ::ZeroMemory(buf, sizeof(buf));
+ do {
+ cursor = path.FindChar('%', cursor);
+ if (cursor < 0)
+ break;
+
+ temp = path.FindChar('%', cursor + 1);
+ ++cursor;
+
+ ::ZeroMemory(&buf, sizeof(buf));
+
+ ::GetEnvironmentVariableW(nsAutoString(Substring(path, cursor, temp - cursor)).get(),
+ buf, sizeof(buf));
+
+ // "+ 2" is to subtract the extra characters used to delimit the environment
+ // variable ('%').
+ path.Replace((cursor - 1), temp - cursor + 2, nsDependentString(buf));
+
+ ++cursor;
+ }
+ while (cursor < end);
+
+ STARTUPINFOW si;
+ PROCESS_INFORMATION pi;
+
+ ::ZeroMemory(&si, sizeof(STARTUPINFOW));
+ ::ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
+
+ BOOL success = ::CreateProcessW(nullptr, (LPWSTR)path.get(), nullptr,
+ nullptr, FALSE, 0, nullptr, nullptr,
+ &si, &pi);
+ if (!success)
+ return NS_ERROR_FAILURE;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowsShellService::GetDesktopBackgroundColor(uint32_t* aColor)
+{
+ uint32_t color = ::GetSysColor(COLOR_DESKTOP);
+ *aColor = (GetRValue(color) << 16) | (GetGValue(color) << 8) | GetBValue(color);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsWindowsShellService::SetDesktopBackgroundColor(uint32_t aColor)
+{
+ int aParameters[2] = { COLOR_BACKGROUND, COLOR_DESKTOP };
+ BYTE r = (aColor >> 16);
+ BYTE g = (aColor << 16) >> 24;
+ BYTE b = (aColor << 24) >> 24;
+ COLORREF colors[2] = { RGB(r,g,b), RGB(r,g,b) };
+
+ ::SetSysColors(sizeof(aParameters) / sizeof(int), aParameters, colors);
+
+ nsresult rv;
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = regKey->Create(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ NS_LITERAL_STRING("Control Panel\\Colors"),
+ nsIWindowsRegKey::ACCESS_SET_VALUE);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ wchar_t rgb[12];
+ _snwprintf(rgb, 12, L"%u %u %u", r, g, b);
+
+ rv = regKey->WriteStringValue(NS_LITERAL_STRING("Background"),
+ nsDependentString(rgb));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return regKey->Close();
+}
+
+nsWindowsShellService::nsWindowsShellService()
+{
+}
+
+nsWindowsShellService::~nsWindowsShellService()
+{
+}
+
+NS_IMETHODIMP
+nsWindowsShellService::OpenApplicationWithURI(nsIFile* aApplication,
+ const nsACString& aURI)
+{
+ nsresult rv;
+ nsCOMPtr<nsIProcess> process =
+ do_CreateInstance("@mozilla.org/process/util;1", &rv);
+ if (NS_FAILED(rv))
+ return rv;
+
+ rv = process->Init(aApplication);
+ if (NS_FAILED(rv))
+ return rv;
+
+ const nsCString spec(aURI);
+ const char* specStr = spec.get();
+ return process->Run(false, &specStr, 1);
+}
+
+NS_IMETHODIMP
+nsWindowsShellService::GetDefaultFeedReader(nsIFile** _retval)
+{
+ *_retval = nullptr;
+
+ nsresult rv;
+ nsCOMPtr<nsIWindowsRegKey> regKey =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT,
+ NS_LITERAL_STRING("feed\\shell\\open\\command"),
+ nsIWindowsRegKey::ACCESS_READ);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString path;
+ rv = regKey->ReadStringValue(EmptyString(), path);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (path.IsEmpty())
+ return NS_ERROR_FAILURE;
+
+ if (path.First() == '"') {
+ // Everything inside the quotes
+ path = Substring(path, 1, path.FindChar('"', 1) - 1);
+ }
+ else {
+ // Everything up to the first space
+ path = Substring(path, 0, path.FindChar(' '));
+ }
+
+ nsCOMPtr<nsIFile> defaultReader =
+ do_CreateInstance("@mozilla.org/file/local;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = defaultReader->InitWithPath(path);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ rv = defaultReader->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists)
+ return NS_ERROR_FAILURE;
+
+ NS_ADDREF(*_retval = defaultReader);
+ return NS_OK;
+}
diff --git a/components/shell/nsWindowsShellService.h b/components/shell/nsWindowsShellService.h
new file mode 100644
index 0000000..06c6c3c
--- /dev/null
+++ b/components/shell/nsWindowsShellService.h
@@ -0,0 +1,37 @@
+/* -*- 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/. */
+
+#ifndef nswindowsshellservice_h____
+#define nswindowsshellservice_h____
+
+#include "nscore.h"
+#include "nsStringAPI.h"
+#include "nsIWindowsShellService.h"
+#include "nsITimer.h"
+
+#include <windows.h>
+#include <ole2.h>
+
+class nsWindowsShellService : public nsIWindowsShellService
+{
+ virtual ~nsWindowsShellService();
+
+public:
+ nsWindowsShellService();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISHELLSERVICE
+ NS_DECL_NSIWINDOWSSHELLSERVICE
+
+protected:
+ bool IsDefaultBrowserVista(bool aCheckAllTypes, bool* aIsDefaultBrowser);
+ nsresult LaunchControlPanelDefaultsSelectionUI();
+ nsresult LaunchControlPanelDefaultPrograms();
+ nsresult LaunchModernSettingsDialogDefaultApps();
+ nsresult InvokeHTTPOpenAsVerb();
+ nsresult LaunchHTTPHandlerPane();
+};
+
+#endif // nswindowsshellservice_h____
diff --git a/components/statusbar/Downloads.jsm b/components/statusbar/Downloads.jsm
new file mode 100644
index 0000000..091fdad
--- /dev/null
+++ b/components/statusbar/Downloads.jsm
@@ -0,0 +1,674 @@
+/* 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 EXPORTED_SYMBOLS = ["S4EDownloadService"];
+
+const CC = Components.classes;
+const CI = Components.interfaces;
+const CU = Components.utils;
+
+CU.import("resource://gre/modules/Services.jsm");
+CU.import("resource://gre/modules/PluralForm.jsm");
+CU.import("resource://gre/modules/DownloadUtils.jsm");
+CU.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+CU.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function S4EDownloadService(window, gBrowser, service, getters)
+{
+ this._window = window;
+ this._gBrowser = gBrowser;
+ this._service = service;
+ this._getters = getters;
+
+ this._handler = new JSTransferHandler(this._window, this);
+}
+
+S4EDownloadService.prototype =
+{
+ _window: null,
+ _gBrowser: null,
+ _service: null,
+ _getters: null,
+
+ _handler: null,
+ _listening: false,
+
+ _binding: false,
+ _customizing: false,
+
+ _lastTime: Infinity,
+
+ _dlActive: false,
+ _dlPaused: false,
+ _dlFinished: false,
+
+ _dlCountStr: null,
+ _dlTimeStr: null,
+
+ _dlProgressAvg: 0,
+ _dlProgressMax: 0,
+ _dlProgressMin: 0,
+ _dlProgressType: "active",
+
+ _dlNotifyTimer: 0,
+ _dlNotifyGlowTimer: 0,
+
+ init: function()
+ {
+ if(!this._getters.downloadButton)
+ {
+ this.uninit();
+ return;
+ }
+
+ if(this._listening)
+ {
+ return;
+ }
+
+ this._handler.start();
+ this._listening = true;
+
+ this._lastTime = Infinity;
+
+ this.updateBinding();
+ this.updateStatus();
+ },
+
+ uninit: function()
+ {
+ if(!this._listening)
+ {
+ return;
+ }
+
+ this._listening = false;
+ this._handler.stop();
+
+ this.releaseBinding();
+ },
+
+ destroy: function()
+ {
+ this.uninit();
+ this._handler.destroy();
+
+ ["_window", "_gBrowser", "_service", "_getters", "_handler"].forEach(function(prop)
+ {
+ delete this[prop];
+ }, this);
+ },
+
+ updateBinding: function()
+ {
+ if(!this._listening)
+ {
+ this.releaseBinding();
+ return;
+ }
+
+ switch(this._service.downloadButtonAction)
+ {
+ case 1: // Default
+ this.attachBinding();
+ break;
+ default:
+ this.releaseBinding();
+ break;
+ }
+ },
+
+ attachBinding: function()
+ {
+ if(this._binding)
+ {
+ return;
+ }
+
+ let db = this._window.DownloadsButton;
+
+ db._getAnchorS4EBackup = db.getAnchor;
+ db.getAnchor = this.getAnchor.bind(this);
+
+ db._releaseAnchorS4EBackup = db.releaseAnchor;
+ db.releaseAnchor = function() {};
+
+ this._binding = true;
+ },
+
+ releaseBinding: function()
+ {
+ if(!this._binding)
+ {
+ return;
+ }
+
+ let db = this._window.DownloadsButton;
+
+ db.getAnchor = db._getAnchorS4EBackup;
+ db.releaseAnchor = db._releaseAnchorS4EBackup;
+
+ this._binding = false;
+ },
+
+ customizing: function(val)
+ {
+ this._customizing = val;
+ },
+
+ updateStatus: function(lastFinished)
+ {
+ if(!this._getters.downloadButton)
+ {
+ this.uninit();
+ return;
+ }
+
+ let numActive = 0;
+ let numPaused = 0;
+ let activeTotalSize = 0;
+ let activeTransferred = 0;
+ let activeMaxProgress = -Infinity;
+ let activeMinProgress = Infinity;
+ let pausedTotalSize = 0;
+ let pausedTransferred = 0;
+ let pausedMaxProgress = -Infinity;
+ let pausedMinProgress = Infinity;
+ let maxTime = -Infinity;
+
+ let dls = ((this.isPrivateWindow) ? this._handler.activePrivateEntries() : this._handler.activeEntries());
+ for(let dl of dls)
+ {
+ if(dl.state == CI.nsIDownloadManager.DOWNLOAD_DOWNLOADING)
+ {
+ numActive++;
+ if(dl.size > 0)
+ {
+ if(dl.speed > 0)
+ {
+ maxTime = Math.max(maxTime, (dl.size - dl.transferred) / dl.speed);
+ }
+
+ activeTotalSize += dl.size;
+ activeTransferred += dl.transferred;
+
+ let currentProgress = ((dl.transferred * 100) / dl.size);
+ activeMaxProgress = Math.max(activeMaxProgress, currentProgress);
+ activeMinProgress = Math.min(activeMinProgress, currentProgress);
+ }
+ }
+ else if(dl.state == CI.nsIDownloadManager.DOWNLOAD_PAUSED)
+ {
+ numPaused++;
+ if(dl.size > 0)
+ {
+ pausedTotalSize += dl.size;
+ pausedTransferred += dl.transferred;
+
+ let currentProgress = ((dl.transferred * 100) / dl.size);
+ pausedMaxProgress = Math.max(pausedMaxProgress, currentProgress);
+ pausedMinProgress = Math.min(pausedMinProgress, currentProgress);
+ }
+ }
+ }
+
+ if((numActive + numPaused) == 0)
+ {
+ this._dlActive = false;
+ this._dlFinished = lastFinished;
+ this.updateButton();
+ this._lastTime = Infinity;
+ return;
+ }
+
+ let dlPaused = (numActive == 0);
+ let dlStatus = ((dlPaused) ? this._getters.strings.getString("pausedDownloads")
+ : this._getters.strings.getString("activeDownloads"));
+ let dlCount = ((dlPaused) ? numPaused : numActive);
+ let dlTotalSize = ((dlPaused) ? pausedTotalSize : activeTotalSize);
+ let dlTransferred = ((dlPaused) ? pausedTransferred : activeTransferred);
+ let dlMaxProgress = ((dlPaused) ? pausedMaxProgress : activeMaxProgress);
+ let dlMinProgress = ((dlPaused) ? pausedMinProgress : activeMinProgress);
+ let dlProgressType = ((dlPaused) ? "paused" : "active");
+
+ [this._dlTimeStr, this._lastTime] = DownloadUtils.getTimeLeft(maxTime, this._lastTime);
+ this._dlCountStr = PluralForm.get(dlCount, dlStatus).replace("#1", dlCount);
+ this._dlProgressAvg = ((dlTotalSize == 0) ? 100 : ((dlTransferred * 100) / dlTotalSize));
+ this._dlProgressMax = ((dlTotalSize == 0) ? 100 : dlMaxProgress);
+ this._dlProgressMin = ((dlTotalSize == 0) ? 100 : dlMinProgress);
+ this._dlProgressType = dlProgressType + ((dlTotalSize == 0) ? "-unknown" : "");
+ this._dlPaused = dlPaused;
+ this._dlActive = true;
+ this._dlFinished = false;
+
+ this.updateButton();
+ },
+
+ updateButton: function()
+ {
+ let download_button = this._getters.downloadButton;
+ let download_tooltip = this._getters.downloadButtonTooltip;
+ let download_progress = this._getters.downloadButtonProgress;
+ let download_label = this._getters.downloadButtonLabel;
+ if(!download_button)
+ {
+ return;
+ }
+
+ if(!this._dlActive)
+ {
+ download_button.collapsed = true;
+ download_label.value = download_tooltip.label = this._getters.strings.getString("noDownloads");
+
+ download_progress.collapsed = true;
+ download_progress.value = 0;
+
+ if(this._dlFinished && this._handler.hasPBAPI && !this.isUIShowing)
+ {
+ this.callAttention(download_button);
+ }
+ return;
+ }
+
+ switch(this._service.downloadProgress)
+ {
+ case 2:
+ download_progress.value = this._dlProgressMax;
+ break;
+ case 3:
+ download_progress.value = this._dlProgressMin;
+ break;
+ default:
+ download_progress.value = this._dlProgressAvg;
+ break;
+ }
+ download_progress.setAttribute("pmType", this._dlProgressType);
+ download_progress.collapsed = (this._service.downloadProgress == 0);
+
+ download_label.value = this.buildString(this._service.downloadLabel);
+ download_tooltip.label = this.buildString(this._service.downloadTooltip);
+
+ this.clearAttention(download_button);
+ download_button.collapsed = false;
+ },
+
+ callAttention: function(download_button)
+ {
+ if(this._dlNotifyGlowTimer != 0)
+ {
+ this._window.clearTimeout(this._dlNotifyGlowTimer);
+ this._dlNotifyGlowTimer = 0;
+ }
+
+ download_button.setAttribute("attention", "true");
+
+ if(this._service.downloadNotifyTimeout)
+ {
+ this._dlNotifyGlowTimer = this._window.setTimeout(function(self, button)
+ {
+ self._dlNotifyGlowTimer = 0;
+ button.removeAttribute("attention");
+ }, this._service.downloadNotifyTimeout, this, download_button);
+ }
+ },
+
+ clearAttention: function(download_button)
+ {
+ if(this._dlNotifyGlowTimer != 0)
+ {
+ this._window.clearTimeout(this._dlNotifyGlowTimer);
+ this._dlNotifyGlowTimer = 0;
+ }
+
+ download_button.removeAttribute("attention");
+ },
+
+ notify: function()
+ {
+ if(this._dlNotifyTimer == 0 && this._service.downloadNotifyAnimate)
+ {
+ let download_button_anchor = this._getters.downloadButtonAnchor;
+ let download_notify_anchor = this._getters.downloadNotifyAnchor;
+ if(download_button_anchor)
+ {
+ if(!download_notify_anchor.style.transform)
+ {
+ let bAnchorRect = download_button_anchor.getBoundingClientRect();
+ let nAnchorRect = download_notify_anchor.getBoundingClientRect();
+
+ let translateX = bAnchorRect.left - nAnchorRect.left;
+ translateX += .5 * (bAnchorRect.width - nAnchorRect.width);
+
+ let translateY = bAnchorRect.top - nAnchorRect.top;
+ translateY += .5 * (bAnchorRect.height - nAnchorRect.height);
+
+ download_notify_anchor.style.transform = "translate(" + translateX + "px, " + translateY + "px)";
+ }
+
+ download_notify_anchor.setAttribute("notification", "finish");
+ this._dlNotifyTimer = this._window.setTimeout(function(self, anchor)
+ {
+ self._dlNotifyTimer = 0;
+ anchor.removeAttribute("notification");
+ anchor.style.transform = "";
+ }, 1000, this, download_notify_anchor);
+ }
+ }
+ },
+
+ clearFinished: function()
+ {
+ this._dlFinished = false;
+ let download_button = this._getters.downloadButton;
+ if(download_button)
+ {
+ this.clearAttention(download_button);
+ }
+ },
+
+ getAnchor: function(aCallback)
+ {
+ if(this._customizing)
+ {
+ aCallback(null);
+ return;
+ }
+
+ aCallback(this._getters.downloadButtonAnchor);
+ },
+
+ openUI: function(aEvent)
+ {
+ this.clearFinished();
+
+ switch(this._service.downloadButtonAction)
+ {
+ case 1: // Firefox Default
+ this._handler.openUINative();
+ break;
+ case 2: // Show Library
+ this._window.PlacesCommandHook.showPlacesOrganizer("Downloads");
+ break;
+ case 3: // Show Tab
+ let found = this._gBrowser.browsers.some(function(browser, index)
+ {
+ if("about:downloads" == browser.currentURI.spec)
+ {
+ this._gBrowser.selectedTab = this._gBrowser.tabContainer.childNodes[index];
+ return true;
+ }
+ }, this);
+
+ if(!found)
+ {
+ this._window.openUILinkIn("about:downloads", "tab");
+ }
+ break;
+ case 4: // External Command
+ let command = this._service.downloadButtonActionCommand;
+ if(commend)
+ {
+ this._window.goDoCommand(command);
+ }
+ break;
+ default: // Nothing
+ break;
+ }
+
+ aEvent.stopPropagation();
+ },
+
+ get isPrivateWindow()
+ {
+ return this._handler.hasPBAPI && PrivateBrowsingUtils.isWindowPrivate(this._window);
+ },
+
+ get isUIShowing()
+ {
+ switch(this._service.downloadButtonAction)
+ {
+ case 1: // Firefox Default
+ return this._handler.isUIShowingNative;
+ case 2: // Show Library
+ var organizer = Services.wm.getMostRecentWindow("Places:Organizer");
+ if(organizer)
+ {
+ let selectedNode = organizer.PlacesOrganizer._places.selectedNode;
+ let downloadsItemId = organizer.PlacesUIUtils.leftPaneQueries["Downloads"];
+ return selectedNode && selectedNode.itemId === downloadsItemId;
+ }
+ return false;
+ case 3: // Show tab
+ let currentURI = this._gBrowser.currentURI;
+ return currentURI && currentURI.spec == "about:downloads";
+ default: // Nothing
+ return false;
+ }
+ },
+
+ buildString: function(mode)
+ {
+ switch(mode)
+ {
+ case 0:
+ return this._dlCountStr;
+ case 1:
+ return ((this._dlPaused) ? this._dlCountStr : this._dlTimeStr);
+ default:
+ let compStr = this._dlCountStr;
+ if(!this._dlPaused)
+ {
+ compStr += " (" + this._dlTimeStr + ")";
+ }
+ return compStr;
+ }
+ }
+};
+
+function JSTransferHandler(window, downloadService)
+{
+ this._window = window;
+
+ let api = CU.import("resource://gre/modules/Downloads.jsm", {}).Downloads;
+
+ this._activePublic = new JSTransferListener(downloadService, api.getList(api.PUBLIC), false);
+ this._activePrivate = new JSTransferListener(downloadService, api.getList(api.PRIVATE), true);
+}
+
+JSTransferHandler.prototype =
+{
+ _window: null,
+ _activePublic: null,
+ _activePrivate: null,
+
+ destroy: function()
+ {
+ this._activePublic.destroy();
+ this._activePrivate.destroy();
+
+ ["_window", "_activePublic", "_activePrivate"].forEach(function(prop)
+ {
+ delete this[prop];
+ }, this);
+ },
+
+ start: function()
+ {
+ this._activePublic.start();
+ this._activePrivate.start();
+ },
+
+ stop: function()
+ {
+ this._activePublic.stop();
+ this._activePrivate.stop();
+ },
+
+ get hasPBAPI()
+ {
+ return true;
+ },
+
+ openUINative: function()
+ {
+ this._window.DownloadsPanel.showPanel();
+ },
+
+ get isUIShowingNative()
+ {
+ return this._window.DownloadsPanel.isPanelShowing;
+ },
+
+ activeEntries: function()
+ {
+ return this._activePublic.downloads();
+ },
+
+ activePrivateEntries: function()
+ {
+ return this._activePrivate.downloads();
+ }
+};
+
+function JSTransferListener(downloadService, listPromise, isPrivate)
+{
+ this._downloadService = downloadService;
+ this._isPrivate = isPrivate;
+ this._downloads = new Map();
+
+ listPromise.then(this.initList.bind(this)).then(null, CU.reportError);
+}
+
+JSTransferListener.prototype =
+{
+ _downloadService: null,
+ _list: null,
+ _downloads: null,
+ _isPrivate: false,
+ _wantsStart: false,
+
+ initList: function(list)
+ {
+ this._list = list;
+ if(this._wantsStart) {
+ this.start();
+ }
+
+ this._list.getAll().then(this.initDownloads.bind(this)).then(null, CU.reportError);
+ },
+
+ initDownloads: function(downloads)
+ {
+ downloads.forEach(function(download)
+ {
+ this.onDownloadAdded(download);
+ }, this);
+ },
+
+ destroy: function()
+ {
+ this._downloads.clear();
+
+ ["_downloadService", "_list", "_downloads"].forEach(function(prop)
+ {
+ delete this[prop];
+ }, this);
+ },
+
+ start: function()
+ {
+ if(!this._list)
+ {
+ this._wantsStart = true;
+ return;
+ }
+
+ this._list.addView(this);
+ },
+
+ stop: function()
+ {
+ if(!this._list)
+ {
+ this._wantsStart = false;
+ return;
+ }
+
+ this._list.removeView(this);
+ },
+
+ downloads: function()
+ {
+ return this._downloads.values();
+ },
+
+ convertToState: function(dl)
+ {
+ if(dl.succeeded)
+ {
+ return CI.nsIDownloadManager.DOWNLOAD_FINISHED;
+ }
+ if(dl.error && dl.error.becauseBlockedByParentalControls)
+ {
+ return CI.nsIDownloadManager.DOWNLOAD_BLOCKED_PARENTAL;
+ }
+ if(dl.error)
+ {
+ return CI.nsIDownloadManager.DOWNLOAD_FAILED;
+ }
+ if(dl.canceled && dl.hasPartialData)
+ {
+ return CI.nsIDownloadManager.DOWNLOAD_PAUSED;
+ }
+ if(dl.canceled)
+ {
+ return CI.nsIDownloadManager.DOWNLOAD_CANCELED;
+ }
+ if(dl.stopped)
+ {
+ return CI.nsIDownloadManager.DOWNLOAD_NOTSTARTED;
+ }
+ return CI.nsIDownloadManager.DOWNLOAD_DOWNLOADING;
+ },
+
+ onDownloadAdded: function(aDownload)
+ {
+ let dl = this._downloads.get(aDownload);
+ if(!dl)
+ {
+ dl = {};
+ this._downloads.set(aDownload, dl);
+ }
+
+ dl.state = this.convertToState(aDownload);
+ dl.size = aDownload.totalBytes;
+ dl.speed = aDownload.speed;
+ dl.transferred = aDownload.currentBytes;
+ },
+
+ onDownloadChanged: function(aDownload)
+ {
+ this.onDownloadAdded(aDownload);
+
+ if(this._isPrivate != this._downloadService.isPrivateWindow)
+ {
+ return;
+ }
+
+ this._downloadService.updateStatus(aDownload.succeeded);
+
+ if(aDownload.succeeded)
+ {
+ this._downloadService.notify()
+ }
+ },
+
+ onDownloadRemoved: function(aDownload)
+ {
+ this._downloads.delete(aDownload);
+ }
+};
+
diff --git a/components/statusbar/Progress.jsm b/components/statusbar/Progress.jsm
new file mode 100644
index 0000000..69d55db
--- /dev/null
+++ b/components/statusbar/Progress.jsm
@@ -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/. */
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["S4EProgressService"];
+
+const CI = Components.interfaces;
+const CU = Components.utils;
+
+CU.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function S4EProgressService(gBrowser, service, getters, statusService) {
+ this._gBrowser = gBrowser;
+ this._service = service;
+ this._getters = getters;
+ this._statusService = statusService;
+
+ this._gBrowser.addProgressListener(this);
+}
+
+S4EProgressService.prototype =
+{
+ _gBrowser: null,
+ _service: null,
+ _getters: null,
+ _statusService: null,
+
+ _busyUI: false,
+
+ set value(val)
+ {
+ let toolbar_progress = this._getters.toolbarProgress;
+ if(toolbar_progress)
+ {
+ toolbar_progress.value = val;
+ }
+
+ let throbber_progress = this._getters.throbberProgress;
+ if(throbber_progress)
+ {
+ if(val)
+ {
+ throbber_progress.setAttribute("progress", val);
+ }
+ else
+ {
+ throbber_progress.removeAttribute("progress");
+ }
+ }
+ },
+
+ set collapsed(val)
+ {
+ let toolbar_progress = this._getters.toolbarProgress;
+ if(toolbar_progress)
+ {
+ toolbar_progress.collapsed = val;
+ }
+
+ let throbber_progress = this._getters.throbberProgress;
+ if(throbber_progress)
+ {
+ if(val)
+ {
+ throbber_progress.removeAttribute("busy");
+ }
+ else
+ {
+ throbber_progress.setAttribute("busy", true);
+ }
+ }
+ },
+
+ destroy: function()
+ {
+ this._gBrowser.removeProgressListener(this);
+
+ ["_gBrowser", "_service", "_getters", "_statusService"].forEach(function(prop)
+ {
+ delete this[prop];
+ }, this);
+ },
+
+ onStatusChange: function(aWebProgress, aRequest, aStatus, aMessage)
+ {
+ this._statusService.setNetworkStatus(aMessage, this._busyUI);
+ },
+
+ onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus)
+ {
+ let nsIWPL = CI.nsIWebProgressListener;
+
+ if(!this._busyUI
+ && aStateFlags & nsIWPL.STATE_START
+ && aStateFlags & nsIWPL.STATE_IS_NETWORK
+ && !(aStateFlags & nsIWPL.STATE_RESTORING))
+ {
+ this._busyUI = true;
+ this.value = 0;
+ this.collapsed = false;
+ }
+ else if(aStateFlags & nsIWPL.STATE_STOP)
+ {
+ if(aRequest)
+ {
+ let msg = "";
+ let location;
+ if(aRequest instanceof CI.nsIChannel || "URI" in aRequest)
+ {
+ location = aRequest.URI;
+ if(location.spec != "about:blank")
+ {
+ switch (aStatus)
+ {
+ case Components.results.NS_BINDING_ABORTED:
+ msg = this._getters.strings.getString("nv_stopped");
+ break;
+ case Components.results.NS_ERROR_NET_TIMEOUT:
+ msg = this._getters.strings.getString("nv_timeout");
+ break;
+ }
+ }
+ }
+
+ if(!msg && (!location || location.spec != "about:blank"))
+ {
+ msg = this._getters.strings.getString("nv_done");
+ }
+
+ this._statusService.setDefaultStatus(msg);
+ this._statusService.setNetworkStatus("", this._busyUI);
+ }
+
+ if(this._busyUI)
+ {
+ this._busyUI = false;
+ this.collapsed = true;
+ this.value = 0;
+ }
+ }
+ },
+
+ onUpdateCurrentBrowser: function(aStateFlags, aStatus, aMessage, aTotalProgress)
+ {
+ let nsIWPL = CI.nsIWebProgressListener;
+ let loadingDone = aStateFlags & nsIWPL.STATE_STOP;
+
+ this.onStateChange(
+ this._gBrowser.webProgress,
+ { URI: this._gBrowser.currentURI },
+ ((loadingDone ? nsIWPL.STATE_STOP : nsIWPL.STATE_START) | (aStateFlags & nsIWPL.STATE_IS_NETWORK)),
+ aStatus
+ );
+
+ if(!loadingDone)
+ {
+ this.onProgressChange(this._gBrowser.webProgress, null, 0, 0, aTotalProgress, 1);
+ this.onStatusChange(this._gBrowser.webProgress, null, 0, aMessage);
+ }
+ },
+
+ onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress)
+ {
+ if (aMaxTotalProgress > 0 && this._busyUI)
+ {
+ // This is highly optimized. Don't touch this code unless
+ // you are intimately familiar with the cost of setting
+ // attrs on XUL elements. -- hyatt
+ let percentage = (aCurTotalProgress * 100) / aMaxTotalProgress;
+ this.value = percentage;
+ }
+ },
+
+ onProgressChange64: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress)
+ {
+ return this.onProgressChange(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress);
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([ CI.nsIWebProgressListener, CI.nsIWebProgressListener2 ])
+};
+
diff --git a/components/statusbar/Status.jsm b/components/statusbar/Status.jsm
new file mode 100644
index 0000000..19e12dd
--- /dev/null
+++ b/components/statusbar/Status.jsm
@@ -0,0 +1,456 @@
+/* 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 EXPORTED_SYMBOLS = ["S4EStatusService"];
+
+const CU = Components.utils;
+
+CU.import("resource://gre/modules/Services.jsm");
+CU.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function S4EStatusService(window, service, getters)
+{
+ this._window = window;
+ this._service = service;
+ this._getters = getters;
+
+ this._overLinkService = new S4EOverlinkService(this._window, this._service, this);
+}
+
+S4EStatusService.prototype =
+{
+ _window: null,
+ _service: null,
+ _getters: null,
+ _overLinkService: null,
+
+ _overLink: { val: "", type: "" },
+ _network: { val: "", type: "" },
+ _networkXHR: { val: "", type: "" },
+ _status: { val: "", type: "" },
+ _jsStatus: { val: "", type: "" },
+ _defaultStatus: { val: "", type: "" },
+
+ _isFullScreen: false,
+ _isVideo: false,
+
+ _statusText: { val: "", type: "" },
+ _noUpdate: false,
+ _statusChromeTimeoutID: 0,
+ _statusContentTimeoutID: 0,
+
+ getCompositeStatusText: function()
+ {
+ return this._statusText.val;
+ },
+
+ getStatusText: function()
+ {
+ return this._status.val;
+ },
+
+ setNetworkStatus: function(status, busy)
+ {
+ if(busy)
+ {
+ this._network = { val: status, type: "network" };
+ this._networkXHR = { val: "", type: "network xhr" };
+ }
+ else
+ {
+ this._networkXHR = { val: status, type: "network xhr" };
+ }
+ this.updateStatusField();
+ },
+
+ setStatusText: function(status)
+ {
+ this._status = { val: status, type: "status chrome" };
+ this.updateStatusField();
+ },
+
+ setJSStatus: function(status)
+ {
+ this._jsStatus = { val: status, type: "status content" };
+ this.updateStatusField();
+ },
+
+ setJSDefaultStatus: function(status)
+ {
+ // This was removed from Firefox in Bug 862917
+ },
+
+ setDefaultStatus: function(status)
+ {
+ this._defaultStatus = { val: status, type: "status chrome default" };
+ this.updateStatusField();
+ },
+
+ setOverLink: function(link, aAnchor)
+ {
+ this._overLinkService.update(link, aAnchor);
+ },
+
+ setOverLinkInternal: function(link, aAnchor)
+ {
+ let status = this._service.status;
+ let statusLinkOver = this._service.statusLinkOver;
+
+ if(statusLinkOver)
+ {
+ link = link.replace(/[\u200e\u200f\u202a\u202b\u202c\u202d\u202e]/g, encodeURIComponent);
+ if(status == statusLinkOver)
+ {
+ this._overLink = { val: link, type: "overLink", anchor: aAnchor };
+ this.updateStatusField();
+ }
+ else
+ {
+ this.setStatusField(statusLinkOver, { val: link, type: "overLink", anchor: aAnchor }, true);
+ }
+ }
+ },
+
+ setNoUpdate: function(nu)
+ {
+ this._noUpdate = nu;
+ },
+
+ buildBinding: function() {
+ let XULBWPropHandler = function(prop, oldval, newval) {
+ CU.reportError("Attempt to modify XULBrowserWindow." + prop);
+ return oldval;
+ };
+
+ ["updateStatusField", "onStatusChange"].forEach(function(prop)
+ {
+ this._window.XULBrowserWindow.unwatch(prop);
+ this._window.XULBrowserWindow[prop] = function() {};
+ this._window.XULBrowserWindow.watch(prop, XULBWPropHandler);
+ }, this);
+
+ ["getCompositeStatusText", "getStatusText", "setStatusText", "setJSStatus",
+ "setJSDefaultStatus", "setDefaultStatus", "setOverLink"].forEach(function(prop)
+ {
+ this._window.XULBrowserWindow.unwatch(prop);
+ this._window.XULBrowserWindow[prop] = this[prop].bind(this);
+ this._window.XULBrowserWindow.watch(prop, XULBWPropHandler);
+ }, this);
+
+ let XULBWHandler = function(prop, oldval, newval) {
+ if(!newval)
+ {
+ return newval;
+ }
+ CU.reportError("XULBrowserWindow changed. Updating S4E bindings.");
+ this._window.setTimeout(function(self)
+ {
+ self.buildBinding();
+ }, 0, this);
+ return newval;
+ };
+
+ this._window.watch("XULBrowserWindow", XULBWHandler);
+ },
+
+ destroy: function()
+ {
+ // No need to unbind from the XULBrowserWindow, it's already null at this point
+
+ this.clearTimer("_statusChromeTimeoutID");
+ this.clearTimer("_statusContentTimeoutID");
+
+ this._overLinkService.destroy();
+
+ ["_overLink", "_network", "_networkXHR", "_status", "_jsStatus", "_defaultStatus",
+ "_statusText", "_window", "_service", "_getters", "_overLinkService"].forEach(function(prop)
+ {
+ delete this[prop];
+ }, this);
+ },
+
+ buildTextOrder: function()
+ {
+ this.__defineGetter__("_textOrder", function()
+ {
+ let textOrder = ["_overLink"];
+ if(this._service.statusNetwork)
+ {
+ textOrder.push("_network");
+ if(this._service.statusNetworkXHR)
+ {
+ textOrder.push("_networkXHR");
+ }
+ }
+ textOrder.push("_status", "_jsStatus");
+ if(this._service.statusDefault)
+ {
+ textOrder.push("_defaultStatus");
+ }
+
+ delete this._textOrder;
+ return this._textOrder = textOrder;
+ });
+ },
+
+ updateStatusField: function(force)
+ {
+ let text = { val: "", type: "" };
+ for(let i = 0; !text.val && i < this._textOrder.length; i++)
+ {
+ text = this[this._textOrder[i]];
+ }
+
+ if(this._statusText.val != text.val || force)
+ {
+ if(this._noUpdate)
+ {
+ return;
+ }
+
+ this._statusText = text;
+
+ this.setStatusField(this._service.status, text, false);
+
+ if(text.val && this._service.statusTimeout)
+ {
+ this.setTimer(text.type);
+ }
+ }
+ },
+
+ setFullScreenState: function(isFullScreen, isVideo)
+ {
+ this._isFullScreen = isFullScreen;
+ this._isVideo = isFullScreen && isVideo;
+
+ this.clearStatusField();
+ this.updateStatusField(true);
+ },
+
+ setTimer: function(type)
+ {
+ let typeArgs = type.split(" ", 3);
+
+ if(typeArgs.length < 2 || typeArgs[0] != "status")
+ {
+ return;
+ }
+
+ if(typeArgs[1] == "chrome")
+ {
+ this.clearTimer("_statusChromeTimeoutID");
+ this._statusChromeTimeoutID = this._window.setTimeout(function(self, isDefault)
+ {
+ self._statusChromeTimeoutID = 0;
+ if(isDefault)
+ {
+ self.setDefaultStatus("");
+ }
+ else
+ {
+ self.setStatusText("");
+ }
+ }, this._service.statusTimeout, this, (typeArgs.length == 3 && typeArgs[2] == "default"));
+ }
+ else
+ {
+ this.clearTimer("_statusContentTimeoutID");
+ this._statusContentTimeoutID = this._window.setTimeout(function(self)
+ {
+ self._statusContentTimeoutID = 0;
+ self.setJSStatus("");
+ }, this._service.statusTimeout, this);
+ }
+ },
+
+ clearTimer: function(timerName)
+ {
+ if(this[timerName] != 0)
+ {
+ this._window.clearTimeout(this[timerName]);
+ this[timerName] = 0;
+ }
+ },
+
+ clearStatusField: function()
+ {
+ this._getters.statusOverlay.value = "";
+
+ let status_label = this._getters.statusWidgetLabel;
+ if(status_label)
+ {
+ status_label.value = "";
+ }
+
+ },
+
+ setStatusField: function(location, text, allowTooltip)
+ {
+ if(!location)
+ {
+ return;
+ }
+
+ let label = null;
+
+ if(this._isFullScreen)
+ {
+ switch(location)
+ {
+ case 1: // Toolbar
+ location = 3
+ break;
+ case 2: // URL bar
+ if(Services.prefs.getBoolPref("browser.fullscreen.autohide"))
+ {
+ location = 3
+ }
+ break;
+ }
+ }
+
+ switch(location)
+ {
+ case 1: // Toolbar
+ label = this._getters.statusWidgetLabel;
+ break;
+ case 2: // URL Bar
+ break;
+ case 3: // Popup
+ default:
+ if(this._isVideo)
+ {
+ return;
+ }
+ label = this._getters.statusOverlay;
+ break;
+ }
+
+ if(label)
+ {
+ label.setAttribute("previoustype", label.getAttribute("type"));
+ label.setAttribute("type", text.type);
+ label.value = text.val;
+ label.setAttribute("crop", text.type == "overLink" ? "center" : "end");
+ }
+ }
+};
+
+function S4EOverlinkService(window, service, statusService) {
+ this._window = window;
+ this._service = service;
+ this._statusService = statusService;
+}
+
+S4EOverlinkService.prototype =
+{
+ _window: null,
+ _service: null,
+ _statusService: null,
+
+ _timer: 0,
+ _currentLink: { link: "", anchor: null },
+ _pendingLink: { link: "", anchor: null },
+ _listening: false,
+
+ update: function(aLink, aAnchor)
+ {
+ this.clearTimer();
+ this.stopListen();
+ this._pendingLink = { link: aLink, anchor: aAnchor };
+
+ if(!aLink)
+ {
+ if(this._window.XULBrowserWindow.hideOverLinkImmediately || !this._service.statusLinkOverDelayHide)
+ {
+ this._show();
+ }
+ else
+ {
+ this._showDelayed();
+ }
+ }
+ else if(this._currentLink.link || !this._service.statusLinkOverDelayShow)
+ {
+ this._show();
+ }
+ else
+ {
+ this._showDelayed();
+ this.startListen();
+ }
+ },
+
+ destroy: function()
+ {
+ this.clearTimer();
+ this.stopListen();
+
+ ["_currentLink", "_pendingLink", "_statusService", "_window"].forEach(function(prop)
+ {
+ delete this[prop];
+ }, this);
+ },
+
+ startListen: function()
+ {
+ if(!this._listening)
+ {
+ this._window.addEventListener("mousemove", this, true);
+ this._listening = true;
+ }
+ },
+
+ stopListen: function()
+ {
+ if(this._listening)
+ {
+ this._window.removeEventListener("mousemove", this, true);
+ this._listening = false;
+ }
+ },
+
+ clearTimer: function()
+ {
+ if(this._timer != 0)
+ {
+ this._window.clearTimeout(this._timer);
+ this._timer = 0;
+ }
+ },
+
+ handleEvent: function(event)
+ {
+ switch(event.type)
+ {
+ case "mousemove":
+ this.clearTimer();
+ this._showDelayed();
+ }
+ },
+
+ _showDelayed: function()
+ {
+ let delay = ((this._pendingLink.link)
+ ? this._service.statusLinkOverDelayShow
+ : this._service.statusLinkOverDelayHide);
+
+ this._timer = this._window.setTimeout(function(self)
+ {
+ self._timer = 0;
+ self._show();
+ self.stopListen();
+ }, delay, this);
+ },
+
+ _show: function()
+ {
+ this._currentLink = this._pendingLink;
+ this._statusService.setOverLinkInternal(this._currentLink.link, this._currentLink.anchor);
+ }
+};
+
diff --git a/components/statusbar/Status4Evar.jsm b/components/statusbar/Status4Evar.jsm
new file mode 100644
index 0000000..6400f2e
--- /dev/null
+++ b/components/statusbar/Status4Evar.jsm
@@ -0,0 +1,312 @@
+/* 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 EXPORTED_SYMBOLS = ["Status4Evar"];
+
+const CC = Components.classes;
+const CI = Components.interfaces;
+const CU = Components.utils;
+
+const s4e_service = CC["@caligonstudios.com/status4evar;1"].getService(CI.nsIStatus4Evar);
+
+CU.import("resource://gre/modules/Services.jsm");
+CU.import("resource://gre/modules/XPCOMUtils.jsm");
+CU.import("resource://gre/modules/AddonManager.jsm");
+
+CU.import("resource:///modules/statusbar/Status.jsm");
+CU.import("resource:///modules/statusbar/Progress.jsm");
+CU.import("resource:///modules/statusbar/Downloads.jsm");
+CU.import("resource:///modules/statusbar/Toolbars.jsm");
+
+function Status4Evar(window, gBrowser, toolbox)
+{
+ this._window = window;
+ this._toolbox = toolbox;
+
+ this.getters = new S4EWindowGetters(this._window);
+ this.toolbars = new S4EToolbars(this._window, gBrowser, this._toolbox, s4e_service, this.getters);
+ this.statusService = new S4EStatusService(this._window, s4e_service, this.getters);
+ this.progressMeter = new S4EProgressService(gBrowser, s4e_service, this.getters, this.statusService);
+ this.downloadStatus = new S4EDownloadService(this._window, gBrowser, s4e_service, this.getters);
+ this.sizeModeService = new SizeModeService(this._window, gBrowser, this);
+
+ this._window.addEventListener("unload", this, false);
+}
+
+Status4Evar.prototype =
+{
+ _window: null,
+ _toolbox: null,
+
+ getters: null,
+ toolbars: null,
+ statusService: null,
+ progressMeter: null,
+ downloadStatus: null,
+ sizeModeService: null,
+
+ setup: function()
+ {
+ this._toolbox.addEventListener("beforecustomization", this, false);
+ this._toolbox.addEventListener("aftercustomization", this, false);
+
+ this.toolbars.setup();
+ this.updateWindow();
+
+ // OMFG HAX! If a page is already loading, fake a network start event
+ if(this._window.XULBrowserWindow._busyUI)
+ {
+ let nsIWPL = CI.nsIWebProgressListener;
+ this.progressMeter.onStateChange(0, null, nsIWPL.STATE_START | nsIWPL.STATE_IS_NETWORK, 0);
+ }
+ },
+
+ destroy: function()
+ {
+ this._window.removeEventListener("unload", this, false);
+ this._toolbox.removeEventListener("aftercustomization", this, false);
+ this._toolbox.removeEventListener("beforecustomization", this, false);
+
+ this.getters.destroy();
+ this.statusService.destroy();
+ this.downloadStatus.destroy();
+ this.progressMeter.destroy();
+ this.toolbars.destroy();
+ this.sizeModeService.destroy();
+
+ ["_window", "_toolbox", "getters", "statusService", "downloadStatus",
+ "progressMeter", "toolbars", "sizeModeService"].forEach(function(prop)
+ {
+ delete this[prop];
+ }, this);
+ },
+
+ handleEvent: function(aEvent)
+ {
+ switch(aEvent.type)
+ {
+ case "unload":
+ this.destroy();
+ break;
+ case "beforecustomization":
+ this.beforeCustomization();
+ break;
+ case "aftercustomization":
+ this.updateWindow();
+ break;
+ }
+ },
+
+ beforeCustomization: function()
+ {
+ this.toolbars.updateSplitters(false);
+ this.toolbars.updateWindowGripper(false);
+
+ this.statusService.setNoUpdate(true);
+ let status_label = this.getters.statusWidgetLabel;
+ if(status_label)
+ {
+ status_label.value = this.getters.strings.getString("statusText");
+ }
+
+ this.downloadStatus.customizing(true);
+ },
+
+ updateWindow: function()
+ {
+ this.statusService.setNoUpdate(false);
+ this.getters.resetGetters();
+ this.statusService.buildTextOrder();
+ this.statusService.buildBinding();
+ this.downloadStatus.init();
+ this.downloadStatus.customizing(false);
+ this.toolbars.updateSplitters(true);
+
+ s4e_service.updateWindow(this._window);
+ // This also handles the following:
+ // * buildTextOrder()
+ // * updateStatusField(true)
+ // * updateWindowGripper(true)
+ },
+
+ launchOptions: function(currentWindow)
+ {
+ let optionsURL = "chrome://browser/content/statusbar/prefs.xul";
+ let windows = Services.wm.getEnumerator(null);
+ while (windows.hasMoreElements())
+ {
+ let win = windows.getNext();
+ if (win.document.documentURI == optionsURL)
+ {
+ win.focus();
+ return;
+ }
+ }
+
+ let features = "chrome,titlebar,toolbar,centerscreen";
+ try
+ {
+ let instantApply = Services.prefs.getBoolPref("browser.preferences.instantApply");
+ features += instantApply ? ",dialog=no" : ",modal";
+ }
+ catch(e)
+ {
+ features += ",modal";
+ }
+ currentWindow.openDialog(optionsURL, "", features);
+ }
+
+};
+
+function S4EWindowGetters(window)
+{
+ this._window = window;
+}
+
+S4EWindowGetters.prototype =
+{
+ _window: null,
+ _getterMap:
+ [
+ ["addonbar", "addon-bar"],
+ ["addonbarCloseButton", "addonbar-closebutton"],
+ ["browserBottomBox", "browser-bottombox"],
+ ["downloadButton", "status4evar-download-button"],
+ ["downloadButtonTooltip", "status4evar-download-tooltip"],
+ ["downloadButtonProgress", "status4evar-download-progress-bar"],
+ ["downloadButtonLabel", "status4evar-download-label"],
+ ["downloadButtonAnchor", "status4evar-download-anchor"],
+ ["downloadNotifyAnchor", "status4evar-download-notification-anchor"],
+ ["statusBar", "status4evar-status-bar"],
+ ["statusWidget", "status4evar-status-widget"],
+ ["statusWidgetLabel", "status4evar-status-text"],
+ ["strings", "bundle_status4evar"],
+ ["throbberProgress", "status4evar-throbber-widget"],
+ ["toolbarProgress", "status4evar-progress-bar"]
+ ],
+
+ resetGetters: function()
+ {
+ let document = this._window.document;
+
+ this._getterMap.forEach(function(getter)
+ {
+ let [prop, id] = getter;
+ delete this[prop];
+ this.__defineGetter__(prop, function()
+ {
+ delete this[prop];
+ return this[prop] = document.getElementById(id);
+ });
+ }, this);
+
+ delete this.statusOverlay;
+ this.__defineGetter__("statusOverlay", function()
+ {
+ let so = this._window.XULBrowserWindow.statusTextField;
+ if(!so)
+ {
+ return null;
+ }
+
+ delete this.statusOverlay;
+ return this.statusOverlay = so;
+ });
+ },
+
+ destroy: function()
+ {
+ this._getterMap.forEach(function(getter)
+ {
+ let [prop, id] = getter;
+ delete this[prop];
+ }, this);
+
+ ["statusOverlay", "statusOverlay", "_window"].forEach(function(prop)
+ {
+ delete this[prop];
+ }, this);
+ }
+};
+
+function SizeModeService(window, gBrowser, s4e)
+{
+ this._window = window;
+ this._gBrowser = gBrowser;
+ this._s4e = s4e;
+ this._mm = this._window.messageManager;
+
+ this.lastFullScreen = this._window.fullScreen;
+ this.lastwindowState = this._window.windowState;
+
+ if(s4e_service.advancedStatusDetectFullScreen)
+ {
+ this._mm.addMessageListener("status4evar@caligonstudios.com:video-detect-answer", this)
+ this._mm.loadFrameScript("resource:///modules/statusbar/content-thunk.js", true);
+ }
+
+ this._window.addEventListener("sizemodechange", this, false);
+}
+
+SizeModeService.prototype =
+{
+ _window: null,
+ _gBrowser: null,
+ _s4e: null,
+ _mm: null,
+
+ lastFullScreen: null,
+ lastwindowState: null,
+
+ destroy: function()
+ {
+ this._window.removeEventListener("sizemodechange", this, false);
+
+ if(s4e_service.advancedStatusDetectFullScreen)
+ {
+ this._mm.removeDelayedFrameScript("resource:///modules/statusbar/content-thunk.js");
+ this._mm.removeMessageListener("status4evar@caligonstudios.com:video-detect-answer", this);
+ }
+
+ ["_window", "_gBrowser", "_s4e", "_mm"].forEach(function(prop)
+ {
+ delete this[prop];
+ }, this);
+ },
+
+ handleEvent: function(e)
+ {
+ if(this._window.fullScreen != this.lastFullScreen && s4e_service.advancedStatusDetectFullScreen)
+ {
+ this.lastFullScreen = this._window.fullScreen;
+
+ if(this.lastFullScreen && s4e_service.advancedStatusDetectVideo)
+ {
+ this._gBrowser.selectedBrowser.messageManager.sendAsyncMessage("status4evar@caligonstudios.com:video-detect");
+ }
+ else
+ {
+ this._s4e.statusService.setFullScreenState(this.lastFullScreen, false);
+ }
+ }
+
+ if(this._window.windowState != this.lastwindowState)
+ {
+ this.lastwindowState = this._window.windowState;
+ this._s4e.toolbars.updateWindowGripper(true);
+ }
+ },
+
+ receiveMessage: function(message)
+ {
+ if(message.name == "status4evar@caligonstudios.com:video-detect-answer")
+ {
+ this._s4e.statusService.setFullScreenState(this.lastFullScreen, message.data.isVideo);
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([ CI.nsIDOMEventListener, CI.nsIMessageListener ])
+};
diff --git a/components/statusbar/Toolbars.jsm b/components/statusbar/Toolbars.jsm
new file mode 100644
index 0000000..321efd0
--- /dev/null
+++ b/components/statusbar/Toolbars.jsm
@@ -0,0 +1,221 @@
+/* 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 EXPORTED_SYMBOLS = ["S4EToolbars"];
+
+const CI = Components.interfaces;
+const CU = Components.utils;
+
+CU.import("resource://gre/modules/Services.jsm");
+
+function S4EToolbars(window, gBrowser, toolbox, service, getters)
+{
+ this._window = window;
+ this._toolbox = toolbox;
+ this._service = service;
+ this._getters = getters;
+ this._handler = new ClassicS4EToolbars(this._window, this._toolbox);
+}
+
+S4EToolbars.prototype =
+{
+ _window: null,
+ _toolbox: null,
+ _service: null,
+ _getters: null,
+
+ _handler: null,
+
+ setup: function()
+ {
+ this.updateSplitters(false);
+ this.updateWindowGripper(false);
+ this._handler.setup(this._service.firstRun);
+ },
+
+ destroy: function()
+ {
+ this._handler.destroy();
+
+ ["_window", "_toolbox", "_service", "_getters", "_handler"].forEach(function(prop)
+ {
+ delete this[prop];
+ }, this);
+ },
+
+ updateSplitters: function(action)
+ {
+ let document = this._window.document;
+
+ let splitter_before = document.getElementById("status4evar-status-splitter-before");
+ if(splitter_before)
+ {
+ splitter_before.parentNode.removeChild(splitter_before);
+ }
+
+ let splitter_after = document.getElementById("status4evar-status-splitter-after");
+ if(splitter_after)
+ {
+ splitter_after.parentNode.removeChild(splitter_after);
+ }
+
+ let status = this._getters.statusWidget;
+ if(!action || !status)
+ {
+ return;
+ }
+
+ let urlbar = document.getElementById("urlbar-container");
+ let stop = document.getElementById("stop-button");
+ let fullscreenflex = document.getElementById("fullscreenflex");
+
+ let nextSibling = status.nextSibling;
+ let previousSibling = status.previousSibling;
+
+ function getSplitter(splitter, suffix)
+ {
+ if(!splitter)
+ {
+ splitter = document.createElement("splitter");
+ splitter.id = "status4evar-status-splitter-" + suffix;
+ splitter.setAttribute("resizebefore", "flex");
+ splitter.setAttribute("resizeafter", "flex");
+ splitter.className = "chromeclass-toolbar-additional status4evar-status-splitter";
+ }
+ return splitter;
+ }
+
+ if((previousSibling && previousSibling.flex > 0)
+ || (urlbar && stop && urlbar.getAttribute("combined") && stop == previousSibling))
+ {
+ status.parentNode.insertBefore(getSplitter(splitter_before, "before"), status);
+ }
+
+ if(nextSibling && nextSibling.flex > 0 && nextSibling != fullscreenflex)
+ {
+ status.parentNode.insertBefore(getSplitter(splitter_after, "after"), nextSibling);
+ }
+ },
+
+ updateWindowGripper: function(action)
+ {
+ let document = this._window.document;
+
+ let gripper = document.getElementById("status4evar-window-gripper");
+ let toolbar = this._getters.statusBar || this._getters.addonbar;
+
+ if(!action || !toolbar || !this._service.addonbarWindowGripper
+ || this._window.windowState != CI.nsIDOMChromeWindow.STATE_NORMAL || toolbar.toolbox.customizing)
+ {
+ if(gripper)
+ {
+ gripper.parentNode.removeChild(gripper);
+ }
+ return;
+ }
+
+ gripper = this._handler.buildGripper(toolbar, gripper, "status4evar-window-gripper");
+
+ toolbar.appendChild(gripper);
+ }
+};
+
+function ClassicS4EToolbars(window, toolbox)
+{
+ this._window = window;
+ this._toolbox = toolbox;
+}
+
+ClassicS4EToolbars.prototype =
+{
+ _window: null,
+ _toolbox: null,
+
+ setup: function(firstRun)
+ {
+ let document = this._window.document;
+
+ let addon_bar = document.getElementById("addon-bar");
+ if(addon_bar)
+ {
+ let baseSet = "addonbar-closebutton"
+ + ",status4evar-status-widget"
+ + ",status4evar-progress-widget";
+
+ // Update the defaultSet
+ let defaultSet = baseSet;
+ let defaultSetIgnore = ["addonbar-closebutton", "spring", "status-bar"];
+ addon_bar.getAttribute("defaultset").split(",").forEach(function(item)
+ {
+ if(defaultSetIgnore.indexOf(item) == -1)
+ {
+ defaultSet += "," + item;
+ }
+ });
+ defaultSet += ",status-bar"
+ addon_bar.setAttribute("defaultset", defaultSet);
+
+ // Update the currentSet
+ if(firstRun)
+ {
+ let isCustomizableToolbar = function(aElt)
+ {
+ return aElt.localName == "toolbar" && aElt.getAttribute("customizable") == "true";
+ }
+
+ let isCustomizedAlready = false;
+ let toolbars = Array.filter(this._toolbox.childNodes, isCustomizableToolbar).concat(
+ Array.filter(this._toolbox.externalToolbars, isCustomizableToolbar));
+ toolbars.forEach(function(toolbar)
+ {
+ if(toolbar.currentSet.indexOf("status4evar") > -1)
+ {
+ isCustomizedAlready = true;
+ }
+ });
+
+ if(!isCustomizedAlready)
+ {
+ let currentSet = baseSet;
+ let currentSetIgnore = ["addonbar-closebutton", "spring"];
+ addon_bar.currentSet.split(",").forEach(function(item)
+ {
+ if(currentSetIgnore.indexOf(item) == -1)
+ {
+ currentSet += "," + item;
+ }
+ });
+ addon_bar.currentSet = currentSet;
+ addon_bar.setAttribute("currentset", currentSet);
+ document.persist(addon_bar.id, "currentset");
+ this._window.setToolbarVisibility(addon_bar, true);
+ }
+ }
+ }
+ },
+
+ destroy: function()
+ {
+ ["_window", "_toolbox"].forEach(function(prop)
+ {
+ delete this[prop];
+ }, this);
+ },
+
+ buildGripper: function(toolbar, gripper, id)
+ {
+ if(!gripper)
+ {
+ let document = this._window.document;
+
+ gripper = document.createElement("resizer");
+ gripper.id = id;
+ gripper.dir = "bottomend";
+ }
+
+ return gripper;
+ }
+};
diff --git a/components/statusbar/content-thunk.js b/components/statusbar/content-thunk.js
new file mode 100644
index 0000000..fe1fbab
--- /dev/null
+++ b/components/statusbar/content-thunk.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 handleVideoDetect(message)
+{
+ let isVideo = false;
+
+ let fsEl = content.document.mozFullScreenElement;
+ if(fsEl)
+ {
+ isVideo = (
+ fsEl.nodeName == "VIDEO"
+ || (fsEl.nodeName == "IFRAME" && fsEl.contentDocument && fsEl.contentDocument.getElementsByTagName("VIDEO").length > 0)
+ || fsEl.getElementsByTagName("VIDEO").length > 0
+ );
+ }
+
+ sendAsyncMessage("status4evar@caligonstudios.com:video-detect-answer", {isVideo: isVideo});
+}
+
+addMessageListener("status4evar@caligonstudios.com:video-detect", handleVideoDetect);
+
diff --git a/components/statusbar/content/overlay.css b/components/statusbar/content/overlay.css
new file mode 100644
index 0000000..fd34521
--- /dev/null
+++ b/components/statusbar/content/overlay.css
@@ -0,0 +1,14 @@
+/* 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");
+
+/*
+ * Status Popup
+ */
+
+statuspanel {
+ -moz-binding: url("chrome://browser/content/statusbar/tabbrowser.xml#statuspanel");
+}
+
diff --git a/components/statusbar/content/overlay.js b/components/statusbar/content/overlay.js
new file mode 100644
index 0000000..b868aaf
--- /dev/null
+++ b/components/statusbar/content/overlay.js
@@ -0,0 +1,16 @@
+/* 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/. */
+
+if(!caligon) var caligon = {};
+
+window.addEventListener("load", function buildS4E()
+{
+ window.removeEventListener("load", buildS4E, false);
+
+ Components.utils.import("resource:///modules/statusbar/Status4Evar.jsm");
+
+ caligon.status4evar = new Status4Evar(window, gBrowser, gNavToolbox);
+ caligon.status4evar.setup();
+}, false);
+
diff --git a/components/statusbar/content/overlay.xul b/components/statusbar/content/overlay.xul
new file mode 100644
index 0000000..b9934ee
--- /dev/null
+++ b/components/statusbar/content/overlay.xul
@@ -0,0 +1,82 @@
+<?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 overlay SYSTEM "chrome://browser/locale/statusbar/statusbar-overlay.dtd">
+
+<?xml-stylesheet href="chrome://browser/content/statusbar/overlay.css" type="text/css" ?>
+<?xml-stylesheet href="chrome://browser/skin/statusbar/overlay.css" type="text/css" ?>
+<?xml-stylesheet href="chrome://browser/skin/statusbar/dynamic.css" type="text/css" ?>
+
+<overlay id="status4evar-overlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_status4evar" src="chrome://browser/locale/statusbar/overlay.properties" />
+ </stringbundleset>
+
+ <script type="application/javascript" src="chrome://browser/content/statusbar/overlay.js" />
+
+ <commandset>
+ <command id="S4E:Options" oncommand="caligon.status4evar.launchOptions(window);"/>
+ </commandset>
+
+ <popupset id="mainPopupSet">
+ <hbox id="status4evar-download-notification-container" mousethrough="always">
+ <vbox id="status4evar-download-notification-anchor">
+ <vbox id="status4evar-download-notification-icon" />
+ </vbox>
+ </hbox>
+ </popupset>
+
+ <menupopup id="menu_ToolsPopup">
+ <menuitem id="statusbar-options-fx" command="S4E:Options"
+ label="&status4evar.menu.options.label;"/>
+ </menupopup>
+
+ <menupopup id="appmenu_customizeMenu">
+ <menuitem id="statusbar-options-app" command="S4E:Options"
+ label="&status4evar.menu.options.label;"/>
+ </menupopup>
+
+ <toolbarpalette id="BrowserToolbarPalette">
+ <toolbaritem id="status4evar-status-widget"
+ title="&status4evar.status.widget.title;"
+ removable="true" flex="1" persist="width" width="100">
+ <label id="status4evar-status-text" flex="1" crop="end" value="&status4evar.status.widget.title;" />
+ </toolbaritem>
+
+ <toolbarbutton id="status4evar-download-button"
+ title="&status4evar.download.widget.title;"
+ class="toolbarbutton-1 chromeclass-toolbar-additional"
+ removable="true" collapsed="true" tooltip="_child"
+ oncommand="caligon.status4evar.downloadStatus.openUI(event)">
+ <stack id="status4evar-download-anchor" class="toolbarbutton-icon">
+ <vbox id="status4evar-download-icon" />
+ <vbox pack="end">
+ <progressmeter id="status4evar-download-progress-bar" mode="normal" value="0" collapsed="true" min="0" max="100" />
+ </vbox>
+ </stack>
+ <tooltip id="status4evar-download-tooltip" />
+ <label id="status4evar-download-label" value="&status4evar.download.widget.title;" class="toolbarbutton-text" crop="right" flex="1" />
+ </toolbarbutton>
+
+ <toolbaritem id="status4evar-progress-widget"
+ title="&status4evar.progress.widget.title;"
+ removable="true">
+ <progressmeter id="status4evar-progress-bar" class="progressmeter-statusbar"
+ mode="normal" value="0" collapsed="true" min="0" max="100" />
+ </toolbaritem>
+
+ <toolbarbutton id="status4evar-options-button"
+ title="&status4evar.options.widget.title;"
+ class="toolbarbutton-1 chromeclass-toolbar-additional"
+ label="&status4evar.options.widget.label;"
+ removable="true" command="S4E:Options" tooltiptext="&status4evar.options.widget.title;" />
+ </toolbarpalette>
+
+ <statusbar id="status-bar" ordinal="1" />
+</overlay>
+
diff --git a/components/statusbar/content/prefs.css b/components/statusbar/content/prefs.css
new file mode 100644
index 0000000..bafaa61
--- /dev/null
+++ b/components/statusbar/content/prefs.css
@@ -0,0 +1,10 @@
+/* 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");
+
+.css-bg-editor {
+ -moz-binding: url("chrome://browser/content/statusbar/prefs.xml#css-bg-editor");
+}
+
diff --git a/components/statusbar/content/prefs.js b/components/statusbar/content/prefs.js
new file mode 100644
index 0000000..47fd4b6
--- /dev/null
+++ b/components/statusbar/content/prefs.js
@@ -0,0 +1,274 @@
+/* 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 status4evarPrefs =
+{
+ get dynamicProgressStyle()
+ {
+ let styleSheets = window.document.styleSheets;
+ for(let i = 0; i < styleSheets.length; i++)
+ {
+ let styleSheet = styleSheets[i];
+ if(styleSheet.href == "chrome://browser/skin/statusbar/dynamic.css")
+ {
+ delete this.dynamicProgressStyle;
+ return this.dynamicProgressStyle = styleSheet;
+ }
+ }
+
+ return null;
+ },
+
+//
+// Status timeout management
+//
+ get statusTimeoutPref()
+ {
+ delete this.statusTimeoutPref;
+ return this.statusTimeoutPref = document.getElementById("status4evar-pref-status-timeout");
+ },
+
+ get statusTimeoutCheckbox()
+ {
+ delete this.statusTimeoutCheckbox;
+ return this.statusTimeoutCheckbox = document.getElementById("status4evar-status-timeout-check");
+ },
+
+ statusTimeoutChanged: function()
+ {
+ if(this.statusTimeoutPref.value > 0)
+ {
+ this.statusTimeoutPref.disabled = false;
+ this.statusTimeoutCheckbox.checked = true;
+ }
+ else
+ {
+ this.statusTimeoutPref.disabled = true;
+ this.statusTimeoutCheckbox.checked = false;
+ }
+ },
+
+ statusTimeoutSync: function()
+ {
+ this.statusTimeoutChanged();
+ return undefined;
+ },
+
+ statusTimeoutToggle: function()
+ {
+ if(this.statusTimeoutPref.disabled == this.statusTimeoutCheckbox.checked)
+ {
+ if(this.statusTimeoutCheckbox.checked)
+ {
+ this.statusTimeoutPref.value = 10;
+ }
+ else
+ {
+ this.statusTimeoutPref.value = 0;
+ }
+ }
+ },
+
+//
+// Status network management
+//
+ get statusNetworkPref()
+ {
+ delete this.statusNetworkPref;
+ return this.statusNetworkPref = document.getElementById("status4evar-pref-status-network");
+ },
+
+ get statusNetworkXHRPref()
+ {
+ delete this.statusNetworkXHRPref;
+ return this.statusNetworkXHRPref = document.getElementById("status4evar-pref-status-network-xhr");
+ },
+
+ statusNetworkChanged: function()
+ {
+ this.statusNetworkXHRPref.disabled = ! this.statusNetworkPref.value;
+ },
+
+ statusNetworkSync: function()
+ {
+ this.statusNetworkChanged();
+ return undefined;
+ },
+
+//
+// Status Text langth managment
+//
+ get textMaxLengthPref()
+ {
+ delete this.textMaxLengthPref;
+ return this.textMaxLengthPref = document.getElementById("status4evar-pref-status-toolbar-maxLength");
+ },
+
+ get textMaxLengthCheckbox()
+ {
+ delete this.textMaxLengthCheckbox;
+ return this.textMaxLengthCheckbox = document.getElementById("status4evar-status-toolbar-maxLength-check");
+ },
+
+ textLengthChanged: function()
+ {
+ if(this.textMaxLengthPref.value > 0)
+ {
+ this.textMaxLengthPref.disabled = false;
+ this.textMaxLengthCheckbox.checked = true;
+ }
+ else
+ {
+ this.textMaxLengthPref.disabled = true;
+ this.textMaxLengthCheckbox.checked = false;
+ }
+ },
+
+ textLengthSync: function()
+ {
+ this.textLengthChanged();
+ return undefined;
+ },
+
+ textLengthToggle: function()
+ {
+ if(this.textMaxLengthPref.disabled == this.textMaxLengthCheckbox.checked)
+ {
+ if(this.textMaxLengthCheckbox.checked)
+ {
+ this.textMaxLengthPref.value = 800;
+ }
+ else
+ {
+ this.textMaxLengthPref.value = 0;
+ }
+ }
+ },
+
+//
+// Toolbar progress style management
+//
+ get progressToolbarStylePref()
+ {
+ delete this.progressToolbarStylePref;
+ return this.progressToolbarStylePref = document.getElementById("status4evar-pref-progress-toolbar-style");
+ },
+
+ get progressToolbarCSSPref()
+ {
+ delete this.progressToolbarCSSPref;
+ return this.progressToolbarCSSPref = document.getElementById("status4evar-pref-progress-toolbar-css");
+ },
+
+ get progressToolbarProgress()
+ {
+ delete this.progressToolbarProgress;
+ return this.progressToolbarProgress = document.getElementById("status4evar-progress-bar");
+ },
+
+ progressToolbarCSSChanged: function()
+ {
+ if(!this.progressToolbarCSSPref.value)
+ {
+ this.progressToolbarCSSPref.value = "#33FF33";
+ }
+ this.dynamicProgressStyle.cssRules[1].style.background = this.progressToolbarCSSPref.value;
+ },
+
+ progressToolbarStyleChanged: function()
+ {
+ this.progressToolbarCSSChanged();
+ this.progressToolbarCSSPref.disabled = !this.progressToolbarStylePref.value;
+ if(this.progressToolbarStylePref.value)
+ {
+ this.progressToolbarProgress.setAttribute("s4estyle", true);
+ }
+ else
+ {
+ this.progressToolbarProgress.removeAttribute("s4estyle");
+ }
+ },
+
+ progressToolbarStyleSync: function()
+ {
+ this.progressToolbarStyleChanged();
+ return undefined;
+ },
+
+//
+// Download progress management
+//
+ get downloadProgressCheck()
+ {
+ delete this.downloadProgressCheck;
+ return this.downloadProgressCheck = document.getElementById("status4evar-download-progress-check");
+ },
+
+ get downloadProgressPref()
+ {
+ delete this.downloadProgressPref;
+ return this.downloadProgressPref = document.getElementById("status4evar-pref-download-progress");
+ },
+
+ get downloadProgressColorActivePref()
+ {
+ delete this.downloadProgressActiveColorPref;
+ return this.downloadProgressActiveColorPref = document.getElementById("status4evar-pref-download-color-active");
+ },
+
+ get downloadProgressColorPausedPref()
+ {
+ delete this.downloadProgressPausedColorPref;
+ return this.downloadProgressPausedColorPref = document.getElementById("status4evar-pref-download-color-paused");
+ },
+
+ downloadProgressSync: function()
+ {
+ let val = this.downloadProgressPref.value;
+ this.downloadProgressColorActivePref.disabled = (val == 0);
+ this.downloadProgressColorPausedPref.disabled = (val == 0);
+ this.downloadProgressPref.disabled = (val == 0);
+ this.downloadProgressCheck.checked = (val != 0);
+ return ((val == 0) ? 1 : val);
+ },
+
+ downloadProgressToggle: function()
+ {
+ let enabled = this.downloadProgressCheck.checked;
+ this.downloadProgressPref.value = ((enabled) ? 1 : 0);
+ },
+
+//
+// Pref Window load
+//
+ get downloadButtonActionCommandPref()
+ {
+ delete this.downloadButtonActionCommandPref;
+ return this.downloadButtonActionCommandPref = document.getElementById("status4evar-pref-download-button-action-command");
+ },
+
+ get downloadButtonActionThirdPartyItem()
+ {
+ delete this.downloadButtonActionThirdPartyItem;
+ return this.downloadButtonActionThirdPartyItem = document.getElementById("status4evar-download-button-action-menu-thirdparty");
+ },
+
+ onPrefWindowLoad: function()
+ {
+ if(!this.downloadButtonActionCommandPref.value)
+ {
+ this.downloadButtonActionThirdPartyItem.disabled = true;
+ }
+ },
+
+ onPrefWindowUnLoad: function()
+ {
+ }
+}
+
+var XULBrowserWindow = {
+}
+
diff --git a/components/statusbar/content/prefs.xml b/components/statusbar/content/prefs.xml
new file mode 100644
index 0000000..44baab1
--- /dev/null
+++ b/components/statusbar/content/prefs.xml
@@ -0,0 +1,704 @@
+<?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 bindings SYSTEM "chrome://browser/locale/statusbar/statusbar-prefs.dtd">
+
+<bindings id="status4evar-prefs-bindings"
+ 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="css-bg-editor">
+ <content sizetopopup="pref">
+ <xul:vbox flex="1">
+ <xul:deck anonid="css-bg-editor-deck" flex="1">
+ <xul:vbox>
+ <xul:hbox align="center">
+ <xul:label xbl:inherits="disabled">&status4evar.editor.css.color.label;</xul:label>
+ <xul:colorpicker anonid="css-bg-editor-color" type="button" onchange="this._editor._buildCSS();" xbl:inherits="disabled" />
+ </xul:hbox>
+
+ <xul:hbox align="center">
+ <xul:label xbl:inherits="disabled">&status4evar.editor.css.image.label;</xul:label>
+ <xul:textbox anonid="css-bg-editor-image" readonly="true" flex="1" xbl:inherits="disabled" />
+ <xul:button anonid="css-bg-editor-image-browse" label="&status4evar.option.browse;" oncommand="this._editor._imageBrowse();" xbl:inherits="disabled" />
+ </xul:hbox>
+ <xul:hbox align="center" pack="end">
+ <xul:button anonid="css-bg-editor-image-clear" label="&status4evar.option.clear;" oncommand="this._editor._imageClear();" xbl:inherits="disabled=no-image" />
+ </xul:hbox>
+
+ <xul:hbox>
+ <xul:groupbox pack="center">
+ <xul:caption label="" />
+ <xul:hbox flex="1" align="center">
+ <xul:label>X</xul:label>
+ </xul:hbox>
+ <xul:separator class="groove" orient="horizontal" />
+ <xul:hbox flex="1" align="center">
+ <xul:label>Y</xul:label>
+ </xul:hbox>
+ </xul:groupbox>
+
+ <xul:groupbox>
+ <xul:caption label="&status4evar.editor.css.image.repeat;" xbl:inherits="disabled=no-image" />
+ <xul:menulist anonid="css-bg-editor-image-repeat-x" sizetopopup="always" onselect="this._editor._buildCSS();" xbl:inherits="disabled=no-image">
+ <xul:menupopup>
+ <xul:menuitem label="&status4evar.option.no-repeat;" value="no-repeat" />
+ <xul:menuitem label="&status4evar.option.repeat;" value="repeat" />
+<!--
+ <xul:menuitem label="&status4evar.option.space;" value="space" />
+ <xul:menuitem label="&status4evar.option.round;" value="round" />
+-->
+ </xul:menupopup>
+ </xul:menulist>
+ <xul:separator class="groove" orient="horizontal" />
+ <xul:menulist anonid="css-bg-editor-image-repeat-y" sizetopopup="always" onselect="this._editor._buildCSS();" xbl:inherits="disabled=no-image">
+ <xul:menupopup>
+ <xul:menuitem label="&status4evar.option.no-repeat;" value="no-repeat" />
+ <xul:menuitem label="&status4evar.option.repeat;" value="repeat" />
+<!--
+ <xul:menuitem label="&status4evar.option.space;" value="space" />
+ <xul:menuitem label="&status4evar.option.round;" value="round" />
+-->
+ </xul:menupopup>
+ </xul:menulist>
+ </xul:groupbox>
+
+ <xul:groupbox>
+ <xul:caption label="&status4evar.editor.css.image.position;" xbl:inherits="disabled=no-image" />
+ <xul:menulist anonid="css-bg-editor-image-position-x" sizetopopup="always" onselect="this._editor._updatePositionX();" xbl:inherits="disabled=no-image">
+ <xul:menupopup>
+ <xul:menuitem label="&status4evar.option.left;" value="left" />
+ <xul:menuitem label="&status4evar.option.center;" value="center" />
+ <xul:menuitem label="&status4evar.option.right;" value="right" />
+ <xul:menuitem label="&status4evar.option.offset;" value="offset" />
+ </xul:menupopup>
+ </xul:menulist>
+ <xul:separator class="groove" orient="horizontal" />
+ <xul:menulist anonid="css-bg-editor-image-position-y" sizetopopup="always" onselect="this._editor._updatePositionY();" xbl:inherits="disabled=no-image">
+ <xul:menupopup>
+ <xul:menuitem label="&status4evar.option.top;" value="top" />
+ <xul:menuitem label="&status4evar.option.center;" value="center" />
+ <xul:menuitem label="&status4evar.option.bottom;" value="bottom" />
+ <xul:menuitem label="&status4evar.option.offset;" value="offset" />
+ </xul:menupopup>
+ </xul:menulist>
+ </xul:groupbox>
+
+ <xul:groupbox>
+ <xul:caption label="&status4evar.editor.css.image.offset;" xbl:inherits="disabled=no-image" />
+ <xul:hbox>
+ <xul:textbox anonid="css-bg-editor-image-offset-x" type="number" size="4" min="-65535" onchange="this._editor._buildCSS();" />
+ <xul:menulist anonid="css-bg-editor-image-offset-unit-x" sizetopopup="always" onselect="this._editor._buildCSS();">
+ <xul:menupopup>
+ <xul:menuitem label="%" value="%" />
+ <xul:menuitem label="px" value="px" />
+ <xul:menuitem label="em" value="em" />
+ <xul:menuitem label="in" value="in" />
+ <xul:menuitem label="cm" value="cm" />
+ <xul:menuitem label="mm" value="mm" />
+ <xul:menuitem label="pt" value="pt" />
+ <xul:menuitem label="pc" value="pc" />
+ </xul:menupopup>
+ </xul:menulist>
+ </xul:hbox>
+ <xul:separator class="groove" orient="horizontal" />
+ <xul:hbox>
+ <xul:textbox anonid="css-bg-editor-image-offset-y" type="number" size="4" min="-65535" onchange="this._editor._buildCSS();" />
+ <xul:menulist anonid="css-bg-editor-image-offset-unit-y" sizetopopup="always" onselect="this._editor._buildCSS();">
+ <xul:menupopup>
+ <xul:menuitem label="%" value="%" />
+ <xul:menuitem label="px" value="px" />
+ <xul:menuitem label="em" value="em" />
+ <xul:menuitem label="in" value="in" />
+ <xul:menuitem label="cm" value="cm" />
+ <xul:menuitem label="mm" value="mm" />
+ <xul:menuitem label="pt" value="pt" />
+ <xul:menuitem label="pc" value="pc" />
+ </xul:menupopup>
+ </xul:menulist>
+ </xul:hbox>
+ </xul:groupbox>
+ </xul:hbox>
+ </xul:vbox>
+
+ <xul:textbox anonid="css-bg-editor-css-text" multiline="true" rows="6" xbl:inherits="disabled" />
+ </xul:deck>
+ </xul:vbox>
+
+ <xul:hbox align="center" pack="end">
+ <children includes="progressmeter|toolbox" />
+ <xul:label xbl:inherits="disabled">&status4evar.editor.label;</xul:label>
+ <xul:menulist anonid="css-bg-editor-mode-menu" sizetopopup="always" onselect="this._editor._updateMode();" xbl:inherits="disabled">
+ <xul:menupopup>
+ <xul:menuitem label="&status4evar.option.simple;" />
+ <xul:menuitem label="&status4evar.option.advanced;" />
+ </xul:menupopup>
+ </xul:menulist>
+ </xul:hbox>
+ </content>
+
+ <implementation>
+ <constructor><![CDATA[
+ [
+ "_editorColor",
+ "_editorImageBrowse",
+ "_editorImageClear",
+ "_editorImageRepeatX",
+ "_editorImageRepeatY",
+ "_editorImagePositionX",
+ "_editorImagePositionY",
+ "_editorImageOffsetX",
+ "_editorImageOffsetY",
+ "_editorImageOffsetUnitX",
+ "_editorImageOffsetUnitY",
+ "_editorMode"
+ ].forEach(function(prop)
+ {
+ this[prop]._editor = this;
+ }, this);
+
+ this.setAdvanced(true, false);
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ ]]></destructor>
+
+ <field name="_disableBuildCSS"><![CDATA[
+ true
+ ]]></field>
+
+ <field name="_editorColor" readonly="true"><![CDATA[
+ document.getAnonymousElementByAttribute(this, "anonid", "css-bg-editor-color");
+ ]]></field>
+
+ <field name="_editorCSS" readonly="true"><![CDATA[
+ document.getAnonymousElementByAttribute(this, "anonid", "css-bg-editor-css-text");
+ ]]></field>
+
+ <field name="_editorDeck" readonly="true"><![CDATA[
+ document.getAnonymousElementByAttribute(this, "anonid", "css-bg-editor-deck");
+ ]]></field>
+
+ <field name="_editorImage" readonly="true"><![CDATA[
+ document.getAnonymousElementByAttribute(this, "anonid", "css-bg-editor-image");
+ ]]></field>
+
+ <field name="_editorImageBrowse" readonly="true"><![CDATA[
+ document.getAnonymousElementByAttribute(this, "anonid", "css-bg-editor-image-browse");
+ ]]></field>
+
+ <field name="_editorImageClear" readonly="true"><![CDATA[
+ document.getAnonymousElementByAttribute(this, "anonid", "css-bg-editor-image-clear");
+ ]]></field>
+
+ <field name="_editorImageRepeatX" readonly="true"><![CDATA[
+ document.getAnonymousElementByAttribute(this, "anonid", "css-bg-editor-image-repeat-x");
+ ]]></field>
+
+ <field name="_editorImageRepeatY" readonly="true"><![CDATA[
+ document.getAnonymousElementByAttribute(this, "anonid", "css-bg-editor-image-repeat-y");
+ ]]></field>
+
+ <field name="_editorImagePositionX" readonly="true"><![CDATA[
+ document.getAnonymousElementByAttribute(this, "anonid", "css-bg-editor-image-position-x");
+ ]]></field>
+
+ <field name="_editorImagePositionY" readonly="true"><![CDATA[
+ document.getAnonymousElementByAttribute(this, "anonid", "css-bg-editor-image-position-y");
+ ]]></field>
+
+ <field name="_editorImageOffsetX" readonly="true"><![CDATA[
+ document.getAnonymousElementByAttribute(this, "anonid", "css-bg-editor-image-offset-x");
+ ]]></field>
+
+ <field name="_editorImageOffsetY" readonly="true"><![CDATA[
+ document.getAnonymousElementByAttribute(this, "anonid", "css-bg-editor-image-offset-y");
+ ]]></field>
+
+ <field name="_editorImageOffsetUnitX" readonly="true"><![CDATA[
+ document.getAnonymousElementByAttribute(this, "anonid", "css-bg-editor-image-offset-unit-x");
+ ]]></field>
+
+ <field name="_editorImageOffsetUnitY" readonly="true"><![CDATA[
+ document.getAnonymousElementByAttribute(this, "anonid", "css-bg-editor-image-offset-unit-y");
+ ]]></field>
+
+ <field name="_editorMode" readonly="true"><![CDATA[
+ document.getAnonymousElementByAttribute(this, "anonid", "css-bg-editor-mode-menu");
+ ]]></field>
+
+ <field name="_initialized"><![CDATA[
+ false
+ ]]></field>
+
+ <field name="_reRGB" readonly="true"><![CDATA[
+ /^rgb\((\d+), (\d+), (\d+)\)$/
+ ]]></field>
+
+ <field name="_reURL" readonly="true"><![CDATA[
+ /^url\(\s*['"]?(.+?)['"]?\s*\)$/
+ ]]></field>
+
+ <field name="_reBgPosition" readonly="true"><![CDATA[
+ /^(left|center|right)? ?(-?\d+[^\s\d]+)? ?(top|center|bottom)? ?(-?\d+[^\s\d]+)?$/
+ ]]></field>
+
+ <field name="_reCSSUnit" readonly="true"><![CDATA[
+ /^(-?\d+)([^\s\d]+)$/
+ ]]></field>
+
+ <field name="_strings" readonly="true"><![CDATA[
+ document.getElementById("bundle_status4evar");
+ ]]></field>
+
+ <property name="value">
+ <getter><![CDATA[
+ return this._editorCSS.value;
+ ]]></getter>
+ <setter><![CDATA[
+ this._editorCSS.value = val;
+
+ if(!this._initialized)
+ {
+ this.setAdvanced(false, false);
+ this._initialized = true;
+ }
+
+ return val;
+ ]]></setter>
+ </property>
+
+ <property name="disabled">
+ <getter><![CDATA[
+ return this.getAttribute("disabled") == "true";
+ ]]></getter>
+ <setter><![CDATA[
+ if(val)
+ {
+ this.setAttribute("disabled", "true");
+ }
+ else
+ {
+ this.removeAttribute("disabled");
+ }
+
+ this._updateImageControllDisable();
+
+ return val;
+ ]]></setter>
+ </property>
+
+ <method name="setAdvanced">
+ <parameter name="aVal"/>
+ <parameter name="aPrompt"/>
+ <body><![CDATA[
+ if(!aVal)
+ {
+ let success = this._parseCSS();
+ if(!success)
+ {
+ let result = aPrompt && Services.prompt.confirm(window,
+ this._strings.getString("simpleEditorTitle"),
+ this._strings.getString("simpleEditorMessage"));
+ if(result)
+ { // Continue to simple mode
+ this._buildCSS();
+ }
+ else
+ { // Stay on advanced mode
+ aVal = true;
+ }
+ }
+ }
+
+ this._disableBuildCSS = aVal;
+ this._editorDeck.selectedIndex = ((aVal) ? 1 : 0);
+ this._editorMode.selectedIndex = ((aVal) ? 1 : 0);
+ ]]></body>
+ </method>
+
+ <method name="_buildCSS">
+ <body><![CDATA[
+ if(this._disableBuildCSS)
+ {
+ return;
+ }
+
+ let cssVal = this._editorColor.color;
+ let imgVal = this._editorImage.value;
+ if(imgVal)
+ {
+ cssVal += " url(\"" + imgVal + "\")";
+
+ //
+ // Print the background repeat
+ //
+ let bgRX = this._editorImageRepeatX.value;
+ let bgRY = this._editorImageRepeatY.value;
+ if(bgRX == "repeat" && bgRY == "no-repeat")
+ {
+ cssVal += " repeat-x";
+ }
+ else if(bgRX == "no-repeat" && bgRY == "repeat")
+ {
+ cssVal += " repeat-y";
+ }
+ else
+ {
+ cssVal += " " + bgRX;
+ if(bgRX != bgRY)
+ {
+ cssVal += " " + bgRY;
+ }
+ }
+
+ //
+ // Print the background position
+ //
+ let bgPX = this._editorImagePositionX.value;
+ let bgPOX = this._editorImageOffsetX.value;
+ if(bgPX != "offset")
+ {
+ cssVal += " " + bgPX;
+ }
+ else
+ {
+ cssVal += " " + bgPOX + this._editorImageOffsetUnitX.value;
+ }
+
+ let bgPY = this._editorImagePositionY.value;
+ let bgPOY = this._editorImageOffsetY.value;
+ if(bgPY != "offset")
+ {
+ cssVal += " " + bgPY;
+ }
+ else
+ {
+ cssVal += " " + bgPOY + this._editorImageOffsetUnitY.value;
+ }
+ }
+
+ this._editorCSS.value = cssVal;
+
+ let event = document.createEvent("Event");
+ event.initEvent("change", true, true);
+ this._editorCSS.dispatchEvent(event);
+ ]]></body>
+ </method>
+
+ <method name="_parseCSS">
+ <body><![CDATA[
+ let retVal = true;
+
+ let cssParser = document.createElement("div");
+ cssParser.style.background = this._editorCSS.value;
+ if(!cssParser.style.background)
+ {
+ Components.utils.reportError("Error parsing background CSS rule: " + this._editorCSS.value);
+ cssParser.style.background = "#33FF33";
+ retVal = false;
+ }
+
+ //
+ // Parse the background color
+ //
+ let bgC = cssParser.style.backgroundColor;
+ if(this._reRGB.test(bgC))
+ {
+ let digits = this._reRGB.exec(bgC);
+
+ let red = parseInt(digits[1]);
+ let green = parseInt(digits[2]);
+ let blue = parseInt(digits[3]);
+
+ let rgb = blue | (green << 8) | (red << 16);
+ bgC = "#" + rgb.toString(16);
+ }
+ else
+ {
+ Components.utils.reportError("Error parsing background-color value: " + bgC);
+ bgC = "#33FF33";
+ retVal = false;
+ }
+
+ //
+ // Parse the background image
+ //
+ let bgI = cssParser.style.backgroundImage;
+ if(bgI != "none" && !this._reURL.test(bgI))
+ {
+ Components.utils.reportError("Error parsing background-image value: " + bgI);
+ bgI = "none";
+ retVal = false;
+ }
+ bgI = ((bgI != "none") ? this._reURL.exec(bgI)[1].trim() : "");
+
+ //
+ // Parse the background repeat
+ //
+ let bgR = cssParser.style.backgroundRepeat.split(" ");
+ let bgRX = bgR[0];
+ if(bgRX == "repeat-x")
+ {
+ bgRX = "repeat";
+ }
+ else if(bgRX == "repeat-y")
+ {
+ bgRX = "no-repeat";
+ }
+
+ let bgRY = bgR[bgR.length - 1];
+ if(bgRY == "repeat-x")
+ {
+ bgRY = "no-repeat";
+ }
+ else if(bgRY == "repeat-y")
+ {
+ bgRY = "repeat";
+ }
+
+ //
+ // Parse the background position
+ //
+ let bgP = cssParser.style.backgroundPosition;
+ let bgPParts = this._reBgPosition.exec(bgP);
+ let bgPValues = new Array();
+ for(let i = 1; i <= 4; i++)
+ {
+ if(bgPParts[i])
+ {
+ bgPValues.push({
+ "value": bgPParts[i],
+ "group": i
+ });
+ }
+ }
+
+ if(bgPValues.length == 1)
+ {
+ bgPValues.splice(((bgPValues[0].group == 2) ? 0 : 1), 0, {
+ "value": "center",
+ "group": ((bgPValues[0].group == 2) ? 0 : 2)
+ });
+ }
+
+ if(bgPValues.length == 2 && bgPValues[1].group == 2)
+ {
+ bgPValues[1].group = 4;
+ }
+
+ for(let i = 0; i < 4; i++)
+ {
+ let group = (i + 1);
+ if(bgPValues[i] != undefined && bgPValues[i].group == group)
+ {
+ continue;
+ }
+
+ let tmp = "0px";
+ switch(i)
+ {
+ case 0:
+ tmp = "offset";
+ break;
+ case 2:
+ tmp = "offset";
+ break;
+ }
+
+ bgPValues.splice(i, 0, {
+ "value": tmp,
+ "group": group
+ });
+ }
+
+ let bgPOXParts = this._reCSSUnit.exec(bgPValues[1].value);
+ let bgPOYParts = this._reCSSUnit.exec(bgPValues[3].value);
+
+ //
+ // Parse the background size
+ //
+
+ //
+ // Initialize the UI
+ //
+ let disableBuildCSS = this._disableBuildCSS;
+ this._disableBuildCSS = true;
+
+ this._editorColor.color = bgC;
+ this._editorImage.value = bgI;
+ this._editorImageOffsetX.value = bgPOXParts[1];
+ this._editorImageOffsetY.value = bgPOYParts[1];
+
+ [
+ [this._editorImageRepeatX, bgRX, "repeat", "repeat X"],
+ [this._editorImageRepeatY, bgRY, "repeat", "repeat Y"],
+ [this._editorImagePositionX, bgPValues[0].value, "left", "position X"],
+ [this._editorImagePositionY, bgPValues[2].value, "top", "position Y"],
+ [this._editorImageOffsetUnitX, bgPOXParts[2], "px", "offset X unit"],
+ [this._editorImageOffsetUnitY, bgPOYParts[2], "px", "offset Y unit"]
+ ].forEach(function(info)
+ {
+ if(!this._setSelectedItemSafe(info[0], info[1], info[2]))
+ {
+ Components.utils.reportError("Error setting " + info[3] + " to " + info[1]);
+ retVal = false;
+ }
+ }, this);
+
+ this._updateImageControllDisable();
+
+ this._disableBuildCSS = disableBuildCSS;
+
+ return retVal;
+ ]]></body>
+ </method>
+
+ <method name="_imageBrowse">
+ <body><![CDATA[
+ let nsIFilePicker = Components.interfaces.nsIFilePicker;
+ let filePicker = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
+ filePicker.init(window, this._strings.getString("imageSelectTitle"), nsIFilePicker.modeOpen);
+ filePicker.appendFilters(nsIFilePicker.filterImages);
+
+ let res = filePicker.show();
+ if(res == nsIFilePicker.returnOK)
+ {
+ this._editorImage.value = Services.io.newFileURI(filePicker.file).spec;
+ this._updateImageControllDisable();
+ this._buildCSS();
+ }
+ ]]></body>
+ </method>
+
+ <method name="_imageClear">
+ <body><![CDATA[
+ this._editorImage.value = "";
+ this._editorImageRepeatX.value = "repeat";
+ this._editorImageRepeatY.value = "repeat";
+ this._editorImagePositionX.value = "left";
+ this._editorImagePositionY.value = "top";
+ this._editorImageOffsetX.value = 0;
+ this._editorImageOffsetY.value = 0;
+ this._editorImageOffsetUnitX.value = "px";
+ this._editorImageOffsetUnitY.value = "px";
+ this._updateImageControllDisable();
+ this._buildCSS();
+ ]]></body>
+ </method>
+
+ <method name="_processEvent">
+ <parameter name="event"/>
+ <body><![CDATA[
+ if(!("css-bg-editor-css-text" == event.originalTarget.getAttribute("anonid")
+ || "css-bg-editor-css-text" == document.getBindingParent(event.originalTarget).getAttribute("anonid")))
+ {
+ event.stopPropagation();
+ }
+
+ //Components.utils.reportError("Editor event " + event.type + " on " + event.originalTarget.tagName + "::" + event.originalTarget.getAttribute("anonid"));
+ ]]></body>
+ </method>
+
+ <method name="_setSelectedItemSafe">
+ <parameter name="aElement"/>
+ <parameter name="aValue"/>
+ <parameter name="aDefault"/>
+ <body><![CDATA[
+ aElement.value = aValue;
+ if(!aElement.selectedItem || aElement.selectedItem.value != aValue)
+ {
+ aElement.value = aDefault;
+ return false;
+ }
+ return true;
+ ]]></body>
+ </method>
+
+ <method name="_updateImageControllDisable">
+ <body><![CDATA[
+ if(this.disabled || !this._editorImage.value)
+ {
+ this.setAttribute("no-image", "true");
+ this._updatePositionOffsetXDisabled(true);
+ this._updatePositionOffsetYDisabled(true);
+ }
+ else
+ {
+ this.removeAttribute("no-image");
+ this._updatePositionOffsetXDisabled(false);
+ this._updatePositionOffsetYDisabled(false);
+ }
+ ]]></body>
+ </method>
+
+ <method name="_updateMode">
+ <body><![CDATA[
+ if(this._editorMode.selectedIndex == this._editorDeck.selectedIndex)
+ {
+ return;
+ }
+
+ this.setAdvanced(((this._editorMode.selectedIndex == 1) ? true : false), true);
+ ]]></body>
+ </method>
+
+ <method name="_updatePositionOffsetXDisabled">
+ <parameter name="aVal"/>
+ <body><![CDATA[
+ let bgPX = this._editorImagePositionX.value;
+ let disableOffsetX = aVal || (bgPX != "offset");// || bgPX == "center");
+ this._editorImageOffsetX.disabled = disableOffsetX;
+ this._editorImageOffsetUnitX.disabled = disableOffsetX;
+ ]]></body>
+ </method>
+
+ <method name="_updatePositionOffsetYDisabled">
+ <parameter name="aVal"/>
+ <body><![CDATA[
+ let bgPY = this._editorImagePositionY.value;
+ var disableOffsetY = aVal || (bgPY != "offset");// || bgPY == "center");
+ this._editorImageOffsetY.disabled = disableOffsetY;
+ this._editorImageOffsetUnitY.disabled = disableOffsetY;
+ ]]></body>
+ </method>
+
+ <method name="_updatePositionX">
+ <body><![CDATA[
+ this._updatePositionOffsetXDisabled(false);
+ this._buildCSS();
+ ]]></body>
+ </method>
+
+ <method name="_updatePositionY">
+ <body><![CDATA[
+ this._updatePositionOffsetYDisabled(false);
+ this._buildCSS();
+ ]]></body>
+ </method>
+ </implementation>
+
+ <handlers>
+ <handler event="command"><![CDATA[
+ this._processEvent(event);
+ ]]></handler>
+
+ <handler event="change"><![CDATA[
+ this._processEvent(event);
+ ]]></handler>
+
+ <handler event="input"><![CDATA[
+ this._processEvent(event);
+ ]]></handler>
+
+ <handler event="select"><![CDATA[
+ this._processEvent(event);
+ ]]></handler>
+ </handlers>
+ </binding>
+</bindings>
+
diff --git a/components/statusbar/content/prefs.xul b/components/statusbar/content/prefs.xul
new file mode 100644
index 0000000..dd41582
--- /dev/null
+++ b/components/statusbar/content/prefs.xul
@@ -0,0 +1,297 @@
+<?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 prefwindow [
+ <!ENTITY % prefsDTD SYSTEM "chrome://browser/locale/statusbar/statusbar-prefs.dtd">
+ %prefsDTD;
+]>
+
+<?xml-stylesheet href="chrome://global/skin/config.css" type="text/css" ?>
+<?xml-stylesheet href="chrome://browser/skin/browser.css" type="text/css" ?>
+
+<?xml-stylesheet href="chrome://browser/content/statusbar/overlay.css" type="text/css" ?>
+<?xml-stylesheet href="chrome://browser/skin/statusbar/overlay.css" type="text/css" ?>
+<?xml-stylesheet href="chrome://browser/skin/statusbar/dynamic.css" type="text/css" ?>
+
+<?xml-stylesheet href="chrome://browser/content/statusbar/prefs.css" type="text/css" ?>
+<?xml-stylesheet href="chrome://browser/skin/statusbar/prefs.css" type="text/css" ?>
+
+<prefwindow id="status4evar-prefs" title="&status4evar.window.title;"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="status4evarPrefs.onPrefWindowLoad();" onunload="status4evarPrefs.onPrefWindowUnLoad();">
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="bundle_status4evar" src="chrome://browser/locale/statusbar/prefs.properties" />
+ </stringbundleset>
+ <script type="application/javascript" src="chrome://browser/content/statusbar/prefs.js" />
+
+ <prefpane id="status4evar-pane-status" label="&status4evar.pane.status;">
+ <preferences>
+ <preference id="status4evar-pref-status" name="status4evar.status" type="int" />
+ <preference id="status4evar-pref-status-default" name="status4evar.status.default" type="bool" />
+ <preference id="status4evar-pref-status-network" name="status4evar.status.network" type="bool"
+ onchange="status4evarPrefs.statusNetworkChanged();" />
+ <preference id="status4evar-pref-status-network-xhr" name="status4evar.status.network.xhr" type="bool" />
+ <preference id="status4evar-pref-status-timeout" name="status4evar.status.timeout" type="int"
+ onchange="status4evarPrefs.statusTimeoutChanged();" />
+ <preference id="status4evar-pref-status-linkOver" name="status4evar.status.linkOver" type="int" />
+ <preference id="status4evar-pref-status-linkOver-delay-show" name="status4evar.status.linkOver.delay.show" type="int" />
+ <preference id="status4evar-pref-status-linkOver-delay-hide" name="status4evar.status.linkOver.delay.hide" type="int" />
+ <preference id="status4evar-pref-status-toolbar-maxLength" name="status4evar.status.toolbar.maxLength" type="int"
+ onchange="status4evarPrefs.textLengthChanged();" />
+ <preference id="status4evar-pref-status-popup-invertMirror" name="status4evar.status.popup.invertMirror" type="bool" />
+ <preference id="status4evar-pref-status-popup-mouseMirror" name="status4evar.status.popup.mouseMirror" type="bool" />
+ <preference id="toolkit-pref-dom-status-change" name="dom.disable_window_status_change" type="bool" inverted="true" />
+ </preferences>
+
+ <commandset id="status4evar-commandset-status">
+ <command id="status4evar-command-status-timeout" oncommand="status4evarPrefs.statusTimeoutToggle();" />
+ <command id="status4evar-command-status-toolbar-maxLength" oncommand="status4evarPrefs.textLengthToggle();" />
+ </commandset>
+
+ <tabbox id="status4evar-tabbox-status" flex="1">
+ <tabs id="status4evar-tabs-status">
+ <tab id="status4evar-tab-status-general" label="&status4evar.tab.general;" />
+ <tab id="status4evar-tab-status-toolbar" label="&status4evar.tab.toolbar;" />
+ <tab id="status4evar-tab-status-popup" label="&status4evar.tab.popup;" />
+ </tabs>
+
+ <tabpanels id="status4evar-tabpanels-status" flex="1">
+ <tabpanel id="status4evar-tabpanel-status-general" orient="vertical">
+ <groupbox id="status4evar-status-general-status">
+ <caption label="&status4evar.status.general.status.caption;" />
+
+ <hbox align="center">
+ <label id="status4evar-status-label" control="status4evar-status-menu">&status4evar.status.label;</label>
+ <menulist id="status4evar-status-menu" preference="status4evar-pref-status" sizetopopup="always">
+ <menupopup>
+ <menuitem label="&status4evar.option.none;" value="0" />
+ <menuitem label="&status4evar.option.toolbar;" value="1" />
+ <menuitem label="&status4evar.option.popup;" value="3" />
+ </menupopup>
+ </menulist>
+ </hbox>
+
+ <hbox align="center">
+ <checkbox id="status4evar-status-timeout-check" label="&status4evar.status.timeout.label;"
+ command="status4evar-command-status-timeout" />
+ <textbox id="status4evar-status-timeout-value" preference="status4evar-pref-status-timeout" type="number" size="4"
+ onsyncfrompreference="return status4evarPrefs.statusTimeoutSync();" />
+ <label id="status4evar-status-timeout-unit">&status4evar.unit.seconds;</label>
+ </hbox>
+
+ <checkbox id="status4evar-status-default-check" preference="status4evar-pref-status-default" label="&status4evar.status.default.label;" />
+
+ <checkbox id="status4evar-status-network-check" preference="status4evar-pref-status-network" label="&status4evar.status.network.label;"
+ onsyncfrompreference="return status4evarPrefs.statusNetworkSync();" />
+
+ <hbox align="center" class="indent">
+ <checkbox id="status4evar-status-network-xhr-check" preference="status4evar-pref-status-network-xhr" label="&status4evar.status.network.xhr.label;" />
+ </hbox>
+
+ <checkbox id="toolkit-dom-status-change-check" preference="toolkit-pref-dom-status-change" label="&toolkit.dom.status.change.label;" />
+ </groupbox>
+
+ <groupbox id="status4evar-status-general-linkOver">
+ <caption label="&status4evar.status.general.linkOver.caption;" />
+
+ <hbox align="center">
+ <label id="status4evar-status-linkOver-label" control="status4evar-status-linkOver-menu">&status4evar.status.linkOver.label;</label>
+ <menulist id="status4evar-status-linkOver-menu" preference="status4evar-pref-status-linkOver" sizetopopup="always">
+ <menupopup>
+ <menuitem label="&status4evar.option.none;" value="0" />
+ <menuitem label="&status4evar.option.toolbar;" value="1" />
+ <menuitem label="&status4evar.option.popup;" value="3" />
+ </menupopup>
+ </menulist>
+ </hbox>
+
+ <hbox align="center">
+ <label id="status4evar-status-linkOver-delay-show-label" control="status4evar-status-linkOver-delay-show-value">&status4evar.status.linkOver.delay.show.label;</label>
+ <textbox id="status4evar-status-linkOver-delay-show-value" preference="status4evar-pref-status-linkOver-delay-show" type="number" size="5" />
+ <label id="status4evar-status-linkOver-delay-show-unit">&status4evar.unit.milliseconds;</label>
+ </hbox>
+
+ <hbox align="center">
+ <label id="status4evar-status-linkOver-delay-hide-label" control="status4evar-status-linkOver-delay-hide-value">&status4evar.status.linkOver.delay.hide.label;</label>
+ <textbox id="status4evar-status-linkOver-delay-hide-value" preference="status4evar-pref-status-linkOver-delay-hide" type="number" size="5" />
+ <label id="status4evar-status-linkOver-delay-hide-unit">&status4evar.unit.milliseconds;</label>
+ </hbox>
+ </groupbox>
+
+ </tabpanel>
+
+ <tabpanel id="status4evar-tabpanel-status-toolbar" orient="vertical">
+ <hbox align="center">
+ <checkbox id="status4evar-status-toolbar-maxLength-check" label="&status4evar.status.toolbar.maxLength.label;"
+ command="status4evar-command-status-toolbar-maxLength" />
+ <textbox id="status4evar-status-toolbar-maxLength-value" preference="status4evar-pref-status-toolbar-maxLength" type="number" size="4"
+ onsyncfrompreference="return status4evarPrefs.textLengthSync();" />
+ <label id="status4evar-status-toolbar-maxLength-unit">&status4evar.unit.px;</label>
+ </hbox>
+ </tabpanel>
+
+ <tabpanel id="status4evar-tabpanel-status-popup" orient="vertical">
+ <checkbox id="status4evar-status-popup-invertMirror-check" preference="status4evar-pref-status-popup-invertMirror" label="&status4evar.status.popup.invertMirror.label;" />
+
+ <checkbox id="status4evar-status-popup-mouseMirror-check" preference="status4evar-pref-status-popup-mouseMirror" label="&status4evar.status.popup.mouseMirror.label;" />
+ </tabpanel>
+
+ </tabpanels>
+ </tabbox>
+ </prefpane>
+
+ <prefpane id="status4evar-pane-progress" label="&status4evar.pane.progress;">
+ <preferences>
+ <preference id="status4evar-pref-progress-toolbar-force" name="status4evar.progress.toolbar.force" type="bool" />
+ <preference id="status4evar-pref-progress-toolbar-style" name="status4evar.progress.toolbar.style" type="bool"
+ onchange="status4evarPrefs.progressToolbarStyleChanged();" />
+ <preference id="status4evar-pref-progress-toolbar-css" name="status4evar.progress.toolbar.css" type="string"
+ onchange="status4evarPrefs.progressToolbarCSSChanged();" />
+ </preferences>
+
+ <commandset id="status4evar-commandset-status">
+ </commandset>
+
+ <checkbox id="status4evar-progress-toolbar-force-check" preference="status4evar-pref-progress-toolbar-force" label="&status4evar.progress.toolbar.force.label;" />
+
+ <checkbox id="status4evar-progress-toolbar-style-check" preference="status4evar-pref-progress-toolbar-style" label="&status4evar.progress.style.label;"
+ onsyncfrompreference="return status4evarPrefs.progressToolbarStyleSync();" />
+
+ <vbox class="css-bg-editor" preference="status4evar-pref-progress-toolbar-css" preference-editable="true" flex="1">
+ <progressmeter id="status4evar-progress-bar" value="75" flex="1" />
+ </vbox>
+ </prefpane>
+
+ <prefpane id="status4evar-pane-download" label="&status4evar.pane.download;">
+ <preferences>
+ <preference id="status4evar-pref-download-button-action" name="status4evar.download.button.action" type="int" />
+ <preference id="status4evar-pref-download-color-active" name="status4evar.download.color.active" type="string" />
+ <preference id="status4evar-pref-download-color-paused" name="status4evar.download.color.paused" type="string" />
+ <preference id="status4evar-pref-download-force" name="status4evar.download.force" type="bool" />
+ <preference id="status4evar-pref-download-label" name="status4evar.download.label" type="int" />
+ <preference id="status4evar-pref-download-label-force" name="status4evar.download.label.force" type="bool" />
+ <preference id="status4evar-pref-download-notify-animate" name="status4evar.download.notify.animate" type="bool" />
+ <preference id="status4evar-pref-download-notify-timeout" name="status4evar.download.notify.timeout" type="int" />
+ <preference id="status4evar-pref-download-progress" name="status4evar.download.progress" type="int" />
+ <preference id="status4evar-pref-download-tooltip" name="status4evar.download.tooltip" type="int" />
+
+ <preference id="status4evar-pref-download-button-action-command" name="status4evar.download.button.action.command" type="string"/>
+ </preferences>
+
+ <commandset id="status4evar-commandset-download">
+ <command id="status4evar-command-download-progress" oncommand="status4evarPrefs.downloadProgressToggle();" />
+ </commandset>
+
+ <checkbox id="status4evar-download-force-check" preference="status4evar-pref-download-force" label="&status4evar.download.force.label;" />
+
+ <checkbox id="status4evar-download-label-force-check" preference="status4evar-pref-download-label-force" label="&status4evar.download.label.force.label;" />
+
+ <hbox align="center">
+ <label id="status4evar-download-label-label" control="status4evar-download-label-menu">&status4evar.download.label.label;</label>
+ <menulist id="status4evar-download-label-menu" preference="status4evar-pref-download-label" sizetopopup="always">
+ <menupopup>
+ <menuitem value="0" label="&status4evar.option.dlcount;" />
+ <menuitem value="1" label="&status4evar.option.dltime;" />
+ <menuitem value="2" label="&status4evar.option.both;" />
+ </menupopup>
+ </menulist>
+ </hbox>
+
+ <hbox align="center">
+ <label id="status4evar-download-tooltip-label" control="status4evar-download-tooltip-menu">&status4evar.download.tooltip.label;</label>
+ <menulist id="status4evar-download-tooltip-menu" preference="status4evar-pref-download-tooltip" sizetopopup="always">
+ <menupopup>
+ <menuitem value="0" label="&status4evar.option.dlcount;" />
+ <menuitem value="1" label="&status4evar.option.dltime;" />
+ <menuitem value="2" label="&status4evar.option.both;" />
+ </menupopup>
+ </menulist>
+ </hbox>
+
+ <hbox align="center">
+ <label id="status4evar-download-button-action-label" control="status4evar-download-button-action-menu">&status4evar.download.button.action.label;</label>
+ <menulist id="status4evar-download-button-action-menu" preference="status4evar-pref-download-button-action" sizetopopup="always">
+ <menupopup>
+ <menuitem value="0" label="&status4evar.option.nothing;" />
+ <menuitem value="1" label="&status4evar.option.firefoxdefault;" />
+ <menuitem value="2" label="&status4evar.option.download.library;" />
+ <menuitem value="3" label="&status4evar.option.download.tab;" />
+ <menuitem value="4" label="&status4evar.option.download.thirdparty;" id="status4evar-download-button-action-menu-thirdparty" />
+ </menupopup>
+ </menulist>
+ </hbox>
+
+ <hbox align="center">
+ <label id="status4evar-download-notify-timeout-label" control="status4evar-download-notify-timeout-value">&status4evar.download.notify.timeout.label;</label>
+ <textbox id="status4evar-download-notify-timeout-value" preference="status4evar-pref-download-notify-timeout" type="number" size="3" />
+ <label id="status4evar-download-notify-timeout-unit">&status4evar.unit.seconds;</label>
+ </hbox>
+
+ <checkbox id="status4evar-download-notify-animate-check" preference="status4evar-pref-download-notify-animate" label="&status4evar.download.notify.animate.label;" />
+
+ <checkbox id="status4evar-download-progress-check" command="status4evar-command-download-progress" label="&status4evar.download.progress.label;" />
+
+ <vbox class="indent">
+ <hbox align="center">
+ <radiogroup id="status4evar-download-progress-radiogroup" preference="status4evar-pref-download-progress"
+ onsyncfrompreference="return status4evarPrefs.downloadProgressSync();">
+ <radio value="1" label="&status4evar.download.progress.average.label;" />
+ <radio value="2" label="&status4evar.download.progress.max.label;" />
+ <radio value="3" label="&status4evar.download.progress.min.label;" />
+ </radiogroup>
+ </hbox>
+
+ <hbox align="center">
+ <label id="status4evar-download-color-active-label" control="status4evar-download-color-active-picker">&status4evar.download.color.active.label;</label>
+ <colorpicker id="status4evar-download-color-active-picker" preference="status4evar-pref-download-color-active" type="button" />
+ </hbox>
+
+ <hbox align="center">
+ <label id="status4evar-download-color-paused-label" control="status4evar-download-color-paused-picker">&status4evar.download.color.paused.label;</label>
+ <colorpicker id="status4evar-download-color-paused-picker" preference="status4evar-pref-download-color-paused" type="button" />
+ </hbox>
+ </vbox>
+ </prefpane>
+
+ <prefpane id="status4evar-pane-addonbar" label="&status4evar.pane.statusbar;">
+ <preferences>
+ <preference id="status4evar-pref-addonbar-borderStyle" name="status4evar.addonbar.borderStyle" type="bool" />
+ <preference id="status4evar-pref-addonbar-closeButton" name="status4evar.addonbar.closeButton" type="bool" />
+ <preference id="status4evar-pref-addonbar-windowGripper" name="status4evar.addonbar.windowGripper" type="bool" />
+ </preferences>
+
+ <checkbox id="status4evar-addonbar-borderStyle-check" preference="status4evar-pref-addonbar-borderStyle" label="&status4evar.addonbar.borderStyle;" />
+
+ <checkbox id="status4evar-addonbar-closeButton-check" preference="status4evar-pref-addonbar-closeButton" label="&status4evar.addonbar.closeButton;" />
+
+ <checkbox id="status4evar-addonbar-windowGripper-check" preference="status4evar-pref-addonbar-windowGripper" label="&status4evar.addonbar.windowGripper;" />
+ </prefpane>
+
+ <prefpane id="status4evar-pane-advanced" label="&status4evar.pane.advanced;">
+ <preferences>
+ <preference id="status4evar-pref-advanced-status-detectFullScreen" name="status4evar.advanced.status.detectFullScreen" type="bool" />
+ <preference id="status4evar-pref-advanced-status-detectVideo" name="status4evar.advanced.status.detectVideo" type="bool" />
+ <preference id="browser-pref-urlbar-trimming-enabled" name="browser.urlbar.trimURLs" type="bool" />
+ </preferences>
+
+ <vbox flex="1">
+ <groupbox id="status4evar-status-urlbar-builtin">
+ <caption label="&status4evar.status.urlbar.firefox.builtin.caption;" />
+
+ <checkbox id="browser-urlbar-trimming-enabled-ckeck" preference="browser-pref-urlbar-trimming-enabled" label="&browser.urlbar.trimming.enabled.label;" />
+ </groupbox>
+
+ <groupbox id="status4evar-advanced-status">
+ <caption label="&status4evar.pane.status;" />
+
+ <checkbox id="status4evar-advanced-status-detectFullScreen-check" preference="status4evar-pref-advanced-status-detectFullScreen" label="&status4evar.advanced.status.detectFullScreen;" />
+ <checkbox id="status4evar-advanced-status-detectVideo-check" preference="status4evar-pref-advanced-status-detectVideo" label="&status4evar.advanced.status.detectVideo;" />
+ </groupbox>
+ </vbox>
+ </prefpane>
+</prefwindow>
+
diff --git a/components/statusbar/content/tabbrowser.xml b/components/statusbar/content/tabbrowser.xml
new file mode 100644
index 0000000..2f47577
--- /dev/null
+++ b/components/statusbar/content/tabbrowser.xml
@@ -0,0 +1,218 @@
+<?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/. -->
+
+<bindings id="status4evar-bindings"
+ 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="statuspanel" display="xul:hbox" extends="chrome://browser/content/tabbrowser.xml#statuspanel">
+ <implementation>
+ <!-- -->
+ <!-- Inverted mirror handling -->
+ <!-- -->
+
+ <field name="_invertMirror"><![CDATA[
+ false
+ ]]></field>
+
+ <property name="invertMirror">
+ <setter><![CDATA[
+ this._invertMirror = val;
+ this.mirror = this._isMirrored;
+ return val;
+ ]]></setter>
+ <getter><![CDATA[
+ return this._invertMirror;
+ ]]></getter>
+ </property>
+
+ <!-- -->
+ <!-- Mouse mirror handling -->
+ <!-- -->
+
+ <field name="_mouseMirror"><![CDATA[
+ true
+ ]]></field>
+
+ <field name="_mouseMirrorListen"><![CDATA[
+ false
+ ]]></field>
+
+ <property name="mouseMirror">
+ <setter><![CDATA[
+ this._mouseMirror = val;
+ this.setupMouseMirror(this.value);
+ return val;
+ ]]></setter>
+ <getter><![CDATA[
+ return this._mouseMirror;
+ ]]></getter>
+ </property>
+
+ <method name="setupMouseMirror">
+ <parameter name="val"/>
+ <body><![CDATA[
+ if(val && this._mouseMirror)
+ {
+ this._calcMouseTargetRect();
+ if(!this._mouseMirrorListen)
+ {
+ MousePosTracker.addListener(this);
+ this._mouseMirrorListen = true;
+ }
+ }
+ else
+ {
+ this.mirror = false;
+ if(this._mouseMirrorListen)
+ {
+ MousePosTracker.removeListener(this);
+ this._mouseMirrorListen = false;
+ }
+ }
+ ]]></body>
+ </method>
+
+ <method name="_calcMouseTargetRect">
+ <body><![CDATA[
+ let alignRight = false;
+ let isRTL = (getComputedStyle(document.documentElement).direction == "rtl");
+ if((this._invertMirror && !isRTL) || (!this._invertMirror && isRTL))
+ {
+ alignRight = true;
+ }
+
+ 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="onMouseEnter">
+ <body><![CDATA[
+ this.mirror = true;
+ ]]></body>
+ </method>
+
+ <method name="onMouseLeave">
+ <body><![CDATA[
+ this.mirror = false;
+ ]]></body>
+ </method>
+
+ <!-- -->
+ <!-- Mirror handling -->
+ <!-- -->
+
+ <field name="_isMirrored"><![CDATA[
+ false
+ ]]></field>
+
+ <property name="mirror">
+ <setter><![CDATA[
+ this._isMirrored = val;
+ if(this._invertMirror)
+ {
+ val = !val;
+ }
+
+ this.setBooleanAttr("mirror", val);
+ ]]></setter>
+ <getter><![CDATA[
+ return this._isMirrored;
+ ]]></getter>
+ </property>
+
+ <method name="_mirror">
+ <body><![CDATA[
+ this.mirror = !this._isMirrored;
+ ]]></body>
+ </method>
+
+ <!-- -->
+ <!-- Value handling -->
+ <!-- -->
+
+ <property name="label">
+ <setter><![CDATA[
+ if(window.caligon && window.caligon.status4evar)
+ {
+ window.caligon.status4evar.statusService.setStatusText(val);
+ }
+ return undefined;
+ ]]></setter>
+ <getter><![CDATA[
+ if(window.caligon && window.caligon.status4evar)
+ {
+ return window.caligon.status4evar.statusService.getStatusText();
+ }
+ return "";
+ ]]></getter>
+ </property>
+
+ <property name="value">
+ <setter><![CDATA[
+ this.setValue(val);
+ this.setupMouseMirror(val);
+ return val;
+ ]]></setter>
+ <getter><![CDATA[
+ return ((this.hasAttribute("inactive")) ? "" : this.getAttribute("label"));
+ ]]></getter>
+ </property>
+
+ <method name="setValue">
+ <parameter name="val"/>
+ <body><![CDATA[
+ if((this.getAttribute("type") || "").indexOf("network") > -1 && (this.getAttribute("previoustype") || "").indexOf("network") > -1)
+ {
+ this.style.minWidth = getComputedStyle(this).width;
+ }
+ else
+ {
+ this.style.minWidth = "";
+ }
+
+ if(val)
+ {
+ this.setAttribute("label", val);
+ this.setBooleanAttr("inactive", false);
+ }
+ else
+ {
+ this.setBooleanAttr("inactive", true);
+ }
+ ]]></body>
+ </method>
+
+ <!-- -->
+ <!-- Helpers -->
+ <!-- -->
+
+ <method name="setBooleanAttr">
+ <parameter name="name"/>
+ <parameter name="val"/>
+ <body><![CDATA[
+ if(val)
+ {
+ this.setAttribute(name, "true");
+ }
+ else
+ {
+ this.removeAttribute(name);
+ }
+ ]]></body>
+ </method>
+ </implementation>
+ </binding>
+</bindings>
+
diff --git a/components/statusbar/jar.mn b/components/statusbar/jar.mn
new file mode 100644
index 0000000..b5a8d09
--- /dev/null
+++ b/components/statusbar/jar.mn
@@ -0,0 +1,15 @@
+# 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:
+% overlay chrome://browser/content/browser.xul chrome://browser/content/statusbar/overlay.xul
+% style chrome://global/content/customizeToolbar.xul chrome://browser/skin/statusbar/overlay.css
+ content/browser/statusbar/overlay.js (content/overlay.js)
+ content/browser/statusbar/prefs.js (content/prefs.js)
+ content/browser/statusbar/prefs.xml (content/prefs.xml)
+ content/browser/statusbar/tabbrowser.xml (content/tabbrowser.xml)
+ content/browser/statusbar/overlay.xul (content/overlay.xul)
+ content/browser/statusbar/prefs.xul (content/prefs.xul)
+ content/browser/statusbar/overlay.css (content/overlay.css)
+ content/browser/statusbar/prefs.css (content/prefs.css) \ No newline at end of file
diff --git a/components/statusbar/moz.build b/components/statusbar/moz.build
new file mode 100644
index 0000000..0f7f597
--- /dev/null
+++ b/components/statusbar/moz.build
@@ -0,0 +1,25 @@
+# -*- 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/.
+
+JAR_MANIFESTS += [ 'jar.mn' ]
+
+XPIDL_SOURCES += [ 'status4evar.idl' ]
+
+XPIDL_MODULE = 'status4evar'
+
+EXTRA_COMPONENTS += [
+ 'status4evar.js',
+ 'status4evar.manifest',
+]
+
+EXTRA_JS_MODULES.statusbar = [
+ 'content-thunk.js',
+ 'Downloads.jsm',
+ 'Progress.jsm',
+ 'Status.jsm',
+ 'Status4Evar.jsm',
+ 'Toolbars.jsm',
+] \ No newline at end of file
diff --git a/components/statusbar/status4evar.idl b/components/statusbar/status4evar.idl
new file mode 100644
index 0000000..534dea3
--- /dev/null
+++ b/components/statusbar/status4evar.idl
@@ -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/. */
+
+#include "nsISupports.idl"
+
+interface nsIDOMWindow;
+
+[scriptable, uuid(33d0433d-07be-4dc4-87fd-954057310efd)]
+interface nsIStatus4Evar : nsISupports
+{
+ readonly attribute boolean addonbarBorderStyle;
+ readonly attribute boolean addonbarCloseButton;
+ readonly attribute boolean addonbarLegacyShim;
+ readonly attribute boolean addonbarWindowGripper;
+
+ readonly attribute boolean advancedStatusDetectFullScreen;
+ readonly attribute boolean advancedStatusDetectVideo;
+
+ readonly attribute long downloadButtonAction;
+ readonly attribute ACString downloadButtonActionCommand;
+ readonly attribute ACString downloadColorActive;
+ readonly attribute ACString downloadColorPaused;
+ readonly attribute boolean downloadForce;
+ readonly attribute long downloadLabel;
+ readonly attribute boolean downloadLabelForce;
+ readonly attribute boolean downloadNotifyAnimate;
+ readonly attribute long downloadNotifyTimeout;
+ readonly attribute long downloadProgress;
+ readonly attribute long downloadTooltip;
+
+ readonly attribute boolean firstRun;
+ readonly attribute boolean firstRunAustralis;
+
+ readonly attribute ACString progressToolbarCSS;
+ readonly attribute boolean progressToolbarForce;
+ readonly attribute boolean progressToolbarStyle;
+ readonly attribute boolean progressToolbarStyleAdvanced;
+
+ readonly attribute long status;
+ readonly attribute boolean statusDefault;
+ readonly attribute boolean statusNetwork;
+ readonly attribute boolean statusNetworkXHR;
+ readonly attribute long statusTimeout;
+ readonly attribute long statusLinkOver;
+ readonly attribute long statusLinkOverDelayShow;
+ readonly attribute long statusLinkOverDelayHide;
+
+ readonly attribute long statusToolbarMaxLength;
+
+ readonly attribute boolean statusToolbarInvertMirror;
+ readonly attribute boolean statusToolbarMouseMirror;
+
+ void resetPrefs();
+ void updateWindow(in nsIDOMWindow win);
+};
+
diff --git a/components/statusbar/status4evar.js b/components/statusbar/status4evar.js
new file mode 100644
index 0000000..4aa2e3e
--- /dev/null
+++ b/components/statusbar/status4evar.js
@@ -0,0 +1,695 @@
+/* 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";
+
+// Component constants
+const CC = Components.classes;
+const CI = Components.interfaces;
+const CU = Components.utils;
+
+CU.import("resource://gre/modules/XPCOMUtils.jsm");
+CU.import("resource://gre/modules/Services.jsm");
+
+const CURRENT_MIGRATION = 8;
+
+function Status_4_Evar(){}
+
+Status_4_Evar.prototype =
+{
+ classID: Components.ID("{33d0433d-07be-4dc4-87fd-954057310efd}"),
+ QueryInterface: XPCOMUtils.generateQI([
+ CI.nsISupportsWeakReference,
+ CI.nsIObserver,
+ CI.nsIStatus4Evar
+ ]),
+
+ prefs: null,
+
+ addonbarBorderStyle: false,
+ addonbarCloseButton: false,
+ addonbarWindowGripper: true,
+
+ advancedStatusDetectFullScreen: true,
+ advancedStatusDetectVideo: true,
+
+ downloadButtonAction: 1,
+ downloadButtonActionCommand: "",
+ downloadColorActive: null,
+ downloadColorPaused: null,
+ downloadForce: false,
+ downloadLabel: 0,
+ downloadLabelForce: true,
+ downloadNotifyAnimate: true,
+ downloadNotifyTimeout: 60000,
+ downloadProgress: 1,
+ downloadTooltip: 1,
+
+ firstRun: true,
+
+ progressToolbarCSS: null,
+ progressToolbarForce: false,
+ progressToolbarStyle: false,
+
+ status: 1,
+ statusDefault: true,
+ statusNetwork: true,
+ statusTimeout: 10000,
+ statusLinkOver: 1,
+ statusLinkOverDelayShow: 70,
+ statusLinkOverDelayHide: 150,
+
+ statusToolbarMaxLength: 0,
+
+ statusToolbarInvertMirror: false,
+ statusToolbarMouseMirror: true,
+
+ pref_registry:
+ {
+ "addonbar.borderStyle":
+ {
+ update: function()
+ {
+ this.addonbarBorderStyle = this.prefs.getBoolPref("addonbar.borderStyle");
+ },
+ updateWindow: function(win)
+ {
+ let browser_bottom_box = win.caligon.status4evar.getters.browserBottomBox;
+ if(browser_bottom_box)
+ {
+ this.setBoolElementAttribute(browser_bottom_box, "s4eboarder", this.addonbarBorderStyle);
+ }
+ }
+ },
+
+ "addonbar.closeButton":
+ {
+ update: function()
+ {
+ this.addonbarCloseButton = this.prefs.getBoolPref("addonbar.closeButton");
+ },
+ updateWindow: function(win)
+ {
+ let addonbar_close_button = win.caligon.status4evar.getters.addonbarCloseButton;
+ if(addonbar_close_button)
+ {
+ addonbar_close_button.hidden = !this.addonbarCloseButton;
+ }
+ }
+ },
+
+ "addonbar.windowGripper":
+ {
+ update: function()
+ {
+ this.addonbarWindowGripper = this.prefs.getBoolPref("addonbar.windowGripper");
+ },
+ updateWindow: function(win)
+ {
+ win.caligon.status4evar.toolbars.updateWindowGripper(true);
+ }
+ },
+
+ "advanced.status.detectFullScreen":
+ {
+ update: function()
+ {
+ this.advancedStatusDetectFullScreen = this.prefs.getBoolPref("advanced.status.detectFullScreen");
+ }
+ },
+
+ "advanced.status.detectVideo":
+ {
+ update: function()
+ {
+ this.advancedStatusDetectVideo = this.prefs.getBoolPref("advanced.status.detectVideo");
+ }
+ },
+
+ "download.button.action":
+ {
+ update: function()
+ {
+ this.downloadButtonAction = this.prefs.getIntPref("download.button.action");
+ },
+ updateWindow: function(win)
+ {
+ win.caligon.status4evar.downloadStatus.updateBinding();
+ }
+ },
+
+ "download.button.action.command":
+ {
+ update: function()
+ {
+ this.downloadButtonActionCommand = this.prefs.getCharPref("download.button.action.command");
+ }
+ },
+
+ "download.color.active":
+ {
+ update: function()
+ {
+ this.downloadColorActive = this.prefs.getCharPref("download.color.active");
+ },
+ updateDynamicStyle: function(sheet)
+ {
+ sheet.cssRules[2].style.backgroundColor = this.downloadColorActive;
+ }
+ },
+
+ "download.color.paused":
+ {
+ update: function()
+ {
+ this.downloadColorPaused = this.prefs.getCharPref("download.color.paused");
+ },
+ updateDynamicStyle: function(sheet)
+ {
+ sheet.cssRules[3].style.backgroundColor = this.downloadColorPaused;
+ }
+ },
+
+ "download.force":
+ {
+ update: function()
+ {
+ this.downloadForce = this.prefs.getBoolPref("download.force");
+ },
+ updateWindow: function(win)
+ {
+ let download_button = win.caligon.status4evar.getters.downloadButton;
+ if(download_button)
+ {
+ this.setBoolElementAttribute(download_button, "forcevisible", this.downloadForce);
+ }
+
+ let download_notify_anchor = win.caligon.status4evar.getters.downloadNotifyAnchor;
+ this.setBoolElementAttribute(download_notify_anchor, "forcevisible", this.downloadForce);
+ }
+ },
+
+ "download.label":
+ {
+ update: function()
+ {
+ this.downloadLabel = this.prefs.getIntPref("download.label");
+ },
+ updateWindow: function(win)
+ {
+ win.caligon.status4evar.downloadStatus.updateButton();
+ }
+ },
+
+ "download.label.force":
+ {
+ update: function()
+ {
+ this.downloadLabelForce = this.prefs.getBoolPref("download.label.force");
+ },
+ updateWindow: function(win)
+ {
+ let download_button = win.caligon.status4evar.getters.downloadButton;
+ if(download_button)
+ {
+ this.setBoolElementAttribute(download_button, "forcelabel", this.downloadLabelForce);
+ }
+ }
+ },
+
+ "download.notify.animate":
+ {
+ update: function()
+ {
+ this.downloadNotifyAnimate = this.prefs.getBoolPref("download.notify.animate");
+ }
+ },
+
+ "download.notify.timeout":
+ {
+ update: function()
+ {
+ this.downloadNotifyTimeout = (this.prefs.getIntPref("download.notify.timeout") * 1000);
+ }
+ },
+
+ "download.progress":
+ {
+ update: function()
+ {
+ this.downloadProgress = this.prefs.getIntPref("download.progress");
+ },
+ updateWindow: function(win)
+ {
+ win.caligon.status4evar.downloadStatus.updateButton();
+ }
+ },
+
+ "download.tooltip":
+ {
+ update: function()
+ {
+ this.downloadTooltip = this.prefs.getIntPref("download.tooltip");
+ },
+ updateWindow: function(win)
+ {
+ win.caligon.status4evar.downloadStatus.updateButton();
+ }
+ },
+
+ "progress.toolbar.css":
+ {
+ update: function()
+ {
+ this.progressToolbarCSS = this.prefs.getCharPref("progress.toolbar.css");
+ },
+ updateDynamicStyle: function(sheet)
+ {
+ sheet.cssRules[1].style.background = this.progressToolbarCSS;
+ }
+ },
+
+ "progress.toolbar.force":
+ {
+ update: function()
+ {
+ this.progressToolbarForce = this.prefs.getBoolPref("progress.toolbar.force");
+ },
+ updateWindow: function(win)
+ {
+ let toolbar_progress = win.caligon.status4evar.getters.toolbarProgress;
+ if(toolbar_progress)
+ {
+ this.setBoolElementAttribute(toolbar_progress, "forcevisible", this.progressToolbarForce);
+ }
+ }
+ },
+
+ "progress.toolbar.style":
+ {
+ update: function()
+ {
+ this.progressToolbarStyle = this.prefs.getBoolPref("progress.toolbar.style");
+ },
+ updateWindow: function(win)
+ {
+ let toolbar_progress = win.caligon.status4evar.getters.toolbarProgress;
+ if(toolbar_progress)
+ {
+ this.setBoolElementAttribute(toolbar_progress, "s4estyle", this.progressToolbarStyle);
+ }
+ }
+ },
+
+ "status":
+ {
+ update: function()
+ {
+ this.status = this.prefs.getIntPref("status");
+ },
+ updateWindow: function(win)
+ {
+ win.caligon.status4evar.statusService.clearStatusField();
+ win.caligon.status4evar.statusService.updateStatusField(true);
+ }
+ },
+
+ "status.default":
+ {
+ update: function()
+ {
+ this.statusDefault = this.prefs.getBoolPref("status.default");
+ },
+ updateWindow: function(win)
+ {
+ win.caligon.status4evar.statusService.buildTextOrder();
+ win.caligon.status4evar.statusService.updateStatusField(true);
+ }
+ },
+
+ "status.linkOver":
+ {
+ update: function()
+ {
+ this.statusLinkOver = this.prefs.getIntPref("status.linkOver");
+ }
+ },
+
+ "status.linkOver.delay.show":
+ {
+ update: function()
+ {
+ this.statusLinkOverDelayShow = this.prefs.getIntPref("status.linkOver.delay.show");
+ }
+ },
+
+ "status.linkOver.delay.hide":
+ {
+ update: function()
+ {
+ this.statusLinkOverDelayHide = this.prefs.getIntPref("status.linkOver.delay.hide");
+ }
+ },
+
+ "status.network":
+ {
+ update: function()
+ {
+ this.statusNetwork = this.prefs.getBoolPref("status.network");
+ },
+ updateWindow: function(win)
+ {
+ win.caligon.status4evar.statusService.buildTextOrder();
+ }
+ },
+
+ "status.network.xhr":
+ {
+ update: function()
+ {
+ this.statusNetworkXHR = this.prefs.getBoolPref("status.network.xhr");
+ },
+ updateWindow: function(win)
+ {
+ win.caligon.status4evar.statusService.buildTextOrder();
+ }
+ },
+
+ "status.timeout":
+ {
+ update: function()
+ {
+ this.statusTimeout = (this.prefs.getIntPref("status.timeout") * 1000);
+ },
+ updateWindow: function(win)
+ {
+ win.caligon.status4evar.statusService.updateStatusField(true);
+ }
+ },
+
+ "status.toolbar.maxLength":
+ {
+ update: function()
+ {
+ this.statusToolbarMaxLength = this.prefs.getIntPref("status.toolbar.maxLength");
+ },
+ updateWindow: function(win)
+ {
+ let status_widget = win.caligon.status4evar.getters.statusWidget;
+ if(status_widget)
+ {
+ status_widget.maxWidth = (this.statusToolbarMaxLength || "");
+ }
+ }
+ },
+
+ "status.popup.invertMirror":
+ {
+ update: function()
+ {
+ this.statusToolbarInvertMirror = this.prefs.getBoolPref("status.popup.invertMirror");
+ },
+ updateWindow: function(win)
+ {
+ let statusOverlay = win.caligon.status4evar.getters.statusOverlay;
+ if(statusOverlay)
+ {
+ statusOverlay.invertMirror = this.statusToolbarInvertMirror;
+ }
+ }
+ },
+
+ "status.popup.mouseMirror":
+ {
+ update: function()
+ {
+ this.statusToolbarMouseMirror = this.prefs.getBoolPref("status.popup.mouseMirror");
+ },
+ updateWindow: function(win)
+ {
+ let statusOverlay = win.caligon.status4evar.getters.statusOverlay;
+ if(statusOverlay)
+ {
+ statusOverlay.mouseMirror = this.statusToolbarMouseMirror;
+ }
+ }
+ }
+
+ },
+
+ // nsIObserver
+ observe: function(subject, topic, data)
+ {
+ try
+ {
+ switch(topic)
+ {
+ case "profile-after-change":
+ this.startup();
+ break;
+ case "quit-application":
+ this.shutdown();
+ break;
+ case "nsPref:changed":
+ this.updatePref(data, true);
+ break;
+ }
+ }
+ catch(e)
+ {
+ CU.reportError(e);
+ }
+ },
+
+ startup: function()
+ {
+ this.prefs = Services.prefs.getBranch("status4evar.").QueryInterface(CI.nsIPrefBranch2);
+
+ this.firstRun = this.prefs.getBoolPref("firstRun");
+ if(this.firstRun)
+ {
+ this.prefs.setBoolPref("firstRun", false);
+ }
+
+ this.migrate();
+
+ for(let pref in this.pref_registry)
+ {
+ let pro = this.pref_registry[pref];
+
+ pro.update = pro.update.bind(this);
+ if(pro.updateWindow)
+ {
+ pro.updateWindow = pro.updateWindow.bind(this);
+ }
+ if(pro.updateDynamicStyle)
+ {
+ pro.updateDynamicStyle = pro.updateDynamicStyle.bind(this);
+ }
+
+ this.prefs.addObserver(pref, this, true);
+
+ this.updatePref(pref, false);
+ }
+
+ Services.obs.addObserver(this, "quit-application", true);
+ },
+
+ shutdown: function()
+ {
+ Services.obs.removeObserver(this, "quit-application");
+
+ for(let pref in this.pref_registry)
+ {
+ this.prefs.removeObserver(pref, this);
+ }
+
+ this.prefs = null;
+ },
+
+ migrate: function()
+ {
+ if(!this.firstRun)
+ {
+ let migration = 0;
+ try
+ {
+ migration = this.prefs.getIntPref("migration");
+ }
+ catch(e) {}
+
+ switch(migration)
+ {
+ case 5:
+ this.migrateBoolPref("status.detectFullScreen", "advanced.status.detectFullScreen");
+ case 6:
+ let oldDownloadAction = this.prefs.getIntPref("download.button.action");
+ let newDownloadAction = 1;
+ switch(oldDownloadAction)
+ {
+ case 2:
+ newDownloadAction = 1;
+ break;
+ case 3:
+ newDownloadAction = 2;
+ break;
+ case 4:
+ newDownloadAction = 1;
+ break;
+ }
+ this.prefs.setIntPref("download.button.action", newDownloadAction);
+ case 7:
+ let progressLocation = this.prefs.getIntPref("status");
+ if (progressLocation == 2)
+ this.prefs.setIntPref("status", 1);
+ let linkOverLocation = this.prefs.getIntPref("status.linkOver");
+ if (linkOverLocation == 2)
+ this.prefs.setIntPref("status.linkOver", 1);
+ break;
+ case CURRENT_MIGRATION:
+ break;
+ }
+ }
+
+ this.prefs.setIntPref("migration", CURRENT_MIGRATION);
+ },
+
+ migrateBoolPref: function(oldPref, newPref)
+ {
+ if(this.prefs.prefHasUserValue(oldPref))
+ {
+ this.prefs.setBoolPref(newPref, this.prefs.getBoolPref(oldPref));
+ this.prefs.clearUserPref(oldPref);
+ }
+ },
+
+ migrateIntPref: function(oldPref, newPref)
+ {
+ if(this.prefs.prefHasUserValue(oldPref))
+ {
+ this.prefs.setIntPref(newPref, this.prefs.getIntPref(oldPref));
+ this.prefs.clearUserPref(oldPref);
+ }
+ },
+
+ migrateCharPref: function(oldPref, newPref)
+ {
+ if(this.prefs.prefHasUserValue(oldPref))
+ {
+ this.prefs.setCharPref(newPref, this.prefs.getCharPref(oldPref));
+ this.prefs.clearUserPref(oldPref);
+ }
+ },
+
+ updatePref: function(pref, updateWindows)
+ {
+ if(!(pref in this.pref_registry))
+ {
+ return;
+ }
+ let pro = this.pref_registry[pref];
+
+ pro.update();
+
+ if(updateWindows)
+ {
+ let windowsEnum = Services.wm.getEnumerator("navigator:browser");
+ while(windowsEnum.hasMoreElements())
+ {
+ this.updateWindow(windowsEnum.getNext(), pro);
+ }
+ }
+
+ if(pro.alsoUpdate)
+ {
+ pro.alsoUpdate.forEach(function (alsoPref)
+ {
+ this.updatePref(alsoPref);
+ }, this);
+ }
+ },
+
+ // Updtate a browser window
+ updateWindow: function(win, pro)
+ {
+ if(!(win instanceof CI.nsIDOMWindow)
+ || !(win.document.documentElement.getAttribute("windowtype") == "navigator:browser"))
+ {
+ return;
+ }
+
+ if(pro)
+ {
+ this.handlePro(win, pro);
+ }
+ else
+ {
+ for(let pref in this.pref_registry)
+ {
+ this.handlePro(win, this.pref_registry[pref]);
+ }
+ }
+ },
+
+ handlePro: function(win, pro)
+ {
+ if(pro.updateWindow)
+ {
+ pro.updateWindow(win);
+ }
+
+ if(pro.updateDynamicStyle)
+ {
+ let styleSheets = win.document.styleSheets;
+ for(let i = 0; i < styleSheets.length; i++)
+ {
+ let styleSheet = styleSheets[i];
+ if(styleSheet.href == "chrome://browser/skin/statusbar/dynamic.css")
+ {
+ pro.updateDynamicStyle(styleSheet);
+ break;
+ }
+ }
+ }
+ },
+
+ setBoolElementAttribute: function(elem, attr, val)
+ {
+ if(val)
+ {
+ elem.setAttribute(attr, "true");
+ }
+ else
+ {
+ elem.removeAttribute(attr);
+ }
+ },
+
+ setStringElementAttribute: function(elem, attr, val)
+ {
+ if(val)
+ {
+ elem.setAttribute(attr, val);
+ }
+ else
+ {
+ elem.removeAttribute(attr);
+ }
+ },
+
+ resetPrefs: function()
+ {
+ let childPrefs = this.prefs.getChildList("");
+ childPrefs.forEach(function(pref)
+ {
+ if(this.prefs.prefHasUserValue(pref))
+ {
+ this.prefs.clearUserPref(pref);
+ }
+ }, this);
+ }
+};
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([Status_4_Evar]);
+
diff --git a/components/statusbar/status4evar.manifest b/components/statusbar/status4evar.manifest
new file mode 100644
index 0000000..4bcf697
--- /dev/null
+++ b/components/statusbar/status4evar.manifest
@@ -0,0 +1,3 @@
+component {33d0433d-07be-4dc4-87fd-954057310efd} status4evar.js
+contract @caligonstudios.com/status4evar;1 {33d0433d-07be-4dc4-87fd-954057310efd}
+category profile-after-change Status-4-Evar @caligonstudios.com/status4evar;1
diff --git a/components/sync/aboutSyncTabs-bindings.xml b/components/sync/aboutSyncTabs-bindings.xml
new file mode 100644
index 0000000..e610820
--- /dev/null
+++ b/components/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/components/sync/aboutSyncTabs.css b/components/sync/aboutSyncTabs.css
new file mode 100644
index 0000000..5a35317
--- /dev/null
+++ b/components/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/components/sync/aboutSyncTabs.js b/components/sync/aboutSyncTabs.js
new file mode 100644
index 0000000..410494b
--- /dev/null
+++ b/components/sync/aboutSyncTabs.js
@@ -0,0 +1,313 @@
+/* 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 Cu = Components.utils;
+
+Cu.import("resource://services-common/utils.js");
+Cu.import("resource://services-sync/main.js");
+Cu.import("resource:///modules/PlacesUIUtils.jsm");
+Cu.import("resource://gre/modules/PlacesUtils.jsm", this);
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var 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");
+ },
+
+ 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().includes(val) &&
+ !item.getAttribute("title").toLowerCase().includes(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);
+ }
+ },
+
+ getIcon: function (iconUri, defaultIcon) {
+ try {
+ let iconURI = Weave.Utils.makeURI(iconUri);
+ return PlacesUtils.favicons.getFaviconLinkForIcon(iconURI).spec;
+ } catch (ex) {
+ // Do nothing.
+ }
+
+ // Just give the provided default icon or the system's default.
+ return defaultIcon || PlacesUtils.favicons.defaultFavicon.spec;
+ },
+
+ _waitingForBuildList: false,
+
+ _buildListRequested: false,
+
+ buildList: function (force) {
+ if (this._waitingForBuildList) {
+ this._buildListRequested = true;
+ return;
+ }
+
+ this._waitingForBuildList = true;
+ this._buildListRequested = false;
+
+ this._clearTabList();
+
+ if (Weave.Service.isLoggedIn && this._refetchTabs(force)) {
+ this._generateWeaveTabList();
+ } else {
+ //XXXzpao We should say something about not being logged in & not having data
+ // or tell the appropriate condition. (bug 583344)
+ }
+
+ function complete() {
+ this._waitingForBuildList = false;
+ if (this._buildListRequested) {
+ CommonUtils.nextTick(this.buildList, this);
+ }
+ }
+
+ complete();
+ },
+
+ _clearTabList: function () {
+ 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);
+ }
+ }
+ },
+
+ _generateWeaveTabList: function () {
+ let engine = Weave.Service.engineManager.get("tabs");
+ let list = this._tabsList;
+
+ let seenURLs = new Set();
+ let localURLs = engine.getOpenURLs();
+
+ 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;
+
+ client.tabs.forEach(function({title, urlHistory, icon}) {
+ let url = urlHistory[0];
+ if (!url || localURLs.has(url) || seenURLs.has(url)) {
+ return;
+ }
+ seenURLs.add(url);
+
+ 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: this.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, we 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.buildList(false);
+ }
+ 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/components/sync/aboutSyncTabs.xul b/components/sync/aboutSyncTabs.xul
new file mode 100644
index 0000000..a4aa003
--- /dev/null
+++ b/components/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/components/sync/addDevice.js b/components/sync/addDevice.js
new file mode 100644
index 0000000..0390d43
--- /dev/null
+++ b/components/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/. */
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+var 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;
+
+var 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/components/sync/addDevice.xul b/components/sync/addDevice.xul
new file mode 100644
index 0000000..f2371aa
--- /dev/null
+++ b/components/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="http://www.palemoon.org/sync/help/easy-setup.shtml"/>
+ </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/components/sync/genericChange.js b/components/sync/genericChange.js
new file mode 100644
index 0000000..df66391
--- /dev/null
+++ b/components/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/. */
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+
+Components.utils.import("resource://services-sync/main.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var 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/components/sync/genericChange.xul b/components/sync/genericChange.xul
new file mode 100644
index 0000000..3c0b2cd
--- /dev/null
+++ b/components/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="http://www.palemoon.org/sync/help/recoverykey.shtml">
+ &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/components/sync/jar.mn b/components/sync/jar.mn
new file mode 100644
index 0000000..3782038
--- /dev/null
+++ b/components/sync/jar.mn
@@ -0,0 +1,22 @@
+# 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/sync/aboutSyncTabs.xul
+ content/browser/sync/aboutSyncTabs.js
+ content/browser/sync/aboutSyncTabs.css
+ content/browser/sync/aboutSyncTabs-bindings.xml
+ content/browser/sync/setup.xul
+ content/browser/sync/addDevice.js
+ content/browser/sync/addDevice.xul
+ content/browser/sync/setup.js
+ content/browser/sync/genericChange.xul
+ content/browser/sync/genericChange.js
+ content/browser/sync/key.xhtml
+ content/browser/sync/notification.xml
+ content/browser/sync/quota.xul
+ content/browser/sync/quota.js
+ content/browser/sync/utils.js
+ content/browser/sync/progress.js
+ content/browser/sync/progress.xhtml \ No newline at end of file
diff --git a/components/sync/key.xhtml b/components/sync/key.xhtml
new file mode 100644
index 0000000..92abf0e
--- /dev/null
+++ b/components/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="http://www.palemoon.org/sync/">http://www.palemoon.org/sync/</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/components/sync/moz.build b/components/sync/moz.build
new file mode 100644
index 0000000..2d64d50
--- /dev/null
+++ b/components/sync/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/.
+
+JAR_MANIFESTS += ['jar.mn']
+
diff --git a/components/sync/notification.xml b/components/sync/notification.xml
new file mode 100644
index 0000000..8ac881e
--- /dev/null
+++ b/components/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 close-icon 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,type"/>
+ <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/components/sync/progress.js b/components/sync/progress.js
new file mode 100644
index 0000000..101160f
--- /dev/null
+++ b/components/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");
+
+var gProgressBar;
+var 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/components/sync/progress.xhtml b/components/sync/progress.xhtml
new file mode 100644
index 0000000..d403cb2
--- /dev/null
+++ b/components/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/components/sync/quota.js b/components/sync/quota.js
new file mode 100644
index 0000000..b416a44
--- /dev/null
+++ b/components/sync/quota.js
@@ -0,0 +1,247 @@
+/* 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");
+
+var 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);
+ }
+
+};
+
+var 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 cell = this.treeBox.getCellAt(event.clientX, event.clientY);
+ if (cell.col && cell.col.id == "enabled")
+ this.toggle(cell.row);
+ },
+
+ /*
+ * 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);
+ },
+
+ /*
+ * Return a list of engines (or rather their pref names) that should be
+ * disabled.
+ */
+ getEnginesToDisable: function getEnginesToDisable() {
+ // Tycho: return [coll.name for each (coll in this._collections) if (!coll.enabled)];
+ let engines = [];
+ for each (let coll in this._collections) {
+ if (!coll.enabled) {
+ engines.push(coll.name);
+ }
+ }
+ return engines;
+ },
+
+ // 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/components/sync/quota.xul b/components/sync/quota.xul
new file mode 100644
index 0000000..99e6ed7
--- /dev/null
+++ b/components/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/components/sync/setup.js b/components/sync/setup.js
new file mode 100644
index 0000000..e8d67a5
--- /dev/null
+++ b/components/sync/setup.js
@@ -0,0 +1,1071 @@
+/* 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 Ci = Components.interfaces;
+var Cc = Components.classes;
+var Cr = Components.results;
+var 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;
+ }
+ },
+
+ 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");
+ 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]));
+ }
+
+ // XXX: Addons syncing is currently not operational;
+ // Make doubly-sure to always disable addons syncing pref
+ Weave.Svc.Prefs.set("engine.addons", false);
+
+ 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/components/sync/setup.xul b/components/sync/setup.xul
new file mode 100644
index 0000000..cf2cc77
--- /dev/null
+++ b/components/sync/setup.xul
@@ -0,0 +1,491 @@
+<?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="http://www.palemoon.org/sync/help/easy-setup.shtml"/>
+ </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>
+ <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="http://www.palemoon.org/sync/help/easy-setup.shtml"/>
+ </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="http://www.palemoon.org/sync/help/recoverykey.shtml">
+ &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="false"
+ hidden="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/components/sync/utils.js b/components/sync/utils.js
new file mode 100644
index 0000000..d41ecf1
--- /dev/null
+++ b/components/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 0o600 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.
+var 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/config/mozconfig b/config/mozconfig
new file mode 100644
index 0000000..ad2e3a4
--- /dev/null
+++ b/config/mozconfig
@@ -0,0 +1,9 @@
+# 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 specifies the build flags for Firefox. You can use it by adding:
+# . $topsrcdir/browser/config/mozconfig
+# to the top of your mozconfig file.
+
+ac_add_options --enable-application=browser
diff --git a/config/mozconfigs/common b/config/mozconfigs/common
new file mode 100644
index 0000000..febf562
--- /dev/null
+++ b/config/mozconfigs/common
@@ -0,0 +1,7 @@
+# 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 included by all browser mozconfigs
+
+. "$topsrcdir/build/mozconfig.common"
diff --git a/config/mozconfigs/linux32/beta b/config/mozconfigs/linux32/beta
new file mode 100644
index 0000000..6a42104
--- /dev/null
+++ b/config/mozconfigs/linux32/beta
@@ -0,0 +1,7 @@
+. "$topsrcdir/browser/config/mozconfigs/linux32/common-opt"
+
+ac_add_options --enable-official-branding
+
+mk_add_options MOZ_PGO=1
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/config/mozconfigs/linux32/common-opt b/config/mozconfigs/linux32/common-opt
new file mode 100644
index 0000000..9e5e78d
--- /dev/null
+++ b/config/mozconfigs/linux32/common-opt
@@ -0,0 +1,19 @@
+# This file is sourced by nightly, beta, and release mozconfigs.
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-update-packaging
+ac_add_options --with-google-api-keyfile=/builds/gapi.data
+
+. $topsrcdir/build/unix/mozconfig.linux32
+
+# Avoid dependency on libstdc++ 4.5
+ac_add_options --enable-stdcxx-compat
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+# Treat warnings as errors in directories with FAIL_ON_WARNINGS.
+ac_add_options --enable-warnings-as-errors
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
diff --git a/config/mozconfigs/linux32/debug b/config/mozconfigs/linux32/debug
new file mode 100644
index 0000000..2328367
--- /dev/null
+++ b/config/mozconfigs/linux32/debug
@@ -0,0 +1,22 @@
+ac_add_options --enable-debug
+ac_add_options --enable-trace-malloc
+ac_add_options --enable-signmar
+
+. $topsrcdir/build/unix/mozconfig.linux32
+
+# Avoid dependency on libstdc++ 4.5
+ac_add_options --enable-stdcxx-compat
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+#Use ccache
+ac_add_options --with-ccache=/usr/bin/ccache
+
+# Treat warnings as errors in directories with FAIL_ON_WARNINGS.
+ac_add_options --enable-warnings-as-errors
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/config/mozconfigs/linux32/debug-asan b/config/mozconfigs/linux32/debug-asan
new file mode 100644
index 0000000..4766ce2
--- /dev/null
+++ b/config/mozconfigs/linux32/debug-asan
@@ -0,0 +1,20 @@
+# Use at least -O1 for optimization to avoid stack space
+# exhaustions caused by Clang function inlining.
+ac_add_options --enable-debug
+ac_add_options --enable-optimize="-O1"
+
+# ASan specific options on Linux
+ac_add_options --enable-valgrind
+
+. $topsrcdir/build/unix/mozconfig.asan
+
+# Avoid dependency on libstdc++ 4.5
+ac_add_options --enable-stdcxx-compat
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
+
+# Need this to prevent name conflicts with the normal nightly build packages
+export MOZ_PKG_SPECIAL=asan
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/config/mozconfigs/linux32/l10n-mozconfig b/config/mozconfigs/linux32/l10n-mozconfig
new file mode 100644
index 0000000..d8d6228
--- /dev/null
+++ b/config/mozconfigs/linux32/l10n-mozconfig
@@ -0,0 +1,12 @@
+ac_add_options --with-l10n-base=../../l10n
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-update-packaging
+
+# Avoid dependency on libstdc++ 4.5
+ac_add_options --enable-stdcxx-compat
+
+. $topsrcdir/build/unix/mozconfig.linux32
+
+export MOZILLA_OFFICIAL=1
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/config/mozconfigs/linux32/release b/config/mozconfigs/linux32/release
new file mode 100644
index 0000000..b32f1b3
--- /dev/null
+++ b/config/mozconfigs/linux32/release
@@ -0,0 +1,13 @@
+# This make file should be identical to the beta mozconfig, apart from the
+# safeguard below
+. "$topsrcdir/browser/config/mozconfigs/linux32/common-opt"
+
+ac_add_options --enable-official-branding
+
+mk_add_options MOZ_PGO=1
+
+# safeguard against someone forgetting to re-set EARLY_BETA_OR_EARLIER in
+# defines.sh during the beta cycle
+export BUILDING_RELEASE=1
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/config/mozconfigs/linux32/valgrind b/config/mozconfigs/linux32/valgrind
new file mode 100644
index 0000000..a39b1b0
--- /dev/null
+++ b/config/mozconfigs/linux32/valgrind
@@ -0,0 +1,11 @@
+. $topsrcdir/browser/config/mozconfigs/linux32/nightly
+
+ac_add_options --enable-valgrind
+ac_add_options --disable-jemalloc
+ac_add_options --disable-elf-hack
+ac_add_options --enable-optimize="-g -O -freorder-blocks"
+ac_add_options --disable-install-strip
+
+# Include the override mozconfig again (even though the above includes it)
+# since it's supposed to override everything.
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/config/mozconfigs/linux64/beta b/config/mozconfigs/linux64/beta
new file mode 100644
index 0000000..7c05460
--- /dev/null
+++ b/config/mozconfigs/linux64/beta
@@ -0,0 +1,7 @@
+. "$topsrcdir/browser/config/mozconfigs/linux64/common-opt"
+
+ac_add_options --enable-official-branding
+
+mk_add_options MOZ_PGO=1
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/config/mozconfigs/linux64/common-opt b/config/mozconfigs/linux64/common-opt
new file mode 100644
index 0000000..c448460
--- /dev/null
+++ b/config/mozconfigs/linux64/common-opt
@@ -0,0 +1,19 @@
+# This file is sourced by the nightly, beta, and release mozconfigs.
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-update-packaging
+ac_add_options --with-google-api-keyfile=/builds/gapi.data
+
+. $topsrcdir/build/unix/mozconfig.linux
+
+# Avoid dependency on libstdc++ 4.5
+ac_add_options --enable-stdcxx-compat
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+# Treat warnings as errors in directories with FAIL_ON_WARNINGS.
+ac_add_options --enable-warnings-as-errors
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
diff --git a/config/mozconfigs/linux64/debug b/config/mozconfigs/linux64/debug
new file mode 100644
index 0000000..c917660
--- /dev/null
+++ b/config/mozconfigs/linux64/debug
@@ -0,0 +1,22 @@
+ac_add_options --enable-debug
+ac_add_options --enable-trace-malloc
+ac_add_options --enable-signmar
+
+. $topsrcdir/build/unix/mozconfig.linux
+
+# Avoid dependency on libstdc++ 4.5
+ac_add_options --enable-stdcxx-compat
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+# Use ccache
+ac_add_options --with-ccache=/usr/bin/ccache
+
+# Treat warnings as errors in directories with FAIL_ON_WARNINGS.
+ac_add_options --enable-warnings-as-errors
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/config/mozconfigs/linux64/debug-asan b/config/mozconfigs/linux64/debug-asan
new file mode 100644
index 0000000..4766ce2
--- /dev/null
+++ b/config/mozconfigs/linux64/debug-asan
@@ -0,0 +1,20 @@
+# Use at least -O1 for optimization to avoid stack space
+# exhaustions caused by Clang function inlining.
+ac_add_options --enable-debug
+ac_add_options --enable-optimize="-O1"
+
+# ASan specific options on Linux
+ac_add_options --enable-valgrind
+
+. $topsrcdir/build/unix/mozconfig.asan
+
+# Avoid dependency on libstdc++ 4.5
+ac_add_options --enable-stdcxx-compat
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
+
+# Need this to prevent name conflicts with the normal nightly build packages
+export MOZ_PKG_SPECIAL=asan
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/config/mozconfigs/linux64/debug-static-analysis-clang b/config/mozconfigs/linux64/debug-static-analysis-clang
new file mode 100644
index 0000000..214d156
--- /dev/null
+++ b/config/mozconfigs/linux64/debug-static-analysis-clang
@@ -0,0 +1,15 @@
+. "$topsrcdir/build/mozconfig.common"
+
+ac_add_options --enable-debug
+
+# Use Clang as specified in manifest
+export CC="$topsrcdir/clang/bin/clang"
+export CXX="$topsrcdir/clang/bin/clang++"
+
+# Add the static checker
+ac_add_options --enable-clang-plugin
+
+# Avoid dependency on libstdc++ 4.5
+ac_add_options --enable-stdcxx-compat
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/config/mozconfigs/linux64/l10n-mozconfig b/config/mozconfigs/linux64/l10n-mozconfig
new file mode 100644
index 0000000..52470c5
--- /dev/null
+++ b/config/mozconfigs/linux64/l10n-mozconfig
@@ -0,0 +1,12 @@
+ac_add_options --with-l10n-base=../../l10n
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-update-packaging
+
+# Avoid dependency on libstdc++ 4.5
+ac_add_options --enable-stdcxx-compat
+
+. $topsrcdir/build/unix/mozconfig.linux
+
+export MOZILLA_OFFICIAL=1
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/config/mozconfigs/linux64/release b/config/mozconfigs/linux64/release
new file mode 100644
index 0000000..e52f33f
--- /dev/null
+++ b/config/mozconfigs/linux64/release
@@ -0,0 +1,13 @@
+# This make file should be identical to the beta mozconfig, apart from the
+# safeguard below
+. "$topsrcdir/browser/config/mozconfigs/linux64/common-opt"
+
+ac_add_options --enable-official-branding
+
+mk_add_options MOZ_PGO=1
+
+# safeguard against someone forgetting to re-set EARLY_BETA_OR_EARLIER in
+# defines.sh during the beta cycle
+export BUILDING_RELEASE=1
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/config/mozconfigs/linux64/valgrind b/config/mozconfigs/linux64/valgrind
new file mode 100644
index 0000000..ccf8971
--- /dev/null
+++ b/config/mozconfigs/linux64/valgrind
@@ -0,0 +1,11 @@
+. $topsrcdir/browser/config/mozconfigs/linux64/nightly
+
+ac_add_options --enable-valgrind
+ac_add_options --disable-jemalloc
+ac_add_options --disable-elf-hack
+ac_add_options --enable-optimize="-g -O -freorder-blocks"
+ac_add_options --disable-install-strip
+
+# Include the override mozconfig again (even though the above includes it)
+# since it's supposed to override everything.
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/config/mozconfigs/macosx-universal/beta b/config/mozconfigs/macosx-universal/beta
new file mode 100644
index 0000000..cc2f44d
--- /dev/null
+++ b/config/mozconfigs/macosx-universal/beta
@@ -0,0 +1,5 @@
+. "$topsrcdir/browser/config/mozconfigs/macosx-universal/common-opt"
+
+ac_add_options --enable-official-branding
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/config/mozconfigs/macosx-universal/common-opt b/config/mozconfigs/macosx-universal/common-opt
new file mode 100644
index 0000000..414f690
--- /dev/null
+++ b/config/mozconfigs/macosx-universal/common-opt
@@ -0,0 +1,19 @@
+# This file is sourced by the nightly, beta, and release mozconfigs.
+
+. $topsrcdir/build/macosx/universal/mozconfig
+
+# Universal builds override the default of browser (bug 575283 comment 29)
+ac_add_options --enable-application=browser
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-update-packaging
+ac_add_options --with-google-api-keyfile=/builds/gapi.data
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+# Treat warnings as errors in directories with FAIL_ON_WARNINGS.
+ac_add_options --enable-warnings-as-errors
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
diff --git a/config/mozconfigs/macosx-universal/l10n-mozconfig b/config/mozconfigs/macosx-universal/l10n-mozconfig
new file mode 100644
index 0000000..2f88f19
--- /dev/null
+++ b/config/mozconfigs/macosx-universal/l10n-mozconfig
@@ -0,0 +1,11 @@
+. "$topsrcdir/browser/config/mozconfigs/common"
+
+ac_add_options --with-l10n-base=../../../l10n
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-update-packaging
+ac_add_options --with-macbundlename-prefix=Firefox
+ac_add_options --with-ccache
+
+export MOZILLA_OFFICIAL=1
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/config/mozconfigs/macosx-universal/release b/config/mozconfigs/macosx-universal/release
new file mode 100644
index 0000000..ab8a469
--- /dev/null
+++ b/config/mozconfigs/macosx-universal/release
@@ -0,0 +1,11 @@
+# This make file should be identical to the beta mozconfig, apart from the
+# safeguard below
+. "$topsrcdir/browser/config/mozconfigs/macosx-universal/common-opt"
+
+ac_add_options --enable-official-branding
+
+# safeguard against someone forgetting to re-set EARLY_BETA_OR_EARLIER in
+# defines.sh during the beta cycle
+export BUILDING_RELEASE=1
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/config/mozconfigs/macosx64/debug b/config/mozconfigs/macosx64/debug
new file mode 100644
index 0000000..ebf8099
--- /dev/null
+++ b/config/mozconfigs/macosx64/debug
@@ -0,0 +1,19 @@
+. $topsrcdir/build/macosx/mozconfig.common
+
+ac_add_options --enable-debug
+ac_add_options --enable-trace-malloc
+ac_add_options --enable-accessibility
+ac_add_options --enable-signmar
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+ac_add_options --with-macbundlename-prefix=Firefox
+
+# Treat warnings as errors in directories with FAIL_ON_WARNINGS.
+ac_add_options --enable-warnings-as-errors
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/config/mozconfigs/macosx64/debug-asan b/config/mozconfigs/macosx64/debug-asan
new file mode 100644
index 0000000..97ced38
--- /dev/null
+++ b/config/mozconfigs/macosx64/debug-asan
@@ -0,0 +1,16 @@
+. $topsrcdir/build/unix/mozconfig.asan
+
+ac_add_options --enable-application=browser
+ac_add_options --enable-debug
+ac_add_options --enable-optimize="-O1"
+ac_add_options --enable-accessibility
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
+
+ac_add_options --with-macbundlename-prefix=Firefox
+
+# Need this to prevent name conflicts with the normal nightly build packages
+export MOZ_PKG_SPECIAL=asan
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/config/mozconfigs/macosx64/l10n-mozconfig b/config/mozconfigs/macosx64/l10n-mozconfig
new file mode 100644
index 0000000..b2cfa21
--- /dev/null
+++ b/config/mozconfigs/macosx64/l10n-mozconfig
@@ -0,0 +1,8 @@
+. "$topsrcdir/browser/config/mozconfigs/common"
+
+ac_add_options --with-l10n-base=../../l10n
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-update-packaging
+ac_add_options --with-ccache
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/config/mozconfigs/win32/beta b/config/mozconfigs/win32/beta
new file mode 100644
index 0000000..322af12
--- /dev/null
+++ b/config/mozconfigs/win32/beta
@@ -0,0 +1,7 @@
+. "$topsrcdir/browser/config/mozconfigs/win32/common-opt"
+
+mk_add_options MOZ_PGO=1
+
+ac_add_options --enable-official-branding
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/config/mozconfigs/win32/common-opt b/config/mozconfigs/win32/common-opt
new file mode 100644
index 0000000..040f13a
--- /dev/null
+++ b/config/mozconfigs/win32/common-opt
@@ -0,0 +1,33 @@
+# This file is sourced by the nightly, beta, and release mozconfigs.
+
+. "$topsrcdir/browser/config/mozconfigs/common"
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-update-packaging
+ac_add_options --enable-jemalloc
+if [ -f /c/builds/gapi.data ]; then
+ _gapi_keyfile=/c/builds/gapi.data
+else
+ _gapi_keyfile=/e/builds/gapi.data
+fi
+ac_add_options --with-google-api-keyfile=${_gapi_keyfile}
+
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+if test -z "${_PYMAKE}"; then
+ mk_add_options MOZ_MAKE_FLAGS=-j1
+fi
+
+if test "$PROCESSOR_ARCHITECTURE" = "AMD64" -o "$PROCESSOR_ARCHITEW6432" = "AMD64"; then
+ . $topsrcdir/build/win32/mozconfig.vs2010-win64
+else
+ . $topsrcdir/build/win32/mozconfig.vs2010
+fi
+
+# Treat warnings as errors in directories with FAIL_ON_WARNINGS.
+ac_add_options --enable-warnings-as-errors
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
diff --git a/config/mozconfigs/win32/debug b/config/mozconfigs/win32/debug
new file mode 100644
index 0000000..3b4dcc1
--- /dev/null
+++ b/config/mozconfigs/win32/debug
@@ -0,0 +1,26 @@
+. "$topsrcdir/browser/config/mozconfigs/common"
+
+ac_add_options --enable-debug
+ac_add_options --enable-trace-malloc
+ac_add_options --enable-signmar
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+if test -z "${_PYMAKE}"; then
+ mk_add_options MOZ_MAKE_FLAGS=-j1
+fi
+
+if test "$PROCESSOR_ARCHITECTURE" = "AMD64" -o "$PROCESSOR_ARCHITEW6432" = "AMD64"; then
+ . $topsrcdir/build/win32/mozconfig.vs2010-win64
+else
+ . $topsrcdir/build/win32/mozconfig.vs2010
+fi
+
+# Treat warnings as errors in directories with FAIL_ON_WARNINGS.
+ac_add_options --enable-warnings-as-errors
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/config/mozconfigs/win32/l10n-mozconfig b/config/mozconfigs/win32/l10n-mozconfig
new file mode 100644
index 0000000..4113d99
--- /dev/null
+++ b/config/mozconfigs/win32/l10n-mozconfig
@@ -0,0 +1,16 @@
+. "$topsrcdir/browser/config/mozconfigs/common"
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-update-packaging
+ac_add_options --with-l10n-base=../../l10n
+ac_add_options --with-windows-version=601
+
+export MOZILLA_OFFICIAL=1
+
+if test "$PROCESSOR_ARCHITECTURE" = "AMD64" -o "$PROCESSOR_ARCHITEW6432" = "AMD64"; then
+ . $topsrcdir/build/win32/mozconfig.vs2010-win64
+else
+ . $topsrcdir/build/win32/mozconfig.vs2010
+fi
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/config/mozconfigs/win32/release b/config/mozconfigs/win32/release
new file mode 100644
index 0000000..7feb125
--- /dev/null
+++ b/config/mozconfigs/win32/release
@@ -0,0 +1,13 @@
+# This make file should be identical to the beta mozconfig, apart from the
+# safeguard below
+. "$topsrcdir/browser/config/mozconfigs/win32/common-opt"
+
+mk_add_options MOZ_PGO=1
+
+ac_add_options --enable-official-branding
+
+# safeguard against someone forgetting to re-set EARLY_BETA_OR_EARLIER in
+# defines.sh during the beta cycle
+export BUILDING_RELEASE=1
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/config/mozconfigs/win64/debug b/config/mozconfigs/win64/debug
new file mode 100644
index 0000000..7320d9c
--- /dev/null
+++ b/config/mozconfigs/win64/debug
@@ -0,0 +1,22 @@
+. "$topsrcdir/browser/config/mozconfigs/common"
+
+ac_add_options --target=x86_64-pc-mingw32
+ac_add_options --host=x86_64-pc-mingw32
+
+ac_add_options --enable-debug
+ac_add_options --enable-trace-malloc
+ac_add_options --enable-signmar
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+if test -z "${_PYMAKE}"; then
+ mk_add_options MOZ_MAKE_FLAGS=-j1
+fi
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
+
+. $topsrcdir/build/win64/mozconfig.vs2010
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/config/mozconfigs/win64/nightly b/config/mozconfigs/win64/nightly
new file mode 100644
index 0000000..59b31df
--- /dev/null
+++ b/config/mozconfigs/win64/nightly
@@ -0,0 +1,26 @@
+. "$topsrcdir/browser/config/mozconfigs/common"
+
+ac_add_options --target=x86_64-pc-mingw32
+ac_add_options --host=x86_64-pc-mingw32
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --enable-update-packaging
+ac_add_options --enable-jemalloc
+ac_add_options --enable-signmar
+
+# Nightlies only since this has a cost in performance
+ac_add_options --enable-js-diagnostics
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+if test -z "${_PYMAKE}"; then
+ mk_add_options MOZ_MAKE_FLAGS=-j1
+fi
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
+
+. $topsrcdir/build/win64/mozconfig.vs2010
+
+. "$topsrcdir/build/mozconfig.common.override"
diff --git a/config/tooltool-manifests/linux32/clang.manifest b/config/tooltool-manifests/linux32/clang.manifest
new file mode 100644
index 0000000..8ce74c6
--- /dev/null
+++ b/config/tooltool-manifests/linux32/clang.manifest
@@ -0,0 +1,17 @@
+[
+{
+"clang_version": "r170890"
+},
+{
+"filename": "setup.sh",
+"algorithm": "sha512",
+"digest": "2005a41fe97a5e00997063705f39d42b6a43b1cf7ba306cbc7b1513de34cdcd050fc6326efa2107f19ba0cc67914745dbf13154fa748010a93cf072481ef4aaa",
+"size": 47
+},
+{
+"filename": "clang.tar.bz2",
+"algorithm": "sha512",
+"digest": "0bcfc19f05cc0f042befb3823c7ecce9ba411b152921aa29e97e7adc846e0258fd7da521b1620cb1e61a19d2fcac9b60e6d613c922b6c153e01b9b0766651d09",
+"size": 62708281
+}
+]
diff --git a/config/tooltool-manifests/linux32/releng.manifest b/config/tooltool-manifests/linux32/releng.manifest
new file mode 100644
index 0000000..fe51488
--- /dev/null
+++ b/config/tooltool-manifests/linux32/releng.manifest
@@ -0,0 +1 @@
+[]
diff --git a/config/tooltool-manifests/linux64/clang.manifest b/config/tooltool-manifests/linux64/clang.manifest
new file mode 100644
index 0000000..39ba559
--- /dev/null
+++ b/config/tooltool-manifests/linux64/clang.manifest
@@ -0,0 +1,17 @@
+[
+{
+"clang_version": "r170890"
+},
+{
+"filename": "setup.sh",
+"algorithm": "sha512",
+"digest": "2005a41fe97a5e00997063705f39d42b6a43b1cf7ba306cbc7b1513de34cdcd050fc6326efa2107f19ba0cc67914745dbf13154fa748010a93cf072481ef4aaa",
+"size": 47
+},
+{
+"filename": "clang.tar.bz2",
+"algorithm": "sha512",
+"digest": "e14ccefd965372a57c540647b2b99e21a4aa82f81a8b9a9e18dac7cba4c3436181bef0dfab8c51bcb5c343f504a693fdcfbe7d609f10291b5dd65ab059979d29",
+"size": 63034761
+}
+]
diff --git a/config/tooltool-manifests/linux64/releng.manifest b/config/tooltool-manifests/linux64/releng.manifest
new file mode 100644
index 0000000..fe51488
--- /dev/null
+++ b/config/tooltool-manifests/linux64/releng.manifest
@@ -0,0 +1 @@
+[]
diff --git a/config/tooltool-manifests/macosx64/releng.manifest b/config/tooltool-manifests/macosx64/releng.manifest
new file mode 100644
index 0000000..ede87d3
--- /dev/null
+++ b/config/tooltool-manifests/macosx64/releng.manifest
@@ -0,0 +1,17 @@
+[
+{
+"clang_version": "r170890"
+},
+{
+"size": 47,
+"digest": "2005a41fe97a5e00997063705f39d42b6a43b1cf7ba306cbc7b1513de34cdcd050fc6326efa2107f19ba0cc67914745dbf13154fa748010a93cf072481ef4aaa",
+"algorithm": "sha512",
+"filename": "setup.sh"
+},
+{
+"size": 56126352,
+"digest": "e156e2a39abd5bf272ee30748a6825f22ddd27565b097c66662a2a6f2e9892bc5b4bf30a3552dffbe867dbfc39e7ee086e0b2cd7935f6ea216c0cf936178a88f",
+"algorithm": "sha512",
+"filename": "clang.tar.bz2"
+}
+]
diff --git a/config/version.txt b/config/version.txt
new file mode 100644
index 0000000..af61be3
--- /dev/null
+++ b/config/version.txt
@@ -0,0 +1 @@
+28.7.2
diff --git a/configure.in b/configure.in
new file mode 100644
index 0000000..70ddf66
--- /dev/null
+++ b/configure.in
@@ -0,0 +1,37 @@
+dnl -*- Mode: Autoconf; tab-width: 2; indent-tabs-mode: nil; -*-
+dnl vi: set tabstop=2 shiftwidth=2 expandtab:
+dnl This Source Code Form is subject to the terms of the Mozilla Public
+dnl License, v. 2.0. If a copy of the MPL was not distributed with this
+dnl file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+dnl Things we need to carry from confvars.sh
+AC_DEFINE(MOZ_PHOENIX)
+AC_SUBST(MOZ_PHOENIX)
+AC_DEFINE(MC_PALEMOON)
+AC_SUBST(MC_PALEMOON)
+AC_DEFINE(MOZ_PHOENIX_EXTENSIONS)
+AC_SUBST(MOZ_PHOENIX_EXTENSIONS)
+
+dnl Optional parts of the build.
+
+dnl ========================================================
+dnl = Disable Sync
+dnl ========================================================
+MOZ_ARG_DISABLE_BOOL(sync,
+[ --disable-sync Disable Sync],
+ MOZ_SERVICES_SYNC=,
+ MOZ_SERVICES_SYNC=1)
+
+dnl ========================================================
+dnl = Disable Lightweight Themes
+dnl ========================================================
+MOZ_ARG_DISABLE_BOOL(personas,
+[ --disable-personas Disable lightweight theme support],
+ MOZ_PERSONAS=,
+ MOZ_PERSONAS=1)
+
+if test -n "$MOZ_PERSONAS"; then
+ AC_DEFINE(MOZ_PERSONAS)
+fi
+
+AC_SUBST(MOZ_PERSONAS) \ No newline at end of file
diff --git a/confvars.sh b/confvars.sh
new file mode 100644
index 0000000..f76eec2
--- /dev/null
+++ b/confvars.sh
@@ -0,0 +1,104 @@
+#! /bin/sh
+# 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/.
+
+# Application Basename and Vendor
+# MOZ_APP_BASENAME and MOZ_APP_VENDOR must not have spaces.
+# These values where appropriate are hardcoded in application.ini
+# to "Pale Moon" and "Moonchild Productions" respectively for
+# Pale Moon
+MOZ_APP_BASENAME=Webbrowser
+MOZ_APP_VENDOR=Thomas
+
+# Application Version
+# MOZ_APP_VERSION is read from ./config/version.txt
+# MOZ_APP_VERSION_DISPLAY is not used in Pale Moon so set it
+# to MOZ_APP_VERSION
+MOZ_APP_VERSION=`cat ${_topsrcdir}/$MOZ_BUILD_APP/config/version.txt`
+MOZ_APP_VERSION_DISPLAY=$MOZ_APP_VERSION
+
+# Application ID
+# This is a unique identifier used for the application
+# Most frequently the AppID is used for targetApplication
+# in extensions and for chrome manifests
+MOZ_APP_ID={8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
+
+# Use static Application INI File
+MOZ_APP_STATIC_INI=1
+
+# Application Branding
+# The default is MOZ_BRANDING_DIRECTORY and should never point to
+# official branding by default.
+# Changing MOZ_*BRANDING_DIRECTORY requires a clobber because branding
+# dependencies are broken.
+# MOZ_APP_DISPLAYNAME will be set by [branding]/configure.sh
+MOZ_BRANDING_DIRECTORY=$MOZ_BUILD_APP/branding/unofficial
+MOZ_OFFICIAL_BRANDING_DIRECTORY=$MOZ_BUILD_APP/branding/official
+
+# Enables conditional code in the platform for Pale Moon only
+MC_PALEMOON=1
+
+# Enables conditional code in the platform for historically
+# Firefox-like browsers
+MOZ_PHOENIX=1
+
+# Lightweight Themes
+MOZ_PERSONAS=1
+
+# Browser Feature: Profile Migration Component
+MOZ_PROFILE_MIGRATOR=
+
+# Platform Feature: Application Update Service
+# MAR_CHANNEL_ID must not contained the follow 3 characters: ",\t"
+# ACCEPTED_MAR_CHANNEL_IDS should usually be the same as MAR_CHANNEL_ID
+# If more than one ID is needed, then you should use a comma seperated list.
+MOZ_UPDATER=
+MAR_CHANNEL_ID=unofficial
+ACCEPTED_MAR_CHANNEL_IDS=unofficial,unstable,release
+
+# Platform Feature: Developer Tools
+# XXX: Devtools are disabled until they can be made to work with Pale Moon
+MOZ_DEVTOOLS=1
+
+# Platform Feature: "Phoenix" Extensions Support aka Dual-guid system.
+# Allows installation of Firefox GUID targeted extensions despite having
+# a different Application ID
+# On UXP this is a possible feature only for the Tycho Add-ons Manager
+MOZ_PHOENIX_EXTENSIONS=1
+
+# Platform Feature: Sync Service
+MOZ_SERVICES_COMMON=1
+MOZ_SERVICES_SYNC=1
+
+# Platform Feature: JS based Downloads Manager
+MOZ_JSDOWNLOADS=1
+
+# Platform Feature: Conformant WebGL
+# Exposes the "webgl" context name, which is reserved for
+# conformant implementations.
+MOZ_WEBGL_CONFORMANT=1
+
+# Set the chrome packing format
+# Possible values are omni, jar, and flat
+# Currently, only omni and flat are supported
+MOZ_CHROME_FILE_FORMAT=omni
+
+# Set the default top-level extensions
+MOZ_EXTENSIONS_DEFAULT=" gio"
+
+# Fold Libs
+if test "$OS_TARGET" = "WINNT" -o "$OS_TARGET" = "Darwin"; then
+ MOZ_FOLD_LIBS=1
+fi
+
+# Include bundled fonts
+if test "$OS_ARCH" = "WINNT" -o \
+ "$OS_ARCH" = "Linux"; then
+ MOZ_BUNDLED_FONTS=1
+fi
+
+# Short-circuit a few services to be removed
+MOZ_SERVICES_HEALTHREPORT=
+MOZ_ADDON_SIGNING=0
+MOZ_REQUIRE_SIGNING=0
diff --git a/defs.mk b/defs.mk
new file mode 100644
index 0000000..fd88c65
--- /dev/null
+++ b/defs.mk
@@ -0,0 +1 @@
+XPI_ROOT_APPID=$(MOZ_APP_ID)
diff --git a/fonts/README.txt b/fonts/README.txt
new file mode 100644
index 0000000..bf5cb7e
--- /dev/null
+++ b/fonts/README.txt
@@ -0,0 +1,9 @@
+Twemoji Mozilla
+================
+
+The upstream repository of Twemoji Mozilla can be found at
+
+ https://github.com/mozilla/twemoji-colr
+
+Please refer commit history for the current version of the font.
+This file purposely omits the version, so there is no need to update it here.
diff --git a/fonts/TwemojiMozilla.ttf b/fonts/TwemojiMozilla.ttf
new file mode 100644
index 0000000..c47cbbf
--- /dev/null
+++ b/fonts/TwemojiMozilla.ttf
Binary files differ
diff --git a/fonts/moz.build b/fonts/moz.build
new file mode 100644
index 0000000..8840a87
--- /dev/null
+++ b/fonts/moz.build
@@ -0,0 +1,9 @@
+# -*- 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/.
+
+if CONFIG['OS_ARCH'] in ('WINNT', 'Linux'):
+ DIST_SUBDIR = ''
+ FINAL_TARGET_FILES.fonts += ['TwemojiMozilla.ttf']
diff --git a/installer/Makefile.in b/installer/Makefile.in
new file mode 100644
index 0000000..a0c38f2
--- /dev/null
+++ b/installer/Makefile.in
@@ -0,0 +1,189 @@
+# 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/.
+
+STANDALONE_MAKEFILE := 1
+DIST_SUBDIR := browser
+
+include $(topsrcdir)/config/rules.mk
+
+MOZ_PKG_REMOVALS = $(srcdir)/removed-files.in
+
+MOZ_PKG_MANIFEST_P = $(srcdir)/package-manifest.in
+
+ifdef MOZ_MULET
+MOZ_PKG_MANIFEST_P += $(topsrcdir)/b2g/installer/package-manifest.in
+endif
+
+# Some files have been already bundled with xulrunner
+ifndef MOZ_MULET
+MOZ_PKG_FATAL_WARNINGS = 1
+endif
+
+DEFINES += -DMOZ_APP_NAME=$(MOZ_APP_NAME) -DPREF_DIR=$(PREF_DIR)
+
+ifdef LIBXUL_SDK
+DEFINES += -DLIBXUL_SDK=1
+endif
+
+ifdef MOZ_DEBUG
+DEFINES += -DMOZ_DEBUG=1
+endif
+
+ifdef MOZ_ENABLE_GNOME_COMPONENT
+DEFINES += -DMOZ_ENABLE_GNOME_COMPONENT=1
+endif
+
+ifneq (,$(filter gtk%,$(MOZ_WIDGET_TOOLKIT)))
+DEFINES += -DMOZ_GTK=1
+ifeq ($(MOZ_WIDGET_TOOLKIT),gtk3)
+DEFINES += -DMOZ_GTK3=1
+endif
+endif
+
+ifdef MOZ_SYSTEM_NSPR
+DEFINES += -DMOZ_SYSTEM_NSPR=1
+endif
+
+ifdef MOZ_SYSTEM_NSS
+DEFINES += -DMOZ_SYSTEM_NSS=1
+endif
+
+ifdef NSS_DISABLE_DBM
+DEFINES += -DNSS_DISABLE_DBM=1
+endif
+
+DEFINES += -DJAREXT=
+
+ifdef MOZ_ANGLE_RENDERER
+DEFINES += -DMOZ_ANGLE_RENDERER=$(MOZ_ANGLE_RENDERER)
+ifdef MOZ_D3DCOMPILER_VISTA_DLL
+DEFINES += -DMOZ_D3DCOMPILER_VISTA_DLL=$(MOZ_D3DCOMPILER_VISTA_DLL)
+endif
+endif
+
+DEFINES += -DMOZ_CHILD_PROCESS_NAME=$(MOZ_CHILD_PROCESS_NAME)
+
+# Set MSVC dlls version to package, if any.
+ifdef MOZ_NO_DEBUG_RTL
+ifdef WIN32_REDIST_DIR
+DEFINES += -DMOZ_PACKAGE_MSVC_DLLS=1
+DEFINES += -DMSVC_C_RUNTIME_DLL=$(MSVC_C_RUNTIME_DLL)
+DEFINES += -DMSVC_CXX_RUNTIME_DLL=$(MSVC_CXX_RUNTIME_DLL)
+endif
+ifdef WIN_UCRT_REDIST_DIR
+DEFINES += -DMOZ_PACKAGE_WIN_UCRT_DLLS=1
+endif
+endif
+
+ifneq (,$(filter WINNT Darwin Android,$(OS_TARGET)))
+DEFINES += -DMOZ_SHARED_MOZGLUE=1
+endif
+
+ifdef NECKO_WIFI
+DEFINES += -DNECKO_WIFI
+endif
+
+ifdef GKMEDIAS_SHARED_LIBRARY
+DEFINES += -DGKMEDIAS_SHARED_LIBRARY
+endif
+
+ifdef MOZ_PKG_MANIFEST_P
+MOZ_PKG_MANIFEST = package-manifest
+
+$(MOZ_PKG_MANIFEST): $(MOZ_PKG_MANIFEST_P) $(GLOBAL_DEPS)
+ $(call py_action,preprocessor,$(DEFINES) $(ACDEFINES) $(MOZ_PKG_MANIFEST_P) -o $@)
+
+GARBAGE += $(MOZ_PKG_MANIFEST)
+endif
+
+ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
+MOZ_PKG_MAC_DSSTORE=branding/dsstore
+MOZ_PKG_MAC_BACKGROUND=branding/background.png
+MOZ_PKG_MAC_ICON=branding/disk.icns
+MOZ_PKG_MAC_EXTRA=--symlink '/Applications:/ '
+endif
+
+ifndef LIBXUL_SDK
+INSTALL_SDK = 1
+endif
+
+include $(topsrcdir)/toolkit/mozapps/installer/signing.mk
+include $(topsrcdir)/toolkit/mozapps/installer/packager.mk
+
+ifeq (bundle, $(MOZ_FS_LAYOUT))
+BINPATH = $(_BINPATH)
+DEFINES += -DAPPNAME='$(_APPNAME)'
+else
+# Every other platform just winds up in dist/bin
+BINPATH = bin
+endif
+DEFINES += -DBINPATH='$(BINPATH)'
+
+ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
+RESPATH = $(_APPNAME)/Contents/Resources
+else
+RESPATH = $(BINPATH)
+endif
+DEFINES += -DRESPATH='$(RESPATH)'
+
+AB = $(firstword $(subst -, ,$(AB_CD)))
+DEFINES += -DAB=$(AB)
+
+DEFINES += -DMOZ_ICU_VERSION=$(MOZ_ICU_VERSION)
+ifdef MOZ_SYSTEM_ICU
+DEFINES += -DMOZ_SYSTEM_ICU
+endif
+ifdef MOZ_ICU_DATA_ARCHIVE
+DEFINES += -DMOZ_ICU_DATA_ARCHIVE
+endif
+ifdef MOZ_JEMALLOC3
+DEFINES += -DMOZ_JEMALLOC3
+endif
+DEFINES += -DMOZ_ICU_DBG_SUFFIX=$(MOZ_ICU_DBG_SUFFIX)
+DEFINES += -DICU_DATA_FILE=$(ICU_DATA_FILE)
+ifdef CLANG_CXX
+DEFINES += -DCLANG_CXX
+endif
+ifdef CLANG_CL
+DEFINES += -DCLANG_CL
+endif
+ifeq (x86,$(CPU_ARCH))
+ifdef _MSC_VER
+ifndef CLANG_CL
+DEFINES += -DWOW_HELPER
+endif
+endif
+endif
+
+
+libs::
+ $(MAKE) -C $(DEPTH)/application/palemoon/locales langpack
+
+ifeq (WINNT,$(OS_ARCH))
+PKGCOMP_FIND_OPTS =
+else
+PKGCOMP_FIND_OPTS = -L
+endif
+ifeq (Darwin, $(OS_ARCH))
+FINDPATH = $(_APPNAME)/Contents/MacOS
+else
+FINDPATH=bin
+endif
+
+package-compare:: $(MOZ_PKG_MANIFEST)
+ifdef MOZ_PKG_MANIFEST_P
+ cd $(DIST); find $(PKGCOMP_FIND_OPTS) '$(FINDPATH)' -type f | sort > bin-list.txt
+ grep '^$(BINPATH)' $(MOZ_PKG_MANIFEST) | sed -e 's/^\///' | sort > $(DIST)/pack-list.txt
+ -diff -u $(DIST)/pack-list.txt $(DIST)/bin-list.txt
+ rm -f $(DIST)/pack-list.txt $(DIST)/bin-list.txt
+endif
+
+installer::
+ifdef INSTALLER_DIR
+ $(MAKE) -C $(INSTALLER_DIR)
+endif
+
+ifdef ENABLE_MARIONETTE
+DEFINES += -DENABLE_MARIONETTE=1
+endif
diff --git a/installer/moz.build b/installer/moz.build
new file mode 100644
index 0000000..895d119
--- /dev/null
+++ b/installer/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/installer/package-manifest.in b/installer/package-manifest.in
new file mode 100644
index 0000000..f95f18f
--- /dev/null
+++ b/installer/package-manifest.in
@@ -0,0 +1,337 @@
+; 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/.
+
+; Package file for the Firefox build.
+;
+; Packaging manifest is used to copy files from dist/bin
+; to the staging directory.
+; Some other files are built in the staging directory directly,
+; so they will be implicitly packaged too.
+;
+; File format:
+;
+; [] designates a toplevel component. Example: [xpcom]
+; - in front of a file specifies it to be removed from the destination
+; * wildcard support to recursively copy the entire directory
+; ; file comment
+;
+
+; Due to Apple Mac OS X packaging requirements, files that are in the same
+; directory on other platforms must be located in different directories on
+; Mac OS X. The following defines allow specifying the Mac OS X bundle
+; location which also work on other platforms.
+;
+; @BINPATH@
+; Equals Contents/MacOS/ on Mac OS X and is the path to the main binary on other
+; platforms.
+;
+; @RESPATH@
+; Equals Contents/Resources/ on Mac OS X and is equivalent to @BINPATH@ on other
+; platforms.
+
+#filter substitution
+
+#ifdef XP_MACOSX
+; Mac bundle stuff
+@APPNAME@/Contents/Info.plist
+@APPNAME@/Contents/PkgInfo
+@RESPATH@/firefox.icns
+@RESPATH@/document.icns
+#endif
+
+[@AB_CD@]
+@RESPATH@/browser/chrome/@AB_CD@@JAREXT@
+@RESPATH@/browser/chrome/@AB_CD@.manifest
+@RESPATH@/chrome/@AB_CD@@JAREXT@
+@RESPATH@/chrome/@AB_CD@.manifest
+@RESPATH@/browser/defaults/profile/bookmarks.html
+@RESPATH@/browser/defaults/profile/chrome/*
+@RESPATH@/browser/defaults/profile/localstore.rdf
+@RESPATH@/browser/defaults/profile/mimeTypes.rdf
+@RESPATH@/dictionaries/*
+#if defined(XP_WIN) || defined(XP_LINUX)
+@RESPATH@/fonts/*
+#endif
+@RESPATH@/hyphenation/*
+@RESPATH@/browser/@PREF_DIR@/palemoon-l10n.js
+@RESPATH@/browser/searchplugins/*
+#ifdef XP_WIN32
+@BINPATH@/uninstall/helper.exe
+#endif
+@RESPATH@/update.locale
+#ifdef MOZ_UPDATER
+@RESPATH@/updater.ini
+#endif
+
+[xpcom]
+@RESPATH@/dependentlibs.list
+#ifdef MOZ_SHARED_MOZGLUE
+@BINPATH@/@DLL_PREFIX@mozglue@DLL_SUFFIX@
+#endif
+#ifndef MOZ_STATIC_JS
+@BINPATH@/@DLL_PREFIX@mozjs@DLL_SUFFIX@
+#endif
+#ifndef MOZ_SYSTEM_NSPR
+#ifndef MOZ_FOLD_LIBS
+@BINPATH@/@DLL_PREFIX@nspr4@DLL_SUFFIX@
+@BINPATH@/@DLL_PREFIX@plc4@DLL_SUFFIX@
+@BINPATH@/@DLL_PREFIX@plds4@DLL_SUFFIX@
+#endif
+#endif
+#ifdef XP_MACOSX
+@BINPATH@/XUL
+#else
+@BINPATH@/@DLL_PREFIX@xul@DLL_SUFFIX@
+#endif
+#ifdef XP_MACOSX
+@BINPATH@/@MOZ_CHILD_PROCESS_NAME@.app/
+@BINPATH@/@DLL_PREFIX@plugin_child_interpose@DLL_SUFFIX@
+#else
+@BINPATH@/@MOZ_CHILD_PROCESS_NAME@
+#endif
+#ifdef XP_WIN32
+@BINPATH@/plugin-hang-ui@BIN_SUFFIX@
+#if MOZ_PACKAGE_MSVC_DLLS
+@BINPATH@/@MSVC_C_RUNTIME_DLL@
+@BINPATH@/@MSVC_CXX_RUNTIME_DLL@
+#endif
+#if MOZ_PACKAGE_WIN_UCRT_DLLS
+@BINPATH@/api-ms-win-*.dll
+@BINPATH@/ucrtbase.dll
+#endif
+#endif
+#ifdef MOZ_ICU_DATA_ARCHIVE
+@RESPATH@/@ICU_DATA_FILE@
+#endif
+#ifdef MOZ_GTK3
+@BINPATH@/@DLL_PREFIX@mozgtk@DLL_SUFFIX@
+@BINPATH@/gtk2/@DLL_PREFIX@mozgtk@DLL_SUFFIX@
+#endif
+
+[browser]
+; [Base Browser Files]
+#ifndef XP_UNIX
+@BINPATH@/@MOZ_APP_NAME@.exe
+@BINPATH@/@MOZ_APP_NAME@.VisualElementsManifest.xml
+@BINPATH@/browser/VisualElements/VisualElements_150.png
+@BINPATH@/browser/VisualElements/VisualElements_70.png
+#else
+@RESPATH@/@MOZ_APP_NAME@-bin
+@BINPATH@/@MOZ_APP_NAME@
+#endif
+@RESPATH@/application.ini
+#ifdef MOZ_UPDATER
+@RESPATH@/update-settings.ini
+#endif
+@RESPATH@/platform.ini
+#ifndef MOZ_SYSTEM_SQLITE
+#ifndef MOZ_FOLD_LIBS
+@BINPATH@/@DLL_PREFIX@mozsqlite3@DLL_SUFFIX@
+#endif
+#endif
+@BINPATH@/@DLL_PREFIX@lgpllibs@DLL_SUFFIX@
+#ifdef MOZ_FFVPX
+@BINPATH@/@DLL_PREFIX@mozavutil@DLL_SUFFIX@
+@BINPATH@/@DLL_PREFIX@mozavcodec@DLL_SUFFIX@
+#endif
+@RESPATH@/browser/blocklist.xml
+#ifdef XP_UNIX
+#ifndef XP_MACOSX
+@RESPATH@/run-mozilla.sh
+#endif
+#endif
+
+; [Components]
+@RESPATH@/components/*
+@RESPATH@/browser/components/*
+#ifdef MOZ_ARTIFACT_BUILDS
+#endif
+#ifdef ACCESSIBILITY
+#ifdef XP_WIN32
+@BINPATH@/Accessible.tlb
+@BINPATH@/AccessibleMarshal.dll
+@BINPATH@/IA2Marshal.dll
+#endif
+#endif
+#ifdef MOZ_WEBRTC
+#endif
+#ifdef MOZ_WEBSPEECH
+#endif
+#ifdef MOZ_GTK
+#endif
+#ifdef NS_PRINTING
+#endif
+#ifdef NECKO_WIFI
+#endif
+#ifdef MOZ_WEBRTC
+#endif
+#ifdef MOZ_ENABLE_XREMOTE
+#endif
+#ifdef XP_MACOSX
+#endif
+
+@RESPATH@/chrome/marionette@JAREXT@
+@RESPATH@/chrome/marionette.manifest
+
+; Modules
+@RESPATH@/browser/modules/*
+@RESPATH@/modules/*
+
+; ANGLE GLES-on-D3D rendering library
+#ifdef MOZ_ANGLE_RENDERER
+@BINPATH@/libEGL.dll
+@BINPATH@/libGLESv2.dll
+
+#ifdef MOZ_D3DCOMPILER_VISTA_DLL
+@BINPATH@/@MOZ_D3DCOMPILER_VISTA_DLL@
+#endif
+#endif # MOZ_ANGLE_RENDERER
+
+; [Browser Chrome Files]
+@RESPATH@/browser/chrome.manifest
+@RESPATH@/browser/chrome/browser@JAREXT@
+@RESPATH@/browser/chrome/browser.manifest
+@RESPATH@/browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/chrome.manifest
+@RESPATH@/browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/icon.png
+@RESPATH@/browser/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/install.rdf
+@RESPATH@/chrome/toolkit@JAREXT@
+@RESPATH@/chrome/toolkit.manifest
+@RESPATH@/chrome/recording.manifest
+@RESPATH@/chrome/recording/*
+#ifdef MOZ_GTK
+@RESPATH@/browser/chrome/icons/default/default16.png
+@RESPATH@/browser/chrome/icons/default/default32.png
+@RESPATH@/browser/chrome/icons/default/default48.png
+#endif
+
+#ifdef MOZ_DEVTOOLS
+; DevTools
+@RESPATH@/browser/chrome/devtools@JAREXT@
+@RESPATH@/browser/chrome/devtools.manifest
+@RESPATH@/browser/@PREF_DIR@/devtools.js
+#endif
+
+; shell icons
+#ifdef XP_UNIX
+#ifndef XP_MACOSX
+; shell icons
+@RESPATH@/browser/icons/*.png
+#ifdef MOZ_UPDATER
+; updater icon
+@RESPATH@/icons/updater.png
+#endif
+#endif
+#endif
+
+; [Default Preferences]
+; All the pref files must be part of base to prevent migration bugs
+@RESPATH@/browser/defaults/permissions
+@RESPATH@/browser/@PREF_DIR@/palemoon.js
+@RESPATH@/browser/@PREF_DIR@/palemoon-branding.js
+@RESPATH@/greprefs.js
+@RESPATH@/defaults/autoconfig/prefcalls.js
+
+; Warning: changing the path to channel-prefs.js can cause bugs (Bug 756325)
+; Technically this is an app pref file, but we are keeping it in the original
+; gre location for now.
+@RESPATH@/defaults/pref/channel-prefs.js
+
+#ifdef MOZ_SERVICES_SYNC
+; Services (gre) prefs
+@RESPATH@/defaults/pref/services-sync.js
+#endif
+
+; [Layout Engine Resources]
+; Style Sheets, Graphics and other Resources used by the layout engine.
+@RESPATH@/res/EditorOverride.css
+@RESPATH@/res/contenteditable.css
+@RESPATH@/res/designmode.css
+@RESPATH@/res/ImageDocument.css
+@RESPATH@/res/TopLevelImageDocument.css
+@RESPATH@/res/TopLevelVideoDocument.css
+@RESPATH@/res/table-add-column-after-active.gif
+@RESPATH@/res/table-add-column-after-hover.gif
+@RESPATH@/res/table-add-column-after.gif
+@RESPATH@/res/table-add-column-before-active.gif
+@RESPATH@/res/table-add-column-before-hover.gif
+@RESPATH@/res/table-add-column-before.gif
+@RESPATH@/res/table-add-row-after-active.gif
+@RESPATH@/res/table-add-row-after-hover.gif
+@RESPATH@/res/table-add-row-after.gif
+@RESPATH@/res/table-add-row-before-active.gif
+@RESPATH@/res/table-add-row-before-hover.gif
+@RESPATH@/res/table-add-row-before.gif
+@RESPATH@/res/table-remove-column-active.gif
+@RESPATH@/res/table-remove-column-hover.gif
+@RESPATH@/res/table-remove-column.gif
+@RESPATH@/res/table-remove-row-active.gif
+@RESPATH@/res/table-remove-row-hover.gif
+@RESPATH@/res/table-remove-row.gif
+@RESPATH@/res/grabber.gif
+#ifdef XP_MACOSX
+@RESPATH@/res/cursors/*
+#endif
+@RESPATH@/res/fonts/*
+@RESPATH@/res/dtd/*
+@RESPATH@/res/html/*
+#if defined(XP_MACOSX)
+; For SafariProfileMigrator.js.
+@RESPATH@/res/langGroups.properties
+#endif
+@RESPATH@/res/language.properties
+@RESPATH@/res/entityTables/*
+#ifdef XP_MACOSX
+@RESPATH@/res/MainMenu.nib/
+#endif
+
+; svg
+@RESPATH@/res/svg.css
+
+; [Personal Security Manager]
+;
+; NSS libraries are signed in the staging directory,
+; meaning their .chk files are created there directly.
+;
+#ifndef MOZ_SYSTEM_NSS
+#if defined(XP_LINUX) && !defined(ANDROID)
+@BINPATH@/@DLL_PREFIX@freeblpriv3@DLL_SUFFIX@
+#else
+@BINPATH@/@DLL_PREFIX@freebl3@DLL_SUFFIX@
+#endif
+@BINPATH@/@DLL_PREFIX@nss3@DLL_SUFFIX@
+@BINPATH@/@DLL_PREFIX@nssckbi@DLL_SUFFIX@
+#ifndef NSS_DISABLE_DBM
+@BINPATH@/@DLL_PREFIX@nssdbm3@DLL_SUFFIX@
+#endif
+#ifndef MOZ_FOLD_LIBS
+@BINPATH@/@DLL_PREFIX@nssutil3@DLL_SUFFIX@
+@BINPATH@/@DLL_PREFIX@smime3@DLL_SUFFIX@
+@BINPATH@/@DLL_PREFIX@ssl3@DLL_SUFFIX@
+#endif
+@BINPATH@/@DLL_PREFIX@softokn3@DLL_SUFFIX@
+#endif
+@RESPATH@/chrome/pippki@JAREXT@
+@RESPATH@/chrome/pippki.manifest
+
+; [Updater]
+;
+#ifdef MOZ_UPDATER
+#ifdef XP_MACOSX
+@BINPATH@/updater.app/
+#else
+@BINPATH@/updater@BIN_SUFFIX@
+#endif
+#endif
+
+; Shutdown Terminator
+
+#if defined(CLANG_CXX)
+#if defined(MOZ_ASAN) || defined(MOZ_TSAN)
+@BINPATH@/llvm-symbolizer
+#endif
+#endif
+
+#if defined(MOZ_ASAN) && defined(CLANG_CL)
+@BINPATH@/clang_rt.asan_dynamic-*.dll
+#endif
diff --git a/installer/removed-files.in b/installer/removed-files.in
new file mode 100644
index 0000000..8801d1c
--- /dev/null
+++ b/installer/removed-files.in
@@ -0,0 +1,116 @@
+# 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 removed-files.in file specifies files and directories to be removed during
+# an application update that are not automatically removed by the application
+# update process. The application update process handles the vast majority of
+# file and directory removals automatically so this file should not be used in
+# the vast majority of cases.
+
+# When to use removed-files.in file to remove files and directories:
+# * Files and directories located in the installation's "distribution/" and
+# "extensions/" directories that were added before Firefox 27. Files and
+# directories located in these directories were not included in the
+# application update file removals for a complete update prior to Firefox 27.
+# * Empty directories that were accidentally added to the installation
+# directory.
+# * Third party files and directories that were added to the installation
+# directory. Under normal circumstances this should only be done after release
+# drivers have approved the removal of these third party files.
+
+# If you are not sure whether a file or directory should be removed using the
+# removed-files.in file please contact one of the developers that work on
+# application update.
+
+# Note: the "distribution/" and "browser/extensions/" directories should never
+# be removed recursively since these directories are used by Partner builds and
+# custom installations.
+
+# To specify a file to be removed add the path to the file.
+# * If the file doesn't exist the update will succeed.
+# * If the file exists and can't be removed (e.g. the file is locked) the
+# update will fail.
+#
+# Example: path/to/file
+
+# To specify a directory to be removed only if it is empty add the path to the
+# directory with a trailing forward slash.
+# * If the directory doesn't exist the update will succeed.
+# * If the directory can't be removed (e.g. the directory is locked, contains
+# files, etc.) the update will succeed.
+#
+# Example: path/to/dir/
+
+# To specify a directory that should be recursively removed add the path to the
+# directory with a trailing forward slash and "*".
+# * If the directory doesn't exist the update will succeed.
+# * If all of the files the directory contains can be removed but the directory
+# or a subdirectory can't be removed (e.g. the directory is locked) the update
+# will succeed.
+# * If a file within the directory can't be removed the update will fail.
+#
+# Example: path/to/dir/*
+
+# Due to Apple Mac OS X packaging requirements files that are in the same
+# directory on other platforms must be located in different directories on
+# Mac OS X. The following defines allow specifying the Mac OS X bundle
+# location which also work on other platforms.
+#
+# @DIR_MACOS@
+# Equals Contents/MacOS/ on Mac OS X and is an empty string on other platforms.
+#
+# @DIR_RESOURCES@
+# Equals Contents/Resources/ on Mac OS X and is an empty string on other
+# platforms.
+
+# Common File Removals
+# This is located under the "distribution/" directory and it was added before
+# Firefox 27
+@DIR_MACOS@distribution/extensions/testpilot@labs.mozilla.com.xpi
+
+# Mac OS X v2 signing removals
+#ifdef XP_MACOSX
+ @DIR_MACOS@active-update.xml
+ @DIR_MACOS@update-settings.ini
+ @DIR_MACOS@updates.xml
+ @DIR_MACOS@defaults/*
+ @DIR_MACOS@updates/*
+#endif
+
+# Common Directory removals
+@DIR_MACOS@chrome/
+#ifdef XP_UNIX
+ #ifndef XP_MACOSX
+ chrome/icons/
+ chrome/icons/default/
+ #endif
+#endif
+@DIR_MACOS@chrome/overlayinfo/
+@DIR_MACOS@components/
+@DIR_MACOS@defaults/autoconfig/
+@DIR_MACOS@defaults/profile/
+@DIR_MACOS@defaults/profile/chrome/
+@DIR_MACOS@defaults/profile/US/*
+@DIR_MACOS@defaults/profile/extensions/
+@DIR_MACOS@defaults/profile/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/*
+@DIR_MACOS@distribution/
+@DIR_MACOS@distribution/extensions/
+@DIR_MACOS@extensions/
+@DIR_MACOS@extensions/inspector@mozilla.org/*
+@DIR_MACOS@extensions/reporter@mozilla.org/*
+@DIR_MACOS@extensions/talkback@mozilla.org/*
+@DIR_MACOS@extensions/testpilot@labs.mozilla.com/*
+@DIR_MACOS@extensions/{641d8d09-7dda-4850-8228-ac0ab65e2ac9}/*
+@DIR_MACOS@extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}/*
+@DIR_MACOS@greprefs/
+@DIR_MACOS@jssubloader/
+@DIR_MACOS@modules/
+#ifdef XP_MACOSX
+ @DIR_MACOS@plugins/Default Plugin.plugin/*
+ @DIR_MACOS@plugins/JavaEmbeddingPlugin.bundle/*
+ @DIR_MACOS@plugins/MRJPlugin.plugin/*
+ Contents/Plug-Ins/PrintPDE.plugin/*
+#endif
+@DIR_MACOS@searchplugins/*
+@DIR_MACOS@webapprt/components/
diff --git a/installer/windows/Makefile.in b/installer/windows/Makefile.in
new file mode 100644
index 0000000..d2be8ae
--- /dev/null
+++ b/installer/windows/Makefile.in
@@ -0,0 +1,69 @@
+# 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/.
+
+include $(topsrcdir)/toolkit/mozapps/installer/package-name.mk
+
+CONFIG_DIR = instgen
+SFX_MODULE = $(topsrcdir)/other-licenses/7zstub/palemoon/7zSD.sfx
+
+INSTALLER_FILES = \
+ app.tag \
+ nsis/installer.nsi \
+ nsis/uninstaller.nsi \
+ nsis/shared.nsh \
+ $(NULL)
+
+BRANDING_FILES = \
+ branding.nsi \
+ appname.bmp \
+ wizHeader.bmp \
+ wizHeaderRTL.bmp \
+ wizWatermark.bmp \
+ $(NULL)
+
+include $(topsrcdir)/config/config.mk
+
+ifdef LOCALE_MERGEDIR
+PPL_LOCALE_ARGS = \
+ --l10n-dir=$(LOCALE_MERGEDIR)/application/palemoon/installer \
+ --l10n-dir=$(call EXPAND_LOCALE_SRCDIR,application/palemoon/locales)/installer \
+ --l10n-dir=$(topsrcdir)/application/palemoon/locales/en-US/installer \
+ $(NULL)
+else
+PPL_LOCALE_ARGS=$(call EXPAND_LOCALE_SRCDIR,application/palemoon/locales)/installer
+endif
+
+OVERRIDE_DEFAULT_GOAL := installer
+installer::
+ $(MAKE) -C .. installer-stage
+ $(MAKE) $(CONFIG_DIR)/setup.exe
+
+# For building the uninstaller during the application build so it can be
+# included for mar file generation.
+uninstaller::
+ $(RM) -r $(CONFIG_DIR)
+ $(MKDIR) $(CONFIG_DIR)
+ $(INSTALL) $(addprefix $(srcdir)/,$(INSTALLER_FILES)) $(CONFIG_DIR)
+ $(INSTALL) $(addprefix $(DIST)/branding/,$(BRANDING_FILES)) $(CONFIG_DIR)
+ $(call py_action,preprocessor,-Fsubstitution $(DEFINES) $(ACDEFINES) \
+ $(srcdir)/nsis/defines.nsi.in -o $(CONFIG_DIR)/defines.nsi)
+ $(PYTHON) $(topsrcdir)/toolkit/mozapps/installer/windows/nsis/preprocess-locale.py \
+ --preprocess-locale $(topsrcdir) \
+ $(PPL_LOCALE_ARGS) $(AB_CD) $(CONFIG_DIR)
+
+$(CONFIG_DIR)/setup.exe::
+ $(RM) -r $(CONFIG_DIR)
+ $(MKDIR) $(CONFIG_DIR)
+ $(INSTALL) $(addprefix $(srcdir)/,$(INSTALLER_FILES)) $(CONFIG_DIR)
+ $(INSTALL) $(addprefix $(DIST)/branding/,$(BRANDING_FILES)) $(CONFIG_DIR)
+ $(call py_action,preprocessor,-Fsubstitution $(DEFINES) $(ACDEFINES) \
+ $(srcdir)/nsis/defines.nsi.in -o $(CONFIG_DIR)/defines.nsi)
+ $(PYTHON) $(topsrcdir)/toolkit/mozapps/installer/windows/nsis/preprocess-locale.py \
+ --preprocess-locale $(topsrcdir) \
+ $(PPL_LOCALE_ARGS) $(AB_CD) $(CONFIG_DIR)
+
+GARBARGE_DIRS += instgen
+
+include $(topsrcdir)/config/rules.mk
+include $(topsrcdir)/toolkit/mozapps/installer/windows/nsis/makensis.mk
diff --git a/installer/windows/app.tag b/installer/windows/app.tag
new file mode 100644
index 0000000..39235db
--- /dev/null
+++ b/installer/windows/app.tag
@@ -0,0 +1,4 @@
+;!@Install@!UTF-8!
+Title="Pale Moon"
+RunProgram="setup.exe"
+;!@InstallEnd@! \ No newline at end of file
diff --git a/installer/windows/moz.build b/installer/windows/moz.build
new file mode 100644
index 0000000..394a85c
--- /dev/null
+++ b/installer/windows/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; 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/.
+
+DEFINES['MOZ_APP_NAME'] = CONFIG['MOZ_APP_NAME']
+DEFINES['APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
+DEFINES['MOZILLA_VERSION'] = CONFIG['MOZILLA_VERSION']
+
+if CONFIG['MOZ_APP_DISPLAYNAME'] in ('PaleMoon', 'Palemoon'):
+ DEFINES['MOZ_APP_DISPLAYNAME'] = "Pale Moon"
+else:
+ DEFINES['MOZ_APP_DISPLAYNAME'] = CONFIG['MOZ_APP_DISPLAYNAME']
+
diff --git a/installer/windows/nsis/defines.nsi.in b/installer/windows/nsis/defines.nsi.in
new file mode 100644
index 0000000..1764b10
--- /dev/null
+++ b/installer/windows/nsis/defines.nsi.in
@@ -0,0 +1,65 @@
+#filter substitution
+# 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/.
+
+# These defines should match application.ini settings
+!define AppName "Pale Moon"
+!define AppVersion "@APP_VERSION@"
+!define GREVersion @MOZILLA_VERSION@
+!define AB_CD "@AB_CD@"
+
+!define FileMainEXE "@MOZ_APP_NAME@.exe"
+!define WindowClass "Pale MoonMessageWindow"
+!define DDEApplication "Pale Moon"
+!define AppRegName "Pale Moon"
+
+!ifndef DEV_EDITION
+!define BrandShortName "@MOZ_APP_DISPLAYNAME@"
+!endif
+!define BrandFullName "${BrandFullNameInternal}"
+
+!define CERTIFICATE_NAME "Mozilla Corporation"
+!define CERTIFICATE_ISSUER "DigiCert SHA2 Assured ID Code Signing CA"
+; Changing the name or issuer requires us to have both the old and the new
+; in the registry at the same time, temporarily.
+!define CERTIFICATE_NAME_PREVIOUS "Mozilla Corporation"
+!define CERTIFICATE_ISSUER_PREVIOUS "DigiCert Assured ID Code Signing CA-1"
+
+# LSP_CATEGORIES is the permitted LSP categories for the application. Each LSP
+# category value is ANDed together to set multiple permitted categories.
+# See http://msdn.microsoft.com/en-us/library/ms742253%28VS.85%29.aspx
+# The value below removes all LSP categories previously set.
+!define LSP_CATEGORIES "0x00000000"
+
+!if "@MOZ_UPDATE_CHANNEL@" == ""
+!define UpdateChannel "Unknown"
+!else
+!define UpdateChannel "@MOZ_UPDATE_CHANNEL@"
+!endif
+
+# ARCH is used when it is necessary to differentiate the x64 registry keys from
+# the x86 registry keys (e.g. the uninstall registry key).
+#ifdef HAVE_64BIT_BUILD
+!define HAVE_64BIT_BUILD
+!define ARCH "x64"
+!define MinSupportedVer "Microsoft Windows 7 x64"
+#else
+!define ARCH "x86"
+!define MinSupportedVer "Microsoft Windows 7"
+#endif
+
+!define MinSupportedCPU "SSE2"
+
+# File details shared by both the installer and uninstaller
+VIProductVersion "1.0.0.0"
+VIAddVersionKey "ProductName" "${BrandShortName}"
+VIAddVersionKey "CompanyName" "${CompanyName}"
+#ifdef MOZ_OFFICIAL_BRANDING
+VIAddVersionKey "LegalTrademarks" "${BrandShortName} is a Trademark of Moonchild Productions."
+#endif
+VIAddVersionKey "LegalCopyright" "${CompanyName}"
+VIAddVersionKey "FileVersion" "${AppVersion}"
+VIAddVersionKey "ProductVersion" "${AppVersion}"
+# Comments is not used but left below commented out for future reference
+# VIAddVersionKey "Comments" "Comments"
diff --git a/installer/windows/nsis/installer.nsi b/installer/windows/nsis/installer.nsi
new file mode 100644
index 0000000..9f61c9c
--- /dev/null
+++ b/installer/windows/nsis/installer.nsi
@@ -0,0 +1,1162 @@
+# 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/.
+
+# Required Plugins:
+# AppAssocReg http://nsis.sourceforge.net/Application_Association_Registration_plug-in
+# ApplicationID http://nsis.sourceforge.net/ApplicationID_plug-in
+# CityHash http://dxr.mozilla.org/mozilla-central/source/other-licenses/nsis/Contrib/CityHash
+# ShellLink http://nsis.sourceforge.net/ShellLink_plug-in
+# UAC http://nsis.sourceforge.net/UAC_plug-in
+# ServicesHelper Mozilla specific plugin that is located in /other-licenses/nsis
+
+; Set verbosity to 3 (e.g. no script) to lessen the noise in the build logs
+!verbose 3
+
+; 7-Zip provides better compression than the lzma from NSIS so we add the files
+; uncompressed and use 7-Zip to create a SFX archive of it
+SetDatablockOptimize on
+SetCompress off
+CRCCheck on
+
+RequestExecutionLevel user
+
+; The commands inside this ifdef require NSIS 3.0a2 or greater so the ifdef can
+; be removed after we require NSIS 3.0a2 or greater.
+!ifdef NSIS_PACKEDVERSION
+ Unicode true
+ ManifestSupportedOS all
+ ManifestDPIAware true
+!endif
+
+!addplugindir ./
+
+Var TmpVal
+Var InstallType
+Var AddStartMenuSC
+Var AddQuickLaunchSC
+Var AddDesktopSC
+Var InstallMaintenanceService
+Var PageName
+Var PreventRebootRequired
+
+; By defining NO_STARTMENU_DIR an installer that doesn't provide an option for
+; an application's Start Menu PROGRAMS directory and doesn't define the
+; StartMenuDir variable can use the common InstallOnInitCommon macro.
+!define NO_STARTMENU_DIR
+
+; On Vista and above attempt to elevate Standard Users in addition to users that
+; are a member of the Administrators group.
+!define NONADMIN_ELEVATE
+
+!define AbortSurveyURL "http://www.kampyle.com/feedback_form/ff-feedback-form.php?site_code=8166124&form_id=12116&url="
+
+; Other included files may depend upon these includes!
+; The following includes are provided by NSIS.
+!include FileFunc.nsh
+!include LogicLib.nsh
+!include MUI.nsh
+!include WinMessages.nsh
+!include WinVer.nsh
+!include WordFunc.nsh
+
+!insertmacro GetOptions
+!insertmacro GetParameters
+!insertmacro GetSize
+!insertmacro StrFilter
+!insertmacro WordFind
+!insertmacro WordReplace
+
+; The following includes are custom.
+!include branding.nsi
+!include defines.nsi
+!include common.nsh
+!include locales.nsi
+
+VIAddVersionKey "FileDescription" "${BrandShortName} Installer"
+VIAddVersionKey "OriginalFilename" "setup.exe"
+
+; Must be inserted before other macros that use logging
+!insertmacro _LoggingCommon
+
+!insertmacro AddDisabledDDEHandlerValues
+!insertmacro ChangeMUIHeaderImage
+!insertmacro CheckForFilesInUse
+!insertmacro CleanUpdateDirectories
+!insertmacro CopyFilesFromDir
+!insertmacro CreateRegKey
+!insertmacro GetLongPath
+!insertmacro GetPathFromString
+!insertmacro GetParent
+!insertmacro InitHashAppModelId
+!insertmacro IsHandlerForInstallDir
+!insertmacro IsPinnedToTaskBar
+!insertmacro LogDesktopShortcut
+!insertmacro LogQuickLaunchShortcut
+!insertmacro LogStartMenuShortcut
+!insertmacro ManualCloseAppPrompt
+!insertmacro PinnedToStartMenuLnkCount
+!insertmacro RegCleanAppHandler
+!insertmacro RegCleanMain
+!insertmacro RegCleanUninstall
+!insertmacro RemovePrecompleteEntries
+!insertmacro SetAppLSPCategories
+!insertmacro SetBrandNameVars
+!insertmacro UpdateShortcutAppModelIDs
+!insertmacro UnloadUAC
+!insertmacro WriteRegStr2
+!insertmacro WriteRegDWORD2
+
+!include shared.nsh
+
+; Helper macros for ui callbacks. Insert these after shared.nsh
+!insertmacro CheckCustomCommon
+!insertmacro InstallEndCleanupCommon
+!insertmacro InstallOnInitCommon
+!insertmacro InstallStartCleanupCommon
+!insertmacro LeaveDirectoryCommon
+!insertmacro LeaveOptionsCommon
+!insertmacro OnEndCommon
+!insertmacro PreDirectoryCommon
+
+Name "${BrandFullName}"
+OutFile "setup.exe"
+!ifdef HAVE_64BIT_BUILD
+ InstallDir "$PROGRAMFILES64\${BrandFullName}\"
+!else
+ InstallDir "$PROGRAMFILES32\${BrandFullName}\"
+!endif
+ShowInstDetails nevershow
+
+################################################################################
+# Modern User Interface - MUI
+
+!define MOZ_MUI_CUSTOM_ABORT
+!define MUI_CUSTOMFUNCTION_ABORT "CustomAbort"
+!define MUI_ICON setup.ico
+!define MUI_UNICON setup.ico
+!define MUI_WELCOMEPAGE_TITLE_3LINES
+!define MUI_HEADERIMAGE
+!define MUI_HEADERIMAGE_RIGHT
+!define MUI_WELCOMEFINISHPAGE_BITMAP wizWatermark.bmp
+
+; Use a right to left header image when the language is right to left
+!ifdef ${AB_CD}_rtl
+!define MUI_HEADERIMAGE_BITMAP_RTL wizHeaderRTL.bmp
+!else
+!define MUI_HEADERIMAGE_BITMAP wizHeader.bmp
+!endif
+
+/**
+ * Installation Pages
+ */
+; Welcome Page
+!define MUI_PAGE_CUSTOMFUNCTION_PRE preWelcome
+!insertmacro MUI_PAGE_WELCOME
+
+; Custom Options Page
+Page custom preOptions leaveOptions
+
+; Select Install Directory Page
+!define MUI_PAGE_CUSTOMFUNCTION_PRE preDirectory
+!define MUI_PAGE_CUSTOMFUNCTION_LEAVE leaveDirectory
+!define MUI_DIRECTORYPAGE_VERIFYONLEAVE
+!insertmacro MUI_PAGE_DIRECTORY
+
+; Custom Shortcuts Page
+Page custom preShortcuts leaveShortcuts
+
+; Custom Summary Page
+Page custom preSummary leaveSummary
+
+; Install Files Page
+!insertmacro MUI_PAGE_INSTFILES
+
+; Finish Page
+!define MUI_FINISHPAGE_TITLE_3LINES
+!define MUI_FINISHPAGE_RUN
+!define MUI_FINISHPAGE_RUN_FUNCTION LaunchApp
+!define MUI_FINISHPAGE_RUN_TEXT $(LAUNCH_TEXT)
+!define MUI_PAGE_CUSTOMFUNCTION_PRE preFinish
+!insertmacro MUI_PAGE_FINISH
+
+; Use the default dialog for IDD_VERIFY for a simple Banner
+ChangeUI IDD_VERIFY "${NSISDIR}\Contrib\UIs\default.exe"
+
+################################################################################
+# Install Sections
+
+; Cleanup operations to perform at the start of the installation.
+Section "-InstallStartCleanup"
+ SetDetailsPrint both
+ DetailPrint $(STATUS_CLEANUP)
+ SetDetailsPrint none
+
+ SetOutPath "$INSTDIR"
+ ${StartInstallLog} "${BrandFullName}" "${AB_CD}" "${AppVersion}" "${GREVersion}"
+
+ StrCpy $R9 "true"
+ StrCpy $PreventRebootRequired "false"
+ ${GetParameters} $R8
+ ${GetOptions} "$R8" "/INI=" $R7
+ ${Unless} ${Errors}
+ ; The configuration file must also exist
+ ${If} ${FileExists} "$R7"
+ ReadINIStr $R9 $R7 "Install" "RemoveDistributionDir"
+ ReadINIStr $R8 $R7 "Install" "PreventRebootRequired"
+ ${If} $R8 == "true"
+ StrCpy $PreventRebootRequired "true"
+ ${EndIf}
+ ${EndIf}
+ ${EndUnless}
+
+ ; Remove directories and files we always control before parsing the uninstall
+ ; log so empty directories can be removed.
+ ${If} ${FileExists} "$INSTDIR\updates"
+ RmDir /r "$INSTDIR\updates"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\updated"
+ RmDir /r "$INSTDIR\updated"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\defaults\shortcuts"
+ RmDir /r "$INSTDIR\defaults\shortcuts"
+ ${EndIf}
+ ; Only remove the distribution directory if it exists and if the installer
+ ; isn't launched with an ini file that has RemoveDistributionDir=false in the
+ ; install section.
+ ${If} ${FileExists} "$INSTDIR\distribution"
+ ${AndIf} $R9 != "false"
+ RmDir /r "$INSTDIR\distribution"
+ ${EndIf}
+
+ ; Delete the app exe if present to prevent launching the app while we are
+ ; installing.
+ ClearErrors
+ ${DeleteFile} "$INSTDIR\${FileMainEXE}"
+ ${If} ${Errors}
+ ; If the user closed the application it can take several seconds for it to
+ ; shut down completely. If the application is being used by another user we
+ ; can rename the file and then delete is when the system is restarted.
+ Sleep 5000
+ ${DeleteFile} "$INSTDIR\${FileMainEXE}"
+ ClearErrors
+ ${EndIf}
+
+ ; setup the application model id registration value
+ ${InitHashAppModelId} "$INSTDIR" "Software\Mozilla\${AppName}\TaskBarIDs"
+
+ ; Remove the updates directory for Vista and above
+ ${CleanUpdateDirectories} "Mozilla\Pale Moon" "Mozilla\updates"
+
+ ${RemoveDeprecatedFiles}
+ ${RemovePrecompleteEntries} "false"
+
+ ${If} ${FileExists} "$INSTDIR\defaults\pref\channel-prefs.js"
+ Delete "$INSTDIR\defaults\pref\channel-prefs.js"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\defaults\pref"
+ RmDir "$INSTDIR\defaults\pref"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\defaults"
+ RmDir "$INSTDIR\defaults"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\uninstall"
+ ; Remove the uninstall directory that we control
+ RmDir /r "$INSTDIR\uninstall"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\update-settings.ini"
+ Delete "$INSTDIR\update-settings.ini"
+ ${EndIf}
+
+ ; Explictly remove empty webapprt dir in case it exists (bug 757978).
+ RmDir "$INSTDIR\webapprt\components"
+ RmDir "$INSTDIR\webapprt"
+
+ ${InstallStartCleanupCommon}
+SectionEnd
+
+Section "-Application" APP_IDX
+ ${StartUninstallLog}
+
+ SetDetailsPrint both
+ DetailPrint $(STATUS_INSTALL_APP)
+ SetDetailsPrint none
+
+ ${LogHeader} "Installing Main Files"
+ ${CopyFilesFromDir} "$EXEDIR\core" "$INSTDIR" \
+ "$(ERROR_CREATE_DIRECTORY_PREFIX)" \
+ "$(ERROR_CREATE_DIRECTORY_SUFFIX)"
+
+ ; Register DLLs
+ ; XXXrstrong - AccessibleMarshal.dll can be used by multiple applications but
+ ; is only registered for the last application installed. When the last
+ ; application installed is uninstalled AccessibleMarshal.dll will no longer be
+ ; registered. bug 338878
+ ${LogHeader} "DLL Registration"
+ ClearErrors
+ ${RegisterDLL} "$INSTDIR\AccessibleMarshal.dll"
+ ${If} ${Errors}
+ ${LogMsg} "** ERROR Registering: $INSTDIR\AccessibleMarshal.dll **"
+ ${Else}
+ ${LogUninstall} "DLLReg: \AccessibleMarshal.dll"
+ ${LogMsg} "Registered: $INSTDIR\AccessibleMarshal.dll"
+ ${EndIf}
+
+ ClearErrors
+
+ ; Default for creating Start Menu shortcut
+ ; (1 = create, 0 = don't create)
+ ${If} $AddStartMenuSC == ""
+ StrCpy $AddStartMenuSC "1"
+ ${EndIf}
+
+ ; Default for creating Quick Launch shortcut (1 = create, 0 = don't create)
+ ${If} $AddQuickLaunchSC == ""
+ ; Don't install the quick launch shortcut on Windows 7
+ ${If} ${AtLeastWin7}
+ StrCpy $AddQuickLaunchSC "0"
+ ${Else}
+ StrCpy $AddQuickLaunchSC "1"
+ ${EndIf}
+ ${EndIf}
+
+ ; Default for creating Desktop shortcut (1 = create, 0 = don't create)
+ ${If} $AddDesktopSC == ""
+ StrCpy $AddDesktopSC "1"
+ ${EndIf}
+
+ ${LogHeader} "Adding Registry Entries"
+ SetShellVarContext current ; Set SHCTX to HKCU
+ ${RegCleanMain} "Software\Mozilla"
+ ${RegCleanUninstall}
+ ${UpdateProtocolHandlers}
+
+ ClearErrors
+ WriteRegStr HKLM "Software\Mozilla" "${BrandShortName}InstallerTest" "Write Test"
+ ${If} ${Errors}
+ StrCpy $TmpVal "HKCU" ; used primarily for logging
+ ${Else}
+ SetShellVarContext all ; Set SHCTX to HKLM
+ DeleteRegValue HKLM "Software\Mozilla" "${BrandShortName}InstallerTest"
+ StrCpy $TmpVal "HKLM" ; used primarily for logging
+ ${RegCleanMain} "Software\Mozilla"
+ ${RegCleanUninstall}
+ ${UpdateProtocolHandlers}
+
+ ReadRegStr $0 HKLM "Software\mozilla.org\Mozilla" "CurrentVersion"
+ ${If} "$0" != "${GREVersion}"
+ WriteRegStr HKLM "Software\mozilla.org\Mozilla" "CurrentVersion" "${GREVersion}"
+ ${EndIf}
+ ${EndIf}
+
+ ${RemoveDeprecatedKeys}
+
+ ; The previous installer adds several regsitry values to both HKLM and HKCU.
+ ; We now try to add to HKLM and if that fails to HKCU
+
+ ; The order that reg keys and values are added is important if you use the
+ ; uninstall log to remove them on uninstall. When using the uninstall log you
+ ; MUST add children first so they will be removed first on uninstall so they
+ ; will be empty when the key is deleted. This allows the uninstaller to
+ ; specify that only empty keys will be deleted.
+ ${SetAppKeys}
+
+ ${FixClassKeys}
+
+ ; Uninstall keys can only exist under HKLM on some versions of windows. Since
+ ; it doesn't cause problems always add them.
+ ${SetUninstallKeys}
+
+ ; On install always add the PaleMoonHTML and PaleMoonURL keys.
+ ; An empty string is used for the 5th param because PaleMoonHTML is not a
+ ; protocol handler.
+ ${GetLongPath} "$INSTDIR\${FileMainEXE}" $8
+ StrCpy $2 "$\"$8$\" -osint -url $\"%1$\""
+
+ ; In Win8, the delegate execute handler picks up the value in PaleMoonURL and
+ ; PaleMoonHTML to launch the desktop browser when it needs to.
+ ${AddDisabledDDEHandlerValues} "PaleMoonHTML" "$2" "$8,1" \
+ "${AppRegName} Document" ""
+ ${AddDisabledDDEHandlerValues} "PaleMoonURL" "$2" "$8,1" "${AppRegName} URL" \
+ "true"
+
+ ; For pre win8, the following keys should only be set if we can write to HKLM.
+ ; For post win8, the keys below get set in both HKLM and HKCU.
+ ${If} $TmpVal == "HKLM"
+ ; Set the Start Menu Internet and Vista Registered App HKLM registry keys.
+ ${SetStartMenuInternet} "HKLM"
+ ${FixShellIconHandler} "HKLM"
+
+ ; If we are writing to HKLM and create either the desktop or start menu
+ ; shortcuts set IconsVisible to 1 otherwise to 0.
+ ${StrFilter} "${FileMainEXE}" "+" "" "" $R9
+ StrCpy $0 "Software\Clients\StartMenuInternet\$R9\InstallInfo"
+ ${If} $AddDesktopSC == 1
+ ${OrIf} $AddStartMenuSC == 1
+ WriteRegDWORD HKLM "$0" "IconsVisible" 1
+ ${Else}
+ WriteRegDWORD HKLM "$0" "IconsVisible" 0
+ ${EndIf}
+ ${EndIf}
+
+ ${If} ${AtLeastWin8}
+ ; Set the Start Menu Internet and Vista Registered App HKCU registry keys.
+ ${SetStartMenuInternet} "HKCU"
+ ${FixShellIconHandler} "HKCU"
+
+ ; If we create either the desktop or start menu shortcuts, then
+ ; set IconsVisible to 1 otherwise to 0.
+ ${StrFilter} "${FileMainEXE}" "+" "" "" $R9
+ StrCpy $0 "Software\Clients\StartMenuInternet\$R9\InstallInfo"
+ ${If} $AddDesktopSC == 1
+ ${OrIf} $AddStartMenuSC == 1
+ WriteRegDWORD HKCU "$0" "IconsVisible" 1
+ ${Else}
+ WriteRegDWORD HKCU "$0" "IconsVisible" 0
+ ${EndIf}
+ ${EndIf}
+
+ ; These need special handling on uninstall since they may be overwritten by
+ ; an install into a different location.
+ StrCpy $0 "Software\Microsoft\Windows\CurrentVersion\App Paths\${FileMainEXE}"
+ ${WriteRegStr2} $TmpVal "$0" "" "$INSTDIR\${FileMainEXE}" 0
+ ${WriteRegStr2} $TmpVal "$0" "Path" "$INSTDIR" 0
+
+ StrCpy $0 "Software\Microsoft\MediaPlayer\ShimInclusionList\$R9"
+ ${CreateRegKey} "$TmpVal" "$0" 0
+ StrCpy $0 "Software\Microsoft\MediaPlayer\ShimInclusionList\plugin-container.exe"
+ ${CreateRegKey} "$TmpVal" "$0" 0
+
+ ${If} $TmpVal == "HKLM"
+ ; Set the permitted LSP Categories for WinVista and above
+ ${SetAppLSPCategories} ${LSP_CATEGORIES}
+ ${EndIf}
+
+ ; Create shortcuts
+ ${LogHeader} "Adding Shortcuts"
+
+ ; Remove the start menu shortcuts and directory if the SMPROGRAMS section
+ ; exists in the shortcuts_log.ini and the SMPROGRAMS. The installer's shortcut
+ ; creation code will create the shortcut in the root of the Start Menu
+ ; Programs directory.
+ ${RemoveStartMenuDir}
+
+ ; Always add the application's shortcuts to the shortcuts log ini file. The
+ ; DeleteShortcuts macro will do the right thing on uninstall if the
+ ; shortcuts don't exist.
+ ${LogStartMenuShortcut} "${BrandFullName}.lnk"
+ ${LogQuickLaunchShortcut} "${BrandFullName}.lnk"
+ ${LogDesktopShortcut} "${BrandFullName}.lnk"
+
+ ; Best effort to update the Win7 taskbar and start menu shortcut app model
+ ; id's. The possible contexts are current user / system and the user that
+ ; elevated the installer.
+ Call FixShortcutAppModelIDs
+ ; If the current context is all also perform Win7 taskbar and start menu link
+ ; maintenance for the current user context.
+ ${If} $TmpVal == "HKLM"
+ SetShellVarContext current ; Set SHCTX to HKCU
+ Call FixShortcutAppModelIDs
+ SetShellVarContext all ; Set SHCTX to HKLM
+ ${EndIf}
+
+ ; If running elevated also perform Win7 taskbar and start menu link
+ ; maintenance for the unelevated user context in case that is different than
+ ; the current user.
+ ClearErrors
+ ${GetParameters} $0
+ ${GetOptions} "$0" "/UAC:" $0
+ ${Unless} ${Errors}
+ GetFunctionAddress $0 FixShortcutAppModelIDs
+ UAC::ExecCodeSegment $0
+ ${EndUnless}
+
+ ; UAC only allows elevating to an Admin account so there is no need to add
+ ; the Start Menu or Desktop shortcuts from the original unelevated process
+ ; since this will either add it for the user if unelevated or All Users if
+ ; elevated.
+ ${If} $AddStartMenuSC == 1
+ CreateShortCut "$SMPROGRAMS\${BrandFullName}.lnk" "$INSTDIR\${FileMainEXE}"
+ ${If} ${FileExists} "$SMPROGRAMS\${BrandFullName}.lnk"
+ ShellLink::SetShortCutWorkingDirectory "$SMPROGRAMS\${BrandFullName}.lnk" \
+ "$INSTDIR"
+ ${If} ${AtLeastWin7}
+ ${AndIf} "$AppUserModelID" != ""
+ ApplicationID::Set "$SMPROGRAMS\${BrandFullName}.lnk" "$AppUserModelID" "true"
+ ${EndIf}
+ ${LogMsg} "Added Shortcut: $SMPROGRAMS\${BrandFullName}.lnk"
+ ${Else}
+ ${LogMsg} "** ERROR Adding Shortcut: $SMPROGRAMS\${BrandFullName}.lnk"
+ ${EndIf}
+ ${EndIf}
+
+ ; Update lastwritetime of the Start Menu shortcut to clear the tile cache.
+ ${If} ${AtLeastWin8}
+ ${AndIf} ${FileExists} "$SMPROGRAMS\${BrandFullName}.lnk"
+ FileOpen $0 "$SMPROGRAMS\${BrandFullName}.lnk" a
+ FileClose $0
+ ${EndIf}
+
+ ${If} $AddDesktopSC == 1
+ CreateShortCut "$DESKTOP\${BrandFullName}.lnk" "$INSTDIR\${FileMainEXE}"
+ ${If} ${FileExists} "$DESKTOP\${BrandFullName}.lnk"
+ ShellLink::SetShortCutWorkingDirectory "$DESKTOP\${BrandFullName}.lnk" \
+ "$INSTDIR"
+ ${If} ${AtLeastWin7}
+ ${AndIf} "$AppUserModelID" != ""
+ ApplicationID::Set "$DESKTOP\${BrandFullName}.lnk" "$AppUserModelID" "true"
+ ${EndIf}
+ ${LogMsg} "Added Shortcut: $DESKTOP\${BrandFullName}.lnk"
+ ${Else}
+ ${LogMsg} "** ERROR Adding Shortcut: $DESKTOP\${BrandFullName}.lnk"
+ ${EndIf}
+ ${EndIf}
+
+ ; If elevated the Quick Launch shortcut must be added from the unelevated
+ ; original process.
+ ${If} $AddQuickLaunchSC == 1
+ ${Unless} ${AtLeastWin7}
+ ClearErrors
+ ${GetParameters} $0
+ ${GetOptions} "$0" "/UAC:" $0
+ ${If} ${Errors}
+ Call AddQuickLaunchShortcut
+ ${LogMsg} "Added Shortcut: $QUICKLAUNCH\${BrandFullName}.lnk"
+ ${Else}
+ ; It is not possible to add a log entry from the unelevated process so
+ ; add the log entry without the path since there is no simple way to
+ ; know the correct full path.
+ ${LogMsg} "Added Quick Launch Shortcut: ${BrandFullName}.lnk"
+ GetFunctionAddress $0 AddQuickLaunchShortcut
+ UAC::ExecCodeSegment $0
+ ${EndIf}
+ ${EndUnless}
+ ${EndIf}
+SectionEnd
+
+; Cleanup operations to perform at the end of the installation.
+Section "-InstallEndCleanup"
+ SetDetailsPrint both
+ DetailPrint "$(STATUS_CLEANUP)"
+ SetDetailsPrint none
+
+ ${Unless} ${Silent}
+ ClearErrors
+ ${MUI_INSTALLOPTIONS_READ} $0 "summary.ini" "Field 4" "State"
+ ${If} "$0" == "1"
+ ; NB: this code is duplicated in stub.nsi. Please keep in sync.
+ ; For data migration in the app, we want to know what the default browser
+ ; value was before we changed it. To do so, we read it here and store it
+ ; in our own registry key.
+ StrCpy $0 ""
+ ${If} ${AtLeastWinVista}
+ AppAssocReg::QueryCurrentDefault "http" "protocol" "effective"
+ Pop $1
+ ; If the method hasn't failed, $1 will contain the progid. Check:
+ ${If} "$1" != "method failed"
+ ${AndIf} "$1" != "method not available"
+ ; Read the actual command from the progid
+ ReadRegStr $0 HKCR "$1\shell\open\command" ""
+ ${EndIf}
+ ${EndIf}
+ ; If using the App Association Registry didn't happen or failed, fall back
+ ; to the effective http default:
+ ${If} "$0" == ""
+ ReadRegStr $0 HKCR "http\shell\open\command" ""
+ ${EndIf}
+ ; If we have something other than empty string now, write the value.
+ ${If} "$0" != ""
+ ClearErrors
+ WriteRegStr HKCU "Software\Mozilla\Pale Moon" "OldDefaultBrowserCommand" "$0"
+ ${EndIf}
+
+ ${LogHeader} "Setting as the default browser"
+ ClearErrors
+ ${GetParameters} $0
+ ${GetOptions} "$0" "/UAC:" $0
+ ${If} ${Errors}
+ Call SetAsDefaultAppUserHKCU
+ ${Else}
+ GetFunctionAddress $0 SetAsDefaultAppUserHKCU
+ UAC::ExecCodeSegment $0
+ ${EndIf}
+ ${ElseIfNot} ${Errors}
+ ${LogHeader} "Writing default-browser opt-out"
+ ClearErrors
+ WriteRegStr HKCU "Software\Mozilla\Pale Moon" "DefaultBrowserOptOut" "True"
+ ${If} ${Errors}
+ ${LogMsg} "Error writing default-browser opt-out"
+ ${EndIf}
+ ${EndIf}
+ ${EndUnless}
+
+ ; Adds a pinned Task Bar shortcut (see MigrateTaskBarShortcut for details).
+ ${MigrateTaskBarShortcut}
+
+ ; Add the Firewall entries during install
+ Call AddFirewallEntries
+
+ ; Refresh desktop icons
+ System::Call "shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i ${SHCNF_DWORDFLUSH}, i 0, i 0)"
+
+ ${InstallEndCleanupCommon}
+
+ ${If} $PreventRebootRequired == "true"
+ SetRebootFlag false
+ ${EndIf}
+
+ ${If} ${RebootFlag}
+ ; Admin is required to delete files on reboot so only add the moz-delete if
+ ; the user is an admin. After calling UAC::IsAdmin $0 will equal 1 if the
+ ; user is an admin.
+ UAC::IsAdmin
+ ${If} "$0" == "1"
+ ; When a reboot is required give SHChangeNotify time to finish the
+ ; refreshing the icons so the OS doesn't display the icons from helper.exe
+ Sleep 10000
+ ${LogHeader} "Reboot Required To Finish Installation"
+ ; ${FileMainEXE}.moz-upgrade should never exist but just in case...
+ ${Unless} ${FileExists} "$INSTDIR\${FileMainEXE}.moz-upgrade"
+ Rename "$INSTDIR\${FileMainEXE}" "$INSTDIR\${FileMainEXE}.moz-upgrade"
+ ${EndUnless}
+
+ ${If} ${FileExists} "$INSTDIR\${FileMainEXE}"
+ ClearErrors
+ Rename "$INSTDIR\${FileMainEXE}" "$INSTDIR\${FileMainEXE}.moz-delete"
+ ${Unless} ${Errors}
+ Delete /REBOOTOK "$INSTDIR\${FileMainEXE}.moz-delete"
+ ${EndUnless}
+ ${EndIf}
+
+ ${Unless} ${FileExists} "$INSTDIR\${FileMainEXE}"
+ CopyFiles /SILENT "$INSTDIR\uninstall\helper.exe" "$INSTDIR"
+ FileOpen $0 "$INSTDIR\${FileMainEXE}" w
+ FileWrite $0 "Will be deleted on restart"
+ Rename /REBOOTOK "$INSTDIR\${FileMainEXE}.moz-upgrade" "$INSTDIR\${FileMainEXE}"
+ FileClose $0
+ Delete "$INSTDIR\${FileMainEXE}"
+ Rename "$INSTDIR\helper.exe" "$INSTDIR\${FileMainEXE}"
+ ${EndUnless}
+ ${EndIf}
+ ${EndIf}
+SectionEnd
+
+################################################################################
+# Install Abort Survey Functions
+
+Function CustomAbort
+ ${If} "${AB_CD}" == "en-US"
+ ${AndIf} "$PageName" != ""
+ ${AndIf} ${FileExists} "$EXEDIR\core\distribution\distribution.ini"
+ ReadINIStr $0 "$EXEDIR\core\distribution\distribution.ini" "Global" "about"
+ ClearErrors
+ ${WordFind} "$0" "Funnelcake" "E#" $1
+ ${Unless} ${Errors}
+ ; Yes = fill out the survey and exit, No = don't fill out survey and exit,
+ ; Cancel = don't exit.
+ MessageBox MB_YESNO|MB_ICONEXCLAMATION \
+ "Would you like to tell us why you are canceling this installation?" \
+ IDYes +1 IDNO CustomAbort_finish
+ ${If} "$PageName" == "Welcome"
+ GetFunctionAddress $0 AbortSurveyWelcome
+ ${ElseIf} "$PageName" == "Options"
+ GetFunctionAddress $0 AbortSurveyOptions
+ ${ElseIf} "$PageName" == "Directory"
+ GetFunctionAddress $0 AbortSurveyDirectory
+ ${ElseIf} "$PageName" == "Shortcuts"
+ GetFunctionAddress $0 AbortSurveyShortcuts
+ ${ElseIf} "$PageName" == "Summary"
+ GetFunctionAddress $0 AbortSurveySummary
+ ${EndIf}
+ ClearErrors
+ ${GetParameters} $1
+ ${GetOptions} "$1" "/UAC:" $2
+ ${If} ${Errors}
+ Call $0
+ ${Else}
+ UAC::ExecCodeSegment $0
+ ${EndIf}
+
+ CustomAbort_finish:
+ Return
+ ${EndUnless}
+ ${EndIf}
+
+ MessageBox MB_YESNO|MB_ICONEXCLAMATION "$(MOZ_MUI_TEXT_ABORTWARNING)" \
+ IDYES +1 IDNO +2
+ Return
+ Abort
+FunctionEnd
+
+Function AbortSurveyWelcome
+ ExecShell "open" "${AbortSurveyURL}step1"
+FunctionEnd
+
+Function AbortSurveyOptions
+ ExecShell "open" "${AbortSurveyURL}step2"
+FunctionEnd
+
+Function AbortSurveyDirectory
+ ExecShell "open" "${AbortSurveyURL}step3"
+FunctionEnd
+
+Function AbortSurveyShortcuts
+ ExecShell "open" "${AbortSurveyURL}step4"
+FunctionEnd
+
+Function AbortSurveySummary
+ ExecShell "open" "${AbortSurveyURL}step5"
+FunctionEnd
+
+################################################################################
+# Helper Functions
+
+Function AddQuickLaunchShortcut
+ CreateShortCut "$QUICKLAUNCH\${BrandFullName}.lnk" "$INSTDIR\${FileMainEXE}"
+ ${If} ${FileExists} "$QUICKLAUNCH\${BrandFullName}.lnk"
+ ShellLink::SetShortCutWorkingDirectory "$QUICKLAUNCH\${BrandFullName}.lnk" \
+ "$INSTDIR"
+ ${EndIf}
+FunctionEnd
+
+Function CheckExistingInstall
+ ; If there is a pending file copy from a previous upgrade don't allow
+ ; installing until after the system has rebooted.
+ IfFileExists "$INSTDIR\${FileMainEXE}.moz-upgrade" +1 +4
+ MessageBox MB_YESNO|MB_ICONEXCLAMATION "$(WARN_RESTART_REQUIRED_UPGRADE)" IDNO +2
+ Reboot
+ Quit
+
+ ; If there is a pending file deletion from a previous uninstall don't allow
+ ; installing until after the system has rebooted.
+ IfFileExists "$INSTDIR\${FileMainEXE}.moz-delete" +1 +4
+ MessageBox MB_YESNO|MB_ICONEXCLAMATION "$(WARN_RESTART_REQUIRED_UNINSTALL)" IDNO +2
+ Reboot
+ Quit
+
+ ${If} ${FileExists} "$INSTDIR\${FileMainEXE}"
+ ; Disable the next, cancel, and back buttons
+ GetDlgItem $0 $HWNDPARENT 1 ; Next button
+ EnableWindow $0 0
+ GetDlgItem $0 $HWNDPARENT 2 ; Cancel button
+ EnableWindow $0 0
+ GetDlgItem $0 $HWNDPARENT 3 ; Back button
+ EnableWindow $0 0
+
+ Banner::show /NOUNLOAD "$(BANNER_CHECK_EXISTING)"
+
+ ${If} "$TmpVal" == "FoundMessageWindow"
+ Sleep 5000
+ ${EndIf}
+
+ ${PushFilesToCheck}
+
+ ; Store the return value in $TmpVal so it is less likely to be accidentally
+ ; overwritten elsewhere.
+ ${CheckForFilesInUse} $TmpVal
+
+ Banner::destroy
+
+ ; Enable the next, cancel, and back buttons
+ GetDlgItem $0 $HWNDPARENT 1 ; Next button
+ EnableWindow $0 1
+ GetDlgItem $0 $HWNDPARENT 2 ; Cancel button
+ EnableWindow $0 1
+ GetDlgItem $0 $HWNDPARENT 3 ; Back button
+ EnableWindow $0 1
+
+ ${If} "$TmpVal" == "true"
+ StrCpy $TmpVal "FoundMessageWindow"
+ ${ManualCloseAppPrompt} "${WindowClass}" "$(WARN_MANUALLY_CLOSE_APP_INSTALL)"
+ StrCpy $TmpVal "true"
+ ${EndIf}
+ ${EndIf}
+FunctionEnd
+
+Function LaunchApp
+!ifndef DEV_EDITION
+ ${ManualCloseAppPrompt} "${WindowClass}" "$(WARN_MANUALLY_CLOSE_APP_LAUNCH)"
+!endif
+
+ ClearErrors
+ ${GetParameters} $0
+ ${GetOptions} "$0" "/UAC:" $1
+ ${If} ${Errors}
+ Exec "$\"$INSTDIR\${FileMainEXE}$\""
+ ${Else}
+ GetFunctionAddress $0 LaunchAppFromElevatedProcess
+ UAC::ExecCodeSegment $0
+ ${EndIf}
+FunctionEnd
+
+Function LaunchAppFromElevatedProcess
+ ; Find the installation directory when launching using GetFunctionAddress
+ ; from an elevated installer since $INSTDIR will not be set in this installer
+ ${StrFilter} "${FileMainEXE}" "+" "" "" $R9
+ ReadRegStr $0 HKLM "Software\Clients\StartMenuInternet\$R9\DefaultIcon" ""
+ ${GetPathFromString} "$0" $0
+ ${GetParent} "$0" $1
+ ; Set our current working directory to the application's install directory
+ ; otherwise the 7-Zip temp directory will be in use and won't be deleted.
+ SetOutPath "$1"
+ Exec "$\"$0$\""
+FunctionEnd
+
+################################################################################
+# Language
+
+!insertmacro MOZ_MUI_LANGUAGE 'baseLocale'
+!verbose push
+!verbose 3
+!include "overrideLocale.nsh"
+!include "customLocale.nsh"
+!verbose pop
+
+; Set this after the locale files to override it if it is in the locale
+; using " " for BrandingText will hide the "Nullsoft Install System..." branding
+BrandingText " "
+
+################################################################################
+# Page pre, show, and leave functions
+
+Function preWelcome
+ StrCpy $PageName "Welcome"
+ ${If} ${FileExists} "$EXEDIR\core\distribution\modern-wizard.bmp"
+ Delete "$PLUGINSDIR\modern-wizard.bmp"
+ CopyFiles /SILENT "$EXEDIR\core\distribution\modern-wizard.bmp" "$PLUGINSDIR\modern-wizard.bmp"
+ ${EndIf}
+FunctionEnd
+
+Function preOptions
+ StrCpy $PageName "Options"
+ ${If} ${FileExists} "$EXEDIR\core\distribution\modern-header.bmp"
+ ${AndIf} $hHeaderBitmap == ""
+ Delete "$PLUGINSDIR\modern-header.bmp"
+ CopyFiles /SILENT "$EXEDIR\core\distribution\modern-header.bmp" "$PLUGINSDIR\modern-header.bmp"
+ ${ChangeMUIHeaderImage} "$PLUGINSDIR\modern-header.bmp"
+ ${EndIf}
+ !insertmacro MUI_HEADER_TEXT "$(OPTIONS_PAGE_TITLE)" "$(OPTIONS_PAGE_SUBTITLE)"
+ !insertmacro MUI_INSTALLOPTIONS_DISPLAY "options.ini"
+FunctionEnd
+
+Function leaveOptions
+ ${MUI_INSTALLOPTIONS_READ} $0 "options.ini" "Settings" "State"
+ ${If} $0 != 0
+ Abort
+ ${EndIf}
+ ${MUI_INSTALLOPTIONS_READ} $R0 "options.ini" "Field 2" "State"
+ StrCmp $R0 "1" +1 +2
+ StrCpy $InstallType ${INSTALLTYPE_BASIC}
+ ${MUI_INSTALLOPTIONS_READ} $R0 "options.ini" "Field 3" "State"
+ StrCmp $R0 "1" +1 +2
+ StrCpy $InstallType ${INSTALLTYPE_CUSTOM}
+
+ ${LeaveOptionsCommon}
+
+ ${If} $InstallType == ${INSTALLTYPE_BASIC}
+ Call CheckExistingInstall
+ ${EndIf}
+FunctionEnd
+
+Function preDirectory
+ StrCpy $PageName "Directory"
+ ${PreDirectoryCommon}
+FunctionEnd
+
+Function leaveDirectory
+ ${If} $InstallType == ${INSTALLTYPE_BASIC}
+ Call CheckExistingInstall
+ ${EndIf}
+ ${LeaveDirectoryCommon} "$(WARN_DISK_SPACE)" "$(WARN_WRITE_ACCESS)"
+FunctionEnd
+
+Function preShortcuts
+ StrCpy $PageName "Shortcuts"
+ ${CheckCustomCommon}
+ !insertmacro MUI_HEADER_TEXT "$(SHORTCUTS_PAGE_TITLE)" "$(SHORTCUTS_PAGE_SUBTITLE)"
+ !insertmacro MUI_INSTALLOPTIONS_DISPLAY "shortcuts.ini"
+FunctionEnd
+
+Function leaveShortcuts
+ ${MUI_INSTALLOPTIONS_READ} $0 "shortcuts.ini" "Settings" "State"
+ ${If} $0 != 0
+ Abort
+ ${EndIf}
+ ${MUI_INSTALLOPTIONS_READ} $AddDesktopSC "shortcuts.ini" "Field 2" "State"
+ ${MUI_INSTALLOPTIONS_READ} $AddStartMenuSC "shortcuts.ini" "Field 3" "State"
+
+ ; Don't install the quick launch shortcut on Windows 7
+ ${Unless} ${AtLeastWin7}
+ ${MUI_INSTALLOPTIONS_READ} $AddQuickLaunchSC "shortcuts.ini" "Field 4" "State"
+ ${EndUnless}
+
+ ${If} $InstallType == ${INSTALLTYPE_CUSTOM}
+ Call CheckExistingInstall
+ ${EndIf}
+FunctionEnd
+
+Function preSummary
+ StrCpy $PageName "Summary"
+ ; Setup the summary.ini file for the Custom Summary Page
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Settings" NumFields "3"
+
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 1" Type "label"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 1" Text "$(SUMMARY_INSTALLED_TO)"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 1" Left "0"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 1" Right "-1"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 1" Top "5"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 1" Bottom "15"
+
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 2" Type "text"
+ ; The contents of this control must be set as follows in the pre function
+ ; ${MUI_INSTALLOPTIONS_READ} $1 "summary.ini" "Field 2" "HWND"
+ ; SendMessage $1 ${WM_SETTEXT} 0 "STR:$INSTDIR"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 2" state ""
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 2" Left "0"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 2" Right "-1"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 2" Top "17"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 2" Bottom "30"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 2" flags "READONLY"
+
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 3" Type "label"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 3" Left "0"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 3" Right "-1"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 3" Top "130"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 3" Bottom "150"
+
+ ${If} ${FileExists} "$INSTDIR\${FileMainEXE}"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 3" Text "$(SUMMARY_UPGRADE_CLICK)"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Settings" NextButtonText "$(UPGRADE_BUTTON)"
+ ${Else}
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 3" Text "$(SUMMARY_INSTALL_CLICK)"
+ DeleteINIStr "$PLUGINSDIR\summary.ini" "Settings" NextButtonText
+ ${EndIf}
+
+
+ ; Remove the "Field 4" ini section in case the user hits back and changes the
+ ; installation directory which could change whether the make default checkbox
+ ; should be displayed.
+ DeleteINISec "$PLUGINSDIR\summary.ini" "Field 4"
+
+ ; Check if it is possible to write to HKLM
+ ClearErrors
+ WriteRegStr HKLM "Software\Mozilla" "${BrandShortName}InstallerTest" "Write Test"
+ ${Unless} ${Errors}
+ DeleteRegValue HKLM "Software\Mozilla" "${BrandShortName}InstallerTest"
+ ; Check if Pale Moon is the http handler for this user.
+ SetShellVarContext current ; Set SHCTX to the current user
+ ${IsHandlerForInstallDir} "http" $R9
+ ${If} $TmpVal == "HKLM"
+ SetShellVarContext all ; Set SHCTX to all users
+ ${EndIf}
+ ; If Pale Moon isn't the http handler for this user show the option to set
+ ; Pale Moon as the default browser.
+ ${If} "$R9" != "true"
+ ${AndIf} ${AtMostWin2008R2}
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Settings" NumFields "4"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 4" Type "checkbox"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 4" Text "$(SUMMARY_TAKE_DEFAULTS)"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 4" Left "0"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 4" Right "-1"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 4" State "1"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 4" Top "32"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field 4" Bottom "53"
+ ${EndIf}
+ ${EndUnless}
+
+ ${If} "$TmpVal" == "true"
+ ; If there is already a Type entry in the "Field 4" section with a value of
+ ; checkbox then the set as the default browser checkbox is displayed and
+ ; this text must be moved below it.
+ ReadINIStr $0 "$PLUGINSDIR\summary.ini" "Field 4" "Type"
+ ${If} "$0" == "checkbox"
+ StrCpy $0 "5"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field $0" Top "53"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field $0" Bottom "68"
+ ${Else}
+ StrCpy $0 "4"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field $0" Top "35"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field $0" Bottom "50"
+ ${EndIf}
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Settings" NumFields "$0"
+
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field $0" Type "label"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field $0" Text "$(SUMMARY_REBOOT_REQUIRED_INSTALL)"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field $0" Left "0"
+ WriteINIStr "$PLUGINSDIR\summary.ini" "Field $0" Right "-1"
+ ${EndIf}
+
+ !insertmacro MUI_HEADER_TEXT "$(SUMMARY_PAGE_TITLE)" "$(SUMMARY_PAGE_SUBTITLE)"
+
+ ; The Summary custom page has a textbox that will automatically receive
+ ; focus. This sets the focus to the Install button instead.
+ !insertmacro MUI_INSTALLOPTIONS_INITDIALOG "summary.ini"
+ GetDlgItem $0 $HWNDPARENT 1
+ System::Call "user32::SetFocus(i r0, i 0x0007, i,i)i"
+ ${MUI_INSTALLOPTIONS_READ} $1 "summary.ini" "Field 2" "HWND"
+ SendMessage $1 ${WM_SETTEXT} 0 "STR:$INSTDIR"
+ !insertmacro MUI_INSTALLOPTIONS_SHOW
+FunctionEnd
+
+Function leaveSummary
+ ; Try to delete the app executable and if we can't delete it try to find the
+ ; app's message window and prompt the user to close the app. This allows
+ ; running an instance that is located in another directory. If for whatever
+ ; reason there is no message window we will just rename the app's files and
+ ; then remove them on restart.
+ ClearErrors
+ ${DeleteFile} "$INSTDIR\${FileMainEXE}"
+ ${If} ${Errors}
+ ${ManualCloseAppPrompt} "${WindowClass}" "$(WARN_MANUALLY_CLOSE_APP_INSTALL)"
+ ${EndIf}
+FunctionEnd
+
+; When we add an optional action to the finish page the cancel button is
+; enabled. This disables it and leaves the finish button as the only choice.
+Function preFinish
+ StrCpy $PageName ""
+ ${EndInstallLog} "${BrandFullName}"
+ !insertmacro MUI_INSTALLOPTIONS_WRITE "ioSpecial.ini" "settings" "cancelenabled" "0"
+FunctionEnd
+
+################################################################################
+# Initialization Functions
+
+Function .onInit
+ ; Remove the current exe directory from the search order.
+ ; This only effects LoadLibrary calls and not implicitly loaded DLLs.
+ System::Call 'kernel32::SetDllDirectoryW(w "")'
+
+ StrCpy $PageName ""
+ StrCpy $LANGUAGE 0
+ ${SetBrandNameVars} "$EXEDIR\core\distribution\setup.ini"
+
+ ; Don't install on systems that don't support SSE2. The parameter value of
+ ; 10 is for PF_XMMI64_INSTRUCTIONS_AVAILABLE which will check whether the
+ ; SSE2 instruction set is available. Result returned in $R7.
+ System::Call "kernel32::IsProcessorFeaturePresent(i 10)i .R7"
+
+ ; Windows NT 6.0 and lower are not supported on any architecture.
+ ${Unless} ${AtLeastWin7}
+ ${If} "$R7" == "0"
+ strCpy $R7 "$(WARN_MIN_SUPPORTED_OSVER_CPU_MSG)"
+ ${Else}
+ strCpy $R7 "$(WARN_MIN_SUPPORTED_OSVER_MSG)"
+ ${EndIf}
+ MessageBox MB_OKCANCEL|MB_ICONSTOP "$R7" IDCANCEL +2
+ ExecShell "open" "${URLSystemRequirements}"
+ Quit
+ ${EndUnless}
+
+ ; SSE2 support
+ ${If} "$R7" == "0"
+ MessageBox MB_OKCANCEL|MB_ICONSTOP "$(WARN_MIN_SUPPORTED_CPU_MSG)" IDCANCEL +2
+ ExecShell "open" "${URLSystemRequirements}"
+ Quit
+ ${EndIf}
+
+!ifdef HAVE_64BIT_BUILD
+ ${Unless} ${RunningX64}
+ MessageBox MB_OKCANCEL|MB_ICONSTOP "$(WARN_MIN_SUPPORTED_OSVER_MSG)" IDCANCEL +2
+ ExecShell "open" "${URLSystemRequirements}"
+ Quit
+ ${EndUnless}
+ SetRegView 64
+!endif
+
+ ${InstallOnInitCommon} "$(WARN_MIN_SUPPORTED_OSVER_CPU_MSG)"
+
+; The commands inside this ifndef are needed prior to NSIS 3.0a2 and can be
+; removed after we require NSIS 3.0a2 or greater.
+!ifndef NSIS_PACKEDVERSION
+ System::Call 'user32::SetProcessDPIAware()'
+!endif
+
+ !insertmacro InitInstallOptionsFile "options.ini"
+ !insertmacro InitInstallOptionsFile "shortcuts.ini"
+ !insertmacro InitInstallOptionsFile "summary.ini"
+
+ WriteINIStr "$PLUGINSDIR\options.ini" "Settings" NumFields "5"
+
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 1" Type "label"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 1" Text "$(OPTIONS_SUMMARY)"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 1" Left "0"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 1" Right "-1"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 1" Top "0"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 1" Bottom "10"
+
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 2" Type "RadioButton"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 2" Text "$(OPTION_STANDARD_RADIO)"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 2" Left "0"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 2" Right "-1"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 2" Top "25"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 2" Bottom "35"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 2" State "1"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 2" Flags "GROUP"
+
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 3" Type "RadioButton"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 3" Text "$(OPTION_CUSTOM_RADIO)"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 3" Left "0"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 3" Right "-1"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 3" Top "55"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 3" Bottom "65"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 3" State "0"
+
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 4" Type "label"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 4" Text "$(OPTION_STANDARD_DESC)"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 4" Left "15"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 4" Right "-1"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 4" Top "37"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 4" Bottom "57"
+
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 5" Type "label"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 5" Text "$(OPTION_CUSTOM_DESC)"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 5" Left "15"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 5" Right "-1"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 5" Top "67"
+ WriteINIStr "$PLUGINSDIR\options.ini" "Field 5" Bottom "87"
+
+ ; Setup the shortcuts.ini file for the Custom Shortcuts Page
+ ; Don't offer to install the quick launch shortcut on Windows 7
+ ${If} ${AtLeastWin7}
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Settings" NumFields "3"
+ ${Else}
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Settings" NumFields "4"
+ ${EndIf}
+
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 1" Type "label"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 1" Text "$(CREATE_ICONS_DESC)"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 1" Left "0"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 1" Right "-1"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 1" Top "5"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 1" Bottom "15"
+
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 2" Type "checkbox"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 2" Text "$(ICONS_DESKTOP)"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 2" Left "0"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 2" Right "-1"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 2" Top "20"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 2" Bottom "30"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 2" State "1"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 2" Flags "GROUP"
+
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 3" Type "checkbox"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 3" Text "$(ICONS_STARTMENU)"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 3" Left "0"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 3" Right "-1"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 3" Top "40"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 3" Bottom "50"
+ WriteINIStr "$PLUGINSDIR\shortcuts.ini" "Field 3" State "1"
+
+ ; There must always be a core directory.
+ ${GetSize} "$EXEDIR\core\" "/S=0K" $R5 $R7 $R8
+ SectionSetSize ${APP_IDX} $R5
+
+ ; Initialize $hHeaderBitmap to prevent redundant changing of the bitmap if
+ ; the user clicks the back button
+ StrCpy $hHeaderBitmap ""
+FunctionEnd
+
+Function .onGUIEnd
+ ${OnEndCommon}
+FunctionEnd
diff --git a/installer/windows/nsis/shared.nsh b/installer/windows/nsis/shared.nsh
new file mode 100644
index 0000000..385a411
--- /dev/null
+++ b/installer/windows/nsis/shared.nsh
@@ -0,0 +1,1306 @@
+# 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/.
+
+!macro PostUpdate
+ ; PostUpdate is called from both session 0 and from the user session
+ ; for service updates, make sure that we only register with the user session
+ ; Otherwise ApplicationID::Set can fail intermittently with a file in use error.
+ System::Call "kernel32::GetCurrentProcessId() i.r0"
+ System::Call "kernel32::ProcessIdToSessionId(i $0, *i ${NSIS_MAX_STRLEN} r9)"
+
+ ; Determine if we're the protected UserChoice default or not. If so fix the
+ ; start menu tile. In case there are 2 PaleMoon installations, we only do
+ ; this if the application being updated is the default.
+ ReadRegStr $0 HKCU "Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice" "ProgId"
+ ${If} $0 == "PaleMoonURL"
+ ${AndIf} $9 != 0 ; We're not running in session 0
+ ReadRegStr $0 HKCU "Software\Classes\PaleMoonURL\shell\open\command" ""
+ ${GetPathFromString} "$0" $0
+ ${GetParent} "$0" $0
+ ${If} ${FileExists} "$0"
+ ${GetLongPath} "$0" $0
+ ${EndIf}
+ ${EndIf}
+
+ ${CreateShortcutsLog}
+
+ ; Remove registry entries for non-existent apps and for apps that point to our
+ ; install location in the Software\Mozilla key and uninstall registry entries
+ ; that point to our install location for both HKCU and HKLM.
+ SetShellVarContext current ; Set SHCTX to the current user (e.g. HKCU)
+ ${RegCleanMain} "Software\Mozilla"
+ ${RegCleanUninstall}
+ ${UpdateProtocolHandlers}
+
+ ; setup the application model id registration value
+ ${InitHashAppModelId} "$INSTDIR" "Software\Mozilla\${AppName}\TaskBarIDs"
+
+ ; Win7 taskbar and start menu link maintenance
+ Call FixShortcutAppModelIDs
+
+ ClearErrors
+ WriteRegStr HKLM "Software\Mozilla" "${BrandShortName}InstallerTest" "Write Test"
+ ${If} ${Errors}
+ StrCpy $TmpVal "HKCU" ; used primarily for logging
+ ${Else}
+ SetShellVarContext all ; Set SHCTX to all users (e.g. HKLM)
+ DeleteRegValue HKLM "Software\Mozilla" "${BrandShortName}InstallerTest"
+ StrCpy $TmpVal "HKLM" ; used primarily for logging
+ ${RegCleanMain} "Software\Mozilla"
+ ${RegCleanUninstall}
+ ${UpdateProtocolHandlers}
+ ${FixShellIconHandler} "HKLM"
+ ${SetAppLSPCategories} ${LSP_CATEGORIES}
+
+ ; Win7 taskbar and start menu link maintenance
+ Call FixShortcutAppModelIDs
+
+ ; Add the Firewall entries after an update
+ Call AddFirewallEntries
+
+ ; Only update the Clients\StartMenuInternet registry key values in HKLM if
+ ; they don't exist or this installation is the same as the one set in those
+ ; keys.
+ ${StrFilter} "${FileMainEXE}" "+" "" "" $1
+ ReadRegStr $0 HKLM "Software\Clients\StartMenuInternet\$1\DefaultIcon" ""
+ ${GetPathFromString} "$0" $0
+ ${GetParent} "$0" $0
+ ${If} ${FileExists} "$0"
+ ${GetLongPath} "$0" $0
+ ${EndIf}
+ ${If} "$0" == "$INSTDIR"
+ ${SetStartMenuInternet} "HKLM"
+ ${EndIf}
+
+ ; Only update the Clients\StartMenuInternet registry key values in HKCU if
+ ; they don't exist or this installation is the same as the one set in those
+ ; keys. This is only done in Windows 8 to avoid a UAC prompt.
+ ${If} ${AtLeastWin8}
+ ReadRegStr $0 HKCU "Software\Clients\StartMenuInternet\$1\DefaultIcon" ""
+ ${GetPathFromString} "$0" $0
+ ${GetParent} "$0" $0
+ ${If} ${FileExists} "$0"
+ ${GetLongPath} "$0" $0
+ ${EndIf}
+ ${If} "$0" == "$INSTDIR"
+ ${SetStartMenuInternet} "HKCU"
+ ${EndIf}
+ ${EndIf}
+
+ ReadRegStr $0 HKLM "Software\mozilla.org\Mozilla" "CurrentVersion"
+ ${If} "$0" != "${GREVersion}"
+ WriteRegStr HKLM "Software\mozilla.org\Mozilla" "CurrentVersion" "${GREVersion}"
+ ${EndIf}
+ ${EndIf}
+
+ ; Migrate the application's Start Menu directory to a single shortcut in the
+ ; root of the Start Menu Programs directory.
+ ${MigrateStartMenuShortcut}
+
+ ; Update lastwritetime of the Start Menu shortcut to clear the tile cache.
+ ${If} ${AtLeastWin8}
+ ${AndIf} ${FileExists} "$SMPROGRAMS\${BrandFullName}.lnk"
+ FileOpen $0 "$SMPROGRAMS\${BrandFullName}.lnk" a
+ FileClose $0
+ ${EndIf}
+
+ ; Adds a pinned Task Bar shortcut (see MigrateTaskBarShortcut for details).
+ ${MigrateTaskBarShortcut}
+
+ ${RemoveDeprecatedKeys}
+
+ ${SetAppKeys}
+ ${FixClassKeys}
+ ${SetUninstallKeys}
+
+ ; Remove files that may be left behind by the application in the
+ ; VirtualStore directory.
+ ${CleanVirtualStore}
+
+ ${RemoveDeprecatedFiles}
+
+ ; Fix the distribution.ini file if applicable
+ ${FixDistributionsINI}
+
+ RmDir /r /REBOOTOK "$INSTDIR\${TO_BE_DELETED}"
+!macroend
+!define PostUpdate "!insertmacro PostUpdate"
+
+!macro SetAsDefaultAppGlobal
+ ${RemoveDeprecatedKeys} ; Does not use SHCTX
+
+ SetShellVarContext all ; Set SHCTX to all users (e.g. HKLM)
+ ${SetHandlers} ; Uses SHCTX
+ ${SetStartMenuInternet} "HKLM"
+ ${FixShellIconHandler} "HKLM"
+ ${ShowShortcuts}
+ ${StrFilter} "${FileMainEXE}" "+" "" "" $R9
+ WriteRegStr HKLM "Software\Clients\StartMenuInternet" "" "$R9"
+!macroend
+!define SetAsDefaultAppGlobal "!insertmacro SetAsDefaultAppGlobal"
+
+; Removes shortcuts for this installation. This should also remove the
+; application from Open With for the file types the application handles
+; (bug 370480).
+!macro HideShortcuts
+ ${StrFilter} "${FileMainEXE}" "+" "" "" $0
+ StrCpy $R1 "Software\Clients\StartMenuInternet\$0\InstallInfo"
+ WriteRegDWORD HKLM "$R1" "IconsVisible" 0
+ ${If} ${AtLeastWin8}
+ WriteRegDWORD HKCU "$R1" "IconsVisible" 0
+ ${EndIf}
+
+ SetShellVarContext all ; Set $DESKTOP to All Users
+ ${Unless} ${FileExists} "$DESKTOP\${BrandFullName}.lnk"
+ SetShellVarContext current ; Set $DESKTOP to the current user's desktop
+ ${EndUnless}
+
+ ${If} ${FileExists} "$DESKTOP\${BrandFullName}.lnk"
+ ShellLink::GetShortCutArgs "$DESKTOP\${BrandFullName}.lnk"
+ Pop $0
+ ${If} "$0" == ""
+ ShellLink::GetShortCutTarget "$DESKTOP\${BrandFullName}.lnk"
+ Pop $0
+ ${GetLongPath} "$0" $0
+ ${If} "$0" == "$INSTDIR\${FileMainEXE}"
+ Delete "$DESKTOP\${BrandFullName}.lnk"
+ ${EndIf}
+ ${EndIf}
+ ${EndIf}
+
+ SetShellVarContext all ; Set $SMPROGRAMS to All Users
+ ${Unless} ${FileExists} "$SMPROGRAMS\${BrandFullName}.lnk"
+ SetShellVarContext current ; Set $SMPROGRAMS to the current user's Start
+ ; Menu Programs directory
+ ${EndUnless}
+
+ ${If} ${FileExists} "$SMPROGRAMS\${BrandFullName}.lnk"
+ ShellLink::GetShortCutArgs "$SMPROGRAMS\${BrandFullName}.lnk"
+ Pop $0
+ ${If} "$0" == ""
+ ShellLink::GetShortCutTarget "$SMPROGRAMS\${BrandFullName}.lnk"
+ Pop $0
+ ${GetLongPath} "$0" $0
+ ${If} "$0" == "$INSTDIR\${FileMainEXE}"
+ Delete "$SMPROGRAMS\${BrandFullName}.lnk"
+ ${EndIf}
+ ${EndIf}
+ ${EndIf}
+
+ ${If} ${FileExists} "$QUICKLAUNCH\${BrandFullName}.lnk"
+ ShellLink::GetShortCutArgs "$QUICKLAUNCH\${BrandFullName}.lnk"
+ Pop $0
+ ${If} "$0" == ""
+ ShellLink::GetShortCutTarget "$QUICKLAUNCH\${BrandFullName}.lnk"
+ Pop $0
+ ${GetLongPath} "$0" $0
+ ${If} "$0" == "$INSTDIR\${FileMainEXE}"
+ Delete "$QUICKLAUNCH\${BrandFullName}.lnk"
+ ${EndIf}
+ ${EndIf}
+ ${EndIf}
+!macroend
+!define HideShortcuts "!insertmacro HideShortcuts"
+
+; Adds shortcuts for this installation. This should also add the application
+; to Open With for the file types the application handles (bug 370480).
+!macro ShowShortcuts
+ ${StrFilter} "${FileMainEXE}" "+" "" "" $0
+ StrCpy $R1 "Software\Clients\StartMenuInternet\$0\InstallInfo"
+ WriteRegDWORD HKLM "$R1" "IconsVisible" 1
+ ${If} ${AtLeastWin8}
+ WriteRegDWORD HKCU "$R1" "IconsVisible" 1
+ ${EndIf}
+
+ SetShellVarContext all ; Set $DESKTOP to All Users
+ ${Unless} ${FileExists} "$DESKTOP\${BrandFullName}.lnk"
+ CreateShortCut "$DESKTOP\${BrandFullName}.lnk" "$INSTDIR\${FileMainEXE}"
+ ${If} ${FileExists} "$DESKTOP\${BrandFullName}.lnk"
+ ShellLink::SetShortCutWorkingDirectory "$DESKTOP\${BrandFullName}.lnk" "$INSTDIR"
+ ${If} ${AtLeastWin7}
+ ${AndIf} "$AppUserModelID" != ""
+ ApplicationID::Set "$DESKTOP\${BrandFullName}.lnk" "$AppUserModelID" "true"
+ ${EndIf}
+ ${Else}
+ SetShellVarContext current ; Set $DESKTOP to the current user's desktop
+ ${Unless} ${FileExists} "$DESKTOP\${BrandFullName}.lnk"
+ CreateShortCut "$DESKTOP\${BrandFullName}.lnk" "$INSTDIR\${FileMainEXE}"
+ ${If} ${FileExists} "$DESKTOP\${BrandFullName}.lnk"
+ ShellLink::SetShortCutWorkingDirectory "$DESKTOP\${BrandFullName}.lnk" \
+ "$INSTDIR"
+ ${If} ${AtLeastWin7}
+ ${AndIf} "$AppUserModelID" != ""
+ ApplicationID::Set "$DESKTOP\${BrandFullName}.lnk" "$AppUserModelID" "true"
+ ${EndIf}
+ ${EndIf}
+ ${EndUnless}
+ ${EndIf}
+ ${EndUnless}
+
+ SetShellVarContext all ; Set $SMPROGRAMS to All Users
+ ${Unless} ${FileExists} "$SMPROGRAMS\${BrandFullName}.lnk"
+ CreateShortCut "$SMPROGRAMS\${BrandFullName}.lnk" "$INSTDIR\${FileMainEXE}"
+ ${If} ${FileExists} "$SMPROGRAMS\${BrandFullName}.lnk"
+ ShellLink::SetShortCutWorkingDirectory "$SMPROGRAMS\${BrandFullName}.lnk" \
+ "$INSTDIR"
+ ${If} ${AtLeastWin7}
+ ${AndIf} "$AppUserModelID" != ""
+ ApplicationID::Set "$SMPROGRAMS\${BrandFullName}.lnk" "$AppUserModelID" "true"
+ ${EndIf}
+ ${Else}
+ SetShellVarContext current ; Set $SMPROGRAMS to the current user's Start
+ ; Menu Programs directory
+ ${Unless} ${FileExists} "$SMPROGRAMS\${BrandFullName}.lnk"
+ CreateShortCut "$SMPROGRAMS\${BrandFullName}.lnk" "$INSTDIR\${FileMainEXE}"
+ ${If} ${FileExists} "$SMPROGRAMS\${BrandFullName}.lnk"
+ ShellLink::SetShortCutWorkingDirectory "$SMPROGRAMS\${BrandFullName}.lnk" \
+ "$INSTDIR"
+ ${If} ${AtLeastWin7}
+ ${AndIf} "$AppUserModelID" != ""
+ ApplicationID::Set "$SMPROGRAMS\${BrandFullName}.lnk" "$AppUserModelID" "true"
+ ${EndIf}
+ ${EndIf}
+ ${EndUnless}
+ ${EndIf}
+ ${EndUnless}
+
+ ; Windows 7 doesn't use the QuickLaunch directory
+ ${Unless} ${AtLeastWin7}
+ ${AndUnless} ${FileExists} "$QUICKLAUNCH\${BrandFullName}.lnk"
+ CreateShortCut "$QUICKLAUNCH\${BrandFullName}.lnk" \
+ "$INSTDIR\${FileMainEXE}"
+ ${If} ${FileExists} "$QUICKLAUNCH\${BrandFullName}.lnk"
+ ShellLink::SetShortCutWorkingDirectory "$QUICKLAUNCH\${BrandFullName}.lnk" \
+ "$INSTDIR"
+ ${EndIf}
+ ${EndUnless}
+!macroend
+!define ShowShortcuts "!insertmacro ShowShortcuts"
+
+!macro AddAssociationIfNoneExist FILE_TYPE
+ ClearErrors
+ EnumRegKey $7 HKCR "${FILE_TYPE}" 0
+ ${If} ${Errors}
+ WriteRegStr SHCTX "SOFTWARE\Classes\${FILE_TYPE}" "" "PaleMoonHTML"
+ ${EndIf}
+ WriteRegStr SHCTX "SOFTWARE\Classes\${FILE_TYPE}\OpenWithProgids" "PaleMoonHTML" ""
+!macroend
+!define AddAssociationIfNoneExist "!insertmacro AddAssociationIfNoneExist"
+
+; Adds the protocol and file handler registry entries for making PaleMoon the
+; default handler (uses SHCTX).
+!macro SetHandlers
+ ${GetLongPath} "$INSTDIR\${FileMainEXE}" $8
+
+ StrCpy $0 "SOFTWARE\Classes"
+ StrCpy $2 "$\"$8$\" -osint -url $\"%1$\""
+
+ ; Associate the file handlers with PaleMoonHTML
+ ReadRegStr $6 SHCTX "$0\.htm" ""
+ ${If} "$6" != "PaleMoonHTML"
+ WriteRegStr SHCTX "$0\.htm" "" "PaleMoonHTML"
+ ${EndIf}
+
+ ReadRegStr $6 SHCTX "$0\.html" ""
+ ${If} "$6" != "PaleMoonHTML"
+ WriteRegStr SHCTX "$0\.html" "" "PaleMoonHTML"
+ ${EndIf}
+
+ ReadRegStr $6 SHCTX "$0\.shtml" ""
+ ${If} "$6" != "PaleMoonHTML"
+ WriteRegStr SHCTX "$0\.shtml" "" "PaleMoonHTML"
+ ${EndIf}
+
+ ReadRegStr $6 SHCTX "$0\.xht" ""
+ ${If} "$6" != "PaleMoonHTML"
+ WriteRegStr SHCTX "$0\.xht" "" "PaleMoonHTML"
+ ${EndIf}
+
+ ReadRegStr $6 SHCTX "$0\.xhtml" ""
+ ${If} "$6" != "PaleMoonHTML"
+ WriteRegStr SHCTX "$0\.xhtml" "" "PaleMoonHTML"
+ ${EndIf}
+
+ ${AddAssociationIfNoneExist} ".oga"
+ ${AddAssociationIfNoneExist} ".ogg"
+ ${AddAssociationIfNoneExist} ".ogv"
+ ${AddAssociationIfNoneExist} ".webm"
+
+ ; An empty string is used for the 5th param because PaleMoonHTML is not a
+ ; protocol handler
+ ${AddDisabledDDEHandlerValues} "PaleMoonHTML" "$2" "$8,1" \
+ "${AppRegName} HTML Document" ""
+
+ ${AddDisabledDDEHandlerValues} "PaleMoonURL" "$2" "$8,1" "${AppRegName} URL" \
+ "true"
+ ; An empty string is used for the 4th & 5th params because the following
+ ; protocol handlers already have a display name and the additional keys
+ ; required for a protocol handler.
+ ${AddDisabledDDEHandlerValues} "ftp" "$2" "$8,1" "" ""
+ ${AddDisabledDDEHandlerValues} "http" "$2" "$8,1" "" ""
+ ${AddDisabledDDEHandlerValues} "https" "$2" "$8,1" "" ""
+!macroend
+!define SetHandlers "!insertmacro SetHandlers"
+
+; Adds the HKLM\Software\Clients\StartMenuInternet\PALEMOON.EXE registry
+; entries (does not use SHCTX).
+;
+; The values for StartMenuInternet are only valid under HKLM and there can only
+; be one installation registerred under StartMenuInternet per application since
+; the key name is derived from the main application executable.
+; http://support.microsoft.com/kb/297878
+;
+; In Windows 8 this changes slightly, you can store StartMenuInternet entries in
+; HKCU. The icon in start menu for StartMenuInternet is deprecated as of Win7,
+; but the subkeys are what's important. Control panel default programs looks
+; for them only in HKLM pre win8.
+;
+; Note: we might be able to get away with using the full path to the
+; application executable for the key name in order to support multiple
+; installations.
+!macro SetStartMenuInternet RegKey
+ ${GetLongPath} "$INSTDIR\${FileMainEXE}" $8
+ ${GetLongPath} "$INSTDIR\uninstall\helper.exe" $7
+
+ ${StrFilter} "${FileMainEXE}" "+" "" "" $R9
+
+ StrCpy $0 "Software\Clients\StartMenuInternet\$R9"
+
+ WriteRegStr ${RegKey} "$0" "" "${BrandFullName}"
+
+ WriteRegStr ${RegKey} "$0\DefaultIcon" "" "$8,0"
+
+ ; The Reinstall Command is defined at
+ ; http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/shell/programmersguide/shell_adv/registeringapps.asp
+ WriteRegStr ${RegKey} "$0\InstallInfo" "HideIconsCommand" "$\"$7$\" /HideShortcuts"
+ WriteRegStr ${RegKey} "$0\InstallInfo" "ShowIconsCommand" "$\"$7$\" /ShowShortcuts"
+ WriteRegStr ${RegKey} "$0\InstallInfo" "ReinstallCommand" "$\"$7$\" /SetAsDefaultAppGlobal"
+
+ ClearErrors
+ ReadRegDWORD $1 ${RegKey} "$0\InstallInfo" "IconsVisible"
+ ; If the IconsVisible name value pair doesn't exist add it otherwise the
+ ; application won't be displayed in Set Program Access and Defaults.
+ ${If} ${Errors}
+ ${If} ${FileExists} "$QUICKLAUNCH\${BrandFullName}.lnk"
+ WriteRegDWORD ${RegKey} "$0\InstallInfo" "IconsVisible" 1
+ ${Else}
+ WriteRegDWORD ${RegKey} "$0\InstallInfo" "IconsVisible" 0
+ ${EndIf}
+ ${EndIf}
+
+ WriteRegStr ${RegKey} "$0\shell\open\command" "" "$\"$8$\""
+
+ WriteRegStr ${RegKey} "$0\shell\properties" "" "$(CONTEXT_OPTIONS)"
+ WriteRegStr ${RegKey} "$0\shell\properties\command" "" "$\"$8$\" -preferences"
+
+ WriteRegStr ${RegKey} "$0\shell\safemode" "" "$(CONTEXT_SAFE_MODE)"
+ WriteRegStr ${RegKey} "$0\shell\safemode\command" "" "$\"$8$\" -safe-mode"
+
+ ; Vista Capabilities registry keys
+ WriteRegStr ${RegKey} "$0\Capabilities" "ApplicationDescription" "$(REG_APP_DESC)"
+ WriteRegStr ${RegKey} "$0\Capabilities" "ApplicationIcon" "$8,0"
+ WriteRegStr ${RegKey} "$0\Capabilities" "ApplicationName" "${BrandShortName}"
+
+ WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".htm" "PaleMoonHTML"
+ WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".html" "PaleMoonHTML"
+ WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".shtml" "PaleMoonHTML"
+ WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".xht" "PaleMoonHTML"
+ WriteRegStr ${RegKey} "$0\Capabilities\FileAssociations" ".xhtml" "PaleMoonHTML"
+
+ WriteRegStr ${RegKey} "$0\Capabilities\StartMenu" "StartMenuInternet" "$R9"
+
+ WriteRegStr ${RegKey} "$0\Capabilities\URLAssociations" "ftp" "PaleMoonURL"
+ WriteRegStr ${RegKey} "$0\Capabilities\URLAssociations" "http" "PaleMoonURL"
+ WriteRegStr ${RegKey} "$0\Capabilities\URLAssociations" "https" "PaleMoonURL"
+
+ ; Vista Registered Application
+ WriteRegStr ${RegKey} "Software\RegisteredApplications" "${AppRegName}" "$0\Capabilities"
+!macroend
+!define SetStartMenuInternet "!insertmacro SetStartMenuInternet"
+
+; The IconHandler reference for PaleMoonHTML can end up in an inconsistent state
+; due to changes not being detected by the IconHandler for side by side
+; installs (see bug 268512). The symptoms can be either an incorrect icon or no
+; icon being displayed for files associated with PaleMoon (does not use SHCTX).
+!macro FixShellIconHandler RegKey
+ ClearErrors
+ ReadRegStr $1 ${RegKey} "Software\Classes\PaleMoonHTML\ShellEx\IconHandler" ""
+ ${Unless} ${Errors}
+ ReadRegStr $1 ${RegKey} "Software\Classes\PaleMoonHTML\DefaultIcon" ""
+ ${GetLongPath} "$INSTDIR\${FileMainEXE}" $2
+ ${If} "$1" != "$2,1"
+ WriteRegStr ${RegKey} "Software\Classes\PaleMoonHTML\DefaultIcon" "" "$2,1"
+ ${EndIf}
+ ${EndUnless}
+!macroend
+!define FixShellIconHandler "!insertmacro FixShellIconHandler"
+
+; Add Software\Mozilla\ registry entries (uses SHCTX).
+!macro SetAppKeys
+ ; Check if this is an ESR release and if so add registry values so it is
+ ; possible to determine that this is an ESR install (bug 726781).
+ ClearErrors
+ ${WordFind} "${UpdateChannel}" "esr" "E#" $3
+ ${If} ${Errors}
+ StrCpy $3 ""
+ ${Else}
+ StrCpy $3 " ESR"
+ ${EndIf}
+
+ ${GetLongPath} "$INSTDIR" $8
+ StrCpy $0 "Software\Mozilla\${BrandFullNameInternal}\${AppVersion}$3 (${ARCH} ${AB_CD})\Main"
+ ${WriteRegStr2} $TmpVal "$0" "Install Directory" "$8" 0
+ ${WriteRegStr2} $TmpVal "$0" "PathToExe" "$8\${FileMainEXE}" 0
+
+ StrCpy $0 "Software\Mozilla\${BrandFullNameInternal}\${AppVersion}$3 (${ARCH} ${AB_CD})\Uninstall"
+ ${WriteRegStr2} $TmpVal "$0" "Description" "${BrandFullNameInternal} ${AppVersion}$3 (${ARCH} ${AB_CD})" 0
+
+ StrCpy $0 "Software\Mozilla\${BrandFullNameInternal}\${AppVersion}$3 (${ARCH} ${AB_CD})"
+ ${WriteRegStr2} $TmpVal "$0" "" "${AppVersion}$3 (${ARCH} ${AB_CD})" 0
+ ${If} "$3" == ""
+ DeleteRegValue SHCTX "$0" "ESR"
+ ${Else}
+ ${WriteRegDWORD2} $TmpVal "$0" "ESR" 1 0
+ ${EndIf}
+
+ StrCpy $0 "Software\Mozilla\${BrandFullNameInternal} ${AppVersion}$3\bin"
+ ${WriteRegStr2} $TmpVal "$0" "PathToExe" "$8\${FileMainEXE}" 0
+
+ StrCpy $0 "Software\Mozilla\${BrandFullNameInternal} ${AppVersion}$3\extensions"
+ ${WriteRegStr2} $TmpVal "$0" "Components" "$8\components" 0
+ ${WriteRegStr2} $TmpVal "$0" "Plugins" "$8\plugins" 0
+
+ StrCpy $0 "Software\Mozilla\${BrandFullNameInternal} ${AppVersion}$3"
+ ${WriteRegStr2} $TmpVal "$0" "GeckoVer" "${GREVersion}" 0
+ ${If} "$3" == ""
+ DeleteRegValue SHCTX "$0" "ESR"
+ ${Else}
+ ${WriteRegDWORD2} $TmpVal "$0" "ESR" 1 0
+ ${EndIf}
+
+ StrCpy $0 "Software\Mozilla\${BrandFullNameInternal}$3"
+ ${WriteRegStr2} $TmpVal "$0" "" "${GREVersion}" 0
+ ${WriteRegStr2} $TmpVal "$0" "CurrentVersion" "${AppVersion}$3 (${ARCH} ${AB_CD})" 0
+!macroend
+!define SetAppKeys "!insertmacro SetAppKeys"
+
+; Add uninstall registry entries. This macro tests for write access to determine
+; if the uninstall keys should be added to HKLM or HKCU.
+!macro SetUninstallKeys
+ ; Check if this is an ESR release and if so add registry values so it is
+ ; possible to determine that this is an ESR install (bug 726781).
+ ClearErrors
+ ${WordFind} "${UpdateChannel}" "esr" "E#" $3
+ ${If} ${Errors}
+ StrCpy $3 ""
+ ${Else}
+ StrCpy $3 " ESR"
+ ${EndIf}
+
+ StrCpy $0 "Software\Microsoft\Windows\CurrentVersion\Uninstall\${BrandFullNameInternal} ${AppVersion}$3 (${ARCH} ${AB_CD})"
+
+ StrCpy $2 ""
+ ClearErrors
+ WriteRegStr HKLM "$0" "${BrandShortName}InstallerTest" "Write Test"
+ ${If} ${Errors}
+ ; If the uninstall keys already exist in HKLM don't create them in HKCU
+ ClearErrors
+ ReadRegStr $2 "HKLM" $0 "DisplayName"
+ ${If} $2 == ""
+ ; Otherwise we don't have any keys for this product in HKLM so proceeed
+ ; to create them in HKCU. Better handling for this will be done in:
+ ; Bug 711044 - Better handling for 2 uninstall icons
+ StrCpy $1 "HKCU"
+ SetShellVarContext current ; Set SHCTX to the current user (e.g. HKCU)
+ ${EndIf}
+ ClearErrors
+ ${Else}
+ StrCpy $1 "HKLM"
+ SetShellVarContext all ; Set SHCTX to all users (e.g. HKLM)
+ DeleteRegValue HKLM "$0" "${BrandShortName}InstallerTest"
+ ${EndIf}
+
+ ${If} $2 == ""
+ ${GetLongPath} "$INSTDIR" $8
+
+ ; Write the uninstall registry keys
+ ${WriteRegStr2} $1 "$0" "Comments" "${BrandFullNameInternal} ${AppVersion}$3 (${ARCH} ${AB_CD})" 0
+ ${WriteRegStr2} $1 "$0" "DisplayIcon" "$8\${FileMainEXE},0" 0
+ ${WriteRegStr2} $1 "$0" "DisplayName" "${BrandFullNameInternal} ${AppVersion}$3 (${ARCH} ${AB_CD})" 0
+ ${WriteRegStr2} $1 "$0" "DisplayVersion" "${AppVersion}" 0
+ ${WriteRegStr2} $1 "$0" "HelpLink" "${HelpLink}" 0
+ ${WriteRegStr2} $1 "$0" "InstallLocation" "$8" 0
+ ${WriteRegStr2} $1 "$0" "Publisher" "Moonchild Productions" 0
+ ${WriteRegStr2} $1 "$0" "UninstallString" "$\"$8\uninstall\helper.exe$\"" 0
+ DeleteRegValue SHCTX "$0" "URLInfoAbout"
+; Don't add URLUpdateInfo which is the release notes url except for the release
+; and esr channels since nightly, aurora, and beta do not have release notes.
+; Note: URLUpdateInfo is only defined in the official branding.nsi.
+!ifdef URLUpdateInfo
+!ifndef BETA_UPDATE_CHANNEL
+ ${WriteRegStr2} $1 "$0" "URLUpdateInfo" "${URLUpdateInfo}" 0
+!endif
+!endif
+ ${WriteRegStr2} $1 "$0" "URLInfoAbout" "${URLInfoAbout}" 0
+ ${WriteRegDWORD2} $1 "$0" "NoModify" 1 0
+ ${WriteRegDWORD2} $1 "$0" "NoRepair" 1 0
+
+ ${GetSize} "$8" "/S=0K" $R2 $R3 $R4
+ ${WriteRegDWORD2} $1 "$0" "EstimatedSize" $R2 0
+
+ ${If} "$TmpVal" == "HKLM"
+ SetShellVarContext all ; Set SHCTX to all users (e.g. HKLM)
+ ${Else}
+ SetShellVarContext current ; Set SHCTX to the current user (e.g. HKCU)
+ ${EndIf}
+ ${EndIf}
+!macroend
+!define SetUninstallKeys "!insertmacro SetUninstallKeys"
+
+; Due to a bug when associating some file handlers, only SHCTX was checked for
+; some file types such as ".pdf". SHCTX is set to HKCU or HKLM depending on
+; whether the installer has write access to HKLM. The bug would happen when
+; HCKU was checked and didn't exist since programs aren't required to set the
+; HKCU Software\Classes keys when associating handlers. The fix uses the merged
+; view in HKCR to check for existance of an existing association. This macro
+; cleans affected installations by removing the HKLM and HKCU value if it is set
+; to PaleMoonHTML when there is a value for PersistentHandler or by removing the
+; HKCU value when the HKLM value has a value other than an empty string.
+!macro FixBadFileAssociation FILE_TYPE
+ ; Only delete the default value in case the key has values for OpenWithList,
+ ; OpenWithProgids, PersistentHandler, etc.
+ ReadRegStr $0 HKCU "Software\Classes\${FILE_TYPE}" ""
+ ReadRegStr $1 HKLM "Software\Classes\${FILE_TYPE}" ""
+ ReadRegStr $2 HKCR "${FILE_TYPE}\PersistentHandler" ""
+ ${If} "$2" != ""
+ ; Since there is a persistent handler remove PaleMoonHTML as the default
+ ; value from both HKCU and HKLM if it set to PaleMoonHTML.
+ ${If} "$0" == "PaleMoonHTML"
+ DeleteRegValue HKCU "Software\Classes\${FILE_TYPE}" ""
+ ${EndIf}
+ ${If} "$1" == "PaleMoonHTML"
+ DeleteRegValue HKLM "Software\Classes\${FILE_TYPE}" ""
+ ${EndIf}
+ ${ElseIf} "$0" == "PaleMoonHTML"
+ ; Since KHCU is set to PaleMoonHTML remove PaleMoonHTML as the default value
+ ; from HKCU if HKLM is set to a value other than an empty string.
+ ${If} "$1" != ""
+ DeleteRegValue HKCU "Software\Classes\${FILE_TYPE}" ""
+ ${EndIf}
+ ${EndIf}
+!macroend
+!define FixBadFileAssociation "!insertmacro FixBadFileAssociation"
+
+; Add app specific handler registry entries under Software\Classes if they
+; don't exist (does not use SHCTX).
+!macro FixClassKeys
+ StrCpy $1 "SOFTWARE\Classes"
+
+ ; File handler keys and name value pairs that may need to be created during
+ ; install or upgrade.
+ ReadRegStr $0 HKCR ".shtml" "Content Type"
+ ${If} "$0" == ""
+ StrCpy $0 "$1\.shtml"
+ ${WriteRegStr2} $TmpVal "$1\.shtml" "" "shtmlfile" 0
+ ${WriteRegStr2} $TmpVal "$1\.shtml" "Content Type" "text/html" 0
+ ${WriteRegStr2} $TmpVal "$1\.shtml" "PerceivedType" "text" 0
+ ${EndIf}
+
+ ReadRegStr $0 HKCR ".xht" "Content Type"
+ ${If} "$0" == ""
+ ${WriteRegStr2} $TmpVal "$1\.xht" "" "xhtfile" 0
+ ${WriteRegStr2} $TmpVal "$1\.xht" "Content Type" "application/xhtml+xml" 0
+ ${EndIf}
+
+ ReadRegStr $0 HKCR ".xhtml" "Content Type"
+ ${If} "$0" == ""
+ ${WriteRegStr2} $TmpVal "$1\.xhtml" "" "xhtmlfile" 0
+ ${WriteRegStr2} $TmpVal "$1\.xhtml" "Content Type" "application/xhtml+xml" 0
+ ${EndIf}
+
+ ; Remove possibly badly associated file types
+ ${FixBadFileAssociation} ".oga"
+ ${FixBadFileAssociation} ".ogg"
+ ${FixBadFileAssociation} ".ogv"
+ ${FixBadFileAssociation} ".webm"
+!macroend
+!define FixClassKeys "!insertmacro FixClassKeys"
+
+; Updates protocol handlers if their registry open command value is for this
+; install location (uses SHCTX).
+!macro UpdateProtocolHandlers
+ ; Store the command to open the app with an url in a register for easy access.
+ ${GetLongPath} "$INSTDIR\${FileMainEXE}" $8
+ StrCpy $2 "$\"$8$\" -osint -url $\"%1$\""
+
+ ; Only set the file and protocol handlers if the existing one under HKCR is
+ ; for this install location.
+
+ ${IsHandlerForInstallDir} "PaleMoonHTML" $R9
+ ${If} "$R9" == "true"
+ ; An empty string is used for the 5th param because PaleMoonHTML is not a
+ ; protocol handler.
+ ${AddDisabledDDEHandlerValues} "PaleMoonHTML" "$2" "$8,1" \
+ "${AppRegName} HTML Document" ""
+ ${EndIf}
+
+ ${IsHandlerForInstallDir} "PaleMoonURL" $R9
+ ${If} "$R9" == "true"
+ ${AddDisabledDDEHandlerValues} "PaleMoonURL" "$2" "$8,1" \
+ "${AppRegName} URL" "true"
+ ${EndIf}
+
+ ; An empty string is used for the 4th & 5th params because the following
+ ; protocol handlers already have a display name and the additional keys
+ ; required for a protocol handler.
+ ${IsHandlerForInstallDir} "ftp" $R9
+ ${If} "$R9" == "true"
+ ${AddDisabledDDEHandlerValues} "ftp" "$2" "$8,1" "" ""
+ ${EndIf}
+
+ ${IsHandlerForInstallDir} "http" $R9
+ ${If} "$R9" == "true"
+ ${AddDisabledDDEHandlerValues} "http" "$2" "$8,1" "" ""
+ ${EndIf}
+
+ ${IsHandlerForInstallDir} "https" $R9
+ ${If} "$R9" == "true"
+ ${AddDisabledDDEHandlerValues} "https" "$2" "$8,1" "" ""
+ ${EndIf}
+!macroend
+!define UpdateProtocolHandlers "!insertmacro UpdateProtocolHandlers"
+
+; Removes various registry entries for reasons noted below (does not use SHCTX).
+!macro RemoveDeprecatedKeys
+ StrCpy $0 "SOFTWARE\Classes"
+ ; Remove support for launching gopher urls from the shell during install or
+ ; update if the DefaultIcon is from palemoon.exe.
+ ${RegCleanAppHandler} "gopher"
+
+ ; Remove support for launching chrome urls from the shell during install or
+ ; update if the DefaultIcon is from palemoon.exe (Bug 301073).
+ ${RegCleanAppHandler} "chrome"
+
+ ; Remove protocol handler registry keys added by the MS shim
+ DeleteRegKey HKLM "Software\Classes\PaleMoon.URL"
+ DeleteRegKey HKCU "Software\Classes\PaleMoon.URL"
+
+ ; Delete gopher from Capabilities\URLAssociations if it is present.
+ ${StrFilter} "${FileMainEXE}" "+" "" "" $R9
+ StrCpy $0 "Software\Clients\StartMenuInternet\$R9"
+ ClearErrors
+ ReadRegStr $2 HKLM "$0\Capabilities\URLAssociations" "gopher"
+ ${Unless} ${Errors}
+ DeleteRegValue HKLM "$0\Capabilities\URLAssociations" "gopher"
+ ${EndUnless}
+
+ ; Delete gopher from the user's UrlAssociations if it points to PaleMoonURL.
+ StrCpy $0 "Software\Microsoft\Windows\Shell\Associations\UrlAssociations\gopher"
+ ReadRegStr $2 HKCU "$0\UserChoice" "Progid"
+ ${If} "$2" == "PaleMoonURL"
+ DeleteRegKey HKCU "$0"
+ ${EndIf}
+!macroend
+!define RemoveDeprecatedKeys "!insertmacro RemoveDeprecatedKeys"
+
+; Removes various directories and files for reasons noted below.
+!macro RemoveDeprecatedFiles
+ ; Remove talkback if it is present (remove after bug 386760 is fixed)
+ ${If} ${FileExists} "$INSTDIR\extensions\talkback@mozilla.org"
+ RmDir /r /REBOOTOK "$INSTDIR\extensions\talkback@mozilla.org"
+ ${EndIf}
+
+ ; Remove the Java Console extension (bug 1165156)
+ ${If} ${FileExists} "$INSTDIR\extensions\{CAFEEFAC-0016-0000-0031-ABCDEFFEDCBA}"
+ RmDir /r /REBOOTOK "$INSTDIR\extensions\{CAFEEFAC-0016-0000-0031-ABCDEFFEDCBA}"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\extensions\{CAFEEFAC-0016-0000-0034-ABCDEFFEDCBA}"
+ RmDir /r /REBOOTOK "$INSTDIR\extensions\{CAFEEFAC-0016-0000-0034-ABCDEFFEDCBA}"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\extensions\{CAFEEFAC-0016-0000-0039-ABCDEFFEDCBA}"
+ RmDir /r /REBOOTOK "$INSTDIR\extensions\{CAFEEFAC-0016-0000-0039-ABCDEFFEDCBA}"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\extensions\{CAFEEFAC-0016-0000-0045-ABCDEFFEDCBA}"
+ RmDir /r /REBOOTOK "$INSTDIR\extensions\{CAFEEFAC-0016-0000-0045-ABCDEFFEDCBA}"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\extensions\{CAFEEFAC-0017-0000-0000-ABCDEFFEDCBA}"
+ RmDir /r /REBOOTOK "$INSTDIR\extensions\{CAFEEFAC-0017-0000-0000-ABCDEFFEDCBA}"
+ ${EndIf}
+!macroend
+!define RemoveDeprecatedFiles "!insertmacro RemoveDeprecatedFiles"
+
+; Converts specific partner distribution.ini from ansi to utf-8 (bug 882989)
+!macro FixDistributionsINI
+ StrCpy $1 "$INSTDIR\distribution\distribution.ini"
+ StrCpy $2 "$INSTDIR\distribution\utf8fix"
+ StrCpy $0 "0" ; Default to not attempting to fix
+
+ ; Check if the distribution.ini settings are for a partner build that needs
+ ; to have its distribution.ini converted from ansi to utf-8.
+ ${If} ${FileExists} "$1"
+ ${Unless} ${FileExists} "$2"
+ ReadINIStr $3 "$1" "Preferences" "app.distributor"
+ ${If} "$3" == "yahoo"
+ ReadINIStr $3 "$1" "Preferences" "app.distributor.channel"
+ ${If} "$3" == "de"
+ ${OrIf} "$3" == "es"
+ ${OrIf} "$3" == "e1"
+ ${OrIf} "$3" == "mx"
+ StrCpy $0 "1"
+ ${EndIf}
+ ${EndIf}
+ ; Create the utf8fix so this only runs once
+ FileOpen $3 "$2" w
+ FileClose $3
+ ${EndUnless}
+ ${EndIf}
+
+ ${If} "$0" == "1"
+ StrCpy $0 "0"
+ ClearErrors
+ ReadINIStr $3 "$1" "Global" "version"
+ ${Unless} ${Errors}
+ StrCpy $4 "$3" 2
+ ${If} "$4" == "1."
+ StrCpy $4 "$3" "" 2 ; Everything after "1."
+ ${If} $4 < 23
+ StrCpy $0 "1"
+ ${EndIf}
+ ${EndIf}
+ ${EndUnless}
+ ${EndIf}
+
+ ${If} "$0" == "1"
+ ClearErrors
+ FileOpen $3 "$1" r
+ ${If} ${Errors}
+ FileClose $3
+ ${Else}
+ StrCpy $2 "$INSTDIR\distribution\distribution.new"
+ ClearErrors
+ FileOpen $4 "$2" w
+ ${If} ${Errors}
+ FileClose $3
+ FileClose $4
+ ${Else}
+ StrCpy $0 "0" ; Default to not replacing the original distribution.ini
+ ${Do}
+ FileReadByte $3 $5
+ ${If} $5 == ""
+ ${Break}
+ ${EndIf}
+ ${If} $5 == 233 ; ansi é
+ StrCpy $0 "1"
+ FileWriteByte $4 195
+ FileWriteByte $4 169
+ ${ElseIf} $5 == 241 ; ansi ñ
+ StrCpy $0 "1"
+ FileWriteByte $4 195
+ FileWriteByte $4 177
+ ${ElseIf} $5 == 252 ; ansi ü
+ StrCpy $0 "1"
+ FileWriteByte $4 195
+ FileWriteByte $4 188
+ ${ElseIf} $5 < 128
+ FileWriteByte $4 $5
+ ${EndIf}
+ ${Loop}
+ FileClose $3
+ FileClose $4
+ ${If} "$0" == "1"
+ ClearErrors
+ Rename "$1" "$1.bak"
+ ${Unless} ${Errors}
+ Rename "$2" "$1"
+ Delete "$1.bak"
+ ${EndUnless}
+ ${Else}
+ Delete "$2"
+ ${EndIf}
+ ${EndIf}
+ ${EndIf}
+ ${EndIf}
+!macroend
+!define FixDistributionsINI "!insertmacro FixDistributionsINI"
+
+; Adds a pinned shortcut to Task Bar on update for Windows 7 and above if this
+; macro has never been called before and the application is default (see
+; PinToTaskBar for more details).
+; Since defaults handling is handled by Windows in Win8 and later, we always
+; attempt to pin a taskbar on that OS. If Windows sets the defaults at
+; installation time, then we don't get the opportunity to run this code at
+; that time.
+!macro MigrateTaskBarShortcut
+ ${GetShortcutsLogPath} $0
+ ${If} ${FileExists} "$0"
+ ClearErrors
+ ReadINIStr $1 "$0" "TASKBAR" "Migrated"
+ ${If} ${Errors}
+ ClearErrors
+ WriteIniStr "$0" "TASKBAR" "Migrated" "true"
+ ${If} ${AtLeastWin7}
+ ; No need to check the default on Win8 and later
+ ${If} ${AtMostWin2008R2}
+ ; Check if the Pale Moon is the http handler for this user
+ SetShellVarContext current ; Set SHCTX to the current user
+ ${IsHandlerForInstallDir} "http" $R9
+ ${If} $TmpVal == "HKLM"
+ SetShellVarContext all ; Set SHCTX to all users
+ ${EndIf}
+ ${EndIf}
+ ${If} "$R9" == "true"
+ ${OrIf} ${AtLeastWin8}
+ ${PinToTaskBar}
+ ${EndIf}
+ ${EndIf}
+ ${EndIf}
+ ${EndIf}
+!macroend
+!define MigrateTaskBarShortcut "!insertmacro MigrateTaskBarShortcut"
+
+; Adds a pinned Task Bar shortcut on Windows 7 if there isn't one for the main
+; application executable already. Existing pinned shortcuts for the same
+; application model ID must be removed first to prevent breaking the pinned
+; item's lists but multiple installations with the same application model ID is
+; an edgecase. If removing existing pinned shortcuts with the same application
+; model ID removes a pinned pinned Start Menu shortcut this will also add a
+; pinned Start Menu shortcut.
+!macro PinToTaskBar
+ ${If} ${AtLeastWin7}
+ StrCpy $8 "false" ; Whether a shortcut had to be created
+ ${IsPinnedToTaskBar} "$INSTDIR\${FileMainEXE}" $R9
+ ${If} "$R9" == "false"
+ ; Find an existing Start Menu shortcut or create one to use for pinning
+ ${GetShortcutsLogPath} $0
+ ${If} ${FileExists} "$0"
+ ClearErrors
+ ReadINIStr $1 "$0" "STARTMENU" "Shortcut0"
+ ${Unless} ${Errors}
+ SetShellVarContext all ; Set SHCTX to all users
+ ${Unless} ${FileExists} "$SMPROGRAMS\$1"
+ SetShellVarContext current ; Set SHCTX to the current user
+ ${Unless} ${FileExists} "$SMPROGRAMS\$1"
+ StrCpy $8 "true"
+ CreateShortCut "$SMPROGRAMS\$1" "$INSTDIR\${FileMainEXE}"
+ ${If} ${FileExists} "$SMPROGRAMS\$1"
+ ShellLink::SetShortCutWorkingDirectory "$SMPROGRAMS\$1" \
+ "$INSTDIR"
+ ${If} "$AppUserModelID" != ""
+ ApplicationID::Set "$SMPROGRAMS\$1" "$AppUserModelID" "true"
+ ${EndIf}
+ ${EndIf}
+ ${EndUnless}
+ ${EndUnless}
+
+ ${If} ${FileExists} "$SMPROGRAMS\$1"
+ ; Count of Start Menu pinned shortcuts before unpinning.
+ ${PinnedToStartMenuLnkCount} $R9
+
+ ; Having multiple shortcuts pointing to different installations with
+ ; the same AppUserModelID (e.g. side by side installations of the
+ ; same version) will make the TaskBar shortcut's lists into an bad
+ ; state where the lists are not shown. To prevent this first
+ ; uninstall the pinned item.
+ ApplicationID::UninstallPinnedItem "$SMPROGRAMS\$1"
+
+ ; Count of Start Menu pinned shortcuts after unpinning.
+ ${PinnedToStartMenuLnkCount} $R8
+
+ ; If there is a change in the number of Start Menu pinned shortcuts
+ ; assume that unpinning unpinned a side by side installation from
+ ; the Start Menu and pin this installation to the Start Menu.
+ ${Unless} $R8 == $R9
+ ; Pin the shortcut to the Start Menu. 5381 is the shell32.dll
+ ; resource id for the "Pin to Start Menu" string.
+ InvokeShellVerb::DoIt "$SMPROGRAMS" "$1" "5381"
+ ${EndUnless}
+
+ ; Pin the shortcut to the TaskBar. 5386 is the shell32.dll resource
+ ; id for the "Pin to Taskbar" string.
+ InvokeShellVerb::DoIt "$SMPROGRAMS" "$1" "5386"
+
+ ; Delete the shortcut if it was created
+ ${If} "$8" == "true"
+ Delete "$SMPROGRAMS\$1"
+ ${EndIf}
+ ${EndIf}
+
+ ${If} $TmpVal == "HKCU"
+ SetShellVarContext current ; Set SHCTX to the current user
+ ${Else}
+ SetShellVarContext all ; Set SHCTX to all users
+ ${EndIf}
+ ${EndUnless}
+ ${EndIf}
+ ${EndIf}
+ ${EndIf}
+!macroend
+!define PinToTaskBar "!insertmacro PinToTaskBar"
+
+; Adds a shortcut to the root of the Start Menu Programs directory if the
+; application's Start Menu Programs directory exists with a shortcut pointing to
+; this installation directory. This will also remove the old shortcuts and the
+; application's Start Menu Programs directory by calling the RemoveStartMenuDir
+; macro.
+!macro MigrateStartMenuShortcut
+ ${GetShortcutsLogPath} $0
+ ${If} ${FileExists} "$0"
+ ClearErrors
+ ReadINIStr $5 "$0" "SMPROGRAMS" "RelativePathToDir"
+ ${Unless} ${Errors}
+ ClearErrors
+ ReadINIStr $1 "$0" "STARTMENU" "Shortcut0"
+ ${If} ${Errors}
+ ; The STARTMENU ini section doesn't exist.
+ ${LogStartMenuShortcut} "${BrandFullName}.lnk"
+ ${GetLongPath} "$SMPROGRAMS" $2
+ ${GetLongPath} "$2\$5" $1
+ ${If} "$1" != ""
+ ClearErrors
+ ReadINIStr $3 "$0" "SMPROGRAMS" "Shortcut0"
+ ${Unless} ${Errors}
+ ${If} ${FileExists} "$1\$3"
+ ShellLink::GetShortCutTarget "$1\$3"
+ Pop $4
+ ${If} "$INSTDIR\${FileMainEXE}" == "$4"
+ CreateShortCut "$SMPROGRAMS\${BrandFullName}.lnk" \
+ "$INSTDIR\${FileMainEXE}"
+ ${If} ${FileExists} "$SMPROGRAMS\${BrandFullName}.lnk"
+ ShellLink::SetShortCutWorkingDirectory "$SMPROGRAMS\${BrandFullName}.lnk" \
+ "$INSTDIR"
+ ${If} ${AtLeastWin7}
+ ${AndIf} "$AppUserModelID" != ""
+ ApplicationID::Set "$SMPROGRAMS\${BrandFullName}.lnk" \
+ "$AppUserModelID" "true"
+ ${EndIf}
+ ${EndIf}
+ ${EndIf}
+ ${EndIf}
+ ${EndUnless}
+ ${EndIf}
+ ${EndIf}
+ ; Remove the application's Start Menu Programs directory, shortcuts, and
+ ; ini section.
+ ${RemoveStartMenuDir}
+ ${EndUnless}
+ ${EndIf}
+!macroend
+!define MigrateStartMenuShortcut "!insertmacro MigrateStartMenuShortcut"
+
+; Removes the application's start menu directory along with its shortcuts if
+; they exist and if they exist creates a start menu shortcut in the root of the
+; start menu directory (bug 598779). If the application's start menu directory
+; is not empty after removing the shortucts the directory will not be removed
+; since these additional items were not created by the installer (uses SHCTX).
+!macro RemoveStartMenuDir
+ ${GetShortcutsLogPath} $0
+ ${If} ${FileExists} "$0"
+ ; Delete Start Menu Programs shortcuts, directory if it is empty, and
+ ; parent directories if they are empty up to but not including the start
+ ; menu directory.
+ ${GetLongPath} "$SMPROGRAMS" $1
+ ClearErrors
+ ReadINIStr $2 "$0" "SMPROGRAMS" "RelativePathToDir"
+ ${Unless} ${Errors}
+ ${GetLongPath} "$1\$2" $2
+ ${If} "$2" != ""
+ ; Delete shortucts in the Start Menu Programs directory.
+ StrCpy $3 0
+ ${Do}
+ ClearErrors
+ ReadINIStr $4 "$0" "SMPROGRAMS" "Shortcut$3"
+ ; Stop if there are no more entries
+ ${If} ${Errors}
+ ${ExitDo}
+ ${EndIf}
+ ${If} ${FileExists} "$2\$4"
+ ShellLink::GetShortCutTarget "$2\$4"
+ Pop $5
+ ${If} "$INSTDIR\${FileMainEXE}" == "$5"
+ Delete "$2\$4"
+ ${EndIf}
+ ${EndIf}
+ IntOp $3 $3 + 1 ; Increment the counter
+ ${Loop}
+ ; Delete Start Menu Programs directory and parent directories
+ ${Do}
+ ; Stop if the current directory is the start menu directory
+ ${If} "$1" == "$2"
+ ${ExitDo}
+ ${EndIf}
+ ClearErrors
+ RmDir "$2"
+ ; Stop if removing the directory failed
+ ${If} ${Errors}
+ ${ExitDo}
+ ${EndIf}
+ ${GetParent} "$2" $2
+ ${Loop}
+ ${EndIf}
+ DeleteINISec "$0" "SMPROGRAMS"
+ ${EndUnless}
+ ${EndIf}
+!macroend
+!define RemoveStartMenuDir "!insertmacro RemoveStartMenuDir"
+
+; Creates the shortcuts log ini file with the appropriate entries if it doesn't
+; already exist.
+!macro CreateShortcutsLog
+ ${GetShortcutsLogPath} $0
+ ${Unless} ${FileExists} "$0"
+ ${LogStartMenuShortcut} "${BrandFullName}.lnk"
+ ${LogQuickLaunchShortcut} "${BrandFullName}.lnk"
+ ${LogDesktopShortcut} "${BrandFullName}.lnk"
+ ${EndUnless}
+!macroend
+!define CreateShortcutsLog "!insertmacro CreateShortcutsLog"
+
+; The files to check if they are in use during (un)install so the restart is
+; required message is displayed. All files must be located in the $INSTDIR
+; directory.
+!macro PushFilesToCheck
+ ; The first string to be pushed onto the stack MUST be "end" to indicate
+ ; that there are no more files to check in $INSTDIR and the last string
+ ; should be ${FileMainEXE} so if it is in use the CheckForFilesInUse macro
+ ; returns after the first check.
+ Push "end"
+ Push "AccessibleMarshal.dll"
+ Push "IA2Marshal.dll"
+ Push "freebl3.dll"
+ Push "nssckbi.dll"
+ Push "nspr4.dll"
+ Push "nssdbm3.dll"
+ Push "mozsqlite3.dll"
+ Push "xpcom.dll"
+ Push "crashreporter.exe"
+ Push "minidump-analyzer.exe"
+ Push "updater.exe"
+ Push "${FileMainEXE}"
+!macroend
+!define PushFilesToCheck "!insertmacro PushFilesToCheck"
+
+
+; Pushes the string "true" to the top of the stack if the Firewall service is
+; running and pushes the string "false" to the top of the stack if it isn't.
+!define SC_MANAGER_ALL_ACCESS 0x3F
+!define SERVICE_QUERY_CONFIG 0x0001
+!define SERVICE_QUERY_STATUS 0x0004
+!define SERVICE_RUNNING 0x4
+
+!macro IsFirewallSvcRunning
+ Push $R9
+ Push $R8
+ Push $R7
+ Push $R6
+ Push "false"
+
+ System::Call 'advapi32::OpenSCManagerW(n, n, i ${SC_MANAGER_ALL_ACCESS}) i.R6'
+ ${If} $R6 != 0
+ ; MpsSvc is the Firewall service on Windows Vista and above.
+ ; When opening the service with SERVICE_QUERY_CONFIG the return value will
+ ; be 0 if the service is not installed.
+ System::Call 'advapi32::OpenServiceW(i R6, t "MpsSvc", i ${SERVICE_QUERY_CONFIG}) i.R7'
+ ${If} $R7 != 0
+ System::Call 'advapi32::CloseServiceHandle(i R7) n'
+ ; Open the service with SERVICE_QUERY_CONFIG so its status can be queried.
+ System::Call 'advapi32::OpenServiceW(i R6, t "MpsSvc", i ${SERVICE_QUERY_STATUS}) i.R7'
+ ${Else}
+ ; SharedAccess is the Firewall service on Windows XP.
+ ; When opening the service with SERVICE_QUERY_CONFIG the return value will
+ ; be 0 if the service is not installed.
+ System::Call 'advapi32::OpenServiceW(i R6, t "SharedAccess", i ${SERVICE_QUERY_CONFIG}) i.R7'
+ ${If} $R7 != 0
+ System::Call 'advapi32::CloseServiceHandle(i R7) n'
+ ; Open the service with SERVICE_QUERY_CONFIG so its status can be
+ ; queried.
+ System::Call 'advapi32::OpenServiceW(i R6, t "SharedAccess", i ${SERVICE_QUERY_STATUS}) i.R7'
+ ${EndIf}
+ ${EndIf}
+ ; Did the calls to OpenServiceW succeed?
+ ${If} $R7 != 0
+ System::Call '*(i,i,i,i,i,i,i) i.R9'
+ ; Query the current status of the service.
+ System::Call 'advapi32::QueryServiceStatus(i R7, i $R9) i'
+ System::Call '*$R9(i, i.R8)'
+ System::Free $R9
+ System::Call 'advapi32::CloseServiceHandle(i R7) n'
+ IntFmt $R8 "0x%X" $R8
+ ${If} $R8 == ${SERVICE_RUNNING}
+ Pop $R9
+ Push "true"
+ ${EndIf}
+ ${EndIf}
+ System::Call 'advapi32::CloseServiceHandle(i R6) n'
+ ${EndIf}
+
+ Exch 1
+ Pop $R6
+ Exch 1
+ Pop $R7
+ Exch 1
+ Pop $R8
+ Exch 1
+ Pop $R9
+!macroend
+!define IsFirewallSvcRunning "!insertmacro IsFirewallSvcRunning"
+!define un.IsFirewallSvcRunning "!insertmacro IsFirewallSvcRunning"
+
+; Sets this installation as the default browser by setting the registry keys
+; under HKEY_CURRENT_USER via registry calls and using the AppAssocReg NSIS
+; plugin for Vista and above. This is a function instead of a macro so it is
+; easily called from an elevated instance of the binary. Since this can be
+; called by an elevated instance logging is not performed in this function.
+Function SetAsDefaultAppUserHKCU
+ ; Only set as the user's StartMenuInternet browser if the StartMenuInternet
+ ; registry keys are for this install.
+ ${StrFilter} "${FileMainEXE}" "+" "" "" $R9
+ ClearErrors
+ ReadRegStr $0 HKCU "Software\Clients\StartMenuInternet\$R9\DefaultIcon" ""
+ ${If} ${Errors}
+ ${OrIf} ${AtMostWin2008R2}
+ ClearErrors
+ ReadRegStr $0 HKLM "Software\Clients\StartMenuInternet\$R9\DefaultIcon" ""
+ ${EndIf}
+ ${Unless} ${Errors}
+ ${GetPathFromString} "$0" $0
+ ${GetParent} "$0" $0
+ ${If} ${FileExists} "$0"
+ ${GetLongPath} "$0" $0
+ ${If} "$0" == "$INSTDIR"
+ WriteRegStr HKCU "Software\Clients\StartMenuInternet" "" "$R9"
+ ${EndIf}
+ ${EndIf}
+ ${EndUnless}
+
+ SetShellVarContext current ; Set SHCTX to the current user (e.g. HKCU)
+
+ ${If} ${AtLeastWin8}
+ ${SetStartMenuInternet} "HKCU"
+ ${FixShellIconHandler} "HKCU"
+ ${FixClassKeys} ; Does not use SHCTX
+ ${EndIf}
+
+ ${SetHandlers}
+
+ ${If} ${AtLeastWinVista}
+ ; Only register as the handler on Vista and above if the app registry name
+ ; exists under the RegisteredApplications registry key. The protocol and
+ ; file handlers set previously at the user level will associate this install
+ ; as the default browser.
+ ClearErrors
+ ReadRegStr $0 HKLM "Software\RegisteredApplications" "${AppRegName}"
+ ${Unless} ${Errors}
+ ; This is all protected by a user choice hash in Windows 8 so it won't
+ ; help, but it also won't hurt.
+ AppAssocReg::SetAppAsDefaultAll "${AppRegName}"
+ ${EndUnless}
+ ${EndIf}
+ ${RemoveDeprecatedKeys}
+ ${MigrateTaskBarShortcut}
+FunctionEnd
+
+; Helper for updating the shortcut application model IDs.
+Function FixShortcutAppModelIDs
+ ${If} ${AtLeastWin7}
+ ${AndIf} "$AppUserModelID" != ""
+ ${UpdateShortcutAppModelIDs} "$INSTDIR\${FileMainEXE}" "$AppUserModelID" $0
+ ${EndIf}
+FunctionEnd
+
+; Helper for adding Firewall exceptions during install and after app update.
+Function AddFirewallEntries
+ ${IsFirewallSvcRunning}
+ Pop $0
+ ${If} "$0" == "true"
+ liteFirewallW::AddRule "$INSTDIR\${FileMainEXE}" "${BrandShortName} ($INSTDIR)"
+ ${EndIf}
+FunctionEnd
+
+; The !ifdef NO_LOG prevents warnings when compiling the installer.nsi due to
+; this function only being used by the uninstaller.nsi.
+!ifdef NO_LOG
+
+Function SetAsDefaultAppUser
+ ; On Win8, we want to avoid having a UAC prompt since we'll already have
+ ; another action for control panel default browser selection popping up
+ ; to the user. Win8 is the first OS where the start menu keys can be
+ ; added into HKCU. The call to SetAsDefaultAppUserHKCU will have already
+ ; set the HKCU keys for SetStartMenuInternet.
+ ${If} ${AtLeastWin8}
+ ; Check if this is running in an elevated process
+ ClearErrors
+ ${GetParameters} $0
+ ${GetOptions} "$0" "/UAC:" $0
+ ${If} ${Errors} ; Not elevated
+ Call SetAsDefaultAppUserHKCU
+ ${Else} ; Elevated - execute the function in the unelevated process
+ GetFunctionAddress $0 SetAsDefaultAppUserHKCU
+ UAC::ExecCodeSegment $0
+ ${EndIf}
+ Return ; Nothing more needs to be done
+ ${EndIf}
+
+ ; Before Win8, it is only possible to set this installation of the application
+ ; as the StartMenuInternet handler if it was added to the HKLM
+ ; StartMenuInternet registry keys.
+ ; http://support.microsoft.com/kb/297878
+
+ ; Check if this install location registered as the StartMenuInternet client
+ ${StrFilter} "${FileMainEXE}" "+" "" "" $R9
+ ClearErrors
+ ReadRegStr $0 HKCU "Software\Clients\StartMenuInternet\$R9\DefaultIcon" ""
+ ${If} ${Errors}
+ ${OrIf} ${AtMostWin2008R2}
+ ClearErrors
+ ReadRegStr $0 HKLM "Software\Clients\StartMenuInternet\$R9\DefaultIcon" ""
+ ${EndIf}
+
+ ${Unless} ${Errors}
+ ${GetPathFromString} "$0" $0
+ ${GetParent} "$0" $0
+ ${If} ${FileExists} "$0"
+ ${GetLongPath} "$0" $0
+ ${If} "$0" == "$INSTDIR"
+ ; Check if this is running in an elevated process
+ ClearErrors
+ ${GetParameters} $0
+ ${GetOptions} "$0" "/UAC:" $0
+ ${If} ${Errors} ; Not elevated
+ Call SetAsDefaultAppUserHKCU
+ ${Else} ; Elevated - execute the function in the unelevated process
+ GetFunctionAddress $0 SetAsDefaultAppUserHKCU
+ UAC::ExecCodeSegment $0
+ ${EndIf}
+ Return ; Nothing more needs to be done
+ ${EndIf}
+ ${EndIf}
+ ${EndUnless}
+
+ ; The code after ElevateUAC won't be executed on Vista and above when the
+ ; user:
+ ; a) is a member of the administrators group (e.g. elevation is required)
+ ; b) is not a member of the administrators group and chooses to elevate
+ ${ElevateUAC}
+
+ ${SetStartMenuInternet} "HKLM"
+
+ SetShellVarContext all ; Set SHCTX to all users (e.g. HKLM)
+
+ ${FixClassKeys} ; Does not use SHCTX
+ ${FixShellIconHandler} "HKLM"
+ ${RemoveDeprecatedKeys} ; Does not use SHCTX
+
+ ClearErrors
+ ${GetParameters} $0
+ ${GetOptions} "$0" "/UAC:" $0
+ ${If} ${Errors}
+ Call SetAsDefaultAppUserHKCU
+ ${Else}
+ GetFunctionAddress $0 SetAsDefaultAppUserHKCU
+ UAC::ExecCodeSegment $0
+ ${EndIf}
+FunctionEnd
+!define SetAsDefaultAppUser "Call SetAsDefaultAppUser"
+
+!endif ; NO_LOG
diff --git a/installer/windows/nsis/uninstaller.nsi b/installer/windows/nsis/uninstaller.nsi
new file mode 100644
index 0000000..44b7c06
--- /dev/null
+++ b/installer/windows/nsis/uninstaller.nsi
@@ -0,0 +1,557 @@
+# 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/.
+
+# Required Plugins:
+# AppAssocReg http://nsis.sourceforge.net/Application_Association_Registration_plug-in
+# CityHash http://dxr.mozilla.org/mozilla-central/source/other-licenses/nsis/Contrib/CityHash
+# ShellLink http://nsis.sourceforge.net/ShellLink_plug-in
+# UAC http://nsis.sourceforge.net/UAC_plug-in
+
+; Set verbosity to 3 (e.g. no script) to lessen the noise in the build logs
+!verbose 3
+
+; 7-Zip provides better compression than the lzma from NSIS so we add the files
+; uncompressed and use 7-Zip to create a SFX archive of it
+SetDatablockOptimize on
+SetCompress off
+CRCCheck on
+
+RequestExecutionLevel user
+
+; The commands inside this ifdef require NSIS 3.0a2 or greater so the ifdef can
+; be removed after we require NSIS 3.0a2 or greater.
+!ifdef NSIS_PACKEDVERSION
+ Unicode true
+ ManifestSupportedOS all
+ ManifestDPIAware true
+!endif
+
+!addplugindir ./
+
+; On Vista and above attempt to elevate Standard Users in addition to users that
+; are a member of the Administrators group.
+!define NONADMIN_ELEVATE
+
+; prevents compiling of the reg write logging.
+!define NO_LOG
+
+!define MaintUninstallKey \
+ "Software\Microsoft\Windows\CurrentVersion\Uninstall\MozillaMaintenanceService"
+
+Var TmpVal
+Var MaintCertKey
+
+; Other included files may depend upon these includes!
+; The following includes are provided by NSIS.
+!include FileFunc.nsh
+!include LogicLib.nsh
+!include MUI.nsh
+!include WinMessages.nsh
+!include WinVer.nsh
+!include WordFunc.nsh
+
+!insertmacro GetSize
+!insertmacro StrFilter
+!insertmacro WordReplace
+
+!insertmacro un.GetParent
+
+; The following includes are custom.
+!include branding.nsi
+!include defines.nsi
+!include common.nsh
+!include locales.nsi
+
+; This is named BrandShortName helper because we use this for software update
+; post update cleanup.
+VIAddVersionKey "FileDescription" "${BrandShortName} Helper"
+VIAddVersionKey "OriginalFilename" "helper.exe"
+
+!insertmacro AddDisabledDDEHandlerValues
+!insertmacro CleanVirtualStore
+!insertmacro ElevateUAC
+!insertmacro GetLongPath
+!insertmacro GetPathFromString
+!insertmacro InitHashAppModelId
+!insertmacro IsHandlerForInstallDir
+!insertmacro IsPinnedToTaskBar
+!insertmacro LogDesktopShortcut
+!insertmacro LogQuickLaunchShortcut
+!insertmacro LogStartMenuShortcut
+!insertmacro PinnedToStartMenuLnkCount
+!insertmacro RegCleanAppHandler
+!insertmacro RegCleanMain
+!insertmacro RegCleanUninstall
+!insertmacro SetAppLSPCategories
+!insertmacro SetBrandNameVars
+!insertmacro UpdateShortcutAppModelIDs
+!insertmacro UnloadUAC
+!insertmacro WriteRegDWORD2
+!insertmacro WriteRegStr2
+
+!insertmacro un.ChangeMUIHeaderImage
+!insertmacro un.CheckForFilesInUse
+!insertmacro un.CleanUpdateDirectories
+!insertmacro un.CleanVirtualStore
+!insertmacro un.DeleteShortcuts
+!insertmacro un.GetLongPath
+!insertmacro un.GetSecondInstallPath
+!insertmacro un.InitHashAppModelId
+!insertmacro un.ManualCloseAppPrompt
+!insertmacro un.RegCleanAppHandler
+!insertmacro un.RegCleanFileHandler
+!insertmacro un.RegCleanMain
+!insertmacro un.RegCleanUninstall
+!insertmacro un.RegCleanProtocolHandler
+!insertmacro un.RemoveQuotesFromPath
+!insertmacro un.RemovePrecompleteEntries
+!insertmacro un.SetAppLSPCategories
+!insertmacro un.SetBrandNameVars
+
+!include shared.nsh
+
+; Helper macros for ui callbacks. Insert these after shared.nsh
+!insertmacro OnEndCommon
+!insertmacro UninstallOnInitCommon
+
+!insertmacro un.OnEndCommon
+!insertmacro un.UninstallUnOnInitCommon
+
+Name "${BrandFullName}"
+OutFile "helper.exe"
+!ifdef HAVE_64BIT_BUILD
+ InstallDir "$PROGRAMFILES64\${BrandFullName}\"
+!else
+ InstallDir "$PROGRAMFILES32\${BrandFullName}\"
+!endif
+ShowUnInstDetails nevershow
+
+################################################################################
+# Modern User Interface - MUI
+
+!define MUI_ABORTWARNING
+!define MUI_ICON setup.ico
+!define MUI_UNICON setup.ico
+!define MUI_WELCOMEPAGE_TITLE_3LINES
+!define MUI_HEADERIMAGE
+!define MUI_HEADERIMAGE_RIGHT
+!define MUI_UNWELCOMEFINISHPAGE_BITMAP wizWatermark.bmp
+
+; Use a right to left header image when the language is right to left
+!ifdef ${AB_CD}_rtl
+!define MUI_HEADERIMAGE_BITMAP_RTL wizHeaderRTL.bmp
+!else
+!define MUI_HEADERIMAGE_BITMAP wizHeader.bmp
+!endif
+
+/**
+ * Uninstall Pages
+ */
+; Welcome Page
+!define MUI_PAGE_CUSTOMFUNCTION_PRE un.preWelcome
+!define MUI_PAGE_CUSTOMFUNCTION_LEAVE un.leaveWelcome
+!insertmacro MUI_UNPAGE_WELCOME
+
+; Custom Uninstall Confirm Page
+UninstPage custom un.preConfirm
+
+; Remove Files Page
+!insertmacro MUI_UNPAGE_INSTFILES
+
+; Finish Page
+
+!insertmacro MUI_UNPAGE_FINISH
+
+; Use the default dialog for IDD_VERIFY for a simple Banner
+ChangeUI IDD_VERIFY "${NSISDIR}\Contrib\UIs\default.exe"
+
+################################################################################
+# Install Sections
+; Empty section required for the installer to compile as an uninstaller
+Section ""
+SectionEnd
+
+################################################################################
+# Uninstall Sections
+
+Section "Uninstall"
+ SetDetailsPrint textonly
+ DetailPrint $(STATUS_UNINSTALL_MAIN)
+ SetDetailsPrint none
+
+ ; Delete the app exe to prevent launching the app while we are uninstalling.
+ ClearErrors
+ ${DeleteFile} "$INSTDIR\${FileMainEXE}"
+ ${If} ${Errors}
+ ; If the user closed the application it can take several seconds for it to
+ ; shut down completely. If the application is being used by another user we
+ ; can still delete the files when the system is restarted.
+ Sleep 5000
+ ${DeleteFile} "$INSTDIR\${FileMainEXE}"
+ ClearErrors
+ ${EndIf}
+
+ ; setup the application model id registration value
+ ${un.InitHashAppModelId} "$INSTDIR" "Software\Mozilla\${AppName}\TaskBarIDs"
+
+ SetShellVarContext current ; Set SHCTX to HKCU
+ ${un.RegCleanMain} "Software\Mozilla"
+ ${un.RegCleanUninstall}
+ ${un.DeleteShortcuts}
+
+ ; Unregister resources associated with Win7 taskbar jump lists.
+ ${If} ${AtLeastWin7}
+ ${AndIf} "$AppUserModelID" != ""
+ ApplicationID::UninstallJumpLists "$AppUserModelID"
+ ${EndIf}
+
+ ; Remove the updates directory for Vista and above
+ ${un.CleanUpdateDirectories} "Mozilla\PaleMoon" "Mozilla\updates"
+
+ ; Remove any app model id's stored in the registry for this install path
+ DeleteRegValue HKCU "Software\Mozilla\${AppName}\TaskBarIDs" "$INSTDIR"
+ DeleteRegValue HKLM "Software\Mozilla\${AppName}\TaskBarIDs" "$INSTDIR"
+
+ ClearErrors
+ WriteRegStr HKLM "Software\Mozilla" "${BrandShortName}InstallerTest" "Write Test"
+ ${If} ${Errors}
+ StrCpy $TmpVal "HKCU" ; used primarily for logging
+ ${Else}
+ SetShellVarContext all ; Set SHCTX to HKLM
+ DeleteRegValue HKLM "Software\Mozilla" "${BrandShortName}InstallerTest"
+ StrCpy $TmpVal "HKLM" ; used primarily for logging
+ ${un.RegCleanMain} "Software\Mozilla"
+ ${un.RegCleanUninstall}
+ ${un.DeleteShortcuts}
+ ${un.SetAppLSPCategories}
+ ${EndIf}
+
+ ${un.RegCleanAppHandler} "PaleMoonURL"
+ ${un.RegCleanAppHandler} "PaleMoonHTML"
+ ${un.RegCleanProtocolHandler} "ftp"
+ ${un.RegCleanProtocolHandler} "http"
+ ${un.RegCleanProtocolHandler} "https"
+
+ ClearErrors
+ ReadRegStr $R9 HKCR "PaleMoonHTML" ""
+ ; Don't clean up the file handlers if the PaleMoonHTML key still exists since
+ ; there should be a second installation that may be the default file handler
+ ${If} ${Errors}
+ ${un.RegCleanFileHandler} ".htm" "PaleMoonHTML"
+ ${un.RegCleanFileHandler} ".html" "PaleMoonHTML"
+ ${un.RegCleanFileHandler} ".shtml" "PaleMoonHTML"
+ ${un.RegCleanFileHandler} ".xht" "PaleMoonHTML"
+ ${un.RegCleanFileHandler} ".xhtml" "PaleMoonHTML"
+ ${un.RegCleanFileHandler} ".oga" "PaleMoonHTML"
+ ${un.RegCleanFileHandler} ".ogg" "PaleMoonHTML"
+ ${un.RegCleanFileHandler} ".ogv" "PaleMoonHTML"
+ ${un.RegCleanFileHandler} ".webm" "PaleMoonHTML"
+ ${EndIf}
+
+ SetShellVarContext all ; Set SHCTX to HKLM
+ ${un.GetSecondInstallPath} "Software\Mozilla" $R9
+ ${If} $R9 == "false"
+ SetShellVarContext current ; Set SHCTX to HKCU
+ ${un.GetSecondInstallPath} "Software\Mozilla" $R9
+ ${EndIf}
+
+ StrCpy $0 "Software\Clients\StartMenuInternet\${FileMainEXE}\shell\open\command"
+ ReadRegStr $R1 HKLM "$0" ""
+ ${un.RemoveQuotesFromPath} "$R1" $R1
+ ${un.GetParent} "$R1" $R1
+
+ ; Only remove the StartMenuInternet key if it refers to this install location.
+ ; The StartMenuInternet registry key is independent of the default browser
+ ; settings. The XPInstall base un-installer always removes this key if it is
+ ; uninstalling the default browser and it will always replace the keys when
+ ; installing even if there is another install of PaleMoon that is set as the
+ ; default browser. Now the key is always updated on install but it is only
+ ; removed if it refers to this install location.
+ ${If} "$INSTDIR" == "$R1"
+ DeleteRegKey HKLM "Software\Clients\StartMenuInternet\${FileMainEXE}"
+ DeleteRegValue HKLM "Software\RegisteredApplications" "${AppRegName}"
+ ${EndIf}
+
+ ReadRegStr $R1 HKCU "$0" ""
+ ${un.RemoveQuotesFromPath} "$R1" $R1
+ ${un.GetParent} "$R1" $R1
+
+ ; Only remove the StartMenuInternet key if it refers to this install location.
+ ; The StartMenuInternet registry key is independent of the default browser
+ ; settings. The XPInstall base un-installer always removes this key if it is
+ ; uninstalling the default browser and it will always replace the keys when
+ ; installing even if there is another install of PaleMoon that is set as the
+ ; default browser. Now the key is always updated on install but it is only
+ ; removed if it refers to this install location.
+ ${If} "$INSTDIR" == "$R1"
+ DeleteRegKey HKCU "Software\Clients\StartMenuInternet\${FileMainEXE}"
+ DeleteRegValue HKCU "Software\RegisteredApplications" "${AppRegName}"
+ ${EndIf}
+
+ StrCpy $0 "Software\Microsoft\Windows\CurrentVersion\App Paths\${FileMainEXE}"
+ ${If} $R9 == "false"
+ DeleteRegKey HKLM "$0"
+ DeleteRegKey HKCU "$0"
+ StrCpy $0 "Software\Microsoft\MediaPlayer\ShimInclusionList\${FileMainEXE}"
+ DeleteRegKey HKLM "$0"
+ DeleteRegKey HKCU "$0"
+ StrCpy $0 "Software\Microsoft\MediaPlayer\ShimInclusionList\plugin-container.exe"
+ DeleteRegKey HKLM "$0"
+ DeleteRegKey HKCU "$0"
+ StrCpy $0 "Software\Classes\MIME\Database\Content Type\application/x-xpinstall;app=PaleMoon"
+ DeleteRegKey HKLM "$0"
+ DeleteRegKey HKCU "$0"
+ ${Else}
+ ReadRegStr $R1 HKLM "$0" ""
+ ${un.RemoveQuotesFromPath} "$R1" $R1
+ ${un.GetParent} "$R1" $R1
+ ${If} "$INSTDIR" == "$R1"
+ WriteRegStr HKLM "$0" "" "$R9"
+ ${un.GetParent} "$R9" $R1
+ WriteRegStr HKLM "$0" "Path" "$R1"
+ ${EndIf}
+ ${EndIf}
+
+ ; Remove directories and files we always control before parsing the uninstall
+ ; log so empty directories can be removed.
+ ${If} ${FileExists} "$INSTDIR\updates"
+ RmDir /r /REBOOTOK "$INSTDIR\updates"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\updated"
+ RmDir /r /REBOOTOK "$INSTDIR\updated"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\defaults\shortcuts"
+ RmDir /r /REBOOTOK "$INSTDIR\defaults\shortcuts"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\distribution"
+ RmDir /r /REBOOTOK "$INSTDIR\distribution"
+ ${EndIf}
+
+ ; Remove files that may be left behind by the application in the
+ ; VirtualStore directory.
+ ${un.CleanVirtualStore}
+
+ ; Only unregister the dll if the registration points to this installation
+ ReadRegStr $R1 HKCR "CLSID\{0D68D6D0-D93D-4D08-A30D-F00DD1F45B24}\InProcServer32" ""
+ ${If} "$INSTDIR\AccessibleMarshal.dll" == "$R1"
+ ${UnregisterDLL} "$INSTDIR\AccessibleMarshal.dll"
+ ${EndIf}
+
+ ${un.RemovePrecompleteEntries} "false"
+
+ ${If} ${FileExists} "$INSTDIR\defaults\pref\channel-prefs.js"
+ Delete /REBOOTOK "$INSTDIR\defaults\pref\channel-prefs.js"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\defaults\pref"
+ RmDir /REBOOTOK "$INSTDIR\defaults\pref"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\defaults"
+ RmDir /REBOOTOK "$INSTDIR\defaults"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\uninstall"
+ ; Remove the uninstall directory that we control
+ RmDir /r /REBOOTOK "$INSTDIR\uninstall"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\install.log"
+ Delete /REBOOTOK "$INSTDIR\install.log"
+ ${EndIf}
+ ${If} ${FileExists} "$INSTDIR\update-settings.ini"
+ Delete /REBOOTOK "$INSTDIR\update-settings.ini"
+ ${EndIf}
+
+ ; Explicitly remove empty webapprt dir in case it exists (bug 757978).
+ RmDir "$INSTDIR\webapprt\components"
+ RmDir "$INSTDIR\webapprt"
+
+ ; Remove the installation directory if it is empty
+ RmDir "$INSTDIR"
+
+ ; If PaleMoon.exe was successfully deleted yet we still need to restart to
+ ; remove other files create a dummy PaleMoon.exe.moz-delete to prevent the
+ ; installer from allowing an install without restart when it is required
+ ; to complete an uninstall.
+ ${If} ${RebootFlag}
+ ; Admin is required to delete files on reboot so only add the moz-delete if
+ ; the user is an admin. After calling UAC::IsAdmin $0 will equal 1 if the
+ ; user is an admin.
+ UAC::IsAdmin
+ ${If} "$0" == "1"
+ ${Unless} ${FileExists} "$INSTDIR\${FileMainEXE}.moz-delete"
+ FileOpen $0 "$INSTDIR\${FileMainEXE}.moz-delete" w
+ FileWrite $0 "Will be deleted on restart"
+ Delete /REBOOTOK "$INSTDIR\${FileMainEXE}.moz-delete"
+ FileClose $0
+ ${EndUnless}
+ ${EndIf}
+ ${EndIf}
+
+ ; Refresh desktop icons otherwise the start menu internet item won't be
+ ; removed and other ugly things will happen like recreation of the app's
+ ; clients registry key by the OS under some conditions.
+ System::Call "shell32::SHChangeNotify(i ${SHCNE_ASSOCCHANGED}, i 0, i 0, i 0)"
+
+ ; Users who uninstall then reinstall expecting PaleMoon to use a clean profile
+ ; may be surprised during first-run. This key is checked during startup of PaleMoon and
+ ; subsequently deleted after checking. If the value is found during startup
+ ; the browser will offer to Reset PaleMoon. We use the UpdateChannel to match
+ ; uninstalls of PaleMoon-release with reinstalls of PaleMoon-release, for example.
+ WriteRegStr HKCU "Software\Mozilla\PaleMoon" "Uninstalled-${UpdateChannel}" "True"
+
+ ${un.IsFirewallSvcRunning}
+ Pop $0
+ ${If} "$0" == "true"
+ liteFirewallW::RemoveRule "$INSTDIR\${FileMainEXE}" "${BrandShortName} ($INSTDIR)"
+ ${EndIf}
+SectionEnd
+
+################################################################################
+# Language
+
+!insertmacro MOZ_MUI_LANGUAGE 'baseLocale'
+!verbose push
+!verbose 3
+!include "overrideLocale.nsh"
+!include "customLocale.nsh"
+!verbose pop
+
+; Set this after the locale files to override it if it is in the locale. Using
+; " " for BrandingText will hide the "Nullsoft Install System..." branding.
+BrandingText " "
+
+################################################################################
+# Page pre, show, and leave functions
+
+Function un.preWelcome
+ ${If} ${FileExists} "$INSTDIR\distribution\modern-wizard.bmp"
+ Delete "$PLUGINSDIR\modern-wizard.bmp"
+ CopyFiles /SILENT "$INSTDIR\distribution\modern-wizard.bmp" "$PLUGINSDIR\modern-wizard.bmp"
+ ${EndIf}
+FunctionEnd
+
+Function un.leaveWelcome
+ ${If} ${FileExists} "$INSTDIR\${FileMainEXE}"
+ Banner::show /NOUNLOAD "$(BANNER_CHECK_EXISTING)"
+
+ ; If the message window has been found previously give the app an additional
+ ; five seconds to close.
+ ${If} "$TmpVal" == "FoundMessageWindow"
+ Sleep 5000
+ ${EndIf}
+
+ ${PushFilesToCheck}
+
+ ${un.CheckForFilesInUse} $TmpVal
+
+ Banner::destroy
+
+ ; If there are files in use $TmpVal will be "true"
+ ${If} "$TmpVal" == "true"
+ ; If the message window is found the call to ManualCloseAppPrompt will
+ ; abort leaving the value of $TmpVal set to "FoundMessageWindow".
+ StrCpy $TmpVal "FoundMessageWindow"
+ ${un.ManualCloseAppPrompt} "${WindowClass}" "$(WARN_MANUALLY_CLOSE_APP_UNINSTALL)"
+ ; If the message window is not found set $TmpVal to "true" so the restart
+ ; required message is displayed.
+ StrCpy $TmpVal "true"
+ ${EndIf}
+ ${EndIf}
+FunctionEnd
+
+Function un.preConfirm
+ ${If} ${FileExists} "$INSTDIR\distribution\modern-header.bmp"
+ ${AndIf} $hHeaderBitmap == ""
+ Delete "$PLUGINSDIR\modern-header.bmp"
+ CopyFiles /SILENT "$INSTDIR\distribution\modern-header.bmp" "$PLUGINSDIR\modern-header.bmp"
+ ${un.ChangeMUIHeaderImage} "$PLUGINSDIR\modern-header.bmp"
+ ${EndIf}
+
+ ; Setup the unconfirm.ini file for the Custom Uninstall Confirm Page
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Settings" NumFields "3"
+
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 1" Type "label"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 1" Text "$(UN_CONFIRM_UNINSTALLED_FROM)"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 1" Left "0"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 1" Right "-1"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 1" Top "5"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 1" Bottom "15"
+
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 2" Type "text"
+ ; The contents of this control must be set as follows in the pre function
+ ; ${MUI_INSTALLOPTIONS_READ} $1 "unconfirm.ini" "Field 2" "HWND"
+ ; SendMessage $1 ${WM_SETTEXT} 0 "STR:$INSTDIR"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 2" State ""
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 2" Left "0"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 2" Right "-1"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 2" Top "17"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 2" Bottom "30"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 2" flags "READONLY"
+
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 3" Type "label"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 3" Text "$(UN_CONFIRM_CLICK)"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 3" Left "0"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 3" Right "-1"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 3" Top "130"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 3" Bottom "150"
+
+ ${If} "$TmpVal" == "true"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 4" Type "label"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 4" Text "$(SUMMARY_REBOOT_REQUIRED_UNINSTALL)"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 4" Left "0"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 4" Right "-1"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 4" Top "35"
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Field 4" Bottom "45"
+
+ WriteINIStr "$PLUGINSDIR\unconfirm.ini" "Settings" NumFields "4"
+ ${EndIf}
+
+ !insertmacro MUI_HEADER_TEXT "$(UN_CONFIRM_PAGE_TITLE)" "$(UN_CONFIRM_PAGE_SUBTITLE)"
+ ; The Summary custom page has a textbox that will automatically receive
+ ; focus. This sets the focus to the Install button instead.
+ !insertmacro MUI_INSTALLOPTIONS_INITDIALOG "unconfirm.ini"
+ GetDlgItem $0 $HWNDPARENT 1
+ System::Call "user32::SetFocus(i r0, i 0x0007, i,i)i"
+ ${MUI_INSTALLOPTIONS_READ} $1 "unconfirm.ini" "Field 2" "HWND"
+ SendMessage $1 ${WM_SETTEXT} 0 "STR:$INSTDIR"
+ !insertmacro MUI_INSTALLOPTIONS_SHOW
+FunctionEnd
+
+################################################################################
+# Initialization Functions
+
+Function .onInit
+ ; Remove the current exe directory from the search order.
+ ; This only effects LoadLibrary calls and not implicitly loaded DLLs.
+ System::Call 'kernel32::SetDllDirectoryW(w "")'
+
+ ; We need this set up for most of the helper.exe operations.
+ ${UninstallOnInitCommon}
+FunctionEnd
+
+Function un.onInit
+ ; Remove the current exe directory from the search order.
+ ; This only effects LoadLibrary calls and not implicitly loaded DLLs.
+ System::Call 'kernel32::SetDllDirectoryW(w "")'
+
+ StrCpy $LANGUAGE 0
+
+ ${un.UninstallUnOnInitCommon}
+
+; The commands inside this ifndef are needed prior to NSIS 3.0a2 and can be
+; removed after we require NSIS 3.0a2 or greater.
+!ifndef NSIS_PACKEDVERSION
+ ${If} ${AtLeastWinVista}
+ System::Call 'user32::SetProcessDPIAware()'
+ ${EndIf}
+!endif
+
+ !insertmacro InitInstallOptionsFile "unconfirm.ini"
+FunctionEnd
+
+Function .onGUIEnd
+ ${OnEndCommon}
+FunctionEnd
+
+Function un.onGUIEnd
+ ${un.OnEndCommon}
+FunctionEnd
diff --git a/installer/windows/nsis/updater_append.ini b/installer/windows/nsis/updater_append.ini
new file mode 100644
index 0000000..af7742c
--- /dev/null
+++ b/installer/windows/nsis/updater_append.ini
@@ -0,0 +1,12 @@
+
+; IMPORTANT: This file should always start with a newline in case a locale
+; provided updater.ini does not end with a newline.
+; Application to launch after an update has been successfully applied. This
+; must be in the same directory or a sub-directory of the directory of the
+; application executable that initiated the software update.
+[PostUpdateWin]
+; ExeRelPath is the path to the PostUpdateWin executable relative to the
+; application executable.
+ExeRelPath=uninstall\helper.exe
+; ExeArg is the argument to pass to the PostUpdateWin exe
+ExeArg=/PostUpdate
diff --git a/locales/Makefile.in b/locales/Makefile.in
new file mode 100644
index 0000000..897fa0b
--- /dev/null
+++ b/locales/Makefile.in
@@ -0,0 +1,222 @@
+# vim:set ts=8 sw=8 sts=8 noet:
+# 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/.
+
+include $(topsrcdir)/config/config.mk
+
+vpath %.xml @srcdir@/en-US/searchplugins
+vpath %.xml $(LOCALE_SRCDIR)/searchplugins
+
+ifdef LOCALE_MERGEDIR
+vpath book%.inc $(LOCALE_MERGEDIR)/browser/profile
+endif
+vpath book%.inc $(LOCALE_SRCDIR)/profile
+ifdef LOCALE_MERGEDIR
+vpath book%.inc @srcdir@/en-US/profile
+endif
+
+
+SUBMAKEFILES += \
+ $(DEPTH)/$(MOZ_BRANDING_DIRECTORY)/Makefile \
+ $(DEPTH)/$(MOZ_BRANDING_DIRECTORY)/locales/Makefile \
+ $(NULL)
+
+# This makefile uses variable overrides from the libs-% target to
+# build non-default locales to non-default dist/ locations. Be aware!
+
+PWD := $(CURDIR)
+
+# These are defaulted to be compatible with the files the wget-en-US target
+# pulls. You may override them if you provide your own files. You _must_
+# override them when MOZ_PKG_PRETTYNAMES is defined - the defaults will not
+# work in that case.
+ZIP_IN ?= $(_ABS_DIST)/$(PACKAGE)
+WIN32_INSTALLER_IN ?= $(_ABS_DIST)/$(PKG_INST_PATH)$(PKG_INST_BASENAME).exe
+RETRIEVE_WINDOWS_INSTALLER = 1
+
+MOZ_LANGPACK_EID=langpack-$(AB_CD)@palemoon.org
+
+L10N_PREF_JS_EXPORTS = $(call MERGE_FILE,palemoon-l10n.js)
+L10N_PREF_JS_EXPORTS_PATH = $(FINAL_TARGET)/$(PREF_DIR)
+L10N_PREF_JS_EXPORTS_FLAGS = $(PREF_PPFLAGS) --silence-missing-directive-warnings
+PP_TARGETS += L10N_PREF_JS_EXPORTS
+
+ifneq (,$(filter cocoa,$(MOZ_WIDGET_TOOLKIT)))
+MOZ_PKG_MAC_DSSTORE=$(_ABS_DIST)/branding/dsstore
+MOZ_PKG_MAC_BACKGROUND=$(_ABS_DIST)/branding/background.png
+MOZ_PKG_MAC_ICON=$(_ABS_DIST)/branding/disk.icns
+MOZ_PKG_MAC_EXTRA=--symlink "/Applications:/ "
+endif
+
+ifeq (WINNT,$(OS_ARCH))
+UNINSTALLER_PACKAGE_HOOK = $(RM) -r $(STAGEDIST)/uninstall; \
+ $(NSINSTALL) -D $(STAGEDIST)/uninstall; \
+ cp ../installer/windows/l10ngen/helper.exe $(STAGEDIST)/uninstall; \
+ $(RM) $(_ABS_DIST)/l10n-stage/setup.exe; \
+ cp ../installer/windows/l10ngen/setup.exe $(_ABS_DIST)/l10n-stage; \
+ $(NULL)
+
+STUB_HOOK = $(NSINSTALL) -D "$(_ABS_DIST)/$(PKG_INST_PATH)"; \
+ $(RM) "$(_ABS_DIST)/$(PKG_INST_PATH)$(PKG_STUB_BASENAME).exe"; \
+ cp ../installer/windows/l10ngen/stub.exe "$(_ABS_DIST)/$(PKG_INST_PATH)$(PKG_STUB_BASENAME).exe"; \
+ chmod 0755 "$(_ABS_DIST)/$(PKG_INST_PATH)$(PKG_STUB_BASENAME).exe"; \
+ $(NULL)
+endif
+
+SEARCHPLUGINS_NAMES = $(shell cat $(call MERGE_FILE,/searchplugins/list.txt))
+SEARCHPLUGINS_PATH := $(FINAL_TARGET)/searchplugins
+SEARCHPLUGINS := $(addsuffix .xml,$(SEARCHPLUGINS_NAMES))
+# Some locale-specific search plugins may have preprocessor directives, but the
+# default en-US ones do not.
+SEARCHPLUGINS_FLAGS := --silence-missing-directive-warnings
+PP_TARGETS += SEARCHPLUGINS
+
+# Required for l10n.mk - defines a list of app sub dirs that should
+# be included in langpack xpis.
+DIST_SUBDIRS = $(DIST_SUBDIR)
+
+include $(topsrcdir)/config/rules.mk
+
+include $(topsrcdir)/toolkit/locales/l10n.mk
+
+$(STAGEDIST): $(DIST)/branding
+
+$(DIST)/branding:
+ $(NSINSTALL) -D $@
+
+PROFILE_FILES = \
+ localstore.rdf \
+ mimeTypes.rdf \
+ $(NULL)
+
+PROFILE_CHROME = userChrome-example.css userContent-example.css
+
+NO_JA_JP_MAC_AB_CD := $(if $(filter ja-JP-mac, $(AB_CD)),ja,$(AB_CD))
+
+%/defaults/profile/bookmarks.html: bookmarks.inc generic/profile/bookmarks.html.in
+ $(SYSINSTALL) -D $(dir $@)
+ $(call py_action,preprocessor,$< -DAB_CD=$(NO_JA_JP_MAC_AB_CD) $(srcdir)/generic/profile/bookmarks.html.in -o $@)
+
+libs:: $(FINAL_TARGET)/defaults/profile/bookmarks.html ;
+
+libs:: $(addprefix generic/profile/,$(PROFILE_FILES))
+ $(SYSINSTALL) $(IFLAGS1) $^ $(FINAL_TARGET)/defaults/profile
+
+libs:: $(call MERGE_FILES,$(addprefix profile/chrome/,$(PROFILE_CHROME)))
+ $(SYSINSTALL) $(IFLAGS1) $^ $(FINAL_TARGET)/defaults/profile/chrome
+
+install:: $(DESTDIR)$(mozappdir)/defaults/profile/bookmarks.html ;
+
+install:: $(addprefix generic/profile/,$(PROFILE_FILES))
+ $(SYSINSTALL) $(IFLAGS1) $^ $(DESTDIR)$(mozappdir)/defaults/profile
+
+install:: $(call MERGE_FILES,$(addprefix profile/chrome/,$(PROFILE_CHROME)))
+ $(SYSINSTALL) $(IFLAGS1) $^ $(DESTDIR)$(mozappdir)/defaults/profile/chrome
+
+# metro build calls back here for search engine plugins
+searchplugins: $(addprefix $(FINAL_TARGET)/searchplugins/,$(SEARCHPLUGINS))
+.PHONY: searchplugins
+
+libs-%:
+ $(NSINSTALL) -D $(DIST)/install
+ @$(MAKE) -C ../../../toolkit/locales libs-$*
+ifdef MOZ_SERVICES_SYNC
+ @$(MAKE) -C ../../../services/sync/locales AB_CD=$* XPI_NAME=locale-$*
+endif
+ @$(MAKE) -C ../../../extensions/spellcheck/locales AB_CD=$* XPI_NAME=locale-$*
+ @$(MAKE) -C ../../../intl/locales AB_CD=$* XPI_NAME=locale-$*
+ifdef MOZ_DEVTOOLS
+ @$(MAKE) -C ../../../devtools/client/locales AB_CD=$* XPI_NAME=locale-$*
+endif
+ @$(MAKE) libs AB_CD=$* XPI_NAME=locale-$* PREF_DIR=$(PREF_DIR)
+ @$(MAKE) -C $(DEPTH)/$(MOZ_BRANDING_DIRECTORY)/locales AB_CD=$* XPI_NAME=locale-$*
+
+repackage-win32-installer: WIN32_INSTALLER_OUT=$(_ABS_DIST)/$(PKG_INST_PATH)$(PKG_INST_BASENAME).exe
+repackage-win32-installer: $(call ESCAPE_WILDCARD,$(WIN32_INSTALLER_IN)) $(SUBMAKEFILES) libs-$(AB_CD)
+ @echo "Repackaging $(WIN32_INSTALLER_IN) into $(WIN32_INSTALLER_OUT)."
+ $(MAKE) -C $(DEPTH)/$(MOZ_BRANDING_DIRECTORY) export
+ $(MAKE) -C ../installer/windows CONFIG_DIR=l10ngen l10ngen/setup.exe l10ngen/7zSD.sfx
+ $(MAKE) repackage-zip \
+ AB_CD=$(AB_CD) \
+ MOZ_PKG_FORMAT=SFX7Z \
+ ZIP_IN="$(WIN32_INSTALLER_IN)" \
+ ZIP_OUT="$(WIN32_INSTALLER_OUT)" \
+ SFX_HEADER="$(PWD)/../installer/windows/l10ngen/7zSD.sfx \
+ $(topsrcdir)/browser/installer/windows/app.tag"
+
+ifeq (WINNT,$(OS_ARCH))
+repackage-win32-installer-%: $(STAGEDIST)
+ @$(MAKE) repackage-win32-installer AB_CD=$* WIN32_INSTALLER_IN="$(WIN32_INSTALLER_IN)"
+
+repackage-zip-%: repackage-win32-installer-%
+else
+repackage-win32-installer-%: ;
+endif
+
+
+clobber-zip:
+ $(RM) $(STAGEDIST)/chrome/$(AB_CD).jar \
+ $(STAGEDIST)/chrome/$(AB_CD).manifest \
+ $(STAGEDIST)/webapprt/chrome/$(AB_CD).jar \
+ $(STAGEDIST)/webapprt/chrome/$(AB_CD).manifest \
+ $(STAGEDIST)/$(PREF_DIR)/palemoon-l10n.js
+ $(RM) -rf $(STAGEDIST)/searchplugins \
+ $(STAGEDIST)/dictionaries \
+ $(STAGEDIST)/hyphenation \
+ $(STAGEDIST)/defaults/profile \
+ $(STAGEDIST)/chrome/$(AB_CD) \
+ $(STAGEDIST)/webapprt/chrome/$(AB_CD)
+
+
+langpack: langpack-$(AB_CD)
+
+# This is a generic target that will make a langpack, repack ZIP (+tarball)
+# builds, and repack an installer if applicable. It is called from the
+# tinderbox scripts. Alter it with caution.
+
+installers-%: clobber-% langpack-% repackage-win32-installer-% repackage-zip-%
+ @echo "repackaging done"
+
+ifdef MOZ_UPDATER
+# Note that we want updater.ini to be in the top directory, not the browser/
+# subdirectory, because that's where the updater is installed and runs.
+libs:: $(call MERGE_FILE,updater/updater.ini) $(call mkdir_deps,$(DIST)/bin)
+ifeq ($(OS_ARCH),WINNT)
+ cat $< $(srcdir)/../installer/windows/nsis/updater_append.ini | \
+ sed -e "s/^InfoText=/Info=/" -e "s/^TitleText=/Title=/" | \
+ sed -e "s/%MOZ_APP_DISPLAYNAME%/$(MOZ_APP_DISPLAYNAME)/" > \
+ $(FINAL_TARGET)/../updater.ini
+else
+ cat $< | \
+ sed -e "s/^InfoText=/Info=/" -e "s/^TitleText=/Title=/" | \
+ sed -e "s/%MOZ_APP_DISPLAYNAME%/$(MOZ_APP_DISPLAYNAME)/" > \
+ $(FINAL_TARGET)/../updater.ini
+endif
+endif
+
+ident:
+ @printf "fx_revision "
+ @$(PYTHON) $(topsrcdir)/config/printconfigsetting.py \
+ '$(STAGEDIST)'/application.ini App SourceStamp
+ @printf "buildid "
+ @$(PYTHON) $(topsrcdir)/config/printconfigsetting.py \
+ '$(STAGEDIST)'/application.ini App BuildID
+
+merge-%:
+ifdef LOCALE_MERGEDIR
+ $(RM) -rf $(LOCALE_MERGEDIR)
+ MACOSX_DEPLOYMENT_TARGET= compare-locales -m $(LOCALE_MERGEDIR) $(srcdir)/l10n.ini $(L10NBASEDIR) $*
+endif
+ @echo
+
+# test target, depends on make package
+# try to repack x-test, with just toolkit/defines.inc being there
+l10n-check:: INNER_UNMAKE_PACKAGE=true
+l10n-check::
+ $(RM) -rf x-test
+ $(NSINSTALL) -D x-test/toolkit
+ echo "#define MOZ_LANG_TITLE Just testing" > x-test/toolkit/defines.inc
+ $(MAKE) installers-x-test L10NBASEDIR="$(PWD)" LOCALE_MERGEDIR="$(PWD)/mergedir"
+ $(PYTHON) $(topsrcdir)/toolkit/mozapps/installer/unpack.py $(DIST)/l10n-stage/$(STAGEPATH)$(MOZ_PKG_DIR)$(_BINPATH)
+ cd $(DIST)/l10n-stage && test $$(cat $(STAGEPATH)$(MOZ_PKG_DIR)$(_BINPATH)/update.locale) = x-test
diff --git a/locales/all-locales b/locales/all-locales
new file mode 100644
index 0000000..54b7b62
--- /dev/null
+++ b/locales/all-locales
@@ -0,0 +1,97 @@
+ach
+af
+ak
+an
+ar
+as
+ast
+be
+bg
+bn-BD
+bn-IN
+br
+bs
+ca
+cs
+csb
+cy
+da
+de
+el
+en-GB
+en-ZA
+eo
+es-AR
+es-CL
+es-ES
+es-MX
+et
+eu
+fa
+ff
+fi
+fr
+fy-NL
+ga-IE
+gd
+gl
+gu-IN
+he
+hi-IN
+hr
+hu
+hy-AM
+id
+is
+it
+ja
+ja-JP-mac
+ka
+kk
+km
+kn
+ko
+ku
+lg
+lij
+lt
+lv
+mai
+mk
+ml
+mn
+mr
+ms
+nb-NO
+nl
+nn-NO
+nso
+oc
+or
+pa-IN
+pl
+pt-BR
+pt-PT
+rm
+ro
+ru
+si
+sk
+sl
+son
+sq
+sr
+sv-SE
+sw
+ta
+ta-LK
+te
+th
+tr
+uk
+ur
+vi
+wo
+zh-CN
+zh-TW
+zu
diff --git a/locales/en-US/chrome/browser-region/region.properties b/locales/en-US/chrome/browser-region/region.properties
new file mode 100644
index 0000000..8782ae4
--- /dev/null
+++ b/locales/en-US/chrome/browser-region/region.properties
@@ -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/.
+
+# Default search engine
+browser.search.defaultenginename=DuckDuckGo
+
+# Search engine order (order displayed in the search bar dropdown)s
+browser.search.order.1=DuckDuckGo
+browser.search.order.2=Yahoo
+browser.search.order.3=Bing
+browser.search.order.4=Ecosia
+
+# This is the default set of web based feed handlers shown in the reader
+# selection UI
+browser.contentHandlers.types.0.title=My Yahoo!
+browser.contentHandlers.types.0.uri=https://add.my.yahoo.com/rss?url=%s
+
+# increment this number when anything gets changed in the list below. This will
+# cause Firefox to re-read these prefs and inject any new handlers into the
+# profile database. Note that "new" is defined as "has a different URL"; this
+# means that it's not possible to update the name of existing handler, so
+# don't make any spelling errors here.
+gecko.handlerService.defaultHandlersVersion=5
+
+# The default set of protocol handlers for mailto:
+gecko.handlerService.schemes.mailto.0.name=Yahoo! Mail
+gecko.handlerService.schemes.mailto.0.uriTemplate=https://compose.mail.yahoo.com/?To=%s
+gecko.handlerService.schemes.mailto.1.name=Gmail
+gecko.handlerService.schemes.mailto.1.uriTemplate=https://mail.google.com/mail/?extsrc=mailto&url=%s
+
+# The default set of protocol handlers for irc:
+gecko.handlerService.schemes.irc.0.name=Mibbit
+gecko.handlerService.schemes.irc.0.uriTemplate=https://www.mibbit.com/?url=%s
+
+# The default set of protocol handlers for ircs:
+gecko.handlerService.schemes.ircs.0.name=Mibbit
+gecko.handlerService.schemes.ircs.0.uriTemplate=https://www.mibbit.com/?url=%s
diff --git a/locales/en-US/chrome/browser/aboutCertError.dtd b/locales/en-US/chrome/browser/aboutCertError.dtd
new file mode 100644
index 0000000..21c722c
--- /dev/null
+++ b/locales/en-US/chrome/browser/aboutCertError.dtd
@@ -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/. -->
+
+<!ENTITY % brandDTD
+ SYSTEM "chrome://branding/locale/brand.dtd">
+ %brandDTD;
+
+<!-- These strings are used by Firefox's custom about:certerror page,
+a replacement for the standard security certificate errors produced
+by NSS/PSM via netError.xhtml. -->
+
+<!ENTITY certerror.pagetitle "Untrusted Connection">
+<!ENTITY certerror.longpagetitle "This Connection is Untrusted">
+
+<!-- Localization note (certerror.introPara1) - The string "#1" will
+be replaced at runtime with the name of the server to which the user
+was trying to connect. -->
+<!ENTITY certerror.introPara1 "You have asked &brandShortName; to connect
+securely to <b>#1</b>, but we can't confirm that your connection is secure.">
+<!ENTITY certerror.introPara2 "Normally, when you try to connect securely,
+sites will present trusted identification to prove that you are
+going to the right place. However, this site's identity can't be verified.">
+
+<!ENTITY certerror.whatShouldIDo.heading "What Should I Do?">
+<!ENTITY certerror.whatShouldIDo.content "If you usually connect to
+this site without problems, this error could mean that someone is
+trying to impersonate the site, and you shouldn't continue.">
+<!ENTITY certerror.getMeOutOfHere.label "Get me out of here!">
+
+<!ENTITY certerror.expert.heading "I Understand the Risks">
+<!ENTITY certerror.expert.content "If you understand what's going on, you
+can tell &brandShortName; to start trusting this site's identification.
+<b>Even if you trust the site, this error could mean that someone is
+tampering with your connection.</b>">
+<!ENTITY certerror.expert.contentPara2 "Don't add an exception unless
+you know there's a good reason why this site doesn't use trusted identification.">
+<!ENTITY certerror.addException.label "Add Exception…">
+
+<!ENTITY certerror.technical.heading "Technical Details">
diff --git a/locales/en-US/chrome/browser/aboutDialog.dtd b/locales/en-US/chrome/browser/aboutDialog.dtd
new file mode 100644
index 0000000..26bf3cd
--- /dev/null
+++ b/locales/en-US/chrome/browser/aboutDialog.dtd
@@ -0,0 +1,91 @@
+<!-- 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/. -->
+<!ENTITY aboutDialog.title "About &brandFullName;">
+
+<!-- LOCALIZATION NOTE (warningDesc.version): This is a warning about the experimental nature of Nightly and Aurora builds. It is only shown in those versions. -->
+<!ENTITY warningDesc.version "&brandShortName; is experimental and may be unstable.">
+
+<!-- LOCALIZATION NOTE (community.exp.*) This paragraph is shown in "experimental" builds, i.e. Nightly and Aurora builds, instead of the other "community.*" strings below. -->
+<!ENTITY community.exp.start "">
+<!-- LOCALIZATION NOTE (community.exp.mozillaLink): This is a link title that links to http://www.mozilla.org/. -->
+<!ENTITY community.exp.mozillaLink "&vendorShortName;">
+<!ENTITY community.exp.middle " is a ">
+<!-- LOCALIZATION NOTE (community.exp.creditslink): This is a link title that links to about:credits. -->
+<!ENTITY community.exp.creditsLink "global community">
+<!ENTITY community.exp.end " working together to keep the Web open, public and accessible to all.">
+
+<!ENTITY community.start2 "&brandShortName; is designed by ">
+<!-- LOCALIZATION NOTE (community.mozillaLink): This is a link title that links to http://www.mozilla.org/. -->
+<!ENTITY community.mozillaLink "&vendorShortName;">
+<!ENTITY community.middle2 ", a ">
+<!-- LOCALIZATION NOTE (community.creditsLink): This is a link title that links to about:credits. -->
+<!ENTITY community.creditsLink "global community">
+<!ENTITY community.end3 " working together to keep the Web open, public and accessible to all.">
+
+<!ENTITY contribute.start "Sound interesting? ">
+<!-- LOCALIZATION NOTE (contribute.getInvolvedLink): This is a link title that links to http://www.mozilla.org/contribute/. -->
+<!ENTITY contribute.getInvolvedLink "Get involved!">
+<!ENTITY contribute.end "">
+
+<!-- LOCALIZATION NOTE (bottomLinks.license): This is a link title that links to about:license. -->
+<!ENTITY bottomLinks.license "Licensing Information">
+
+<!-- LOCALIZATION NOTE (bottomLinks.rights): This is a link title that links to about:rights. -->
+<!ENTITY bottomLinks.rights "End-User Rights">
+
+<!-- LOCALIZATION NOTE (bottomLinks.privacy): This is a link title that links to https://www.mozilla.org/legal/privacy/. -->
+<!ENTITY bottomLinks.privacy "Privacy Policy">
+
+<!-- LOCALIZATION NOTE (update.checkingForUpdates): try to make the localized text short (see bug 596813 for screenshots). -->
+<!ENTITY update.checkingForUpdates "Checking for updates…">
+<!-- LOCALIZATION NOTE (update.checkingAddonCompat): try to make the localized text short (see bug 596813 for screenshots). -->
+<!ENTITY update.checkingAddonCompat "Checking Add-on compatibility…">
+<!-- LOCALIZATION NOTE (update.noUpdatesFound): try to make the localized text short (see bug 596813 for screenshots). -->
+<!ENTITY update.noUpdatesFound "&brandShortName; is up to date">
+<!-- LOCALIZATION NOTE (update.adminDisabled): try to make the localized text short (see bug 596813 for screenshots). -->
+<!ENTITY update.adminDisabled "Updates disabled by your system administrator">
+<!-- LOCALIZATION NOTE (update.otherInstanceHandlingUpdates): try to make the localized text short -->
+<!ENTITY update.otherInstanceHandlingUpdates "&brandShortName; is being updated by another instance">
+
+<!-- LOCALIZATION NOTE (update.failed.start,update.failed.linkText,update.failed.end):
+ update.failed.start, update.failed.linkText, and update.failed.end all go into
+ one line with linkText being wrapped in an anchor that links to a site to download
+ the latest version of Firefox (e.g. http://www.firefox.com). As this is all in
+ one line, try to make the localized text short (see bug 596813 for screenshots). -->
+<!ENTITY update.failed.start "Update failed. ">
+<!ENTITY update.failed.linkText "Download the latest version">
+<!ENTITY update.failed.end "">
+
+<!-- LOCALIZATION NOTE (update.manual.start,update.manual.end): update.manual.start and update.manual.end
+ all go into one line and have an anchor in between with text that is the same as the link to a site
+ to download the latest version of Firefox (e.g. http://www.firefox.com). As this is all in one line,
+ try to make the localized text short (see bug 596813 for screenshots). -->
+<!ENTITY update.manual.start "Updates available at ">
+<!ENTITY update.manual.end "">
+
+<!-- LOCALIZATION NOTE (update.unsupported.start,update.unsupported.linkText,update.unsupported.end):
+ update.unsupported.start, update.unsupported.linkText, and
+ update.unsupported.end all go into one line with linkText being wrapped in
+ an anchor that links to a site to provide additional information regarding
+ why the system is no longer supported. As this is all in one line, try to
+ make the localized text short (see bug 843497 for screenshots). -->
+<!ENTITY update.unsupported.start "You can not perform further updates on this system. ">
+<!ENTITY update.unsupported.linkText "Learn more">
+<!ENTITY update.unsupported.end "">
+
+<!-- LOCALIZATION NOTE (update.downloading.start,update.downloading.end): update.downloading.start and
+ update.downloading.end all go into one line, with the amount downloaded inserted in between. As this
+ is all in one line, try to make the localized text short (see bug 596813 for screenshots). The — is
+ the "em dash" (long dash).
+ example: Downloading update — 111 KB of 13 MB -->
+<!ENTITY update.downloading.start "Downloading update — ">
+<!ENTITY update.downloading.end "">
+
+<!ENTITY update.applying "Applying update…">
+
+<!-- LOCALIZATION NOTE (channel.description.start,channel.description.end): channel.description.start and
+ channel.description.end create one sentence, with the current channel label inserted in between.
+ example: You are currently on the _Stable_ update channel. -->
+<!ENTITY channel.description.start "You are currently on the ">
+<!ENTITY channel.description.end " update channel. ">
diff --git a/locales/en-US/chrome/browser/aboutHome.dtd b/locales/en-US/chrome/browser/aboutHome.dtd
new file mode 100644
index 0000000..d3bd85f
--- /dev/null
+++ b/locales/en-US/chrome/browser/aboutHome.dtd
@@ -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/. -->
+
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+#ifdef MOZ_SERVICES_SYNC
+<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
+%syncBrandDTD;
+#endif
+
+<!-- These strings are used in the about:home page -->
+
+<!ENTITY abouthome.pageTitle "&brandFullName; Start Page">
+
+<!ENTITY abouthome.searchEngineButton.label "Search">
+
+<!ENTITY abouthome.bookmarksButton.label "Bookmarks">
+<!ENTITY abouthome.historyButton.label "History">
+<!ENTITY abouthome.settingsButton.label "Preferences">
+<!ENTITY abouthome.addonsButton.label "Add-ons">
+<!ENTITY abouthome.appsButton.label "Marketplace">
+<!ENTITY abouthome.downloadsButton.label "Downloads">
+#ifdef MOZ_SERVICES_SYNC
+<!ENTITY abouthome.syncButton.label "&syncBrand.shortName.label;">
+#endif \ No newline at end of file
diff --git a/locales/en-US/chrome/browser/aboutPrivateBrowsing.dtd b/locales/en-US/chrome/browser/aboutPrivateBrowsing.dtd
new file mode 100644
index 0000000..ef885d0
--- /dev/null
+++ b/locales/en-US/chrome/browser/aboutPrivateBrowsing.dtd
@@ -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/. -->
+
+<!ENTITY privatebrowsingpage.title "Private Browsing">
+<!ENTITY privatebrowsingpage.title.normal "Would you like to start Private Browsing?">
+
+<!ENTITY privatebrowsingpage.perwindow.issueDesc "&brandShortName; won't remember any history for this window.">
+<!ENTITY privatebrowsingpage.perwindow.issueDesc.normal "You are not currently in a private window.">
+
+<!ENTITY privatebrowsingpage.perwindow.description "In a Private Browsing window, &brandShortName; won't keep any browser history, search history, download history, web form history, cookies, or temporary internet files. However, files you download and bookmarks you make will be kept.">
+
+<!ENTITY privatebrowsingpage.openPrivateWindow.label "Open a Private Window">
+<!ENTITY privatebrowsingpage.openPrivateWindow.accesskey "P">
+
+<!-- LOCALIZATION NOTE (privatebrowsingpage.howToStart3): please leave &basePBMenu.label; intact in the translation -->
+<!ENTITY privatebrowsingpage.howToStart3 "To start Private Browsing, you can also select &basePBMenu.label; &gt; &newPrivateWindow.label;.">
+<!ENTITY privatebrowsingpage.howToStop3 "To stop Private Browsing, you can close this window.">
+
+<!ENTITY privatebrowsingpage.moreInfo "While this computer won't have a record of your browsing history, your internet service provider or employer can still track the pages you visit.">
+<!ENTITY privatebrowsingpage.learnMore "Learn More">
diff --git a/locales/en-US/chrome/browser/aboutSessionRestore.dtd b/locales/en-US/chrome/browser/aboutSessionRestore.dtd
new file mode 100644
index 0000000..f81fffc
--- /dev/null
+++ b/locales/en-US/chrome/browser/aboutSessionRestore.dtd
@@ -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/. -->
+
+<!ENTITY restorepage.tabtitle "Restore Session">
+
+<!-- LOCALIZATION NOTE: The title is intended to be apologetic and disarming, expressing dismay
+ and regret that we are unable to restore the session for the user -->
+<!ENTITY restorepage.errorTitle "There was a problem with your session.">
+<!ENTITY restorepage.problemDesc "&brandShortName; is having trouble recovering your windows and tabs. This is usually caused by a recently opened web page.">
+<!ENTITY restorepage.tryThis "You can try:">
+<!ENTITY restorepage.restoreSome "Removing one or more tabs that you think may be causing the problem">
+<!ENTITY restorepage.startNew "Starting an entirely new browsing session">
+
+<!ENTITY restorepage.tryagainButton "Restore">
+<!ENTITY restorepage.restore.access "R">
+<!ENTITY restorepage.closeButton "Close">
+<!ENTITY restorepage.close.access "C">
+
+<!ENTITY restorepage.restoreHeader "Restore">
+<!ENTITY restorepage.listHeader "Windows and Tabs">
+<!-- LOCALIZATION NOTE: &#37;S will be replaced with a number. -->
+<!ENTITY restorepage.windowLabel "Window &#37;S">
diff --git a/locales/en-US/chrome/browser/aboutSyncTabs.dtd b/locales/en-US/chrome/browser/aboutSyncTabs.dtd
new file mode 100644
index 0000000..5865c12
--- /dev/null
+++ b/locales/en-US/chrome/browser/aboutSyncTabs.dtd
@@ -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/. -->
+
+<!-- LOCALIZATION NOTE (tabs.otherDevices.label): Keep this in sync with syncTabsMenu2.label from browser.dtd -->
+<!ENTITY tabs.otherDevices.label "Tabs From Other Devices">
+
+<!ENTITY tabs.searchText.label "Type here to find tabs…">
+
+<!-- LOCALIZATION NOTE (tabs.context.openTab.accesskey, tabs.context.openMultipleTabs.accesskey):
+ Only one of these will show at a time (based on selection), so reusing accesskey is ok. -->
+<!ENTITY tabs.context.openTab.label "Open This Tab">
+<!ENTITY tabs.context.openTab.accesskey "O">
+<!ENTITY tabs.context.openMultipleTabs.label "Open Selected Tabs">
+<!ENTITY tabs.context.openMultipleTabs.accesskey "O">
+<!ENTITY tabs.context.bookmarkSingleTab.label "Bookmark This Tab…">
+<!ENTITY tabs.context.bookmarkSingleTab.accesskey "B">
+<!ENTITY tabs.context.bookmarkMultipleTabs.label "Bookmark Selected Tabs…">
+<!ENTITY tabs.context.bookmarkMultipleTabs.accesskey "B">
+<!ENTITY tabs.context.refreshList.label "Refresh List">
+<!ENTITY tabs.context.refreshList.accesskey "R">
diff --git a/locales/en-US/chrome/browser/baseMenuOverlay.dtd b/locales/en-US/chrome/browser/baseMenuOverlay.dtd
new file mode 100644
index 0000000..dd88a33
--- /dev/null
+++ b/locales/en-US/chrome/browser/baseMenuOverlay.dtd
@@ -0,0 +1,45 @@
+<!-- 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/. -->
+
+<!ENTITY minimizeWindow.key "m">
+<!ENTITY minimizeWindow.label "Minimize">
+<!ENTITY bringAllToFront.label "Bring All to Front">
+<!ENTITY zoomWindow.label "Zoom">
+<!ENTITY windowMenu.label "Window">
+
+<!ENTITY helpMenu.label "Help">
+<!ENTITY helpMenu.accesskey "H">
+<!-- LOCALIZATION NOTE some localizations of Windows (ex:french, german) use "?"
+ for the help button in the menubar but Gnome does not. -->
+<!ENTITY helpMenuWin.label "Help">
+<!ENTITY helpMenuWin.accesskey "H">
+<!ENTITY updateCmd.label "Check for Updates…">
+<!ENTITY helpReleaseNotes.label "Release Notes">
+<!ENTITY helpReleaseNotes.accesskey "N">
+<!ENTITY aboutProduct.label "About &brandShortName;">
+<!ENTITY aboutProduct.accesskey "A">
+<!ENTITY productHelp.label "&brandShortName; Help">
+<!ENTITY productHelp.accesskey "H">
+<!ENTITY helpMac.commandkey "?">
+<!ENTITY helpSafeMode.label "Restart in Safe Mode…">
+<!ENTITY helpSafeMode.accesskey "R">
+
+<!ENTITY helpTroubleshootingInfo.label "Troubleshooting Information">
+<!ENTITY helpTroubleshootingInfo.accesskey "T">
+
+<!ENTITY helpFeedbackPage.label "Submit Feedback…">
+<!ENTITY helpFeedbackPage.accesskey "S">
+
+<!ENTITY preferencesCmdMac.label "Preferences…">
+<!ENTITY preferencesCmdMac.commandkey ",">
+
+<!ENTITY servicesMenuMac.label "Services">
+
+<!ENTITY hideThisAppCmdMac.label "Hide &brandShortName;">
+<!ENTITY hideThisAppCmdMac.commandkey "H">
+
+<!ENTITY hideOtherAppsCmdMac.label "Hide Others">
+<!ENTITY hideOtherAppsCmdMac.commandkey "H">
+
+<!ENTITY showAllAppsCmdMac.label "Show All">
diff --git a/locales/en-US/chrome/browser/browser.dtd b/locales/en-US/chrome/browser/browser.dtd
new file mode 100644
index 0000000..c810b07
--- /dev/null
+++ b/locales/en-US/chrome/browser/browser.dtd
@@ -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/. -->
+
+<!-- LOCALIZATION NOTE : FILE This file contains the browser main menu items -->
+<!-- LOCALIZATION NOTE : FILE Do not translate commandkeys -->
+
+<!-- LOCALIZATION NOTE (mainWindow.title): DONT_TRANSLATE -->
+<!ENTITY mainWindow.title "&brandFullName;">
+<!-- LOCALIZATION NOTE (mainWindow.titlemodifier) : DONT_TRANSLATE -->
+<!ENTITY mainWindow.titlemodifier "&brandFullName;">
+<!-- LOCALIZATION NOTE (mainWindow.titlemodifiermenuseparator): DONT_TRANSLATE -->
+<!ENTITY mainWindow.titlemodifiermenuseparator " - ">
+<!-- LOCALIZATION NOTE (mainWindow.titlePrivateBrowsingSuffix): This will be appended to the window's title
+ inside the private browsing mode -->
+<!ENTITY mainWindow.titlePrivateBrowsingSuffix "(Private Browsing)">
+
+<!-- Tab context menu -->
+<!ENTITY reloadTab.label "Reload Tab">
+<!ENTITY reloadTab.accesskey "R">
+<!ENTITY reloadAllTabs.label "Reload All Tabs">
+<!ENTITY reloadAllTabs.accesskey "A">
+<!-- LOCALIZATION NOTE (closeTabsToTheEnd.label): This should indicate the
+direction in which tabs are closed, i.e. locales that use RTL mode should say
+left instead of right. -->
+<!ENTITY closeTabsToTheEnd.label "Close Tabs to the Right">
+<!ENTITY closeTabsToTheEnd.accesskey "i">
+<!ENTITY closeOtherTabs.label "Close Other Tabs">
+<!ENTITY closeOtherTabs.accesskey "o">
+
+<!-- LOCALIZATION NOTE (pinTab.label, unpinTab.label): "Pin" is being
+used as a metaphor for expressing the fact that these tabs are "pinned" to the
+left edge of the tabstrip. Really we just want the string to express the idea
+that this is a lightweight and reversible action that keeps your tab where you
+can reach it easily. -->
+<!ENTITY pinTab.label "Pin Tab">
+<!ENTITY pinTab.accesskey "P">
+<!ENTITY unpinTab.label "Unpin Tab">
+<!ENTITY unpinTab.accesskey "b">
+<!ENTITY moveToNewWindow.label "Move to New Window">
+<!ENTITY moveToNewWindow.accesskey "W">
+<!ENTITY bookmarkAllTabs.label "Bookmark All Tabs…">
+<!ENTITY bookmarkAllTabs.accesskey "T">
+<!ENTITY undoCloseTab.label "Undo Close Tab">
+<!ENTITY undoCloseTab.accesskey "U">
+<!ENTITY closeTab.label "Close Tab">
+<!ENTITY closeTab.accesskey "c">
+
+<!ENTITY listAllTabs.label "List all tabs">
+
+<!ENTITY tabCmd.label "New Tab">
+<!ENTITY tabCmd.accesskey "T">
+<!ENTITY tabCmd.commandkey "t">
+<!ENTITY openLocationCmd.label "Open Location…">
+<!ENTITY openLocationCmd.accesskey "L">
+<!ENTITY openFileCmd.label "Open File…">
+<!ENTITY openFileCmd.accesskey "O">
+<!ENTITY openFileCmd.commandkey "o">
+<!ENTITY printSetupCmd.label "Page Setup…">
+<!ENTITY printSetupCmd.accesskey "u">
+<!ENTITY printPreviewCmd.label "Print Preview">
+<!ENTITY printPreviewCmd.accesskey "v">
+<!ENTITY printCmd.label "Print…">
+<!ENTITY printCmd.accesskey "P">
+<!ENTITY printCmd.commandkey "p">
+
+<!ENTITY goOfflineCmd.label "Work Offline">
+<!ENTITY goOfflineCmd.accesskey "k">
+
+<!ENTITY restartCmd.label "Restart…">
+<!ENTITY restartCmd.accesskey "R">
+
+<!ENTITY menubarCmd.label "Menu Bar">
+<!ENTITY menubarCmd.accesskey "M">
+<!ENTITY navbarCmd.label "Navigation Toolbar">
+<!ENTITY navbarCmd.accesskey "N">
+<!ENTITY personalbarCmd.label "Bookmarks Toolbar">
+<!ENTITY personalbarCmd.accesskey "B">
+<!ENTITY bookmarksToolbarItem.label "Bookmarks Toolbar Items">
+<!ENTITY addonBarCmd.label "Add-on Bar">
+<!ENTITY addonBarCmd.accesskey "A">
+<!ENTITY statusBar.label "Status Bar">
+<!ENTITY statusBar.accesskey "S">
+
+<!ENTITY pageSourceCmd.label "Page Source">
+<!ENTITY pageSourceCmd.accesskey "o">
+<!ENTITY pageSourceCmd.commandkey "u">
+<!ENTITY pageInfoCmd.label "Page Info">
+<!ENTITY pageInfoCmd.accesskey "I">
+<!ENTITY pageInfoCmd.commandkey "i">
+<!-- LOCALIZATION NOTE (enterFullScreenCmd.label, exitFullScreenCmd.label):
+These should match what Safari and other Apple applications use on OS X Lion. -->
+<!ENTITY enterFullScreenCmd.label "Enter Full Screen">
+<!ENTITY enterFullScreenCmd.accesskey "F">
+<!ENTITY exitFullScreenCmd.label "Exit Full Screen">
+<!ENTITY exitFullScreenCmd.accesskey "F">
+<!ENTITY fullScreenCmd.label "Full Screen">
+<!ENTITY fullScreenCmd.accesskey "F">
+<!ENTITY fullScreenCmd.macCommandKey "f">
+<!ENTITY showAllTabsCmd.label "Show All Tabs">
+<!ENTITY showAllTabsCmd.accesskey "A">
+
+<!ENTITY fullScreenMinimize.tooltip "Minimize">
+<!ENTITY fullScreenRestore.tooltip "Restore">
+<!ENTITY fullScreenClose.tooltip "Close">
+<!ENTITY fullScreenAutohide.label "Hide Toolbars">
+<!ENTITY fullScreenAutohide.accesskey "H">
+<!ENTITY fullScreenExit.label "Exit Full Screen Mode">
+<!ENTITY fullScreenExit.accesskey "F">
+
+<!ENTITY fullscreenExitHint.value "Press ESC at any time to exit fullscreen.">
+<!ENTITY leaveDOMFullScreen.label "Exit Full Screen">
+<!ENTITY leaveDOMFullScreen.accesskey "u">
+
+<!ENTITY closeWindow.label "Close Window">
+<!ENTITY closeWindow.accesskey "d">
+
+<!ENTITY bookmarksMenu.label "Bookmarks">
+<!ENTITY bookmarksMenu.accesskey "B">
+<!ENTITY bookmarkThisPageCmd.label "Bookmark This Page">
+<!ENTITY bookmarkThisPageCmd.commandkey "d">
+<!ENTITY markPageCmd.commandkey "l">
+<!ENTITY subscribeToPageMenupopup.label "Subscribe to This Page">
+<!ENTITY subscribeToPageMenuitem.label "Subscribe to This Page…">
+<!ENTITY addCurPagesCmd.label "Bookmark All Tabs…">
+<!ENTITY showAllBookmarks2.label "Show All Bookmarks">
+<!ENTITY organizeBookmarks.label "Organize Bookmarks">
+<!ENTITY unsortedBookmarksCmd.label "Unsorted Bookmarks">
+<!ENTITY bookmarksToolbarChevron.tooltip "Show more bookmarks">
+
+<!ENTITY backCmd.label "Back">
+<!ENTITY backCmd.accesskey "B">
+<!ENTITY backButton.tooltip "Go back one page">
+<!ENTITY forwardCmd.label "Forward">
+<!ENTITY forwardCmd.accesskey "F">
+<!ENTITY forwardButton.tooltip "Go forward one page">
+<!ENTITY backForwardButtonMenu.tooltip "Right-click or pull down to show history">
+<!ENTITY backForwardButtonMenuMac.tooltip "Pull down to show history">
+<!ENTITY reloadCmd.label "Reload">
+<!ENTITY reloadCmd.accesskey "R">
+<!ENTITY reloadButton.tooltip "Reload current page">
+<!ENTITY stopCmd.label "Stop">
+<!ENTITY stopCmd.accesskey "S">
+<!ENTITY stopCmd.macCommandKey ".">
+<!ENTITY stopButton.tooltip "Stop loading this page">
+<!ENTITY goEndCap.tooltip "Go to the address in the Location Bar">
+<!ENTITY printButton.label "Print">
+<!ENTITY printButton.tooltip "Print this page">
+
+<!ENTITY backForwardItem.title "Back/Forward">
+<!ENTITY locationItem.title "Location">
+<!ENTITY searchItem.title "Search">
+<!ENTITY throbberItem.title "Activity Indicator">
+<!ENTITY bookmarksItem.title "Bookmarks">
+
+<!-- Toolbar items -->
+<!ENTITY appMenuButton.label "Menu">
+<!ENTITY appMenuButton.tooltip "Open &brandShortName; menu">
+<!ENTITY homeButton.label "Home">
+
+<!ENTITY feedButton.label "Subscribe">
+<!ENTITY feedButton.tooltip "Subscribe to this page…">
+
+<!ENTITY bookmarksButton.label "Bookmarks">
+<!ENTITY bookmarksButton.tooltip "Display your bookmarks">
+<!ENTITY bookmarksCmd.commandkey "b">
+
+<!ENTITY bookmarksMenuButton.label "Bookmarks">
+<!ENTITY bookmarksMenuButton.tooltip "Display your bookmarks">
+<!ENTITY bookmarksMenuButton.unsorted.label "Unsorted Bookmarks">
+<!ENTITY viewBookmarksSidebar.label "Show in Sidebar">
+<!ENTITY viewBookmarksToolbar.label "View Bookmarks Toolbar">
+
+<!-- LOCALIZATION NOTE (bookmarksSidebarGtkCmd.commandkey): This command
+ - key should not contain the letters A-F, since these are reserved
+ - shortcut keys on Linux. -->
+<!ENTITY bookmarksGtkCmd.commandkey "o">
+<!ENTITY bookmarksWinCmd.commandkey "i">
+
+<!ENTITY historyButton.label "History">
+<!ENTITY historyButton.tooltip "Display pages you've viewed recently">
+<!ENTITY historySidebarCmd.commandKey "h">
+
+<!ENTITY toolsMenu.label "Tools">
+<!ENTITY toolsMenu.accesskey "T">
+
+<!ENTITY keywordfield.label "Add a Keyword for this Search…">
+<!ENTITY keywordfield.accesskey "K">
+<!ENTITY search.label "Web Search">
+<!ENTITY search.accesskey "S">
+<!ENTITY downloads.label "Downloads">
+<!ENTITY downloads.tooltip "Display the progress of ongoing downloads">
+<!ENTITY downloads.accesskey "D">
+<!ENTITY downloads.commandkey "j">
+<!ENTITY downloadsUnix.commandkey "y">
+<!ENTITY addons.label "Add-ons">
+<!ENTITY addons.accesskey "A">
+<!ENTITY addons.commandkey "A">
+<!ENTITY permissions.label "Permissions">
+<!ENTITY permissions.accesskey "m">
+<!ENTITY preferencesCmd2.label "Preferences">
+<!ENTITY preferencesCmd2.accesskey "P">
+
+<!ENTITY webDeveloperMenu.label "Web Developer">
+<!ENTITY webDeveloperMenu.accesskey "W">
+
+<!ENTITY errorConsoleCmd.label "Error Console">
+<!ENTITY errorConsoleCmd.accesskey "C">
+
+<!ENTITY inspectContextMenu.label "Inspect Element">
+<!ENTITY inspectContextMenu.accesskey "Q">
+
+<!ENTITY fileMenu.label "File">
+<!ENTITY fileMenu.accesskey "F">
+<!ENTITY newNavigatorCmd.label "New Window">
+<!ENTITY newNavigatorCmd.key "N">
+<!ENTITY newNavigatorCmd.accesskey "N">
+<!ENTITY newPrivateWindow.label "New Private Window">
+<!ENTITY newPrivateWindow.accesskey "W">
+
+<!ENTITY editMenu.label "Edit">
+<!ENTITY editMenu.accesskey "E">
+<!ENTITY undoCmd.label "Undo">
+<!ENTITY undoCmd.key "Z">
+<!ENTITY undoCmd.accesskey "U">
+<!ENTITY redoCmd.label "Redo">
+<!ENTITY redoCmd.key "Y">
+<!ENTITY redoCmd.accesskey "R">
+<!ENTITY cutCmd.label "Cut">
+<!ENTITY cutCmd.key "X">
+<!ENTITY cutCmd.accesskey "t">
+<!ENTITY copyCmd.label "Copy">
+<!ENTITY copyCmd.key "C">
+<!ENTITY copyCmd.accesskey "C">
+<!ENTITY pasteCmd.label "Paste">
+<!ENTITY pasteCmd.key "V">
+<!ENTITY pasteCmd.accesskey "P">
+<!ENTITY deleteCmd.label "Delete">
+<!ENTITY deleteCmd.key "D">
+<!ENTITY deleteCmd.accesskey "D">
+<!ENTITY selectAllCmd.label "Select All">
+<!ENTITY selectAllCmd.key "A">
+<!ENTITY selectAllCmd.accesskey "A">
+
+<!ENTITY clearRecentHistory.label "Clear Recent History…">
+
+<!ENTITY privateBrowsingCmd.commandkey "P">
+
+<!ENTITY viewMenu.label "View">
+<!ENTITY viewMenu.accesskey "V">
+<!ENTITY viewToolbarsMenu.label "Toolbars">
+<!ENTITY viewToolbarsMenu.accesskey "T">
+<!ENTITY viewSidebarMenu.label "Sidebar">
+<!ENTITY viewSidebarMenu.accesskey "e">
+<!ENTITY viewCustomizeToolbar.label "Customize">
+<!ENTITY viewCustomizeToolbar.accesskey "C">
+<!ENTITY viewTabsOnTop.label "Tabs on Top">
+<!ENTITY viewTabsOnTop.accesskey "T">
+
+<!ENTITY historyMenu.label "History">
+<!ENTITY historyMenu.accesskey "s">
+<!ENTITY historyUndoMenu.label "Recently Closed Tabs">
+<!-- LOCALIZATION NOTE (historyUndoWindowMenu): see bug 394759 -->
+<!ENTITY historyUndoWindowMenu.label "Recently Closed Windows">
+<!ENTITY historyRestoreLastSession.label "Restore Previous Session">
+
+<!ENTITY historyHomeCmd.label "Home">
+<!ENTITY showAllHistoryCmd2.label "Show All History">
+<!ENTITY showAllHistoryCmd.commandkey "H">
+
+<!ENTITY appMenuEdit.label "Edit">
+<!ENTITY appMenuCustomize.label "Customize">
+<!ENTITY appMenuToolbarLayout.label "Toolbar Layout">
+<!ENTITY appMenuSidebars.label "Sidebars">
+<!ENTITY appMenuFind.label "Find…">
+<!ENTITY appMenuUnsorted.label "Unsorted Bookmarks">
+<!ENTITY appMenuWebDeveloper.label "Web Developer">
+<!ENTITY appMenuGettingStarted.label "Getting Started">
+<!ENTITY appMenuSafeMode.label "Restart in Safe Mode…">
+
+<!ENTITY openCmd.commandkey "l">
+<!ENTITY urlbar.accesskey "d">
+<!ENTITY urlbar.switchToTab.label "Switch to tab:">
+
+<!--
+ Comment duplicated from browser-sets.inc:
+
+ Search Command Key Logic works like this:
+
+ Unix: Ctrl+J (0.8, 0.9 support)
+ Ctrl+K (cross platform binding)
+ 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.
+
+ -->
+<!ENTITY searchFocus.commandkey "k">
+<!ENTITY searchFocus.commandkey2 "e">
+<!ENTITY searchFocusUnix.commandkey "j">
+
+<!ENTITY openLinkCmdInTab.label "Open Link in New Tab">
+<!ENTITY openLinkCmdInTab.accesskey "T">
+<!ENTITY openLinkCmd.label "Open Link in New Window">
+<!ENTITY openLinkCmd.accesskey "W">
+<!ENTITY openLinkInPrivateWindowCmd.label "Open Link in New Private Window">
+<!ENTITY openLinkInPrivateWindowCmd.accesskey "P">
+<!ENTITY openLinkCmdInCurrent.label "Open Link in Current Tab">
+<!ENTITY openLinkCmdInCurrent.accesskey "O">
+<!ENTITY openFrameCmdInTab.label "Open Frame in New Tab">
+<!ENTITY openFrameCmdInTab.accesskey "T">
+<!ENTITY openFrameCmd.label "Open Frame in New Window">
+<!ENTITY openFrameCmd.accesskey "W">
+<!ENTITY showOnlyThisFrameCmd.label "Show Only This Frame">
+<!ENTITY showOnlyThisFrameCmd.accesskey "S">
+<!ENTITY reloadCmd.commandkey "r">
+<!ENTITY reloadFrameCmd.label "Reload Frame">
+<!ENTITY reloadFrameCmd.accesskey "R">
+<!ENTITY viewPartialSourceForSelectionCmd.label "View Selection Source">
+<!ENTITY viewPartialSourceForMathMLCmd.label "View MathML Source">
+<!-- LOCALIZATION NOTE (viewPartialSourceCmd.accesskey): This accesskey is used for both
+ viewPartialSourceForSelectionCmd.label and viewPartialSourceForMathMLCmd.label -->
+<!ENTITY viewPartialSourceCmd.accesskey "e">
+<!ENTITY viewPageSourceCmd.label "View Page Source">
+<!ENTITY viewPageSourceCmd.accesskey "V">
+<!ENTITY viewFrameSourceCmd.label "View Frame Source">
+<!ENTITY viewFrameSourceCmd.accesskey "V">
+<!ENTITY viewPageInfoCmd.label "View Page Info">
+<!ENTITY viewPageInfoCmd.accesskey "I">
+<!ENTITY viewFrameInfoCmd.label "View Frame Info">
+<!ENTITY viewFrameInfoCmd.accesskey "I">
+<!ENTITY reloadImageCmd.label "Reload Image">
+<!ENTITY reloadImageCmd.accesskey "R">
+<!ENTITY viewImageCmd.label "View Image">
+<!ENTITY viewImageCmd.accesskey "I">
+<!ENTITY viewImageInfoCmd.label "View Image Info">
+<!ENTITY viewImageInfoCmd.accesskey "f">
+<!ENTITY viewVideoCmd.label "View Video">
+<!ENTITY viewVideoCmd.accesskey "I">
+<!ENTITY viewBGImageCmd.label "View Background Image">
+<!ENTITY viewBGImageCmd.accesskey "w">
+<!ENTITY setDesktopBackgroundCmd.label "Set As Desktop Background…">
+<!ENTITY setDesktopBackgroundCmd.accesskey "S">
+<!ENTITY bookmarkPageCmd2.label "Bookmark This Page">
+<!ENTITY bookmarkPageCmd2.accesskey "m">
+<!ENTITY bookmarkThisLinkCmd.label "Bookmark This Link">
+<!ENTITY bookmarkThisLinkCmd.accesskey "L">
+<!ENTITY bookmarkThisFrameCmd.label "Bookmark This Frame">
+<!ENTITY bookmarkThisFrameCmd.accesskey "m">
+<!ENTITY emailPageCmd.label "Email Link…">
+<!ENTITY emailPageCmd.accesskey "E">
+<!ENTITY savePageCmd.label "Save Page As…">
+<!ENTITY savePageCmd.accesskey "A">
+<!ENTITY sendLinkCmd.label "Send Link…">
+<!ENTITY sendLinkCmd.accesskey "d">
+<!ENTITY sendPageCmd.label "Send Page Link…">
+<!ENTITY sendPageCmd.accesskey "e">
+<!-- alternate for content area context menu -->
+<!ENTITY savePageCmd.accesskey2 "P">
+<!ENTITY savePageCmd.commandkey "s">
+<!ENTITY saveFrameCmd.label "Save Frame As…">
+<!ENTITY saveFrameCmd.accesskey "F">
+<!ENTITY printFrameCmd.label "Print Frame…">
+<!ENTITY printFrameCmd.accesskey "P">
+<!ENTITY saveLinkCmd.label "Save Link As…">
+<!ENTITY saveLinkCmd.accesskey "k">
+<!ENTITY saveImageCmd.label "Save Image As…">
+<!ENTITY saveImageCmd.accesskey "v">
+<!ENTITY saveVideoCmd.label "Save Video As…">
+<!ENTITY saveVideoCmd.accesskey "v">
+<!ENTITY saveAudioCmd.label "Save Audio As…">
+<!ENTITY saveAudioCmd.accesskey "v">
+<!ENTITY emailImageCmd.label "Email Image…">
+<!ENTITY emailImageCmd.accesskey "g">
+<!ENTITY emailVideoCmd.label "Email Video…">
+<!ENTITY emailVideoCmd.accesskey "a">
+<!ENTITY emailAudioCmd.label "Email Audio…">
+<!ENTITY emailAudioCmd.accesskey "a">
+<!ENTITY playPluginCmd.label "Activate this plugin">
+<!ENTITY playPluginCmd.accesskey "c">
+<!ENTITY hidePluginCmd.label "Hide this plugin">
+<!ENTITY hidePluginCmd.accesskey "H">
+<!ENTITY copyLinkCmd.label "Copy Link Location">
+<!ENTITY copyLinkCmd.accesskey "a">
+<!ENTITY copyImageCmd.label "Copy Image Location">
+<!ENTITY copyImageCmd.accesskey "o">
+<!ENTITY copyImageContentsCmd.label "Copy Image">
+<!ENTITY copyImageContentsCmd.accesskey "y">
+<!ENTITY copyVideoURLCmd.label "Copy Video Location">
+<!ENTITY copyVideoURLCmd.accesskey "o">
+<!ENTITY copyAudioURLCmd.label "Copy Audio Location">
+<!ENTITY copyAudioURLCmd.accesskey "o">
+<!ENTITY copyEmailCmd.label "Copy Email Address">
+<!ENTITY copyEmailCmd.accesskey "E">
+<!ENTITY thisFrameMenu.label "This Frame">
+<!ENTITY thisFrameMenu.accesskey "h">
+
+<!-- Media (video/audio) controls -->
+<!-- LOCALIZATION NOTE: The access keys for "Play" and
+"Pause" are the same because the two context-menu
+items are mutually exclusive. -->
+<!ENTITY mediaPlay.label "Play">
+<!ENTITY mediaPlay.accesskey "P">
+<!ENTITY mediaPause.label "Pause">
+<!ENTITY mediaPause.accesskey "P">
+<!-- LOCALIZATION NOTE: The access keys for "Mute" and
+"Unmute" are the same because the two context-menu
+items are mutually exclusive. -->
+<!ENTITY mediaMute.label "Mute">
+<!ENTITY mediaMute.accesskey "M">
+<!ENTITY mediaUnmute.label "Unmute">
+<!ENTITY mediaUnmute.accesskey "m">
+<!ENTITY mediaPlaybackRate2.label "Play Speed">
+<!ENTITY mediaPlaybackRate2.accesskey "d">
+<!ENTITY mediaPlaybackRate050x.label "Slow Motion (0.5×)">
+<!ENTITY mediaPlaybackRate050x.accesskey "S">
+<!ENTITY mediaPlaybackRate100x.label "Normal Speed">
+<!ENTITY mediaPlaybackRate100x.accesskey "N">
+<!ENTITY mediaPlaybackRate150x.label "High Speed (1.5×)">
+<!ENTITY mediaPlaybackRate150x.accesskey "H">
+<!-- LOCALIZATION NOTE: "Ludicrous Speed" is a reference to the
+movie "Space Balls" and is meant to say that this speed is very
+fast. -->
+<!ENTITY mediaPlaybackRate200x.label "Ludicrous Speed (2×)">
+<!ENTITY mediaPlaybackRate200x.accesskey "L">
+<!ENTITY mediaLoop.label "Loop">
+<!ENTITY mediaLoop.accesskey "L">
+<!-- LOCALIZATION NOTE: The access keys for "Show Controls" and
+"Hide Controls" are the same because the two context-menu
+items are mutually exclusive. -->
+<!ENTITY mediaShowControls.label "Show Controls">
+<!ENTITY mediaShowControls.accesskey "C">
+<!ENTITY mediaHideControls.label "Hide Controls">
+<!ENTITY mediaHideControls.accesskey "C">
+<!ENTITY videoFullScreen.label "Full Screen">
+<!ENTITY videoFullScreen.accesskey "F">
+<!ENTITY videoSaveImage.label "Save Snapshot As…">
+<!ENTITY videoSaveImage.accesskey "S">
+<!-- LOCALIZATION NOTE: The access keys for "Show Statistics" and
+"Hide Statistics" are the same because the two context-menu
+items are mutually exclusive. -->
+<!ENTITY videoShowStats.label "Show Statistics">
+<!ENTITY videoShowStats.accesskey "t">
+<!ENTITY videoHideStats.label "Hide Statistics">
+<!ENTITY videoHideStats.accesskey "t">
+
+<!-- LOCALIZATION NOTE :
+fullZoomEnlargeCmd.commandkey3, fullZoomReduceCmd.commandkey2 and
+fullZoomResetCmd.commandkey2 are alternative acceleration keys for zoom.
+If shift key is needed with your locale popular keyboard for them,
+you can use these alternative items. Otherwise, their values should be empty. -->
+
+<!ENTITY fullZoomEnlargeCmd.label "Zoom In">
+<!ENTITY fullZoomEnlargeCmd.accesskey "I">
+<!ENTITY fullZoomEnlargeCmd.commandkey "+">
+<!ENTITY fullZoomEnlargeCmd.commandkey2 "="> <!-- + is above this key on many keyboards -->
+<!ENTITY fullZoomEnlargeCmd.commandkey3 "">
+
+<!ENTITY fullZoomReduceCmd.label "Zoom Out">
+<!ENTITY fullZoomReduceCmd.accesskey "O">
+<!ENTITY fullZoomReduceCmd.commandkey "-">
+<!ENTITY fullZoomReduceCmd.commandkey2 "">
+
+<!ENTITY fullZoomResetCmd.label "Reset">
+<!ENTITY fullZoomResetCmd.accesskey "R">
+<!ENTITY fullZoomResetCmd.commandkey "0">
+<!ENTITY fullZoomResetCmd.commandkey2 "">
+
+<!ENTITY fullZoomToggleCmd.label "Zoom Text Only">
+<!ENTITY fullZoomToggleCmd.accesskey "T">
+<!ENTITY fullZoom.label "Zoom">
+<!ENTITY fullZoom.accesskey "Z">
+
+<!ENTITY newTabButton.tooltip "Open a new tab">
+<!ENTITY newWindowButton.tooltip "Open a new window">
+<!ENTITY sidebarCloseButton.tooltip "Close sidebar">
+
+<!ENTITY cutButton.tooltip "Cut">
+<!ENTITY copyButton.tooltip "Copy">
+<!ENTITY pasteButton.tooltip "Paste">
+
+<!ENTITY fullScreenButton.tooltip "Display the window in full screen">
+
+<!ENTITY zoomOutButton.tooltip "Zoom out">
+<!ENTITY zoomInButton.tooltip "Zoom in">
+<!ENTITY zoomControls.label "Zoom Controls">
+
+<!ENTITY appMenuRestart.label "Restart…">
+<!ENTITY appMenuRestart.accesskey "R">
+
+<!ENTITY quitApplicationCmdWin.label "Exit">
+<!ENTITY quitApplicationCmdWin.accesskey "x">
+<!ENTITY goBackCmd.commandKey "[">
+<!ENTITY goForwardCmd.commandKey "]">
+<!ENTITY quitApplicationCmd.label "Quit">
+<!ENTITY quitApplicationCmd.accesskey "Q">
+<!ENTITY quitApplicationCmdMac.label "Quit &brandShortName;">
+<!-- LOCALIZATION NOTE(quitApplicationCmdUnix.key): This keyboard shortcut is used by both Linux and OSX builds. -->
+<!ENTITY quitApplicationCmdUnix.key "Q">
+
+<!ENTITY closeCmd.label "Close">
+<!ENTITY closeCmd.key "W">
+<!ENTITY closeCmd.accesskey "C">
+
+<!ENTITY toggleMuteCmd.key "M">
+
+<!ENTITY pageStyleMenu.label "Page Style">
+<!ENTITY pageStyleMenu.accesskey "y">
+<!ENTITY pageStyleNoStyle.label "No Style">
+<!ENTITY pageStyleNoStyle.accesskey "n">
+<!ENTITY pageStylePersistentOnly.label "Basic Page Style">
+<!ENTITY pageStylePersistentOnly.accesskey "b">
+
+<!ENTITY pageReportIcon.tooltip "Change pop-up blocking settings for this website">
+
+<!ENTITY allowPopups.accesskey "p">
+<!ENTITY editPopupSettingsUnix.label "Edit Pop-up Blocker Preferences">
+<!ENTITY editPopupSettings.label "Edit Pop-up Blocker Preferences">
+<!ENTITY editPopupSettings.accesskey "E">
+<!ENTITY dontShowMessage.accesskey "D">
+
+<!ENTITY bidiSwitchPageDirectionItem.label "Switch Page Direction">
+<!ENTITY bidiSwitchPageDirectionItem.accesskey "D">
+<!ENTITY bidiSwitchTextDirectionItem.label "Switch Text Direction">
+<!ENTITY bidiSwitchTextDirectionItem.accesskey "w">
+<!ENTITY bidiSwitchTextDirectionItem.commandkey "X">
+
+<!ENTITY findOnCmd.label "Find in This Page…">
+<!ENTITY findOnCmd.accesskey "F">
+<!ENTITY findOnCmd.commandkey "f">
+<!ENTITY findAgainCmd.label "Find Again">
+<!ENTITY findAgainCmd.accesskey "g">
+<!ENTITY findAgainCmd.commandkey "g">
+<!ENTITY findAgainCmd.commandkey2 "VK_F3">
+
+<!ENTITY spellAddDictionaries.label "Add Dictionaries…">
+<!ENTITY spellAddDictionaries.accesskey "A">
+
+<!ENTITY editBookmark.done.label "Done">
+<!ENTITY editBookmark.cancel.label "Cancel">
+<!ENTITY editBookmark.removeBookmark.accessKey "R">
+
+<!ENTITY identity.unverifiedsite2 "This website does not supply identity information.">
+<!ENTITY identity.connectedTo "You are connected to">
+<!-- Localization note (identity.runBy) : This string appears between a
+domain name (above) and an organization name (below). E.g.
+
+example.com
+which is run by
+Example Enterprises, Inc.
+
+The layout of the identity dialog prevents combining this into a single string with
+substitution variables. If it is difficult to translate the sense of the string
+with that structure, consider a translation which ignores the preceding domain and
+just addresses the organization to follow, e.g. "This site is run by " -->
+<!ENTITY identity.runBy "which is run by">
+
+<!ENTITY identity.moreInfoLinkText "More Information…">
+
+<!ENTITY allTabs.filter.emptyText "Search Tabs">
+<!-- Name for the tabs toolbar as spoken by screen readers.
+ The word "toolbar" is appended automatically and should not be contained below! -->
+<!ENTITY tabsToolbar.label "Browser tabs">
+
+#ifdef MOZ_SERVICES_SYNC
+<!-- LOCALIZATION NOTE (syncTabsMenu2.label): This appears in the history menu -->
+<!ENTITY syncTabsMenu2.label "Tabs From Other Devices">
+
+<!ENTITY syncBrand.shortName.label "Sync">
+
+<!ENTITY syncSetup.label "Set Up &syncBrand.shortName.label;…">
+<!ENTITY syncSetup.accesskey "Y">
+<!ENTITY syncSyncNowItem.label "Sync Now">
+<!ENTITY syncSyncNowItem.accesskey "S">
+<!ENTITY syncToolbarButton.label "Sync">
+#endif
+
+<!ENTITY addonBarCloseButton.tooltip "Close Add-on Bar">
+<!ENTITY toggleAddonBarCmd.key "/">
+
+<!ENTITY getUserMedia.selectCamera.label "Camera to share:">
+<!ENTITY getUserMedia.selectCamera.accesskey "C">
+<!ENTITY getUserMedia.selectMicrophone.label "Microphone to share:">
+<!ENTITY getUserMedia.selectMicrophone.accesskey "M">
+
+#ifdef MOZ_WEBRTC
+<!ENTITY webrtcIndicatorButton.label "Camera / Microphone Access">
+<!ENTITY webrtcIndicatorButton.tooltip "Display sites you are currently sharing your camera or microphone with">
+#endif
+
+<!ENTITY mixedContentBlocked.moreinfo "Most websites will still work properly even when this content is blocked.">
+
+<!ENTITY pointerLock.notification.message "Press ESC at any time to show it again.">
+
+<!ENTITY pluginNotification.showAll.label "Show All">
+<!ENTITY pluginNotification.showAll.accesskey "S">
+
+<!-- LOCALIZATION NOTE (pluginActivateNow.label, pluginActivateAlways.label, pluginBlockNow.label): These should be the same as the matching strings in browser.properties -->
+<!ENTITY pluginActivateNow.label "Allow Now">
+<!ENTITY pluginActivateAlways.label "Allow and Remember">
+<!ENTITY pluginBlockNow.label "Block Plugin">
diff --git a/locales/en-US/chrome/browser/browser.properties b/locales/en-US/chrome/browser/browser.properties
new file mode 100644
index 0000000..4c45e25
--- /dev/null
+++ b/locales/en-US/chrome/browser/browser.properties
@@ -0,0 +1,420 @@
+# 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/.
+
+nv_timeout=Timed Out
+openFile=Open File
+
+droponhometitle=Set Home Page
+droponhomemsg=Do you want this document to be your new home page?
+droponhomemsgMultiple=Do you want these documents to be your new home pages?
+
+# context menu strings
+
+# LOCALIZATION NOTE (contextMenuSearch): %1$S is the search engine,
+# %2$S is the selection string.
+contextMenuSearch=Search %1$S for "%2$S"
+contextMenuSearch.accesskey=S
+
+# bookmark dialog strings
+
+bookmarkAllTabsDefault=[Folder Name]
+
+xpinstallPromptWarning=%S prevented this site (%S) from asking you to install software on your computer.
+xpinstallPromptAllowButton=Allow
+
+xpinstallPromptWarningOrigin=This add-on could not be downloaded because its origin could not be verified.
+# Accessibility Note:
+# Be sure you do not choose an accesskey that is used elsewhere in the active context (e.g. main menu bar, submenu of the warning popup button)
+# See http://www.mozilla.org/access/keyboard/accesskey for details
+xpinstallPromptAllowButton.accesskey=A
+xpinstallDisabledMessageLocked=Software installation has been disabled by your system administrator.
+xpinstallDisabledMessage=Software installation is currently disabled. Click Enable and try again.
+xpinstallDisabledButton=Enable
+xpinstallDisabledButton.accesskey=n
+
+# LOCALIZATION NOTE (addonDownloading, addonDownloadCancelled, addonDownloadRestart):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# Also see https://bugzilla.mozilla.org/show_bug.cgi?id=570012 for mockups
+addonDownloading=Add-on downloading;Add-ons downloading
+addonDownloadCancelled=Add-on download cancelled.;Add-on downloads cancelled.
+addonDownloadRestart=Restart Download;Restart Downloads
+addonDownloadRestart.accessKey=R
+addonDownloadCancelTooltip=Cancel
+
+# LOCALIZATION NOTE (addonsInstalled, addonsInstalledNeedsRestart):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 first add-on's name, #2 number of add-ons, #3 application name
+addonsInstalled=#1 has been installed successfully.;#2 add-ons have been installed successfully.
+addonsInstalledNeedsRestart=#1 will be installed after you restart #3.;#2 add-ons will be installed after you restart #3.
+addonInstallRestartButton=Restart Now
+addonInstallRestartButton.accesskey=R
+
+# LOCALIZATION NOTE (addonError-1, addonError-2, addonError-3, addonError-4):
+# #1 is the add-on name, #2 is the host name, #3 is the application name
+# #4 is the application version
+addonError-1=The add-on could not be downloaded because of a connection failure on #2.
+addonError-2=The add-on from #2 could not be installed because it does not match the add-on #3 expected.
+addonError-3=The add-on downloaded from #2 could not be installed because it appears to be corrupt.
+addonError-4=#1 could not be installed because #3 cannot modify the needed file.
+addonError-8=The add-on downloaded from #2 could not be installed because #3 does not support Jetpack (SDK) extensions.
+addonError-9=The add-on downloaded from #2 could not be installed because #3 does not support WebExtensions.
+
+# LOCALIZATION NOTE (addonLocalError-1, addonLocalError-2, addonLocalError-3, addonLocalError-4, addonErrorIncompatible, addonErrorBlocklisted):
+# #1 is the add-on name, #3 is the application name, #4 is the application version
+addonLocalError-1=This add-on could not be installed because of a filesystem error.
+addonLocalError-2=This add-on could not be installed because it does not match the add-on #3 expected.
+addonLocalError-3=This add-on could not be installed because it appears to be corrupt.
+addonLocalError-4=#1 could not be installed because #3 cannot modify the needed file.
+addonLocalError-8=This add-on could not be installed because #3 does not support Jetpack (SDK) extensions.
+addonLocalError-9=This add-on could not be installed because #3 does not support WebExtensions.
+addonErrorIncompatible=#1 could not be installed because it is not compatible with #3 #4.
+addonErrorBlocklisted=#1 could not be installed because it has a high risk of causing stability or security problems.
+
+
+# LOCALIZATION NOTE (lwthemeInstallRequest.message): %S will be replaced with
+# the host name of the site.
+lwthemeInstallRequest.message=This site (%S) attempted to install a theme.
+lwthemeInstallRequest.allowButton=Allow
+lwthemeInstallRequest.allowButton.accesskey=a
+
+lwthemePostInstallNotification.message=A new theme has been installed.
+lwthemePostInstallNotification.undoButton=Undo
+lwthemePostInstallNotification.undoButton.accesskey=U
+lwthemePostInstallNotification.manageButton=Manage Themes…
+lwthemePostInstallNotification.manageButton.accesskey=M
+
+# LOCALIZATION NOTE (lwthemeNeedsRestart.message):
+# %S will be replaced with the new theme name.
+lwthemeNeedsRestart.message=%S will be installed after you restart.
+lwthemeNeedsRestart.button=Restart Now
+lwthemeNeedsRestart.accesskey=R
+
+# LOCALIZATION NOTE (popupWarning.message): Semicolon-separated list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is brandShortName and #2 is the number of pop-ups blocked.
+popupWarning.message=#1 prevented this site from opening a pop-up window.;#1 prevented this site from opening #2 pop-up windows.
+popupWarningButton=Preferences
+popupWarningButton.accesskey=P
+popupAllow=Allow pop-ups for %S
+popupBlock=Block pop-ups for %S
+popupWarningDontShowFromMessage=Don't show this message when pop-ups are blocked
+popupWarningDontShowFromLocationbar=Don't show info bar when pop-ups are blocked
+popupShowPopupPrefix=Show '%S'
+
+crashedpluginsMessage.title=The %S plugin has crashed.
+crashedpluginsMessage.reloadButton.label=Reload page
+crashedpluginsMessage.reloadButton.accesskey=R
+crashedpluginsMessage.submitButton.label=Submit a crash report
+crashedpluginsMessage.submitButton.accesskey=S
+crashedpluginsMessage.learnMore=Learn More…
+
+## Plugin doorhanger strings
+# LOCALIZATION NOTE (pluginActivateNew.message): Used for newly-installed
+# plugins which are not known to be unsafe. %1$S is the plugin name and %2$S
+# is the site domain.
+pluginActivateNew.message=Allow %2$S to run "%1$S"?
+pluginActivateMultiple.message=Allow %S to run plugins?
+pluginActivate.learnMore=Learn More…
+# LOCALIZATION NOTE (pluginActivateOutdated.message, pluginActivateOutdated.label):
+# These strings are used when an unsafe plugin has an update available.
+# %1$S is the plugin name, %2$S is the domain, and %3$S is brandShortName.
+pluginActivateOutdated.message=%3$S has prevented the outdated plugin "%1$S" from running on %2$S.
+pluginActivateOutdated.label=Outdated plugin
+pluginActivate.updateLabel=Update now…
+# LOCALIZATION NOTE (pluginActivateVulnerable.message, pluginActivateVulnerable.label):
+# These strings are used when an unsafe plugin has no update available.
+# %1$S is the plugin name, %2$S is the domain, and %3$S is brandShortName.
+pluginActivateVulnerable.message=%3$S has prevented the unsafe plugin "%1$S" from running on %2$S.
+pluginActivateVulnerable.label=Vulnerable plugin!
+pluginActivate.riskLabel=What's the risk?
+# LOCALIZATION NOTE (pluginActivateBlocked.message): %1$S is the plugin name, %2$S is brandShortName
+pluginActivateBlocked.message=%2$S has blocked "%1$S" for your protection.
+pluginActivateBlocked.label=Blocked for your protection
+pluginActivateDisabled.message="%S" is disabled.
+pluginActivateDisabled.label=Disabled
+pluginActivateDisabled.manage=Manage plugins…
+pluginEnabled.message="%S" is enabled on %S.
+pluginEnabledOutdated.message=Outdated plugin "%S" is enabled on %S.
+pluginEnabledVulnerable.message=Insecure plugin "%S" is enabled on %S.
+pluginInfo.unknownPlugin=Unknown
+
+# LOCALIZATION NOTE (pluginActivateNow.label, pluginActivateAlways.label, pluginBlockNow.label): These should be the same as the matching strings in browser.dtd
+# LOCALIZATION NOTE (pluginActivateNow.label): This button will enable the
+# plugin in the current session for an short time (about an hour), auto-renewed
+# if the site keeps using the plugin.
+pluginActivateNow.label=Allow Now
+pluginActivateNow.accesskey=N
+# LOCALIZATION NOTE (pluginActivateAlways.label): This button will enable the
+# plugin for a long while (90 days), auto-renewed if the site keeps using the
+# plugin.
+pluginActivateAlways.label=Allow and Remember
+pluginActivateAlways.accesskey=R
+pluginBlockNow.label=Block Plugin
+pluginBlockNow.accesskey=B
+pluginContinue.label=Continue Allowing
+pluginContinue.accesskey=C
+
+# in-page UI
+PluginClickToActivate=Activate %S.
+PluginVulnerableUpdatable=This plugin is vulnerable and should be updated.
+PluginVulnerableNoUpdate=This plugin has security vulnerabilities.
+
+# Sanitize
+# LOCALIZATION NOTE (sanitizeDialog2.everything.title): When "Time range to
+# clear" is set to "Everything", the Clear Recent History dialog's title is
+# changed to this. See UI mockup and comment 11 at bug 480169 -->
+sanitizeDialog2.everything.title=Clear All History
+sanitizeButtonOK=Clear Now
+# LOCALIZATION NOTE (sanitizeButtonClearing): The label for the default
+# button between the user clicking it and the window closing. Indicates the
+# items are being cleared.
+sanitizeButtonClearing=Clearing
+
+# LOCALIZATION NOTE (sanitizeEverythingWarning2): Warning that appears when
+# "Time range to clear" is set to "Everything" in Clear Recent History dialog,
+# provided that the user has not modified the default set of history items to clear.
+sanitizeEverythingWarning2=All history will be cleared.
+# LOCALIZATION NOTE (sanitizeSelectedWarning): Warning that appears when
+# "Time range to clear" is set to "Everything" in Clear Recent History dialog,
+# provided that the user has modified the default set of history items to clear.
+sanitizeSelectedWarning=All selected items will be cleared.
+
+# Check for Updates in the About Dialog - button labels and accesskeys
+# LOCALIZATION NOTE - all of the following update buttons labels will only be
+# displayed one at a time. So, if a button is displayed nothing else will
+# be displayed alongside of the button. The button when displayed is located
+# directly under the Firefox version in the about dialog (see bug 596813 for
+# screenshots).
+update.checkInsideButton.label=Check for Updates
+update.checkInsideButton.accesskey=C
+update.resumeButton.label=Resume Downloading %S…
+update.resumeButton.accesskey=D
+update.openUpdateUI.applyButton.label=Update available! Apply Update…
+update.openUpdateUI.applyButton.accesskey=A
+update.restart.updateButton.label=Restart to Update
+update.restart.updateButton.accesskey=R
+update.openUpdateUI.upgradeButton.label=Update available! Upgrade Now…
+update.openUpdateUI.upgradeButton.accesskey=U
+update.restart.upgradeButton.label=Upgrade Now
+update.restart.upgradeButton.accesskey=U
+
+# Check for Updates in the Help Menu
+# LOCALIZATION NOTE (updatesItem_*): these are alternative labels for Check for Update item in Help menu.
+# Which one is used depends on Update process state.
+updatesItem_default=Check for Updates…
+updatesItem_defaultFallback=Check for Updates…
+updatesItem_default.accesskey=C
+updatesItem_downloading=Downloading %S…
+updatesItem_downloadingFallback=Downloading Update…
+updatesItem_downloading.accesskey=D
+updatesItem_resume=Resume Downloading %S…
+updatesItem_resumeFallback=Resume Downloading Update…
+updatesItem_resume.accesskey=D
+updatesItem_pending=Apply Downloaded Update Now…
+updatesItem_pendingFallback=Apply Downloaded Update Now…
+updatesItem_pending.accesskey=D
+
+# RSS Pretty Print
+feedShowFeedNew=Subscribe to '%S'…
+
+menuOpenAllInTabs.label=Open All in Tabs
+
+# History menu
+menuRestoreAllTabs.label=Restore All Tabs
+# LOCALIZATION NOTE (menuRestoreAllWindows, menuUndoCloseWindowLabel, menuUndoCloseWindowSingleTabLabel):
+# see bug 394759
+menuRestoreAllWindows.label=Restore All Windows
+# LOCALIZATION NOTE (menuUndoCloseWindowLabel): Semicolon-separated list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 Window Title, #2 Number of tabs
+menuUndoCloseWindowLabel=#1 (and #2 other tab);#1 (and #2 other tabs)
+menuUndoCloseWindowSingleTabLabel=#1
+
+# Unified Back-/Forward Popup
+tabHistory.current=Stay on this page
+tabHistory.goBack=Go back to this page
+tabHistory.goForward=Go forward to this page
+
+# URL Bar
+urlbar.placeholder=Search or enter address
+urlbar.placeholderURLOnly=Enter address
+pasteAndGo.label=Paste & Go
+
+# Block autorefresh
+refreshBlocked.goButton=Allow
+refreshBlocked.goButton.accesskey=A
+refreshBlocked.refreshLabel=%S prevented this page from automatically reloading.
+refreshBlocked.redirectLabel=%S prevented this page from automatically redirecting to another page.
+
+# Star button
+starButtonOn.tooltip=Edit this bookmark
+starButtonOff.tooltip=Bookmark this page
+
+# Offline web applications
+offlineApps.available=This website (%S) is asking to store data on your computer for offline use.
+offlineApps.allow=Allow
+offlineApps.allowAccessKey=A
+offlineApps.never=Never for This Site
+offlineApps.neverAccessKey=e
+offlineApps.notNow=Not Now
+offlineApps.notNowAccessKey=N
+
+offlineApps.usage=This website (%S) is now storing more than %SMB of data on your computer for offline use.
+offlineApps.manageUsage=Show settings
+offlineApps.manageUsageAccessKey=S
+
+# LOCALIZATION NOTE (indexedDB.usage): %1$S is the website host name
+# %2$S a number of megabytes.
+indexedDB.usage=This website (%1$S) is attempting to store more than %2$S MB of data on your computer for offline use.
+
+identity.identified.verifier=Verified by: %S
+identity.identified.verified_by_you=You have added a security exception for this site.
+identity.identified.state_and_country=%S, %S
+
+identity.encrypted=Your connection to this website is encrypted to prevent eavesdropping.
+identity.unencrypted=Your connection to this website is not encrypted.
+identity.mixed_content=Your connection to this site is only partially encrypted, and does not prevent eavesdropping.
+
+identity.unknown.tooltip=This website does not supply identity information.
+
+identity.ownerUnknown2=(unknown)
+
+# Edit Bookmark UI
+editBookmarkPanel.pageBookmarkedTitle=Page Bookmarked
+editBookmarkPanel.pageBookmarkedDescription=%S will always remember this page for you.
+editBookmarkPanel.bookmarkedRemovedTitle=Bookmark Removed
+editBookmarkPanel.editBookmarkTitle=Edit This Bookmark
+
+# LOCALIZATION NOTE (editBookmark.removeBookmarks.label): Semicolon-separated list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# Replacement for #1 is the number of bookmarks to be removed.
+# If this causes problems with localization you can also do "Remove Bookmarks (#1)"
+# instead of "Remove #1 Bookmarks".
+editBookmark.removeBookmarks.label=Remove Bookmark;Remove #1 Bookmarks
+
+# Post Update Notifications
+pu.notifyButton.label=Details…
+pu.notifyButton.accesskey=D
+# LOCALIZATION NOTE %S will be replaced by the short name of the application.
+puNotifyText=%S has been updated
+puAlertTitle=%S Updated
+puAlertText=Click here for details
+
+# Geolocation UI
+
+# LOCALIZATION NOTE (geolocation.shareLocation geolocation.alwaysShareLocation geolocation.neverShareLocation):
+#If you're having trouble with the word Share, please use Allow and Block in your language.
+geolocation.shareLocation=Share Location
+geolocation.shareLocation.accesskey=a
+geolocation.alwaysShareLocation=Always Share Location
+geolocation.alwaysShareLocation.accesskey=A
+geolocation.neverShareLocation=Never Share Location
+geolocation.neverShareLocation.accesskey=N
+geolocation.shareWithSite=Would you like to share your location with %S?
+geolocation.shareWithFile=Would you like to share your location with the file %S?
+
+webNotifications.showForSession=Show for this session
+webNotifications.showForSession.accesskey=s
+webNotifications.alwaysShow=Always Show Notifications
+webNotifications.alwaysShow.accesskey=A
+webNotifications.neverShow=Always Block Notifications
+webNotifications.neverShow.accesskey=N
+webNotifications.showFromSite=Would you like to show notifications from %S?
+# LOCALIZATION NOTE (webNotifications.upgradeTitle): When using native notifications on OS X, the title may be truncated around 32 characters.
+webNotifications.upgradeTitle=Upgraded notifications
+# LOCALIZATION NOTE (webNotifications.upgradeBody): When using native notifications on OS X, the body may be truncated around 100 characters in some views.
+webNotifications.upgradeBody=You can now receive notifications from sites that are not currently loaded. Click to learn more.
+
+# Pointer lock UI
+
+pointerLock.allow2=Hide pointer
+pointerLock.allow2.accesskey=H
+pointerLock.alwaysAllow=Always allow hiding
+pointerLock.alwaysAllow.accesskey=A
+pointerLock.neverAllow=Never allow hiding
+pointerLock.neverAllow.accesskey=N
+pointerLock.title2=Would you like to allow the pointer to be hidden on %S?
+pointerLock.autoLock.title2=%S will hide the pointer.
+
+# Ctrl-Tab
+# LOCALIZATION NOTE (ctrlTab.showAll.label): #1 represents the number
+# of tabs in the current browser window. It will always be 2 at least.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+ctrlTab.showAll.label=;Show all #1 tabs
+
+# LOCALIZATION NOTE (addKeywordTitleAutoFill): %S will be replaced by the page's title
+# Used as the bookmark name when saving a keyword for a search field.
+addKeywordTitleAutoFill=Search %S
+
+extensions.{972ce4c6-7e08-4474-a285-3208198ce6fd}.name=Default
+extensions.{972ce4c6-7e08-4474-a285-3208198ce6fd}.description=The default theme.
+
+# safeModeRestart
+safeModeRestartPromptTitle=Restart in Safe Mode
+safeModeRestartPromptMessage=Are you sure you want to restart in Safe Mode?
+safeModeRestartButton=Restart
+
+# LOCALIZATION NOTE - %S is brandShortName
+# restart
+restartPromptTitle=Restart
+restartPromptMessage=Are you sure you want to restart %S?
+restartButton=Restart
+
+# LOCALIZATION NOTE (browser.menu.showCharacterEncoding): Set to the string
+# "true" (spelled and capitalized exactly that way) to show the "Character
+# Encoding" menu in the main Firefox button on Windows. Any other value will
+# hide it. Regardless of the value of this setting, the "Character Encoding"
+# menu will always be accessible via the "Web Developer" menu.
+# This is not a string to translate; it just controls whether the menu shows
+# up in the Firefox button. If users frequently use the "Character Encoding"
+# menu, set this to "true". Otherwise, you can leave it as "false".
+browser.menu.showCharacterEncoding=false
+
+# Mozilla data reporting notification (Telemetry, Firefox Health Report, etc)
+dataReportingNotification.message = %1$S automatically sends some data to %2$S so that we can improve your experience.
+dataReportingNotification.button.label = Choose What I Share
+dataReportingNotification.button.accessKey = C
+
+# LOCALIZATION NOTE (fullscreen.entered): displayed when we enter HTML5 fullscreen mode, %S is the domain name of the focused website (e.g. mozilla.com).
+fullscreen.entered=%S is now fullscreen.
+
+# LOCALIZATION NOTE (getUserMedia.shareCamera.message, getUserMedia.shareMicrophone.message, getUserMedia.shareCameraAndMicrophone.message): %S is the website origin (e.g. www.mozilla.org)
+# LOCALIZATION NOTE (getUserMedia.shareSelectedDevices.label):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# The number of devices can be either one or two.
+getUserMedia.shareCamera.message = Would you like to share your camera with %S?
+getUserMedia.shareMicrophone.message = Would you like to share your microphone with %S?
+getUserMedia.shareCameraAndMicrophone.message = Would you like to share your camera and microphone with %S?
+getUserMedia.noVideo.label = No Video
+getUserMedia.noAudio.label = No Audio
+getUserMedia.shareSelectedDevices.label = Share Selected Device;Share Selected Devices
+getUserMedia.shareSelectedDevices.accesskey = S
+getUserMedia.denyRequest.label = Don't Share
+getUserMedia.denyRequest.accesskey = D
+getUserMedia.sharingCamera.message2 = You are currently sharing your camera with this page.
+getUserMedia.sharingMicrophone.message2 = You are currently sharing your microphone with this page.
+getUserMedia.sharingCameraAndMicrophone.message2 = You are currently sharing your camera and microphone with this page.
+
+# Mixed Content Blocker Doorhanger Notification
+# LOCALIZATION NOTE - %S is brandShortName
+mixedContentBlocked.message = %S has blocked content that isn't secure.
+mixedContentBlocked.keepBlockingButton.label = Keep Blocking
+mixedContentBlocked.keepBlockingButton.accesskey = B
+mixedContentBlocked.unblock.label = Disable Protection on This Page
+mixedContentBlocked.unblock.accesskey = D
+
+# LOCALIZATION NOTE - %S is brandShortName
+slowStartup.message = %S seems slow… to… start.
+slowStartup.helpButton.label = Learn How to Speed It Up
+slowStartup.helpButton.accesskey = L
+slowStartup.disableNotificationButton.label = Don't Tell Me Again
+slowStartup.disableNotificationButton.accesskey = A
+
+muteTab.label = Mute Tab
+muteTab.accesskey = M
+unmuteTab.label = Unmute Tab
+unmuteTab.accesskey = M \ No newline at end of file
diff --git a/locales/en-US/chrome/browser/charsetMenu.dtd b/locales/en-US/chrome/browser/charsetMenu.dtd
new file mode 100644
index 0000000..5600f9b
--- /dev/null
+++ b/locales/en-US/chrome/browser/charsetMenu.dtd
@@ -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/. -->
+
+<!ENTITY charsetMenu.label "Character Encoding">
+<!ENTITY charsetMenu.accesskey "C">
+<!ENTITY charsetMenuAutodet.label "Auto-Detect">
+<!ENTITY charsetMenuAutodet.accesskey "D"><!-- A reserved for Arabic -->
+
+<!ENTITY charsetMenuAutodet.off.label "(off)">
+<!ENTITY charsetMenuAutodet.off.accesskey "o">
+<!ENTITY charsetMenuAutodet.ja.label "Japanese">
+<!ENTITY charsetMenuAutodet.ja.accesskey "J">
+<!ENTITY charsetMenuAutodet.ru.label "Russian">
+<!ENTITY charsetMenuAutodet.ru.accesskey "R">
+<!ENTITY charsetMenuAutodet.uk.label "Ukrainian">
+<!ENTITY charsetMenuAutodet.uk.accesskey "U">
+
diff --git a/locales/en-US/chrome/browser/charsetMenu.properties b/locales/en-US/chrome/browser/charsetMenu.properties
new file mode 100644
index 0000000..2ea92a6
--- /dev/null
+++ b/locales/en-US/chrome/browser/charsetMenu.properties
@@ -0,0 +1,103 @@
+# 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/.
+
+# LOCALIZATION NOTE: The property keys ending with ".key" are for access keys.
+# Localizations may add or delete properties where the property key ends with
+# ".key" as appropriate for the localization. The code that uses this data can
+# deal with the absence of an access key for an item.
+#
+# Make sure the keys defined here don't collide with
+# charsetMenuAutodet.accesskey in charsetMenu.dtd.
+#
+# In the en-US version of this file, access keys are given to the following:
+# * UTF-8
+# * All encodings that are the fallback encoding for some locale in Firefox
+# * All encodings that are the fallback encoding for some locale in IE
+# * All Japanese encodings
+#
+# For the items whose property key does not end in ".key" and whose value
+# includes "(" U+0028 LEFT PARENTHESIS, the "(" character is significant for
+# processing by CharsetMenu.jsm. If your localization does not use ASCII
+# parentheses where en-US does in this file, please file a bug to make
+# CharsetMenu.jsm also recognize the delimiter your localization uses.
+# (When this code was developed, all localizations appeared to use
+# U+0028 LEFT PARENTHESIS for this purpose.)
+
+# Globally-relevant
+UTF-8.key = U
+UTF-8 = Unicode
+windows-1252.key = W
+windows-1252 = Western
+
+# Arabic
+windows-1256.key = A
+windows-1256 = Arabic (Windows)
+ISO-8859-6 = Arabic (ISO)
+
+# Baltic
+windows-1257.key = B
+windows-1257 = Baltic (Windows)
+ISO-8859-4 = Baltic (ISO)
+
+# Central European
+windows-1250.key = E
+windows-1250 = Central European (Windows)
+ISO-8859-2.key = l
+ISO-8859-2 = Central European (ISO)
+
+# Chinese, Simplified
+gbk.key = S
+gbk = Chinese, Simplified (GBK)
+gb18030 = Chinese, Simplified (GB18030)
+
+# Chinese, Traditional
+Big5.key = T
+Big5 = Chinese, Traditional
+
+# Cyrillic
+windows-1251.key = C
+windows-1251 = Cyrillic (Windows)
+ISO-8859-5 = Cyrillic (ISO)
+KOI8-R = Cyrillic (KOI8-R)
+KOI8-U = Cyrillic (KOI8-U)
+IBM866 = Cyrillic (DOS)
+
+# Greek
+windows-1253.key = G
+windows-1253 = Greek (Windows)
+ISO-8859-7.key = O
+ISO-8859-7 = Greek (ISO)
+
+# Hebrew
+windows-1255.key = H
+windows-1255 = Hebrew
+# LOCALIZATION NOTE (ISO-8859-8): The value for this item should begin with
+# the same word for Hebrew as the value for windows-1255 so that this item
+# sorts right after that one in the collation order for your locale.
+ISO-8859-8 = Hebrew, Visual
+
+# Japanese
+Shift_JIS.key = J
+Shift_JIS = Japanese (Shift_JIS)
+EUC-JP.key = p
+EUC-JP = Japanese (EUC-JP)
+ISO-2022-JP.key = n
+ISO-2022-JP = Japanese (ISO-2022-JP)
+
+# Korean
+EUC-KR.key = K
+EUC-KR = Korean
+
+# Thai
+windows-874.key = i
+windows-874 = Thai
+
+# Turkish
+windows-1254.key = r
+windows-1254 = Turkish
+
+# Vietnamese
+windows-1258.key = V
+windows-1258 = Vietnamese
+
diff --git a/locales/en-US/chrome/browser/charsetOverlay.dtd b/locales/en-US/chrome/browser/charsetOverlay.dtd
new file mode 100644
index 0000000..11ee98b
--- /dev/null
+++ b/locales/en-US/chrome/browser/charsetOverlay.dtd
@@ -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/. -->
+
+<!-- extracted from charsetOverlay.xul -->
+<!ENTITY charsetMenu.label "Character Encoding">
+<!ENTITY charsetMenu.accesskey "C">
+<!ENTITY charsetMenuAutodet.label "Auto-Detect">
+<!ENTITY charsetMenuAutodet.accesskey "a">
+<!ENTITY charsetMenuMore.label "More Encodings">
+<!ENTITY charsetMenuMore.accesskey "m">
+<!ENTITY charsetMenuMore1.label "West European">
+<!ENTITY charsetMenuMore1.accesskey "w">
+<!ENTITY charsetMenuMore2.label "East European">
+<!ENTITY charsetMenuMore2.accesskey "E">
+<!ENTITY charsetMenuMore3.label "East Asian">
+<!ENTITY charsetMenuMore3.accesskey "A">
+<!ENTITY charsetMenuMore4.label "SE &amp; SW Asian">
+<!ENTITY charsetMenuMore4.accesskey "S">
+<!ENTITY charsetMenuMore5.label "Middle Eastern">
+<!ENTITY charsetMenuMore5.accesskey "M">
+<!ENTITY charsetCustomize.label "Customize List…">
+<!ENTITY charsetCustomize.accesskey "c">
diff --git a/locales/en-US/chrome/browser/downloads/downloads.dtd b/locales/en-US/chrome/browser/downloads/downloads.dtd
new file mode 100644
index 0000000..feabcc8
--- /dev/null
+++ b/locales/en-US/chrome/browser/downloads/downloads.dtd
@@ -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/. -->
+
+<!-- LOCALIZATION NOTE (downloads.title):
+ Used by screen readers to describe the Downloads Panel.
+ -->
+<!ENTITY downloads.title "Downloads">
+
+<!-- LOCALIZATION NOTE (downloadDetails.width):
+ Width of details for a Downloads Panel item (which directly influences the
+ width of the Downloads Panel) expressed using a CSS unit. The longest
+ labels that should fit in the item width are usually those of in-progress
+ downloads and those of blocked downloads.
+
+ A good rule of thumb is to try to determine the longest string possible
+ that an in-progress download could display, and use that value in ch
+ units.
+
+ For example, in English, a long string would be:
+
+ 59 minutes, 59 seconds remaining - 1022 of 1023 KB
+
+ That's 50 characters, so we set the width at 50ch.
+ -->
+<!ENTITY downloadDetails.width "50ch">
+
+<!-- LOCALIZATION NOTE (downloadsSummary.minWidth2):
+ Minimum width for the main description of the downloads summary,
+ which is displayed at the bottom of the Downloads Panel if the
+ number of downloads exceeds the limit that the panel can display.
+
+ A good rule of thumb here is to look at the otherDownloads2 string
+ in downloads.properties, and make a reasonable estimate of its
+ maximum length. For English, this seems like a reasonable limit:
+
+ + 999 other downloads
+
+ that's 21 characters, so we set the minimum width to 21ch.
+ -->
+<!ENTITY downloadsSummary.minWidth2 "21ch">
+
+<!ENTITY cmd.pause.label "Pause">
+<!ENTITY cmd.pause.accesskey "P">
+<!ENTITY cmd.resume.label "Resume">
+<!ENTITY cmd.resume.accesskey "R">
+<!ENTITY cmd.cancel.label "Cancel">
+<!ENTITY cmd.cancel.accesskey "C">
+<!-- LOCALIZATION NOTE (cmd.show.label, cmd.show.accesskey, cmd.showMac.label,
+ cmd.showMac.accesskey):
+ The show and showMac commands are never shown together, thus they can share
+ the same access key (though the two access keys can also be different).
+ -->
+<!ENTITY cmd.show.label "Open Containing Folder">
+<!ENTITY cmd.show.accesskey "F">
+<!ENTITY cmd.showMac.label "Show In Finder">
+<!ENTITY cmd.showMac.accesskey "F">
+<!ENTITY cmd.retry.label "Retry">
+<!ENTITY cmd.goToDownloadPage.label "Go To Download Page">
+<!ENTITY cmd.goToDownloadPage.accesskey "G">
+<!ENTITY cmd.copyDownloadLink.label "Copy Download Link">
+<!ENTITY cmd.copyDownloadLink.accesskey "L">
+<!ENTITY cmd.removeFromHistory.label "Remove From History">
+<!ENTITY cmd.removeFromHistory.accesskey "e">
+<!ENTITY cmd.clearList.label "Clear List">
+<!ENTITY cmd.clearList.accesskey "a">
+<!ENTITY cmd.clearDownloads.label "Clear Downloads">
+<!ENTITY cmd.clearDownloads.accesskey "D">
+
+<!-- LOCALIZATION NOTE (downloadsHistory.label, downloadsHistory.accesskey):
+ This string is shown at the bottom of the Downloads Panel when all the
+ downloads fit in the available space, or when there are no downloads in
+ the panel at all.
+ -->
+<!ENTITY downloadsHistory.label "Show All Downloads">
+<!ENTITY downloadsHistory.accesskey "S">
+
+<!ENTITY clearDownloadsButton.label "Clear Downloads">
+<!ENTITY clearDownloadsButton.tooltip "Clears completed, canceled and failed downloads">
+
+<!-- LOCALIZATION NOTE (downloadsListEmpty.label):
+ This string is shown when there are no items in the Downloads view, when it
+ is displayed inside a browser tab.
+ -->
+<!ENTITY downloadsListEmpty.label "There are no downloads.">
+
+<!-- LOCALIZATION NOTE (downloadsPanelEmpty.label):
+ This string is shown when there are no items in the Downloads Panel.
+ -->
+<!ENTITY downloadsPanelEmpty.label "No downloads for this session.">
+
+<!-- LOCALIZATION NOTE (downloadsListNoMatch.label):
+ This string is shown when some search terms are specified, but there are no
+ results in the Downloads view.
+ -->
+<!ENTITY downloadsListNoMatch.label "Could not find any matching downloads.">
diff --git a/locales/en-US/chrome/browser/downloads/downloads.properties b/locales/en-US/chrome/browser/downloads/downloads.properties
new file mode 100644
index 0000000..44d9ec9
--- /dev/null
+++ b/locales/en-US/chrome/browser/downloads/downloads.properties
@@ -0,0 +1,78 @@
+# 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/.
+
+# LOCALIZATION NOTE (stateStarting):
+# Indicates that the download is starting.
+stateStarting=Starting…
+# LOCALIZATION NOTE (stateScanning):
+# Indicates that an external program is scanning the download for viruses.
+stateScanning=Scanning for viruses…
+# LOCALIZATION NOTE (stateFailed):
+# Indicates that the download failed because of an error.
+stateFailed=Failed
+# LOCALIZATION NOTE (statePaused):
+# Indicates that the download was paused by the user.
+statePaused=Paused
+# LOCALIZATION NOTE (stateCanceled):
+# Indicates that the download was canceled by the user.
+stateCanceled=Canceled
+# LOCALIZATION NOTE (stateBlockedParentalControls):
+# Indicates that the download was blocked by the Parental Controls feature of
+# Windows. "Parental Controls" should be consistently named and capitalized
+# with the display of this feature in Windows. The following article can
+# provide a reference for the translation of "Parental Controls" in various
+# languages:
+# http://windows.microsoft.com/en-US/windows-vista/Set-up-Parental-Controls
+stateBlockedParentalControls=Blocked by Parental Controls
+# LOCALIZATION NOTE (stateBlockedPolicy):
+# Indicates that the download was blocked on Windows because of the "Launching
+# applications and unsafe files" setting of the "security zone" associated with
+# the target site. "Security zone" should be consistently named and capitalized
+# with the display of this feature in Windows. The following article can
+# provide a reference for the translation of "security zone" in various
+# languages:
+# http://support.microsoft.com/kb/174360
+stateBlockedPolicy=Blocked by your security zone policy
+# LOCALIZATION NOTE (stateDirty):
+# Indicates that the download was blocked after scanning.
+stateDirty=Blocked: May contain a virus or spyware
+
+# LOCALIZATION NOTE (sizeWithUnits):
+# %1$S is replaced with the size number, and %2$S with the measurement unit.
+sizeWithUnits=%1$S %2$S
+sizeUnknown=Unknown size
+
+# LOCALIZATION NOTE (shortTimeLeftSeconds, shortTimeLeftMinutes,
+# shortTimeLeftHours, shortTimeLeftDays):
+# These values are displayed in the downloads indicator in the main browser
+# window, where space is available for three characters maximum. %1$S is
+# replaced with the time left for the given measurement unit. Even for days,
+# the value is never longer than two digits.
+shortTimeLeftSeconds=%1$Ss
+shortTimeLeftMinutes=%1$Sm
+shortTimeLeftHours=%1$Sh
+shortTimeLeftDays=%1$Sd
+
+# LOCALIZATION NOTE (statusSeparator, statusSeparatorBeforeNumber):
+# These strings define templates for the separation of different elements in the
+# status line of a download item. As a separator, by default we use the Unicode
+# character U+2014 'EM DASH' (long dash). Examples of status lines include
+# "Canceled - 222.net", "1.1 MB - website2.com", or "Paused - 1.1 MB". Note
+# that we use a wider space after the separator when it is followed by a number,
+# just to avoid visually confusing it with with a minus sign with some fonts.
+# If you use a different separator, this might not be necessary. However, there
+# is usually no need to change the separator or the order of the substitutions,
+# even for right-to-left languages, unless the defaults are not suitable.
+statusSeparator=%1$S \u2014 %2$S
+statusSeparatorBeforeNumber=%1$S \u2014 %2$S
+
+fileExecutableSecurityWarning="%S" is an executable file. Executable files may contain viruses or other malicious code that could harm your computer. Use caution when opening this file. Are you sure you want to launch "%S"?
+fileExecutableSecurityWarningTitle=Open Executable File?
+
+# LOCALIZATION NOTE (otherDownloads2):
+# This is displayed in an item at the bottom of the Downloads Panel when
+# there are more downloads than can fit in the list in the panel. Use a
+# semi-colon list of plural forms.
+# See: http://developer.mozilla.org/en/Localization_and_Plurals
+otherDownloads2=+ %1$S other download; + %1$S other downloads
diff --git a/locales/en-US/chrome/browser/engineManager.dtd b/locales/en-US/chrome/browser/engineManager.dtd
new file mode 100644
index 0000000..8ad9772
--- /dev/null
+++ b/locales/en-US/chrome/browser/engineManager.dtd
@@ -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/. -->
+
+<!ENTITY engineManager.title "Manage Search Engine List">
+<!ENTITY engineManager.style "min-width: 35em;">
+<!ENTITY engineManager.intro "You have the following search engines installed:">
+
+<!ENTITY columnLabel.name "Name">
+<!ENTITY columnLabel.keyword "Keyword">
+
+<!-- Buttons -->
+<!ENTITY up.label "Move Up">
+<!ENTITY up.accesskey "U">
+<!ENTITY dn.label "Move Down">
+<!ENTITY dn.accesskey "D">
+<!ENTITY remove.label "Remove">
+<!ENTITY remove.accesskey "R">
+<!ENTITY edit.label "Edit Keyword…">
+<!ENTITY edit.accesskey "t">
+
+<!ENTITY addEngine.label "Get more search engines…">
+<!ENTITY addEngine.accesskey "A">
+
+<!ENTITY enableSuggest.label "Show search suggestions">
+<!ENTITY enableSuggest.accesskey "S">
+
+<!ENTITY restoreDefaults.label "Restore Defaults">
+<!ENTITY restoreDefaults.accesskey "e">
diff --git a/locales/en-US/chrome/browser/engineManager.properties b/locales/en-US/chrome/browser/engineManager.properties
new file mode 100644
index 0000000..040a4ca
--- /dev/null
+++ b/locales/en-US/chrome/browser/engineManager.properties
@@ -0,0 +1,9 @@
+# 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/.
+
+editTitle=Edit Keyword
+editMsg=Enter a new keyword for "%S":
+duplicateTitle=Duplicate Keyword
+duplicateEngineMsg=You have chosen a keyword that is currently in use by "%S". Please select another.
+duplicateBookmarkMsg=You have chosen a keyword that is currently in use by a bookmark. Please select another.
diff --git a/locales/en-US/chrome/browser/feeds/subscribe.dtd b/locales/en-US/chrome/browser/feeds/subscribe.dtd
new file mode 100644
index 0000000..9e12419
--- /dev/null
+++ b/locales/en-US/chrome/browser/feeds/subscribe.dtd
@@ -0,0 +1,10 @@
+<!-- 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/. -->
+
+<!ENTITY feedPage.title
+ "Viewing Feed">
+<!ENTITY feedSubscribeNow
+ "Subscribe Now">
+<!ENTITY feedLiveBookmarks
+ "Live Bookmarks">
diff --git a/locales/en-US/chrome/browser/feeds/subscribe.properties b/locales/en-US/chrome/browser/feeds/subscribe.properties
new file mode 100644
index 0000000..27cf505
--- /dev/null
+++ b/locales/en-US/chrome/browser/feeds/subscribe.properties
@@ -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/.
+
+linkTitleTextFormat=Go to %S
+addHandler=Add "%S" (%S) as a Feed Reader?
+addHandlerAddButton=Add Feed Reader
+addHandlerAddButtonAccesskey=A
+handlerRegistered="%S" is already registered as a Feed Reader
+liveBookmarks=Live Bookmarks
+subscribeNow=Subscribe Now
+chooseApplicationMenuItem=Choose Application…
+chooseApplicationDialogTitle=Choose Application
+alwaysUse=Always use %S to subscribe to feeds
+mediaLabel=Media files
+
+# LOCALIZATION NOTE: The next string is for the size of the enclosed media.
+# e.g. enclosureSizeText : "50.23 MB"
+# %1$S = size (in bytes or megabytes, ...)
+# %2$S = unit of measure (bytes, KB, MB, ...)
+enclosureSizeText=%1$S %2$S
+
+bytes=bytes
+kilobyte=KB
+megabyte=MB
+gigabyte=GB
+
+# LOCALIZATION NOTE: The next three strings explains to the user what they're
+# doing.
+# e.g. alwaysUseForVideoPodcasts : "Always use Miro to subscribe to video podcasts."
+# %S = application to use (Miro, iTunes, ...)
+alwaysUseForFeeds=Always use %S to subscribe to feeds.
+alwaysUseForAudioPodcasts=Always use %S to subscribe to podcasts.
+alwaysUseForVideoPodcasts=Always use %S to subscribe to video podcasts.
+
+subscribeFeedUsing=Subscribe to this feed using
+subscribeAudioPodcastUsing=Subscribe to this podcast using
+subscribeVideoPodcastUsing=Subscribe to this video podcast using
+
+feedSubscriptionFeed1=This is a "feed" of frequently changing content on this site.
+feedSubscriptionAudioPodcast1=This is a "podcast" of frequently changing content on this site.
+feedSubscriptionVideoPodcast1=This is a "video podcast" of frequently changing content on this site.
+
+feedSubscriptionFeed2=You can subscribe to this feed to receive updates when this content changes.
+feedSubscriptionAudioPodcast2=You can subscribe to this podcast to receive updates when this content changes.
+feedSubscriptionVideoPodcast2=You can subscribe to this video podcast to receive updates when this content changes.
+
+# Protocol Handling
+# "Add %appName (%appDomain) as an application for %protocolType links?"
+addProtocolHandler=Add %S (%S) as an application for %S links?
+addProtocolHandlerAddButton=Add Application
+# "%appName has already been added as an application for %protocolType links."
+protocolHandlerRegistered=%S has already been added as an application for %S links.
diff --git a/locales/en-US/chrome/browser/newTab.dtd b/locales/en-US/chrome/browser/newTab.dtd
new file mode 100644
index 0000000..5853557
--- /dev/null
+++ b/locales/en-US/chrome/browser/newTab.dtd
@@ -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/. -->
+
+<!-- These strings are used in the about:newtab page -->
+<!ENTITY newtab.pageTitle "Quickdial">
+<!ENTITY newtab.undo.removedLabel "Thumbnail removed.">
+<!ENTITY newtab.undo.undoButton "Undo.">
+<!ENTITY newtab.undo.restoreButton "Restore All.">
+<!ENTITY newtab.undo.closeTooltip "Hide">
+<!ENTITY newtab.searchEngineButton.label "Search">
diff --git a/locales/en-US/chrome/browser/newTab.properties b/locales/en-US/chrome/browser/newTab.properties
new file mode 100644
index 0000000..922aa58
--- /dev/null
+++ b/locales/en-US/chrome/browser/newTab.properties
@@ -0,0 +1,7 @@
+# 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/.
+
+newtab.pin=Pin this site at its current position
+newtab.unpin=Unpin this site
+newtab.block=Remove this site
diff --git a/locales/en-US/chrome/browser/openLocation.dtd b/locales/en-US/chrome/browser/openLocation.dtd
new file mode 100644
index 0000000..2409892
--- /dev/null
+++ b/locales/en-US/chrome/browser/openLocation.dtd
@@ -0,0 +1,14 @@
+<!-- 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/. -->
+
+<!-- extracted from content/openLocation.xul -->
+
+<!ENTITY enter.label "Enter the web location (URL), or specify the local file you would like to open:">
+<!ENTITY chooseFile.label "Choose File…">
+<!ENTITY newTab.label "New Tab">
+<!ENTITY newWindow.label "New Window">
+<!ENTITY topTab.label "Current Tab">
+<!ENTITY caption.label "Open Web Location">
+<!ENTITY openWhere.label "Open in:">
+<!ENTITY openBtn.label "Open">
diff --git a/locales/en-US/chrome/browser/openLocation.properties b/locales/en-US/chrome/browser/openLocation.properties
new file mode 100644
index 0000000..2d08ff8
--- /dev/null
+++ b/locales/en-US/chrome/browser/openLocation.properties
@@ -0,0 +1,5 @@
+# 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/.
+
+chooseFileDialogTitle=Choose File
diff --git a/locales/en-US/chrome/browser/pageInfo.dtd b/locales/en-US/chrome/browser/pageInfo.dtd
new file mode 100644
index 0000000..50382a1
--- /dev/null
+++ b/locales/en-US/chrome/browser/pageInfo.dtd
@@ -0,0 +1,92 @@
+<!-- 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/. -->
+
+<!ENTITY pageInfoWindow.width "600">
+<!ENTITY pageInfoWindow.height "500">
+
+<!ENTITY copy.key "C">
+<!ENTITY copy.label "Copy">
+<!ENTITY copy.accesskey "C">
+<!ENTITY selectall.key "A">
+<!ENTITY selectall.label "Select All">
+<!ENTITY selectall.accesskey "A">
+<!ENTITY closeWindow.key "w">
+
+<!ENTITY generalTab "General">
+<!ENTITY generalTab.accesskey "G">
+<!ENTITY generalURL "Address:">
+<!ENTITY generalType "Type:">
+<!ENTITY generalMode "Render Mode:">
+<!ENTITY generalSize "Size:">
+<!ENTITY generalReferrer "Referring URL:">
+<!ENTITY generalSource "Cache Source:">
+<!ENTITY generalModified "Modified:">
+<!ENTITY generalEncoding "Encoding:">
+<!ENTITY generalMetaName "Name">
+<!ENTITY generalMetaContent "Content">
+<!ENTITY generalSecurityDetails "Details">
+<!ENTITY generalSecurityDetails.accesskey "D">
+
+<!ENTITY mediaTab "Media">
+<!ENTITY mediaTab.accesskey "M">
+<!ENTITY mediaLocation "Location:">
+<!ENTITY mediaText "Associated Text:">
+<!ENTITY mediaAltHeader "Alternate Text">
+<!ENTITY mediaAddress "Address">
+<!ENTITY mediaType "Type">
+<!ENTITY mediaSize "Size">
+<!ENTITY mediaCount "Count">
+<!ENTITY mediaDimension "Dimensions:">
+<!ENTITY mediaLongdesc "Long Description:">
+<!ENTITY mediaBlockImage.accesskey "B">
+<!ENTITY mediaSaveAs "Save As…">
+<!ENTITY mediaSaveAs.accesskey "A">
+<!ENTITY mediaSaveAs2.accesskey "e">
+<!ENTITY mediaPreview "Media Preview:">
+
+<!ENTITY feedTab "Feeds">
+<!ENTITY feedTab.accesskey "F">
+<!ENTITY feedSubscribe "Subscribe">
+<!ENTITY feedSubscribe.accesskey "u">
+
+<!ENTITY permTab "Permissions">
+<!ENTITY permTab.accesskey "P">
+<!ENTITY permUseDefault "Use Default">
+<!ENTITY permAskAlways "Always ask">
+<!ENTITY permAllow "Allow">
+<!ENTITY permAllowSession "Allow for Session">
+<!ENTITY permAllowFirstPartyOnly "Allow First Party Only">
+<!ENTITY permBlock "Block">
+<!ENTITY permissionsFor "Permissions for:">
+<!ENTITY permImage "Load Images">
+<!ENTITY permPopup "Open Pop-up Windows">
+<!ENTITY permCookie "Set Cookies">
+<!ENTITY permNotifications "Show Notifications">
+<!ENTITY permInstall "Install Extensions or Themes">
+<!ENTITY permGeo "Share Location">
+<!ENTITY permPlugins "Activate Plugins">
+
+<!ENTITY securityTab "Security">
+<!ENTITY securityTab.accesskey "S">
+<!ENTITY securityHeader "Security information for this page">
+<!ENTITY securityView.certView "View Certificate">
+<!ENTITY securityView.accesskey "V">
+<!ENTITY securityView.unknown "Unknown">
+
+
+<!ENTITY securityView.identity.header "Website Identity">
+<!ENTITY securityView.identity.owner "Owner:">
+<!ENTITY securityView.identity.domain "Website:">
+<!ENTITY securityView.identity.verifier "Verified by:">
+
+<!ENTITY securityView.privacy.header "Privacy &amp; History">
+<!ENTITY securityView.privacy.history "Have I visited this website prior to today?">
+<!ENTITY securityView.privacy.cookies "Is this website storing information (cookies) on my computer?">
+<!ENTITY securityView.privacy.viewCookies "View Cookies">
+<!ENTITY securityView.privacy.viewCookies.accessKey "k">
+<!ENTITY securityView.privacy.passwords "Have I saved any passwords for this website?">
+<!ENTITY securityView.privacy.viewPasswords "View Saved Passwords">
+<!ENTITY securityView.privacy.viewPasswords.accessKey "w">
+
+<!ENTITY securityView.technical.header "Technical Details">
diff --git a/locales/en-US/chrome/browser/pageInfo.properties b/locales/en-US/chrome/browser/pageInfo.properties
new file mode 100644
index 0000000..a99741e
--- /dev/null
+++ b/locales/en-US/chrome/browser/pageInfo.properties
@@ -0,0 +1,56 @@
+# 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/.
+
+pageInfo.page.title=Page Info - %S
+pageInfo.frame.title=Frame Info - %S
+
+noPageTitle=Untitled Page:
+pageTitle=%S:
+unknown=Unknown
+notset=Not specified
+yes=Yes
+no=No
+
+mediaImg=Image
+mediaVideo=Video
+mediaAudio=Audio
+mediaBGImg=Background
+mediaBorderImg=Border
+mediaListImg=Bullet
+mediaCursor=Cursor
+mediaObject=Object
+mediaEmbed=Embed
+mediaLink=Icon
+mediaInput=Input
+mediaFileSize=%S KB
+mediaSize=%Spx \u00D7 %Spx
+mediaSelectFolder=Select a Folder to Save the Images
+mediaBlockImage=Block Images from %S
+mediaUnknownNotCached=Unknown (not cached)
+mediaImageType=%S Image
+mediaAnimatedImageType=%S Image (animated, %S frames)
+mediaDimensions=%Spx \u00D7 %Spx
+mediaDimensionsScaled=%Spx \u00D7 %Spx (scaled to %Spx \u00D7 %Spx)
+
+generalQuirksMode=Quirks mode
+generalStrictMode=Standards compliance mode
+generalSize=%S KB (%S bytes)
+generalMetaTag=Meta (1 tag)
+generalMetaTags=Meta (%S tags)
+generalSiteIdentity=This website is owned by %S\nThis has been verified by %S
+
+feedRss=RSS
+feedAtom=Atom
+feedXML=XML
+
+securityNoOwner=This website does not supply ownership information.
+securityOneVisit=Yes, once
+securityNVisits=Yes, %S times
+
+# LOCALIZATION NOTE: The next string is for the disk usage of the
+# database
+# e.g. indexedDBUsage : "50.23 MB"
+# %1$S = size (in bytes or megabytes, ...)
+# %2$S = unit of measure (bytes, KB, MB, ...)
+indexedDBUsage=This website is using %1$S %2$S
diff --git a/locales/en-US/chrome/browser/palemoon.dtd b/locales/en-US/chrome/browser/palemoon.dtd
new file mode 100644
index 0000000..4b4fac9
--- /dev/null
+++ b/locales/en-US/chrome/browser/palemoon.dtd
@@ -0,0 +1,15 @@
+<!-- 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/. -->
+
+<!ENTITY chronicles.title.66.1
+'The Chronicles of the Pale Moon, 66:1'>
+
+<!ENTITY chronicles.quote.66.1
+'The <em>landscape changed</em> as time went on: flowing, twisting, corrupting. The dull sheen of <em>tainted metal</em> shining through everywhere.<br/>
+In the trees, roots, animals, and even the <em>mountainous valleys</em> that had always been an <em>oasis of difference</em>.<br/>
+Still, our dragon continued, untainted and resolute, soaring above.<br/>
+There would be a home yet, <em>a sanctuary</em>, a place for all those not given in to this <em>singular</em> invading force that was <em>misshaping</em> the world.'>
+
+<!ENTITY chronicles.from.66.1
+'from <strong>The Chronicles of the Pale Moon,</strong> 66:1'>
diff --git a/locales/en-US/chrome/browser/permissions/aboutPermissions.dtd b/locales/en-US/chrome/browser/permissions/aboutPermissions.dtd
new file mode 100644
index 0000000..5b220a7
--- /dev/null
+++ b/locales/en-US/chrome/browser/permissions/aboutPermissions.dtd
@@ -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/. -->
+
+<!ENTITY permissionsManager.title "Permissions Manager">
+
+<!ENTITY sites.search "Search Sites">
+<!ENTITY sites.allSites "All Sites">
+
+<!-- LOCALIZATION NOTE (permissions.header.start, permissions.header.end): These strings
+ surround the host name of the site to make the header for the permissions page.
+ example: "Permissions for mozilla.org" -->
+<!ENTITY header.site.start "Permissions for">
+<!ENTITY header.site.end "">
+
+<!ENTITY header.defaults "Default Permissions for All Sites">
+
+<!ENTITY permissions.sitesReload "Reload list of sites">
+
+<!ENTITY permissions.forgetSite "Forget About This Site">
+
+<!ENTITY permission.default "Use Default">
+
+<!ENTITY permission.alwaysAsk "Always Ask">
+<!ENTITY permission.allow "Allow">
+<!ENTITY permission.allowForSession "Allow for Session">
+<!ENTITY permission.allowFirstPartyOnly "Allow First Party Only">
+<!ENTITY permission.block "Block">
+
+<!ENTITY password.label "Store Passwords">
+<!ENTITY password.manage "Manage Passwords…">
+
+<!ENTITY image.label "Load Images">
+
+<!ENTITY cookie.label "Store Cookies and Site Data">
+<!ENTITY cookie.remove "Remove Cookies">
+<!ENTITY cookie.manage "Manage Cookies…">
+<!ENTITY cookie.removeAll "Remove All Cookies">
+
+<!ENTITY desktop-notification.label "Show Notifications">
+
+<!ENTITY install.label "Install Extensions or Themes">
+
+<!ENTITY geo.label "Share Location">
+
+<!ENTITY plugins.label "Plugins">
+
+<!ENTITY popup.label "Open Pop-up Windows">
+
+<!ENTITY focusSearch.key "f">
diff --git a/locales/en-US/chrome/browser/permissions/aboutPermissions.properties b/locales/en-US/chrome/browser/permissions/aboutPermissions.properties
new file mode 100644
index 0000000..ca90a6c
--- /dev/null
+++ b/locales/en-US/chrome/browser/permissions/aboutPermissions.properties
@@ -0,0 +1,14 @@
+# 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/.
+
+# LOCALIZATION NOTE (visitCount): Semicolon-separated list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is the number of history visits for a site
+visitCount=#1 visit;#1 visits
+
+# LOCALIZATION NOTE (passwordsCount, cookiesCount): Semicolon-separated list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+passwordsCount=#1 password is stored for this website.;#1 passwords are stored for this website.
+cookiesCount=#1 cookie is set for this website.;#1 cookies are set for this website.
+pluginBlocklisted=This plugin poses a security risk and cannot be set to Allow for all sites.
diff --git a/locales/en-US/chrome/browser/places/bookmarkProperties.properties b/locales/en-US/chrome/browser/places/bookmarkProperties.properties
new file mode 100644
index 0000000..9bcbe78
--- /dev/null
+++ b/locales/en-US/chrome/browser/places/bookmarkProperties.properties
@@ -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/.
+
+dialogAcceptLabelAddItem=Add
+dialogAcceptLabelSaveItem=Save
+dialogAcceptLabelAddLivemark=Subscribe
+dialogAcceptLabelAddMulti=Add Bookmarks
+dialogAcceptLabelEdit=Save
+dialogTitleAddBookmark=New Bookmark
+dialogTitleAddLivemark=Subscribe with Live Bookmark
+dialogTitleAddFolder=New Folder
+dialogTitleAddMulti=New Bookmarks
+dialogTitleEdit=Properties for "%S"
+
+bookmarkAllTabsDefault=[Folder Name]
+newFolderDefault=New Folder
+newBookmarkDefault=New Bookmark
+newLivemarkDefault=New Live Bookmark
diff --git a/locales/en-US/chrome/browser/places/editBookmarkOverlay.dtd b/locales/en-US/chrome/browser/places/editBookmarkOverlay.dtd
new file mode 100644
index 0000000..d78c355
--- /dev/null
+++ b/locales/en-US/chrome/browser/places/editBookmarkOverlay.dtd
@@ -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/. -->
+
+<!ENTITY editBookmarkOverlay.name.label "Name:">
+<!ENTITY editBookmarkOverlay.name.accesskey "N">
+<!ENTITY editBookmarkOverlay.location.label "Location:">
+<!ENTITY editBookmarkOverlay.location.accesskey "L">
+<!ENTITY editBookmarkOverlay.feedLocation.label "Feed Location:">
+<!ENTITY editBookmarkOverlay.feedLocation.accesskey "F">
+<!ENTITY editBookmarkOverlay.siteLocation.label "Site Location:">
+<!ENTITY editBookmarkOverlay.siteLocation.accesskey "S">
+<!ENTITY editBookmarkOverlay.folder.label "Folder:">
+<!ENTITY editBookmarkOverlay.foldersExpanderDown.tooltip "Show all the bookmarks folders">
+<!ENTITY editBookmarkOverlay.expanderUp.tooltip "Hide">
+<!ENTITY editBookmarkOverlay.tags.label "Tags:">
+<!ENTITY editBookmarkOverlay.tags.accesskey "T">
+<!ENTITY editBookmarkOverlay.tagsEmptyDesc.label "Separate tags with commas">
+<!ENTITY editBookmarkOverlay.description.label "Description:">
+<!ENTITY editBookmarkOverlay.description.accesskey "D">
+<!ENTITY editBookmarkOverlay.keyword.label "Keyword:">
+<!ENTITY editBookmarkOverlay.keyword.accesskey "K">
+<!ENTITY editBookmarkOverlay.tagsExpanderDown.tooltip "Show all tags">
+<!ENTITY editBookmarkOverlay.loadInSidebar.label "Load this bookmark in the sidebar">
+<!ENTITY editBookmarkOverlay.loadInSidebar.accesskey "h">
+<!ENTITY editBookmarkOverlay.choose.label "Choose…">
+<!ENTITY editBookmarkOverlay.newFolderButton.label "New Folder">
+<!ENTITY editBookmarkOverlay.newFolderButton.accesskey "o">
diff --git a/locales/en-US/chrome/browser/places/moveBookmarks.dtd b/locales/en-US/chrome/browser/places/moveBookmarks.dtd
new file mode 100644
index 0000000..9f28b80
--- /dev/null
+++ b/locales/en-US/chrome/browser/places/moveBookmarks.dtd
@@ -0,0 +1,9 @@
+<!-- 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/. -->
+
+<!ENTITY window.title "Choose Folder">
+<!ENTITY window.style "width: 36em; height: 18em;">
+<!ENTITY moveTo.label "Move to:">
+<!ENTITY newFolderButton.label "New Folder">
+<!ENTITY newFolderButton.accesskey "N">
diff --git a/locales/en-US/chrome/browser/places/places.dtd b/locales/en-US/chrome/browser/places/places.dtd
new file mode 100644
index 0000000..9578754
--- /dev/null
+++ b/locales/en-US/chrome/browser/places/places.dtd
@@ -0,0 +1,140 @@
+<!-- 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/. -->
+
+<!-- LOCALIZATION NOTE (places.library.title): use "Library", "Archive" or "Repository" -->
+<!ENTITY places.library.title "Library">
+<!ENTITY places.library.width "700">
+<!ENTITY places.library.height "500">
+<!ENTITY organize.label "Organize">
+<!ENTITY organize.accesskey "O">
+<!ENTITY organize.tooltip "Organize your bookmarks">
+
+<!ENTITY file.close.label "Close">
+<!ENTITY file.close.accesskey "C">
+<!ENTITY cmd.close.key "w">
+<!ENTITY views.label "Views">
+<!ENTITY views.accesskey "V">
+<!ENTITY views.tooltip "Change your view">
+<!ENTITY view.columns.label "Show Columns">
+<!ENTITY view.columns.accesskey "C">
+<!ENTITY view.sort.label "Sort">
+<!ENTITY view.sort.accesskey "S">
+<!ENTITY view.unsorted.label "Unsorted">
+<!ENTITY view.unsorted.accesskey "U">
+<!ENTITY view.sortAscending.label "A > Z Sort Order">
+<!ENTITY view.sortAscending.accesskey "A">
+<!ENTITY view.sortDescending.label "Z > A Sort Order">
+<!ENTITY view.sortDescending.accesskey "Z">
+
+<!ENTITY importBookmarksFromHTML.label "Import Bookmarks from HTML…">
+<!ENTITY importBookmarksFromHTML.accesskey "I">
+<!ENTITY exportBookmarksToHTML.label "Export Bookmarks to HTML…">
+<!ENTITY exportBookmarksToHTML.accesskey "E">
+
+<!ENTITY cmd.backup.label "Backup…">
+<!ENTITY cmd.backup.accesskey "B">
+<!ENTITY cmd.restore2.label "Restore">
+<!ENTITY cmd.restore2.accesskey "R">
+<!ENTITY cmd.restoreFromFile.label "Choose File…">
+<!ENTITY cmd.restoreFromFile.accesskey "C">
+
+<!ENTITY cmd.bookmarkLink.label "Bookmark This Page…">
+<!ENTITY cmd.bookmarkLink.accesskey "B">
+<!ENTITY cmd.delete.label "Delete This Page">
+<!ENTITY cmd.delete.accesskey "D">
+<!ENTITY cmd.deleteDomainData.label "Forget About This Site">
+<!ENTITY cmd.deleteDomainData.accesskey "F">
+
+<!ENTITY cmd.open.label "Open">
+<!ENTITY cmd.open.accesskey "O">
+<!ENTITY cmd.open_window.label "Open in a New Window">
+<!ENTITY cmd.open_window.accesskey "N">
+<!ENTITY cmd.open_private_window.label "Open in a New Private Window">
+<!ENTITY cmd.open_private_window.accesskey "P">
+<!ENTITY cmd.open_tab.label "Open in a New Tab">
+<!ENTITY cmd.open_tab.accesskey "w">
+<!ENTITY cmd.open_all_in_tabs.label "Open All in Tabs">
+<!ENTITY cmd.open_all_in_tabs.accesskey "O">
+
+<!ENTITY cmd.openParentFolder.label "Open Containing Folder">
+<!ENTITY cmd.openParentFolder.accesskey "F">
+
+<!ENTITY cmd.properties.label "Properties">
+<!ENTITY cmd.properties.accesskey "i">
+
+<!ENTITY cmd.sortby_name.label "Sort By Name">
+<!ENTITY cmd.sortby_name.accesskey "S">
+<!ENTITY cmd.context_sortby_name.accesskey "r">
+
+<!ENTITY cmd.new_bookmark.label "New Bookmark…">
+<!ENTITY cmd.new_bookmark.accesskey "B">
+<!ENTITY cmd.new_folder.label "New Folder…">
+<!ENTITY cmd.new_folder.accesskey "o">
+<!ENTITY cmd.context_new_folder.accesskey "F">
+<!ENTITY cmd.new_separator.label "New Separator">
+<!ENTITY cmd.new_separator.accesskey "S">
+
+<!ENTITY cmd.reloadLivebookmark.label "Reload Live Bookmark">
+<!ENTITY cmd.reloadLivebookmark.accesskey "R">
+
+<!ENTITY cmd.moveBookmarks.label "Move…">
+<!ENTITY cmd.moveBookmarks.accesskey "M">
+
+<!ENTITY col.name.label "Name">
+<!ENTITY col.tags.label "Tags">
+<!ENTITY col.url.label "Location">
+<!ENTITY col.lastvisit.label "Visit Date">
+<!ENTITY col.visitcount.label "Visit Count">
+<!ENTITY col.keyword.label "Keyword">
+<!ENTITY col.description.label "Description">
+<!ENTITY col.dateadded.label "Added">
+<!ENTITY col.lastmodified.label "Last Modified">
+<!ENTITY col.parentfolder.label "Containing Folder">
+<!ENTITY col.parentfolderpath.label "Containing Folder Path">
+
+<!ENTITY search.label "Search:">
+<!ENTITY search.accesskey "S">
+
+<!ENTITY search.in.label "Search in:">
+<!ENTITY search.scopeFolder.label "Selected Folder">
+<!ENTITY search.scopeFolder.accesskey "r">
+<!ENTITY search.scopeBookmarks.label "Bookmarks">
+<!ENTITY search.scopeBookmarks.accesskey "k">
+<!ENTITY search.scopeDownloads.label "Downloads">
+<!ENTITY search.scopeDownloads.accesskey "D">
+<!ENTITY search.scopeHistory.label "History">
+<!ENTITY search.scopeHistory.accesskey "H">
+<!ENTITY saveSearch.label "Save">
+<!ENTITY saveSearch.accesskey "S">
+
+<!ENTITY cmd.find.key "f">
+
+<!ENTITY maintenance.label "Import and Backup">
+<!ENTITY maintenance.accesskey "I">
+<!ENTITY maintenance.tooltip "Import and backup your bookmarks">
+
+<!ENTITY backButton.tooltip "Go back">
+
+<!ENTITY forwardButton.tooltip "Go forward">
+
+<!ENTITY detailsPane.more.label "More">
+<!ENTITY detailsPane.more.accesskey "e">
+<!ENTITY detailsPane.less.label "Less">
+<!ENTITY detailsPane.less.accesskey "e">
+<!ENTITY detailsPane.selectAnItemText.description "Select an item to view and edit its properties">
+
+<!ENTITY find.label "Search:">
+<!ENTITY find.accesskey "S">
+<!ENTITY view.label "View">
+<!ENTITY view.accesskey "w">
+<!ENTITY byDate.label "By Date">
+<!ENTITY byDate.accesskey "D">
+<!ENTITY bySite.label "By Site">
+<!ENTITY bySite.accesskey "S">
+<!ENTITY byMostVisited.label "By Most Visited">
+<!ENTITY byMostVisited.accesskey "V">
+<!ENTITY byLastVisited.label "By Last Visited">
+<!ENTITY byLastVisited.accesskey "L">
+<!ENTITY byDayAndSite.label "By Date and Site">
+<!ENTITY byDayAndSite.accesskey "t">
diff --git a/locales/en-US/chrome/browser/places/places.properties b/locales/en-US/chrome/browser/places/places.properties
new file mode 100644
index 0000000..5b16ccb
--- /dev/null
+++ b/locales/en-US/chrome/browser/places/places.properties
@@ -0,0 +1,95 @@
+# 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/.
+
+load-js-data-url-error=For security reasons, javascript or data urls cannot be loaded from the history window or sidebar.
+noTitle=(no title)
+
+bookmarksMenuEmptyFolder=(Empty)
+
+bookmarksBackupTitle=Bookmarks backup filename
+
+bookmarksRestoreAlertTitle=Revert Bookmarks
+bookmarksRestoreAlert=This will replace all of your current bookmarks with the backup. Are you sure?
+bookmarksRestoreTitle=Select a bookmarks backup
+bookmarksRestoreFilterName=JSON
+
+bookmarksRestoreFormatError=Unsupported file type.
+bookmarksRestoreParseError=Unable to process the backup file.
+
+bookmarksLivemarkLoading=Live Bookmark loading…
+bookmarksLivemarkFailed=Live Bookmark feed failed to load.
+
+menuOpenLivemarkOrigin.label=Open "%S"
+
+sortByName=Sort '%S' by Name
+sortByNameGeneric=Sort by Name
+view.sortBy.name.label=Sort by Name
+view.sortBy.name.accesskey=N
+view.sortBy.url.label=Sort by Location
+view.sortBy.url.accesskey=L
+view.sortBy.date.label=Sort by Visit Date
+view.sortBy.date.accesskey=V
+view.sortBy.visitCount.label=Sort by Visit Count
+view.sortBy.visitCount.accesskey=C
+view.sortBy.keyword.label=Sort by Keyword
+view.sortBy.keyword.accesskey=K
+view.sortBy.description.label=Sort by Description
+view.sortBy.description.accesskey=D
+view.sortBy.dateAdded.label=Sort by Added
+view.sortBy.dateAdded.accesskey=e
+view.sortBy.lastModified.label=Sort by Last Modified
+view.sortBy.lastModified.accesskey=M
+view.sortBy.tags.label=Sort by Tags
+view.sortBy.tags.accesskey=T
+
+searchBookmarks=Search Bookmarks
+searchHistory=Search History
+searchDownloads=Search Downloads
+searchCurrentDefault=Search in '%S'
+
+tabs.openWarningTitle=Confirm open
+tabs.openWarningMultipleBranded=You are about to open %S tabs. This might slow down %S while the pages are loading. Are you sure you want to continue?
+tabs.openButtonMultiple=Open tabs
+tabs.openWarningPromptMeBranded=Warn me when opening multiple tabs might slow down %S
+
+SelectImport=Import Bookmarks File
+EnterExport=Export Bookmarks File
+
+saveSearch.title=Save Search
+saveSearch.inputLabel=Name:
+saveSearch.inputDefaultText=New Search
+
+detailsPane.noItems=No items
+# LOCALIZATION NOTE (detailsPane.itemsCountLabel): Semicolon-separated list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 number of items
+# example: 111 items
+detailsPane.itemsCountLabel=One item;#1 items
+
+mostVisitedTitle=Most Visited
+recentlyBookmarkedTitle=Recently Bookmarked
+recentTagsTitle=Recent Tags
+
+OrganizerQueryHistory=History
+OrganizerQueryDownloads=Downloads
+OrganizerQueryAllBookmarks=All Bookmarks
+OrganizerQueryTags=Tags
+
+# LOCALIZATION NOTE (tagResultLabel) :
+# Noun used to describe the location bar autocomplete result type
+# to users with screen readers
+# See createResultLabel() in urlbarBindings.xml
+tagResultLabel=Tag
+# LOCALIZATION NOTE (bookmarkResultLabel) :
+# Noun used to describe the location bar autocomplete result type
+# to users with screen readers
+# See createResultLabel() in urlbarBindings.xml
+bookmarkResultLabel=Bookmark
+
+# LOCALIZATION NOTE (lockPrompt.text)
+# %S will be replaced with the application name.
+lockPrompt.title=Browser Startup Error
+lockPrompt.text=The bookmarks and history system will not be functional because one of %S's files is in use by another application. Some security software can cause this problem.
+lockPromptInfoButton.label=Learn More
+lockPromptInfoButton.accessKey=L
diff --git a/locales/en-US/chrome/browser/preferences/advanced.dtd b/locales/en-US/chrome/browser/preferences/advanced.dtd
new file mode 100644
index 0000000..bb8dd12
--- /dev/null
+++ b/locales/en-US/chrome/browser/preferences/advanced.dtd
@@ -0,0 +1,151 @@
+<!-- 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/. -->
+
+<!-- Note: each tab panel must contain unique accesskeys -->
+
+<!ENTITY generalTab.label "General">
+
+<!ENTITY accessibility.label "Accessibility">
+
+<!ENTITY useCursorNavigation.label "Always use the cursor keys to navigate within pages">
+<!ENTITY useCursorNavigation.accesskey "c">
+<!ENTITY searchStartTyping.label "Search for text when I start typing">
+<!ENTITY searchStartTyping.accesskey "x">
+<!ENTITY blockAutoRefresh.label "Warn me when websites try to redirect or reload the page">
+<!ENTITY blockAutoRefresh.accesskey "b">
+
+<!ENTITY browsing.label "Browsing">
+
+<!ENTITY useAutoScroll.label "Use autoscrolling">
+<!ENTITY useAutoScroll.accesskey "a">
+<!ENTITY useSmoothScrolling.label "Use smooth scrolling">
+<!ENTITY useSmoothScrolling.accesskey "m">
+<!ENTITY allowHWAccel.label "Use hardware acceleration when available">
+<!ENTITY allowHWAccel.accesskey "r">
+<!ENTITY checkSpelling.label "Check my spelling as I type">
+<!ENTITY checkSpelling.accesskey "t">
+
+<!ENTITY systemDefaults.label "System Defaults">
+<!ENTITY alwaysCheckDefault.label "Always check to see if &brandShortName; is the default browser on startup"><!--XXX-->
+<!ENTITY alwaysCheckDefault.accesskey "w">
+<!ENTITY setDefault.label "Make &brandShortName; the default browser">
+<!ENTITY setDefault.accesskey "d">
+<!ENTITY isDefault.label "&brandShortName; is currently your default browser">
+
+<!ENTITY UACompatGroup.label "Compatibility">
+<!ENTITY UACompat.label "User Agent Mode:">
+<!ENTITY UACompat.Native "Native">
+<!ENTITY UACompat.Gecko "Gecko Compatibility">
+<!ENTITY UACompat.Firefox "Firefox Compatibility">
+
+<!ENTITY captivePortalGroup.label "Captive portals">
+<!ENTITY captivePortalDetect.label "Detect restricted network access">
+
+<!ENTITY dataChoicesTab.label "Data Choices">
+
+<!ENTITY crashReporterSection.label "Crash Reporter">
+<!ENTITY crashReporterDesc.label "&brandShortName; submits crash reports to help &vendorShortName; make your browser more stable and secure">
+<!ENTITY enableCrashReporter.label "Enable Crash Reporter">
+<!ENTITY enableCrashReporter.accesskey "C">
+<!ENTITY crashReporterLearnMore.label "Learn More">
+
+<!ENTITY networkTab.label "Network">
+
+<!ENTITY connection.label "Connection">
+
+<!ENTITY connectionDesc.label "Configure how &brandShortName; connects to the Internet">
+<!ENTITY connectionSettings.label "Settings…">
+<!ENTITY connectionSettings.accesskey "e">
+
+<!ENTITY httpCache.label "Cached Web Content">
+
+<!ENTITY offlineStorage2.label "Offline Web Content and User Data">
+
+<!-- LOCALIZATION NOTE:
+ The entities limitCacheSizeBefore.label and limitCacheSizeAfter.label appear on a single
+ line in preferences as follows:
+
+ &limitCacheSizeBefore.label [textbox for cache size in MB] &limitCacheSizeAfter.label;
+-->
+<!ENTITY limitCacheSizeBefore.label "Limit cache to">
+<!ENTITY limitCacheSizeBefore.accesskey "L">
+<!ENTITY limitCacheSizeAfter.label "MB of space">
+<!ENTITY clearCacheNow.label "Clear Now">
+<!ENTITY clearCacheNow.accesskey "C">
+<!ENTITY clearOfflineAppCacheNow.label "Clear Now">
+<!ENTITY clearOfflineAppCacheNow.accesskey "N">
+<!ENTITY overrideSmartCacheSize.label "Override automatic cache management">
+<!ENTITY overrideSmartCacheSize.accesskey "O">
+
+<!ENTITY updateTab.label "Update">
+
+<!ENTITY updateApp.label "&brandShortName; updates:">
+<!-- Note either updateAuto1 is used or (updateAutoMetro and updateAutoDesktop),
+ so re-using accesss key in updateAuto1 is OK. updateAutoDesktop can be found
+ in preferences.properties -->
+<!ENTITY updateAuto1.label "Automatically install updates">
+<!ENTITY updateAuto1.accesskey "A">
+<!ENTITY updateAutoMetro.label "Automatically update from desktop and Windows 8 style &brandShortName;">
+<!ENTITY updateAutoMetro.accesskey "s">
+<!ENTITY updateCheck.label "Check for updates, but let me choose whether to install them">
+<!ENTITY updateCheck.accesskey "C">
+<!ENTITY updateManual.label "Never check for updates (not recommended)">
+<!ENTITY updateManual.accesskey "N">
+
+<!ENTITY updateAutoAddonWarn.label "Warn me if this will disable any of my add-ons">
+<!ENTITY updateAutoAddonWarn.accesskey "W">
+
+<!ENTITY updateAutoMetroWarn.label "(Windows 8 style &brandShortName; does not check add-on compatibility)">
+
+<!ENTITY updateHistory.label "Show Update History">
+<!ENTITY updateHistory.accesskey "p">
+
+<!ENTITY updateOthers.label "Automatically update:">
+<!ENTITY enableSearchUpdate.label "Search Engines">
+<!ENTITY enableSearchUpdate.accesskey "E">
+
+<!ENTITY offlineAppsPermissions.label "When a website asks to store data for offline use:">
+<!ENTITY offlineAppsPermissions.Allow "Allow by default">
+<!ENTITY offlineAppsPermissions.Ask "Always ask">
+<!ENTITY offlineAppsPermissions.Deny "Deny">
+<!ENTITY offlineNotifyExceptions.label "Exceptions…">
+<!ENTITY offlineNotifyExceptions.accesskey "x">
+
+<!ENTITY offlineAppsList2.label "The following websites are allowed to store data for offline use:">
+<!ENTITY offlineAppsList.height "7em">
+<!ENTITY offlineAppsListRemove.label "Remove…">
+<!ENTITY offlineAppsListRemove.accesskey "R">
+<!ENTITY offlineAppRemove.confirm "Remove offline data">
+
+<!ENTITY certificateTab.label "Certificates">
+<!ENTITY certGroup.label "Personal Certificate">
+<!ENTITY certSelection.description "When a server requests my personal certificate:">
+<!ENTITY certs.auto "Select one automatically">
+<!ENTITY certs.auto.accesskey "l">
+<!ENTITY certs.ask "Ask me every time">
+<!ENTITY certs.ask.accesskey "i">
+<!ENTITY ocspGroup.label "Certificate Validation">
+<!ENTITY enableOCSP.label "Use OCSP to confirm the current validity of certificates">
+<!ENTITY enableOCSP.accesskey "U">
+<!ENTITY requireOCSP.label "When an OCSP server connection fails, treat the certificate as invalid">
+<!ENTITY requireOCSP.accesskey "f">
+<!ENTITY viewCerts.label "View Certificates">
+<!ENTITY viewCerts.accesskey "s">
+<!ENTITY viewSecurityDevices.label "Security Devices">
+<!ENTITY viewSecurityDevices.accesskey "y">
+
+<!ENTITY scrollparamTab.label "Scrolling">
+<!ENTITY smoothscroll.explain.label "The parameters below only have an effect if smooth scrolling is enabled overall with the checkbox above.">
+<!ENTITY smoothscroll.to "to">
+<!ENTITY smoothscroll.params.label "Smooth scrolling parameters">
+<!ENTITY smoothscroll.mousewheel.label "Smooth scroll with mouse wheel">
+<!ENTITY smoothscroll.mousewheel.duration "Mouse wheel scroll duration:">
+<!ENTITY smoothscroll.arrowkeys.label "Smooth scroll with arrow keys">
+<!ENTITY smoothscroll.arrowkeys.duration "Arrow keys scroll duration:">
+<!ENTITY smoothscroll.pagekeys.label "Smooth scroll page up/down">
+<!ENTITY smoothscroll.pagekeys.duration "Page up/down scroll duration:">
+<!ENTITY smoothscroll.scrollbar.label "Smooth scroll with scrollbars">
+<!ENTITY smoothscroll.scrollbar.duration "Scrollbar smooth scroll duration:">
+
+<!ENTITY smoothscroll.overall.yspeed.label "Overall smooth scroll speed:">
diff --git a/locales/en-US/chrome/browser/preferences/applicationManager.dtd b/locales/en-US/chrome/browser/preferences/applicationManager.dtd
new file mode 100644
index 0000000..d2c76e6
--- /dev/null
+++ b/locales/en-US/chrome/browser/preferences/applicationManager.dtd
@@ -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/. -->
+
+<!ENTITY appManager.title "Application details">
+<!ENTITY appManager.style "width: 30em; min-height: 20em;">
+<!ENTITY remove.label "Remove">
+<!ENTITY remove.accesskey "R">
diff --git a/locales/en-US/chrome/browser/preferences/applicationManager.properties b/locales/en-US/chrome/browser/preferences/applicationManager.properties
new file mode 100644
index 0000000..335363a
--- /dev/null
+++ b/locales/en-US/chrome/browser/preferences/applicationManager.properties
@@ -0,0 +1,14 @@
+# 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/.
+
+# LOCALIZATION NOTE
+# in descriptionApplications, %S will be replaced by one of the 3 following strings
+descriptionApplications=The following applications can be used to handle %S.
+
+handleProtocol=%S links
+handleWebFeeds=Web Feeds
+handleFile=%S content
+
+descriptionWebApp=This web application is hosted at:
+descriptionLocalApp=This application is located at:
diff --git a/locales/en-US/chrome/browser/preferences/applications.dtd b/locales/en-US/chrome/browser/preferences/applications.dtd
new file mode 100644
index 0000000..ea89c31
--- /dev/null
+++ b/locales/en-US/chrome/browser/preferences/applications.dtd
@@ -0,0 +1,14 @@
+<!-- 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/. -->
+
+<!ENTITY typeColumn.label "Content Type">
+<!ENTITY typeColumn.accesskey "T">
+
+<!ENTITY actionColumn2.label "Action">
+<!ENTITY actionColumn2.accesskey "A">
+
+<!ENTITY focusSearch1.key "f">
+<!ENTITY focusSearch2.key "k">
+
+<!ENTITY filter.emptytext "Search">
diff --git a/locales/en-US/chrome/browser/preferences/colors.dtd b/locales/en-US/chrome/browser/preferences/colors.dtd
new file mode 100644
index 0000000..6d6e8d8
--- /dev/null
+++ b/locales/en-US/chrome/browser/preferences/colors.dtd
@@ -0,0 +1,30 @@
+<!-- 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/. -->
+
+<!ENTITY colorsDialog.title "Colors">
+<!ENTITY window.width "38em">
+<!ENTITY window.macWidth "41em">
+
+<!ENTITY overridePageColors.label "Override the colors specified by the page with my selections above:">
+<!ENTITY overridePageColors.accesskey "O">
+
+<!ENTITY overridePageColors.always.label "Always">
+<!ENTITY overridePageColors.auto.label "Only with High Contrast themes">
+<!ENTITY overridePageColors.never.label "Never">
+
+<!ENTITY color "Text and Background">
+<!ENTITY textColor.label "Text:">
+<!ENTITY textColor.accesskey "T">
+<!ENTITY backgroundColor.label "Background:">
+<!ENTITY backgroundColor.accesskey "B">
+<!ENTITY useSystemColors.label "Use system colors">
+<!ENTITY useSystemColors.accesskey "s">
+
+<!ENTITY underlineLinks.label "Underline links">
+<!ENTITY underlineLinks.accesskey "U">
+<!ENTITY links "Link Colors">
+<!ENTITY linkColor.label "Unvisited Links:">
+<!ENTITY linkColor.accesskey "l">
+<!ENTITY visitedLinkColor.label "Visited Links:">
+<!ENTITY visitedLinkColor.accesskey "V">
diff --git a/locales/en-US/chrome/browser/preferences/connection.dtd b/locales/en-US/chrome/browser/preferences/connection.dtd
new file mode 100644
index 0000000..f74fbf5
--- /dev/null
+++ b/locales/en-US/chrome/browser/preferences/connection.dtd
@@ -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/. -->
+
+
+<!ENTITY connectionsDialog.title "Connection Settings">
+<!ENTITY window.width "37em">
+<!ENTITY window.macWidth "39em">
+
+<!ENTITY proxyTitle.label "Configure Proxies to Access the Internet">
+<!ENTITY noProxyTypeRadio.label "No proxy">
+<!ENTITY noProxyTypeRadio.accesskey "y">
+<!ENTITY systemTypeRadio.label "Use system proxy settings">
+<!ENTITY systemTypeRadio.accesskey "u">
+<!ENTITY WPADTypeRadio.label "Auto-detect proxy settings for this network">
+<!ENTITY WPADTypeRadio.accesskey "w">
+<!ENTITY manualTypeRadio.label "Manual proxy configuration:">
+<!ENTITY manualTypeRadio.accesskey "m">
+<!ENTITY autoTypeRadio.label "Automatic proxy configuration URL:">
+<!ENTITY autoTypeRadio.accesskey "A">
+<!ENTITY reload.label "Reload">
+<!ENTITY reload.accesskey "e">
+<!ENTITY ftp.label "FTP Proxy:">
+<!ENTITY ftp.accesskey "F">
+<!ENTITY http.label "HTTP Proxy:">
+<!ENTITY http.accesskey "x">
+<!ENTITY ssl.label "SSL Proxy:">
+<!ENTITY ssl.accesskey "L">
+<!ENTITY socks.label "SOCKS Host:">
+<!ENTITY socks.accesskey "C">
+<!ENTITY socks4.label "SOCKS v4">
+<!ENTITY socks4.accesskey "K">
+<!ENTITY socks5.label "SOCKS v5">
+<!ENTITY socks5.accesskey "v">
+<!ENTITY socksRemoteDNS.label "Use proxy to perform DNS queries (SOCKS v5 only)">
+<!ENTITY socksRemoteDNS.accesskey "d">
+<!ENTITY port.label "Port:">
+<!ENTITY HTTPport.accesskey "P">
+<!ENTITY SSLport.accesskey "o">
+<!ENTITY FTPport.accesskey "r">
+<!ENTITY SOCKSport.accesskey "t">
+<!ENTITY noproxy.label "No Proxy for:">
+<!ENTITY noproxy.accesskey "n">
+<!ENTITY noproxyExplain.label "Example: .palemoon.org, .net.nz, 192.168.1.0/24">
+<!ENTITY shareproxy.label "Use this proxy server for all protocols">
+<!ENTITY shareproxy.accesskey "s">
+<!ENTITY autologinproxy.label "Do not prompt for authentication if password is saved">
+<!ENTITY autologinproxy.accesskey "i">
+<!ENTITY autologinproxy.tooltip "This option silently authenticates you to proxies when you have saved credentials for them. You will be prompted if authentication fails.">
diff --git a/locales/en-US/chrome/browser/preferences/content.dtd b/locales/en-US/chrome/browser/preferences/content.dtd
new file mode 100644
index 0000000..91abab0
--- /dev/null
+++ b/locales/en-US/chrome/browser/preferences/content.dtd
@@ -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/. -->
+
+<!ENTITY blockPopups.label "Block pop-up windows">
+<!ENTITY blockPopups.accesskey "B">
+<!ENTITY popupExceptions.label "Exceptions…">
+<!ENTITY popupExceptions.accesskey "E">
+
+<!ENTITY loadImages.label "Load images:">
+<!ENTITY loadImages.always "Automatically">
+<!ENTITY loadImages.never "Never">
+<!ENTITY loadImages.no3rdparty "Originating server only">
+
+<!ENTITY fontsAndColors.label "Fonts &amp; Colors">
+
+<!ENTITY defaultFont.label "Default font:">
+<!ENTITY defaultFont.accesskey "D">
+<!ENTITY defaultSize.label "Size:">
+<!ENTITY defaultSize.accesskey "S">
+
+<!ENTITY advancedFonts.label "Advanced…">
+<!ENTITY advancedFonts.accesskey "A">
+
+<!ENTITY colors.label "Colors…">
+<!ENTITY colors.accesskey "C">
+
+<!ENTITY languages.label "Languages">
+<!ENTITY chooseLanguage.label "Choose your preferred language for displaying pages">
+<!ENTITY chooseButton.label "Choose…">
+<!ENTITY chooseButton.accesskey "o">
+
+<!ENTITY video.label "Video">
+<!ENTITY videoMSE.label "Enable Media Source Extensions (MSE)">
+<!ENTITY videoMSE.accesskey "M">
+<!ENTITY videoMSEMP4.label "Enable MSE for MP4 video">
+<!ENTITY videoMSEMP4.accesskey "4">
+<!ENTITY videoMSEWebM.label "Enable MSE for WebM video">
+<!ENTITY videoMSEWebM.accesskey "W">
+
+
diff --git a/locales/en-US/chrome/browser/preferences/cookies.dtd b/locales/en-US/chrome/browser/preferences/cookies.dtd
new file mode 100644
index 0000000..c833313
--- /dev/null
+++ b/locales/en-US/chrome/browser/preferences/cookies.dtd
@@ -0,0 +1,27 @@
+<!-- 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/. -->
+
+<!ENTITY window.width "36em">
+
+<!ENTITY cookiesonsystem.label "The following cookies are stored on your computer:">
+<!ENTITY cookiename.label "Cookie Name">
+<!ENTITY cookiedomain.label "Site">
+
+<!ENTITY props.name.label "Name:">
+<!ENTITY props.value.label "Content:">
+<!ENTITY props.domain.label "Host:">
+<!ENTITY props.path.label "Path:">
+<!ENTITY props.secure.label "Send For:">
+<!ENTITY props.expires.label "Expires:">
+
+<!ENTITY window.title "Cookies">
+<!ENTITY windowClose.key "w">
+<!ENTITY focusSearch1.key "f">
+<!ENTITY focusSearch2.key "k">
+
+<!ENTITY filter.label "Search:">
+<!ENTITY filter.accesskey "S">
+
+<!ENTITY button.close.label "Close">
+<!ENTITY button.close.accesskey "C">
diff --git a/locales/en-US/chrome/browser/preferences/fonts.dtd b/locales/en-US/chrome/browser/preferences/fonts.dtd
new file mode 100644
index 0000000..f255cb2
--- /dev/null
+++ b/locales/en-US/chrome/browser/preferences/fonts.dtd
@@ -0,0 +1,107 @@
+<!-- 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/. -->
+
+<!ENTITY fontsDialog.title "Fonts">
+
+<!ENTITY language.label "Fonts for:">
+<!ENTITY language.accesskey "F">
+
+<!ENTITY size.label "Size:">
+<!ENTITY sizeProportional.accesskey "z">
+<!ENTITY sizeMonospace.accesskey "e">
+
+<!ENTITY proportional.label "Proportional:">
+<!ENTITY proportional.accesskey "P">
+
+<!ENTITY serif.label "Serif:">
+<!ENTITY serif.accesskey "S">
+<!ENTITY sans-serif.label "Sans-serif:">
+<!ENTITY sans-serif.accesskey "n">
+<!ENTITY monospace.label "Monospace:">
+<!ENTITY monospace.accesskey "M">
+
+<!-- LOCALIZATION NOTE (font.langGroup.latin) :
+ Translate "Latin" as the name of Latin (Roman) script, not as the name of the Latin language. -->
+<!ENTITY font.langGroup.latin "Latin">
+<!ENTITY font.langGroup.japanese "Japanese">
+<!ENTITY font.langGroup.trad-chinese "Traditional Chinese (Taiwan)">
+<!ENTITY font.langGroup.simpl-chinese "Simplified Chinese">
+<!ENTITY font.langGroup.trad-chinese-hk "Traditional Chinese (Hong Kong)">
+<!ENTITY font.langGroup.korean "Korean">
+<!ENTITY font.langGroup.cyrillic "Cyrillic">
+<!ENTITY font.langGroup.el "Greek">
+<!ENTITY font.langGroup.other "Other Writing Systems">
+<!ENTITY font.langGroup.thai "Thai">
+<!ENTITY font.langGroup.hebrew "Hebrew">
+<!ENTITY font.langGroup.arabic "Arabic">
+<!ENTITY font.langGroup.devanagari "Devanagari">
+<!ENTITY font.langGroup.tamil "Tamil">
+<!ENTITY font.langGroup.armenian "Armenian">
+<!ENTITY font.langGroup.bengali "Bengali">
+<!ENTITY font.langGroup.canadian "Unified Canadian Syllabary">
+<!ENTITY font.langGroup.ethiopic "Ethiopic">
+<!ENTITY font.langGroup.georgian "Georgian">
+<!ENTITY font.langGroup.gujarati "Gujarati">
+<!ENTITY font.langGroup.gurmukhi "Gurmukhi">
+<!ENTITY font.langGroup.khmer "Khmer">
+<!ENTITY font.langGroup.malayalam "Malayalam">
+<!ENTITY font.langGroup.oriya "Oriya">
+<!ENTITY font.langGroup.telugu "Telugu">
+<!ENTITY font.langGroup.kannada "Kannada">
+<!ENTITY font.langGroup.sinhala "Sinhala">
+<!ENTITY font.langGroup.tibetan "Tibetan">
+<!-- Minimum font size -->
+<!ENTITY minSize.label "Minimum font size:">
+<!ENTITY minSize.accesskey "o">
+<!ENTITY minSize.none "None">
+
+<!-- default font type -->
+<!ENTITY useDefaultFontSerif.label "Serif">
+<!ENTITY useDefaultFontSansSerif.label "Sans Serif">
+
+<!ENTITY allowPagesToUse.label "Allow pages to choose their own fonts, instead of my selections above">
+<!ENTITY allowPagesToUse.accesskey "A">
+
+<!ENTITY languages.customize.Fallback.grouplabel "Character Encoding for Legacy Content">
+<!ENTITY languages.customize.Fallback.label "Fallback Character Encoding:">
+<!ENTITY languages.customize.Fallback.accesskey "C">
+<!ENTITY languages.customize.Fallback.desc "This character encoding is used for legacy content that fails to declare its encoding.">
+
+<!ENTITY languages.customize.Fallback.auto "Default for Current Locale">
+<!ENTITY languages.customize.Fallback.utf8 "UTF-8">
+<!-- LOCALIZATION NOTE (languages.customize.Fallback.arabic):
+ Translate "Arabic" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY languages.customize.Fallback.arabic "Arabic">
+<!ENTITY languages.customize.Fallback.baltic "Baltic">
+<!ENTITY languages.customize.Fallback.ceiso "Central European, ISO">
+<!ENTITY languages.customize.Fallback.cewindows "Central European, Microsoft">
+<!-- LOCALIZATION NOTE (languages.customize.Fallback.simplified):
+ Translate "Chinese" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY languages.customize.Fallback.simplified "Chinese, Simplified">
+<!-- LOCALIZATION NOTE (languages.customize.Fallback.traditional):
+ Translate "Chinese" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY languages.customize.Fallback.traditional "Chinese, Traditional">
+<!ENTITY languages.customize.Fallback.cyrillic "Cyrillic">
+<!-- LOCALIZATION NOTE (languages.customize.Fallback.greek):
+ Translate "Greek" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY languages.customize.Fallback.greek "Greek">
+<!-- LOCALIZATION NOTE (languages.customize.Fallback.hebrew):
+ Translate "Hebrew" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY languages.customize.Fallback.hebrew "Hebrew">
+<!-- LOCALIZATION NOTE (languages.customize.Fallback.japanese):
+ Translate "Japanese" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY languages.customize.Fallback.japanese "Japanese">
+<!-- LOCALIZATION NOTE (languages.customize.Fallback.korean):
+ Translate "Korean" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY languages.customize.Fallback.korean "Korean">
+<!-- LOCALIZATION NOTE (languages.customize.Fallback.thai):
+ Translate "Thai" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY languages.customize.Fallback.thai "Thai">
+<!-- LOCALIZATION NOTE (languages.customize.Fallback.turkish):
+ Translate "Turkish" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY languages.customize.Fallback.turkish "Turkish">
+<!-- LOCALIZATION NOTE (languages.customize.Fallback.vietnamese):
+ Translate "Vietnamese" as an adjective for an encoding, not as the name of the language. -->
+<!ENTITY languages.customize.Fallback.vietnamese "Vietnamese">
+<!ENTITY languages.customize.Fallback.other "Other (incl. Western European)">
diff --git a/locales/en-US/chrome/browser/preferences/languages.dtd b/locales/en-US/chrome/browser/preferences/languages.dtd
new file mode 100644
index 0000000..f7e67c9
--- /dev/null
+++ b/locales/en-US/chrome/browser/preferences/languages.dtd
@@ -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/. -->
+
+<!ENTITY window.width "30em">
+
+<!ENTITY languages.customize.Header "Languages">
+<!ENTITY languages.customize.prefLangDescript "Web pages are sometimes offered in more than one language. Choose languages for displaying these web pages, in order of preference.">
+<!ENTITY languages.customize.active.label "Languages in order of preference:">
+<!ENTITY languages.customize.moveUp.label "Move Up">
+<!ENTITY languages.customize.moveUp.accesskey "U">
+<!ENTITY languages.customize.moveDown.label "Move Down">
+<!ENTITY languages.customize.moveDown.accesskey "D">
+<!ENTITY languages.customize.deleteButton.label "Remove">
+<!ENTITY languages.customize.deleteButton.accesskey "R">
+<!ENTITY languages.customize.selectLanguage.label "Select a language to add…">
+<!ENTITY languages.customize.addButton.label "Add">
+<!ENTITY languages.customize.addButton.accesskey "A">
+
diff --git a/locales/en-US/chrome/browser/preferences/main.dtd b/locales/en-US/chrome/browser/preferences/main.dtd
new file mode 100644
index 0000000..15468ee
--- /dev/null
+++ b/locales/en-US/chrome/browser/preferences/main.dtd
@@ -0,0 +1,44 @@
+<!-- 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/. -->
+
+<!ENTITY startup.label "Startup">
+
+<!ENTITY startupPage.label "When &brandShortName; starts:">
+<!ENTITY startupPage.accesskey "s">
+<!ENTITY startupHomePage.label "Show my home page">
+<!ENTITY startupBlankPage.label "Show a blank page">
+<!ENTITY startupLastSession.label "Show my windows and tabs from last time">
+
+<!ENTITY homepage.label "Home Page:">
+<!ENTITY homepage.accesskey "P">
+<!ENTITY useCurrentPage.label "Use Current Page">
+<!ENTITY useCurrentPage.accesskey "C">
+<!ENTITY useMultiple.label "Use Current Pages">
+<!ENTITY chooseBookmark.label "Use Bookmark…">
+<!ENTITY chooseBookmark.accesskey "B">
+<!ENTITY restoreDefault.label "Restore to Default">
+<!ENTITY restoreDefault.accesskey "R">
+
+<!ENTITY downloads.label "Downloads">
+
+<!ENTITY showWhenDownloading.label "Show the Downloads window when downloading a file">
+<!ENTITY showWhenDownloading.accesskey "D">
+<!ENTITY closeWhenDone.label "Close it when all downloads are finished">
+<!ENTITY closeWhenDone.accesskey "w">
+<!ENTITY saveTo.label "Save files to">
+<!ENTITY saveTo.accesskey "v">
+<!ENTITY chooseFolderWin.label "Browse…">
+<!ENTITY chooseFolderWin.accesskey "o">
+<!ENTITY chooseFolderMac.label "Choose…">
+<!ENTITY chooseFolderMac.accesskey "e">
+<!ENTITY alwaysAsk.label "Always ask me where to save files">
+<!ENTITY alwaysAsk.accesskey "A">
+
+<!ENTITY toolkit.classic.download.window.label "Use the classic downloads window">
+
+<!ENTITY zoneInfo.label "Saved files have zone information:">
+<!ENTITY zoneInfo.never "Never">
+<!ENTITY zoneInfo.always "Always">
+<!ENTITY zoneInfo.system "Use system setting">
+
diff --git a/locales/en-US/chrome/browser/preferences/permissions.dtd b/locales/en-US/chrome/browser/preferences/permissions.dtd
new file mode 100644
index 0000000..e61228b
--- /dev/null
+++ b/locales/en-US/chrome/browser/preferences/permissions.dtd
@@ -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/. -->
+
+<!ENTITY window.title "Exceptions">
+<!ENTITY window.width "45em">
+
+<!ENTITY treehead.sitename.label "Site">
+<!ENTITY treehead.status.label "Status">
+<!ENTITY removepermission.label "Remove Site">
+<!ENTITY removepermission.accesskey "R">
+<!ENTITY removeallpermissions.label "Remove All Sites">
+<!ENTITY removeallpermissions.accesskey "e">
+<!ENTITY address.label "Address of website:">
+<!ENTITY address.accesskey "d">
+<!ENTITY block.label "Block">
+<!ENTITY block.accesskey "B">
+<!ENTITY session.label "Allow for Session">
+<!ENTITY session.accesskey "S">
+<!ENTITY allow.label "Allow">
+<!ENTITY allow.accesskey "A">
+<!ENTITY windowClose.key "w">
+
+<!ENTITY button.cancel.label "Cancel">
+<!ENTITY button.cancel.accesskey "C">
+<!ENTITY button.ok.label "Save Changes">
+<!ENTITY button.ok.accesskey "S">
+
diff --git a/locales/en-US/chrome/browser/preferences/preferences.dtd b/locales/en-US/chrome/browser/preferences/preferences.dtd
new file mode 100644
index 0000000..e87749d
--- /dev/null
+++ b/locales/en-US/chrome/browser/preferences/preferences.dtd
@@ -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/. -->
+
+
+<!ENTITY prefWindow.titleWin "Preferences">
+<!ENTITY prefWindow.titleGNOME "&brandShortName; Preferences">
+<!-- When making changes to prefWindow.styleWin test both Windows Classic and
+ Aero since widget heights are different based on the OS theme -->
+<!ENTITY prefWinMinSize.styleWin2 "width: 42em; min-height: 37.5em;">
+<!ENTITY prefWinMinSize.styleMac "width: 47em; min-height: 40em;">
+<!ENTITY prefWinMinSize.styleGNOME "width: 45.5em; min-height: 40.5em;">
+
+<!ENTITY paneGeneral.title "General">
+<!ENTITY paneTabs.title "Tabs">
+<!ENTITY paneContent.title "Content">
+<!ENTITY paneApplications.title "Applications">
+<!ENTITY panePrivacy.title "Privacy">
+<!ENTITY paneSecurity.title "Security">
+<!ENTITY paneAdvanced.title "Advanced">
+
+<!-- LOCALIZATION NOTE (paneSync.title): This should match syncBrand.shortName.label in ../syncBrand.dtd -->
+<!ENTITY paneSync.title "Sync">
diff --git a/locales/en-US/chrome/browser/preferences/preferences.properties b/locales/en-US/chrome/browser/preferences/preferences.properties
new file mode 100644
index 0000000..34f1675
--- /dev/null
+++ b/locales/en-US/chrome/browser/preferences/preferences.properties
@@ -0,0 +1,153 @@
+# 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/.
+
+#### Fonts
+
+labelDefaultFont=Default (%S)
+
+#### Permissions Manager
+
+cookiepermissionstext=You can specify which websites are always or never allowed to use cookies or store site data. Type the exact address of the site you want to manage and then click Block, Allow for Session, or Allow.
+cookiepermissionstitle=Exceptions - Cookies and Site Data
+addonspermissionstext=You can specify which websites are allowed to install add-ons. Type the exact address of the site you want to allow and then click Allow.
+addons_permissions_title=Allowed Sites - Add-ons Installation
+popuppermissionstext=You can specify which websites are allowed to open pop-up windows. Type the exact address of the site you want to allow and then click Allow.
+popuppermissionstitle=Allowed Sites - Pop-ups
+invalidURI=Please enter a valid hostname
+invalidURITitle=Invalid Hostname Entered
+savedLoginsExceptions_title=Exceptions - Saved Logins
+savedLoginsExceptions_desc=Logins for the following sites will not be saved:
+
+#### Master Password
+
+pw_change2empty_in_fips_mode=You are currently in FIPS mode. FIPS requires a non-empty Master Password.
+pw_change_failed_title=Password Change Failed
+
+#### Fonts
+
+# LOCALIZATION NOTE: Next two strings are for language name representations with
+# and without the region.
+# e.g. languageRegionCodeFormat : "French/Canada [fr-ca]" languageCodeFormat : "French [fr]"
+# %1$S = language name, %2$S = region name, %3$S = language-region code
+languageRegionCodeFormat=%1$S/%2$S [%3$S]
+# %1$S = language name, %2$S = language-region code
+languageCodeFormat=%1$S [%2$S]
+
+#### Downloads
+
+desktopFolderName=Desktop
+downloadsFolderName=Downloads
+chooseDownloadFolderTitle=Choose Download Folder:
+
+#### Applications
+
+fileEnding=%S file
+saveFile=Save File
+
+# LOCALIZATION NOTE (useApp, useDefault): %S = Application name
+useApp=Use %S
+useDefault=Use %S (default)
+
+useOtherApp=Use other…
+fpTitleChooseApp=Select Helper Application
+manageApp=Application Details…
+webFeed=Web Feed
+videoPodcastFeed=Video Podcast
+audioPodcastFeed=Podcast
+alwaysAsk=Always ask
+portableDocumentFormat=Portable Document Format (PDF)
+
+# LOCALIZATION NOTE (usePluginIn):
+# %1$S = plugin name (for example "QuickTime Plugin-in 7.2")
+# %2$S = brandShortName from brand.properties (for example "Minefield")
+usePluginIn=Use %S (in %S)
+
+# LOCALIZATION NOTE (previewInApp, addLiveBookmarksInApp): %S = brandShortName
+previewInApp=Preview in %S
+addLiveBookmarksInApp=Add Live Bookmarks in %S
+
+# LOCALIZATION NOTE (typeDescriptionWithType):
+# %1$S = type description (for example "Portable Document Format")
+# %2$S = type (for example "application/pdf")
+typeDescriptionWithType=%S (%S)
+
+
+#### Cookie Viewer
+
+hostColon=Host:
+domainColon=Domain:
+forSecureOnly=Encrypted connections only
+forAnyConnection=Any type of connection
+expireAtEndOfSession=At end of session
+can=Allow
+canAccessFirstParty=Allow first party only
+canSession=Allow for Session
+cannot=Block
+noCookieSelected=<no cookie selected>
+cookiesAll=The following cookies are stored on your computer:
+cookiesFiltered=The following cookies match your search:
+
+# LOCALIZATION NOTE (removeAllCookies, removeAllShownCookies):
+# removeAllCookies and removeAllShownCookies are both used on the same one button,
+# never displayed together and can share the same accesskey.
+# When only partial cookies are shown as a result of keyword search,
+# removeAllShownCookies is displayed as button label.
+# removeAllCookies is displayed when no keyword search and all cookies are shown.
+removeAllCookies.label=Remove All
+removeAllCookies.accesskey=A
+removeAllShownCookies.label=Remove All Shown
+removeAllShownCookies.accesskey=A
+
+# LOCALIZATION NOTE (removeSelectedCookies):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# If you need to display the number of selected elements in your language,
+# you can use #1 in your localization as a placeholder for the number.
+# For example this is the English string with numbers:
+# removeSelectedCookied=Remove #1 Selected;Remove #1 Selected
+removeSelectedCookies.label=Remove Selected;Remove Selected
+removeSelectedCookies.accesskey=R
+
+#### Offline apps
+offlineAppRemoveTitle=Remove offline website data
+offlineAppRemovePrompt=After removing this data, %S will not be available offline. Are you sure you want to remove this offline website?
+offlineAppRemoveConfirm=Remove offline data
+
+# LOCALIZATION NOTE: The next string is for the disk usage of the
+# offline application
+# e.g. offlineAppUsage : "50.23 MB"
+# %1$S = size (in bytes or megabytes, ...)
+# %2$S = unit of measure (bytes, KB, MB, ...)
+offlineAppUsage=%1$S %2$S
+
+offlinepermissionstext=The following websites are not allowed to store data for offline use:
+offlinepermissionstitle=Offline Data
+
+####Preferences::Advanced::Network
+#LOCALIZATION NOTE: The next string is for the disk usage of the web content cache.
+# e.g., "Your web content cache is currently using 200 MB"
+# %1$S = size
+# %2$S = unit (MB, KB, etc.)
+actualDiskCacheSize=Your web content cache is currently using %1$S %2$S of disk space
+
+####Preferences::Advanced::Network
+#LOCALIZATION NOTE: The next string is for the disk usage of the application cache.
+# e.g., "Your application cache is currently using 200 MB"
+# %1$S = size
+# %2$S = unit (MB, KB, etc.)
+actualAppCacheSize=Your application cache is currently using %1$S %2$S of disk space
+
+###Preferences::Advanced::Update
+#LOCALIZATION NOTE: The next string is for updating in Windows 8 only instead of updateAuto1.label. %S = brandShortName
+updateAutoDesktop.label=Automatically install updates from desktop %S
+updateAutoDesktop.accessKey=A
+
+syncUnlink.title=Do you want to unlink your device?
+syncUnlink.label=This device will no longer be associated with your Sync account. All of your personal data, both on this device and in your Sync account, will remain intact.
+syncUnlinkConfirm.label=Unlink
+
+# LOCALIZATION NOTE (featureEnableRequiresRestart, featureDisableRequiresRestart, restartTitle): %S = brandShortName
+featureEnableRequiresRestart=%S must restart to enable this feature.
+featureDisableRequiresRestart=%S must restart to disable this feature.
+shouldRestartTitle=Restart %S
diff --git a/locales/en-US/chrome/browser/preferences/privacy.dtd b/locales/en-US/chrome/browser/preferences/privacy.dtd
new file mode 100644
index 0000000..37f8b78
--- /dev/null
+++ b/locales/en-US/chrome/browser/preferences/privacy.dtd
@@ -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/. -->
+
+<!ENTITY tracking.label "Tracking">
+
+<!ENTITY dntTrackingNotOkay.label2 "Tell sites that I do not want to be tracked">
+<!ENTITY dntTrackingNotOkay.accesskey "n">
+<!ENTITY doNotTrackInfo.label "Learn More">
+
+<!ENTITY history.label "History">
+
+<!ENTITY locationBar.label "Location Bar">
+
+<!ENTITY locbar.suggest.label "When using the location bar, suggest:">
+<!ENTITY locbar.history.label "History">
+<!ENTITY locbar.history.accesskey "H">
+<!ENTITY locbar.bookmarks.label "Bookmarks">
+<!ENTITY locbar.bookmarks.accesskey "k">
+<!ENTITY locbar.openpage.label "Open tabs">
+<!ENTITY locbar.openpage.accesskey "O">
+
+<!ENTITY acceptCookies.label "Allow sites to store cookies and data">
+<!ENTITY acceptCookies.accesskey "A">
+
+<!ENTITY acceptThirdParty.pre.label "Accept third-party cookies and site data:">
+<!ENTITY acceptThirdParty.pre.accesskey "c">
+<!ENTITY acceptThirdParty.always.label "Always">
+<!ENTITY acceptThirdParty.never.label "Never">
+<!ENTITY acceptThirdParty.visited.label "From visited">
+
+<!ENTITY keepUntil.label "Keep until:">
+<!ENTITY keepUntil.accesskey "K">
+
+<!ENTITY expire.label "they expire">
+<!ENTITY close.label "I close &brandShortName;">
+
+<!ENTITY cookieExceptions.label "Exceptions…">
+<!ENTITY cookieExceptions.accesskey "E">
+
+<!ENTITY showCookies.label "Show Cookies…">
+<!ENTITY showCookies.accesskey "S">
+
+<!ENTITY historyHeader.pre.label "&brandShortName; will:">
+<!ENTITY historyHeader.pre.accesskey "w">
+<!ENTITY historyHeader.remember.label "Remember history">
+<!ENTITY historyHeader.dontremember.label "Never remember history">
+<!ENTITY historyHeader.custom.label "Use custom settings for history">
+<!ENTITY historyHeader.post.label "">
+
+<!ENTITY rememberDescription.label "&brandShortName; will remember your browsing, download, form and search history, and keep cookies and site data from websites you visit.">
+
+<!-- LOCALIZATION NOTE (rememberActions.pre.label): include a trailing space as needed -->
+<!-- LOCALIZATION NOTE (rememberActions.middle.label): include a starting and trailing space as needed -->
+<!-- LOCALIZATION NOTE (rememberActions.post.label): include a starting space as needed -->
+<!ENTITY rememberActions.pre.label "You may want to ">
+<!ENTITY rememberActions.clearHistory.label "clear your recent history">
+<!ENTITY rememberActions.middle.label ", or ">
+<!ENTITY rememberActions.removeCookies.label "remove individual cookies">
+<!ENTITY rememberActions.post.label ".">
+
+<!ENTITY dontrememberDescription.label "&brandShortName; will use the same settings as private browsing, and will not remember any history as you browse the Web.">
+
+<!-- LOCALIZATION NOTE (dontrememberActions.pre.label): include a trailing space as needed -->
+<!-- LOCALIZATION NOTE (dontrememberActions.post.label): include a starting space as needed -->
+<!ENTITY dontrememberActions.pre.label "You may also want to ">
+<!ENTITY dontrememberActions.clearHistory.label "clear all current history">
+<!ENTITY dontrememberActions.post.label ".">
+
+<!ENTITY privateBrowsingPermanent2.label "Always use private browsing mode">
+<!ENTITY privateBrowsingPermanent2.accesskey "p">
+
+<!ENTITY rememberHistory2.label "Remember my browsing and download history">
+<!ENTITY rememberHistory2.accesskey "b">
+
+<!ENTITY rememberSearchForm.label "Remember search and form history">
+<!ENTITY rememberSearchForm.accesskey "f">
+
+<!ENTITY clearOnClose.label "Clear history when &brandShortName; closes">
+<!ENTITY clearOnClose.accesskey "r">
+
+<!ENTITY clearOnCloseSettings.label "Settings…">
+<!ENTITY clearOnCloseSettings.accesskey "t">
diff --git a/locales/en-US/chrome/browser/preferences/security.dtd b/locales/en-US/chrome/browser/preferences/security.dtd
new file mode 100644
index 0000000..930736d
--- /dev/null
+++ b/locales/en-US/chrome/browser/preferences/security.dtd
@@ -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/. -->
+
+<!ENTITY addons.label "Add-ons">
+
+<!ENTITY warnAddonInstall.label "Warn me when sites try to install add-ons">
+<!ENTITY warnAddonInstall.accesskey "W">
+
+<!ENTITY addonExceptions.label "Exceptions…">
+<!ENTITY addonExceptions.accesskey "E">
+
+<!ENTITY addonSecuritylevel "Add-on security level:">
+<!ENTITY addonSecurityLevel_Off "Off: No add-ons will be blocked (dangerous)">
+<!ENTITY addonSecurityLevel_Low "Low: Block only add-ons with severe security and stability issues">
+<!ENTITY addonSecurityLevel_High "Medium: Block all harmful add-ons (default, recommended)">
+<!ENTITY addonSecurityLevel_Extreme "High: Block all add-ons with known issues">
+
+<!ENTITY passwords.label "Passwords">
+
+<!ENTITY rememberPasswords.label "Remember passwords for sites">
+<!ENTITY rememberPasswords.accesskey "R">
+<!ENTITY passwordExceptions.label "Exceptions…">
+<!ENTITY passwordExceptions.accesskey "x">
+
+<!ENTITY autofillPasswords.label "Automatically fill in log-in details">
+<!ENTITY autofillPasswords.accesskey "A">
+
+<!ENTITY useMasterPassword.label "Use a master password">
+<!ENTITY useMasterPassword.accesskey "U">
+<!ENTITY changeMasterPassword.label "Change Master Password…">
+<!ENTITY changeMasterPassword.accesskey "M">
+
+<!ENTITY savedPasswords.label "Saved Passwords…">
+<!ENTITY savedPasswords.accesskey "P">
+
+<!ENTITY SecProto.label "Security Protocols">
+<!ENTITY enableHSTS.label "Enable Strict Transport Security (HSTS)">
+<!ENTITY enableHSTS.accesskey "S">
+<!ENTITY enableHPKP.label "Enable Certificate Key Pinning (HPKP)">
+<!ENTITY enableHPKP.accesskey "C">
+
+<!ENTITY OpEnc.label "Opportunistic Encryption (OE)">
+<!ENTITY enableUIROpEnc.label "Enable Upgrade Insecure Requests">
+<!ENTITY enableAltSvcOpEnc.label "Enable HTTP Alternative Services for OE">
+
+<!ENTITY XSSFilt.label "XSS Filter">
+<!ENTITY enableXSSFilt.label "Enable XSS filter">
+<!ENTITY enableXSSFilt.accesskey "f">
diff --git a/locales/en-US/chrome/browser/preferences/selectBookmark.dtd b/locales/en-US/chrome/browser/preferences/selectBookmark.dtd
new file mode 100644
index 0000000..a608286
--- /dev/null
+++ b/locales/en-US/chrome/browser/preferences/selectBookmark.dtd
@@ -0,0 +1,9 @@
+<!-- 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/. -->
+
+<!ENTITY selectBookmark.title
+ "Set Home Page">
+<!ENTITY selectBookmark.label
+ "Choose a Bookmark to be your Home Page. If you choose a folder, the Bookmarks in that folder will be opened in Tabs.">
+
diff --git a/locales/en-US/chrome/browser/preferences/sync.dtd b/locales/en-US/chrome/browser/preferences/sync.dtd
new file mode 100644
index 0000000..f6ef3b8
--- /dev/null
+++ b/locales/en-US/chrome/browser/preferences/sync.dtd
@@ -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/. -->
+
+<!-- The page shown when not logged in... -->
+<!ENTITY setupButton.label "Set Up &syncBrand.fullName.label;">
+<!ENTITY setupButton.accesskey "S">
+<!ENTITY weaveDesc.label "&syncBrand.fullName.label; lets you access your history, bookmarks, passwords and open tabs across all your devices.">
+
+<!-- The page shown when logged in... -->
+
+<!-- Login error feedback -->
+<!ENTITY updatePass.label "Update">
+<!ENTITY resetPass.label "Reset">
+
+<!-- Manage Account -->
+<!ENTITY manageAccount.label "Manage Account">
+<!ENTITY manageAccount.accesskey "n">
+<!ENTITY viewQuota.label "View Quota">
+<!ENTITY changePassword2.label "Change Password…">
+<!ENTITY myRecoveryKey.label "My Recovery Key">
+<!ENTITY resetSync2.label "Reset Sync…">
+
+<!ENTITY pairDevice.label "Pair a Device">
+
+<!ENTITY syncMy.label "Sync My">
+<!ENTITY engine.bookmarks.label "Bookmarks">
+<!ENTITY engine.bookmarks.accesskey "m">
+<!ENTITY engine.tabs.label "Tabs">
+<!ENTITY engine.tabs.accesskey "T">
+<!ENTITY engine.history.label "History">
+<!ENTITY engine.history.accesskey "r">
+<!ENTITY engine.passwords.label "Passwords">
+<!ENTITY engine.passwords.accesskey "P">
+<!ENTITY engine.prefs.label "Preferences">
+<!ENTITY engine.prefs.accesskey "S">
+<!ENTITY engine.addons.label "Add-ons">
+<!ENTITY engine.addons.accesskey "A">
+
+<!-- Device Settings -->
+<!ENTITY syncDeviceName.label "Device Name:">
+<!ENTITY syncDeviceName.accesskey "c">
+<!ENTITY unlinkDevice.label "Unlink This Device">
+
+<!-- Footer stuff -->
+<!ENTITY prefs.tosLink.label "Terms of Service">
+<!ENTITY prefs.ppLink.label "Privacy Policy">
diff --git a/locales/en-US/chrome/browser/preferences/tabs.dtd b/locales/en-US/chrome/browser/preferences/tabs.dtd
new file mode 100644
index 0000000..86e1e88
--- /dev/null
+++ b/locales/en-US/chrome/browser/preferences/tabs.dtd
@@ -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/. -->
+
+<!ENTITY newWindowsAsTabs.label "Open new windows in a new tab instead">
+<!ENTITY newWindowsAsTabs.accesskey "t">
+
+<!ENTITY warnCloseMultipleTabs.label "Warn me when closing multiple tabs">
+<!ENTITY warnCloseMultipleTabs.accesskey "m">
+
+<!ENTITY warnOpenManyTabs.label "Warn me when opening multiple tabs might slow down &brandShortName;">
+<!ENTITY warnOpenManyTabs.accesskey "o">
+
+<!ENTITY restoreTabsOnDemand.label "Don’t load tabs until selected">
+<!ENTITY restoreTabsOnDemand.accesskey "l">
+
+<!ENTITY switchToNewTabs.label "When I open a link in a new tab, switch to it immediately">
+<!ENTITY switchToNewTabs.accesskey "s">
+
+<!ENTITY showTabsInTaskbar.label "Show tab previews in the Windows taskbar">
+<!ENTITY showTabsInTaskbar.accesskey "k">
+
+<!ENTITY showTabBar.label "Always show the tab bar">
+<!ENTITY showTabBar.accesskey "b">
+
+<!ENTITY insertRelatedAfterCurrent.label "Insert related tabs next to the current tab">
+<!ENTITY contextLoadInBackground.label "When performing a context search, switch to the new tab immediately">
+<!ENTITY closeWindowWithLastTab.label "Close the window when the last tab is closed">
+<!ENTITY showTabPreviews.label "Show previews when switching tabs with Ctrl+Tab">
+
+<!ENTITY newtabPage.label "When opening a new tab, show:">
+<!ENTITY newtabPage.custom.label "A custom URL">
+<!ENTITY newtabPage.blank.label "A blank page">
+<!ENTITY newtabPage.home.label "The Pale Moon start page">
+<!ENTITY newtabPage.myhome.label "My home page">
+<!ENTITY newtabPage.quickdial.label "The Quickdial page">
diff --git a/locales/en-US/chrome/browser/quitDialog.properties b/locales/en-US/chrome/browser/quitDialog.properties
new file mode 100644
index 0000000..00f3be5
--- /dev/null
+++ b/locales/en-US/chrome/browser/quitDialog.properties
@@ -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/.
+
+quitDialogTitle=Quit %S
+
+quitTitle=&Quit
+cancelTitle=&Cancel
+saveTitle=&Save and Quit
+neverAsk2=&Do not ask next time
+message=Do you want %S to save your tabs and windows for the next time it starts?
+messageNoWindows=Do you want %S to save your tabs for the next time it starts?
+messagePrivate=You're in private browsing mode. Quitting %S now will discard all your open tabs and windows.
diff --git a/locales/en-US/chrome/browser/safeMode.dtd b/locales/en-US/chrome/browser/safeMode.dtd
new file mode 100644
index 0000000..79d3ca8
--- /dev/null
+++ b/locales/en-US/chrome/browser/safeMode.dtd
@@ -0,0 +1,27 @@
+<!-- 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/. -->
+
+<!ENTITY safeModeDialog.title "&brandShortName; Safe Mode">
+<!ENTITY window.width "37em">
+
+<!ENTITY safeModeDescription.label "&brandShortName; is now running in Safe Mode, which temporarily disables your custom settings, themes, and extensions.">
+<!ENTITY safeModeDescription2.label "You can make some or all of these changes permanent:">
+
+<!ENTITY disableAddons.label "Disable all add-ons">
+<!ENTITY disableAddons.accesskey "D">
+
+<!ENTITY resetToolbars.label "Reset toolbars and controls">
+<!ENTITY resetToolbars.accesskey "R">
+
+<!ENTITY deleteBookmarks.label "Delete all bookmarks except for backups">
+<!ENTITY deleteBookmarks.accesskey "b">
+
+<!ENTITY resetUserPrefs.label "Reset all user preferences to &brandShortName; defaults">
+<!ENTITY resetUserPrefs.accesskey "p">
+
+<!ENTITY restoreSearch.label "Restore default search engines">
+<!ENTITY restoreSearch.accesskey "s">
+
+<!ENTITY changeAndRestartButton.label "Make Changes and Restart">
+<!ENTITY continueButton.label "Continue in Safe Mode">
diff --git a/locales/en-US/chrome/browser/sanitize.dtd b/locales/en-US/chrome/browser/sanitize.dtd
new file mode 100644
index 0000000..b8c07d5
--- /dev/null
+++ b/locales/en-US/chrome/browser/sanitize.dtd
@@ -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/. -->
+
+<!ENTITY sanitizePrefs2.title "Settings for Clearing History">
+<!ENTITY sanitizeDialog2.title "Clear Recent History">
+
+<!ENTITY clearDataSettings2.label "When I quit &brandShortName;, it should automatically clear all:">
+
+<!-- XXX rearrange entities to match physical layout when l10n isn't an issue -->
+<!-- LOCALIZATION NOTE (clearTimeDuration.*): "Time range to clear" dropdown.
+ See UI mockup at bug 480169 -->
+<!ENTITY clearTimeDuration.label "Time range to clear: ">
+<!ENTITY clearTimeDuration.accesskey "T">
+<!ENTITY clearTimeDuration.lastHour "Last Hour">
+<!ENTITY clearTimeDuration.last2Hours "Last Two Hours">
+<!ENTITY clearTimeDuration.last4Hours "Last Four Hours">
+<!ENTITY clearTimeDuration.today "Today">
+<!ENTITY clearTimeDuration.everything "Everything">
+<!-- Localization note (clearTimeDuration.suffix) - trailing entity for languages
+that require it. -->
+<!ENTITY clearTimeDuration.suffix "">
+<!ENTITY clearTimeDuration.dateColumn "Visit Date">
+<!ENTITY clearTimeDuration.nameColumn "Name">
+
+<!-- LOCALIZATION NOTE (detailsProgressiveDisclosure.*): Labels and accesskeys
+ of the "Details" progressive disclosure button. See UI mockup at bug
+ 480169 -->
+<!ENTITY detailsProgressiveDisclosure.label "Details">
+<!ENTITY detailsProgressiveDisclosure.accesskey "e">
+
+<!ENTITY historySection.label "History">
+<!ENTITY dataSection.label "Data">
+
+<!ENTITY itemHistoryAndDownloads.label "Browsing &amp; Download History">
+<!ENTITY itemHistoryAndDownloads.accesskey "B">
+<!ENTITY itemFormSearchHistory.label "Form &amp; Search History">
+<!ENTITY itemFormSearchHistory.accesskey "F">
+<!ENTITY itemPasswords.label "Saved Passwords">
+<!ENTITY itemPasswords.accesskey "P">
+<!ENTITY itemCookies.label "Cookies">
+<!ENTITY itemCookies.accesskey "C">
+<!ENTITY itemCache.label "Cache">
+<!ENTITY itemCache.accesskey "A">
+<!ENTITY itemOfflineApps.label "Offline Website Data">
+<!ENTITY itemOfflineApps.accesskey "O">
+<!ENTITY itemConnectivityData.label "Site Connectivity Data">
+<!ENTITY itemConnectivityData.accesskey "N">
+<!ENTITY itemActiveLogins.label "Active Logins">
+<!ENTITY itemActiveLogins.accesskey "L">
+<!ENTITY itemSitePreferences.label "Site Preferences">
+<!ENTITY itemSitePreferences.accesskey "S">
+
+<!-- LOCALIZATION NOTE (sanitizeEverythingUndoWarning): Second warning paragraph
+ that appears when "Time range to clear" is set to "Everything". See UI
+ mockup at bug 480169 -->
+<!ENTITY sanitizeEverythingUndoWarning "This action cannot be undone.">
+
+<!-- LOCALIZATION NOTE (dialog.width2): width of the Clear Recent History and
+ Clear History on Shutdown dialogs. Should be large enough to contain
+ the item* strings above on a single line. The column width should be set
+ at half of the dialog width. -->
+<!ENTITY dialog.width2 "34em">
+<!ENTITY column.width2 "17em">
diff --git a/locales/en-US/chrome/browser/search.properties b/locales/en-US/chrome/browser/search.properties
new file mode 100644
index 0000000..130eb72
--- /dev/null
+++ b/locales/en-US/chrome/browser/search.properties
@@ -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/.
+
+searchtip=Search using %S
+
+# LOCALIZATION NOTE (cmd_pasteAndSearch): "Search" is a verb, this is the
+# search bar equivalent to the url bar's "Paste & Go"
+cmd_pasteAndSearch=Paste & Search
+
+cmd_clearHistory=Clear Search History
+cmd_clearHistory_accesskey=H
+
+cmd_showSuggestions=Show Suggestions
+cmd_showSuggestions_accesskey=S
+
+cmd_addFoundEngine=Add "%S"
+
diff --git a/locales/en-US/chrome/browser/searchbar.dtd b/locales/en-US/chrome/browser/searchbar.dtd
new file mode 100644
index 0000000..e5d18e4
--- /dev/null
+++ b/locales/en-US/chrome/browser/searchbar.dtd
@@ -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/. -->
+
+<!ENTITY cmd_engineManager.label "Manage Search Engines…">
+<!ENTITY searchEndCap.label "Search">
diff --git a/locales/en-US/chrome/browser/setDesktopBackground.dtd b/locales/en-US/chrome/browser/setDesktopBackground.dtd
new file mode 100644
index 0000000..71aa4b6
--- /dev/null
+++ b/locales/en-US/chrome/browser/setDesktopBackground.dtd
@@ -0,0 +1,15 @@
+<!-- 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/. -->
+
+<!ENTITY position.label "Position:">
+<!ENTITY tile.label "Tile">
+<!ENTITY center.label "Center">
+<!ENTITY stretch.label "Stretch">
+<!ENTITY fill.label "Fill">
+<!ENTITY fit.label "Fit">
+<!ENTITY preview.label "Preview">
+<!ENTITY color.label "Color:">
+<!ENTITY setDesktopBackground.title "Set Desktop Background">
+<!ENTITY openDesktopPrefs.label "Open Desktop Preferences">
+<!ENTITY closeWindow.key "w">
diff --git a/locales/en-US/chrome/browser/shellservice.properties b/locales/en-US/chrome/browser/shellservice.properties
new file mode 100644
index 0000000..d4f449f
--- /dev/null
+++ b/locales/en-US/chrome/browser/shellservice.properties
@@ -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/.
+
+optionsLabel=%S &Options
+safeModeLabel=%S &Safe Mode
+setDefaultBrowserTitle=Default Browser
+setDefaultBrowserMessage=%S is not currently set as your default browser. Would you like to make it your default browser?
+setDefaultBrowserDontAsk=Always perform this check when starting %S.
+alreadyDefaultBrowser=%S is already set as your default browser.
+desktopBackgroundLeafNameWin=Desktop Background.bmp
+DesktopBackgroundDownloading=Saving Picture…
+DesktopBackgroundSet=Set Desktop Background
diff --git a/locales/en-US/chrome/browser/statusbar/meta.properties b/locales/en-US/chrome/browser/statusbar/meta.properties
new file mode 100644
index 0000000..24cb5c0
--- /dev/null
+++ b/locales/en-US/chrome/browser/statusbar/meta.properties
@@ -0,0 +1,9 @@
+# Translator names. If there is more than one, separate with commas. Only include your name, not the locale you're translating.
+translator=Moonchild
+
+# Extension title. This usually should not be translated.
+name=Status-4-Evar
+
+# Extension description. This is displayed in the add-on manager.
+description=Status widgets and progress indicators for Firefox 4+
+
diff --git a/locales/en-US/chrome/browser/statusbar/overlay.properties b/locales/en-US/chrome/browser/statusbar/overlay.properties
new file mode 100644
index 0000000..cda1f66
--- /dev/null
+++ b/locales/en-US/chrome/browser/statusbar/overlay.properties
@@ -0,0 +1,17 @@
+# The #1 gets replaced with the number of active downloads. This is a semicolon list of plural forms: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+activeDownloads=1 Active Download;#1 Active Downloads
+# The #1 gets replaced with the number of paused downloads. This is a semicolon list of plural forms: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+pausedDownloads=1 Paused Download;#1 Paused Downloads
+
+noDownloads=No Downloads
+
+# Page load progress strings. These should match the strings that Firefox has been using.
+nv_done=Done
+nv_stopped=Stopped
+nv_timeout=Timed Out
+
+# Should be the same as status4evar.status.widget.title
+statusText=Status Text
+
+legacyWidgetTitle=Legacy Status Bar
+
diff --git a/locales/en-US/chrome/browser/statusbar/prefs.properties b/locales/en-US/chrome/browser/statusbar/prefs.properties
new file mode 100644
index 0000000..69f8f92
--- /dev/null
+++ b/locales/en-US/chrome/browser/statusbar/prefs.properties
@@ -0,0 +1,4 @@
+simpleEditorTitle=Switch to simple editor
+simpleEditorMessage=The simple editor can not edit your current style. Do you want to discard your style?
+imageSelectTitle=Select an image file
+
diff --git a/locales/en-US/chrome/browser/statusbar/statusbar-overlay.dtd b/locales/en-US/chrome/browser/statusbar/statusbar-overlay.dtd
new file mode 100644
index 0000000..bb0ab95
--- /dev/null
+++ b/locales/en-US/chrome/browser/statusbar/statusbar-overlay.dtd
@@ -0,0 +1,10 @@
+<!ENTITY status4evar.status.toolbar.accessKey "S">
+<!ENTITY status4evar.status.toolbar.title "Status Bar">
+<!ENTITY status4evar.status.widget.title "Status Text">
+<!ENTITY status4evar.progress.widget.title "Progress Meter">
+<!ENTITY status4evar.throbber.widget.title "Activity Indicator">
+<!ENTITY status4evar.download.widget.title "Download Status">
+<!ENTITY status4evar.options.widget.title "Status Bar Preferences">
+<!ENTITY status4evar.options.widget.label "Pale Moon status bar">
+<!ENTITY status4evar.menu.options.label "Status Bar Preferences">
+
diff --git a/locales/en-US/chrome/browser/statusbar/statusbar-prefs.dtd b/locales/en-US/chrome/browser/statusbar/statusbar-prefs.dtd
new file mode 100644
index 0000000..82f9e37
--- /dev/null
+++ b/locales/en-US/chrome/browser/statusbar/statusbar-prefs.dtd
@@ -0,0 +1,99 @@
+<!ENTITY status4evar.window.title "Pale Moon status bar preferences">
+
+<!ENTITY status4evar.pane.status "Status">
+<!ENTITY status4evar.pane.progress "Progress">
+<!ENTITY status4evar.pane.download "Download">
+<!ENTITY status4evar.pane.statusbar "Status Bar">
+<!ENTITY status4evar.pane.advanced "Advanced">
+
+<!ENTITY status4evar.tab.general "General">
+<!ENTITY status4evar.tab.toolbar "Status Bar">
+<!ENTITY status4evar.tab.popup "Pop-up">
+<!ENTITY status4evar.tab.tabs "Tabs">
+
+<!ENTITY status4evar.option.none "None">
+<!ENTITY status4evar.option.nothing "Nothing">
+<!ENTITY status4evar.option.toolbar "Status Bar">
+<!ENTITY status4evar.option.popup "Pop-up">
+<!ENTITY status4evar.option.tooltip "Tooltip">
+<!ENTITY status4evar.option.bottom "Bottom">
+<!ENTITY status4evar.option.top "Top">
+<!ENTITY status4evar.option.dlcount "Download count">
+<!ENTITY status4evar.option.dltime "Time remaining">
+<!ENTITY status4evar.option.both "Both">
+<!ENTITY status4evar.option.right "Right">
+<!ENTITY status4evar.option.left "Left">
+<!ENTITY status4evar.option.simple "Simple">
+<!ENTITY status4evar.option.advanced "Advanced">
+<!ENTITY status4evar.option.browse "Browse">
+<!ENTITY status4evar.option.clear "Clear">
+<!ENTITY status4evar.option.center "Center">
+<!ENTITY status4evar.option.offset "Offset">
+<!ENTITY status4evar.option.repeat "Repeat">
+<!ENTITY status4evar.option.no-repeat "No Repeat">
+<!ENTITY status4evar.option.space "Space">
+<!ENTITY status4evar.option.round "Round">
+<!ENTITY status4evar.option.firefoxdefault "Pale Moon default">
+<!ENTITY status4evar.option.download.library "Library">
+<!ENTITY status4evar.option.download.tab "Tab">
+<!ENTITY status4evar.option.download.thirdparty "3rd Party">
+
+<!ENTITY status4evar.unit.milliseconds "milliseconds">
+<!ENTITY status4evar.unit.seconds "seconds">
+<!ENTITY status4evar.unit.px "px">
+
+<!ENTITY status4evar.status.general.status.caption "Status Text">
+<!ENTITY status4evar.status.label "Show status in:">
+<!ENTITY status4evar.status.timeout.label "Hide the status after:">
+<!ENTITY status4evar.status.default.label "Show default status">
+<!ENTITY status4evar.status.network.label "Show network status">
+<!ENTITY status4evar.status.network.xhr.label "Show background network status">
+<!ENTITY toolkit.dom.status.change.label "Allow web pages to change the status">
+
+<!ENTITY status4evar.status.general.linkOver.caption "Links">
+<!ENTITY status4evar.status.linkOver.label "Show links in:">
+<!ENTITY status4evar.status.linkOver.delay.show.label "Delay before showing links:">
+<!ENTITY status4evar.status.linkOver.delay.hide.label "Delay before hiding links:">
+
+<!ENTITY status4evar.status.toolbar.maxLength.label "Max status text length:">
+
+<!ENTITY status4evar.status.urlbar.firefox.builtin.caption "Firefox compatibility features">
+<!ENTITY browser.urlbar.formatting.enabled.label "Enable domain highlighting">
+<!ENTITY browser.urlbar.trimming.enabled.label "Enable protocol hiding (http:// and ftp://)">
+
+<!ENTITY status4evar.status.popup.invertMirror.label "Default to the right side">
+<!ENTITY status4evar.status.popup.mouseMirror.label "Swap sides when moused over">
+
+<!ENTITY status4evar.progress.style.label "Use a custom style">
+
+<!ENTITY status4evar.progress.toolbar.force.label "Always show the progress meter">
+
+<!ENTITY status4evar.editor.label "Editor:">
+<!ENTITY status4evar.editor.css.color.label "Color:">
+<!ENTITY status4evar.editor.css.image.label "Image:">
+<!ENTITY status4evar.editor.css.image.repeat "Repeat">
+<!ENTITY status4evar.editor.css.image.position "Position">
+<!ENTITY status4evar.editor.css.image.offset "Offset">
+
+
+
+<!ENTITY status4evar.download.button.action.label "Download status button action:">
+<!ENTITY status4evar.download.force.label "Always show the Download Status indicator">
+<!ENTITY status4evar.download.label.force.label "Always show the indicator text">
+<!ENTITY status4evar.download.label.label "Download status indicator text:">
+<!ENTITY status4evar.download.tooltip.label "Download status indicator tooltip:">
+<!ENTITY status4evar.download.progress.label "Show download progress on the button">
+<!ENTITY status4evar.download.progress.average.label "Show average download completion">
+<!ENTITY status4evar.download.progress.max.label "Show most complete download">
+<!ENTITY status4evar.download.progress.min.label "Show least complete download">
+<!ENTITY status4evar.download.color.active.label "Progress color for active downloads:">
+<!ENTITY status4evar.download.color.paused.label "Progress color for paused downloads:">
+<!ENTITY status4evar.download.notify.animate.label "Animate button when download completes">
+<!ENTITY status4evar.download.notify.timeout.label "Highlight button when download completes:">
+
+<!ENTITY status4evar.addonbar.borderStyle "Use alternate toolbar borders">
+<!ENTITY status4evar.addonbar.windowGripper "Show the window re-size gripper">
+<!ENTITY status4evar.addonbar.closeButton "Show the close button">
+
+<!ENTITY status4evar.advanced.status.detectFullScreen "Detect full-screen mode and show links/status appropriately.">
+<!ENTITY status4evar.advanced.status.detectVideo "Hide status popup in full-screen HTML5 video.">
diff --git a/locales/en-US/chrome/browser/syncBrand.dtd b/locales/en-US/chrome/browser/syncBrand.dtd
new file mode 100644
index 0000000..bc4d1b3
--- /dev/null
+++ b/locales/en-US/chrome/browser/syncBrand.dtd
@@ -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/. -->
+
+<!ENTITY syncBrand.shortName.label "Sync">
+<!ENTITY syncBrand.fullName.label "Pale Moon Sync">
diff --git a/locales/en-US/chrome/browser/syncGenericChange.properties b/locales/en-US/chrome/browser/syncGenericChange.properties
new file mode 100644
index 0000000..aea86ad
--- /dev/null
+++ b/locales/en-US/chrome/browser/syncGenericChange.properties
@@ -0,0 +1,37 @@
+# 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/.
+
+#LOCALIZATION NOTE (change.password.title): This (and associated change.password/passphrase) are used when the user elects to change their password.
+change.password.title = Change your Password
+change.password.acceptButton = Change Password
+change.password.status.active = Changing your password…
+change.password.status.success = Your password has been changed.
+change.password.status.error = There was an error changing your password.
+
+change.password3.introText = Your password must be at least 8 characters long. It cannot be the same as either your user name or your Recovery Key.
+change.password.warningText = Note: All of your other devices will be unable to connect to your account once you change this password.
+
+change.recoverykey.title = My Recovery Key
+change.recoverykey.acceptButton = Change Recovery Key
+change.recoverykey.label = Changing Recovery Key and uploading local data, please wait…
+change.recoverykey.error = There was an error while changing your Recovery Key!
+change.recoverykey.success = Your Recovery Key was successfully changed!
+
+change.synckey.introText2 = To ensure your total privacy, all of your data is encrypted prior to being uploaded. The key to decrypt your data is not uploaded.
+# LOCALIZATION NOTE (change.recoverykey.warningText) "Sync" should match &syncBrand.shortName.label; from syncBrand.dtd
+change.recoverykey.warningText = Note: Changing this will erase all data stored on the Sync server and upload new data secured by this Recovery Key. Your other devices will not sync until the new Recovery Key is entered for that device.
+
+new.recoverykey.label = Your Recovery Key
+
+# LOCALIZATION NOTE (new.password.title): This (and associated new.password/passphrase) are used on a second computer when it detects that your password or passphrase has been changed on a different device.
+new.password.title = Update Password
+new.password.introText = Your password was rejected by the server, please update your password.
+new.password.label = Enter your new password
+new.password.confirm = Confirm your new password
+new.password.acceptButton = Update Password
+new.password.status.incorrect = Password incorrect, please try again.
+
+new.recoverykey.title = Update Recovery Key
+new.recoverykey.introText = Your Recovery Key was changed using another device, please enter your updated Recovery Key.
+new.recoverykey.acceptButton = Update Recovery Key
diff --git a/locales/en-US/chrome/browser/syncKey.dtd b/locales/en-US/chrome/browser/syncKey.dtd
new file mode 100644
index 0000000..f37f2c9
--- /dev/null
+++ b/locales/en-US/chrome/browser/syncKey.dtd
@@ -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/. -->
+
+<!ENTITY syncKey.page.title "Your &syncBrand.fullName.label; Key">
+<!ENTITY syncKey.page.description2 "This key is used to decode the data in your &syncBrand.fullName.label; account. You will need to enter the key each time you configure &syncBrand.fullName.label; on a new device.">
+<!ENTITY syncKey.keepItSecret.heading "Keep it secret">
+<!ENTITY syncKey.keepItSecret.description "Your &syncBrand.fullName.label; account is encrypted to protect your privacy. Without this key, it would take years for anyone to decode your personal information. You are the only person who holds this key. This means you're the only one who can access your &syncBrand.fullName.label; data.">
+<!ENTITY syncKey.keepItSafe.heading "Keep it safe">
+<!ENTITY syncKey.keepItSafe1.description "Do not lose this key.">
+<!ENTITY syncKey.keepItSafe2.description " We don't keep a copy of your key (that wouldn't be keeping it secret!) so ">
+<!ENTITY syncKey.keepItSafe3.description "we can't help you recover it">
+<!ENTITY syncKey.keepItSafe4a.description " if it's lost. You'll need to use this key any time you connect a new device to &syncBrand.fullName.label;.">
+<!ENTITY syncKey.findOutMore1.label "Find out more about &syncBrand.fullName.label; and your privacy at ">
+<!ENTITY syncKey.findOutMore2.label ".">
+<!ENTITY syncKey.footer1.label "&syncBrand.fullName.label; Terms of Service are available at ">
+<!ENTITY syncKey.footer2.label ". The Privacy Policy is available at ">
+<!ENTITY syncKey.footer3.label ".">
diff --git a/locales/en-US/chrome/browser/syncProgress.dtd b/locales/en-US/chrome/browser/syncProgress.dtd
new file mode 100644
index 0000000..db45cb9
--- /dev/null
+++ b/locales/en-US/chrome/browser/syncProgress.dtd
@@ -0,0 +1,15 @@
+<!-- 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/. -->
+
+<!ENTITY % brandDTD
+ SYSTEM "chrome://branding/locale/brand.dtd">
+ %brandDTD;
+
+<!-- These strings are used in the sync progress upload page -->
+<!ENTITY syncProgress.pageTitle "Your First Sync">
+<!ENTITY syncProgress.textBlurb "Your data is now being encrypted and uploaded in the background. You can close this tab and continue using &brandShortName;.">
+<!ENTITY syncProgress.closeButton "Close">
+<!ENTITY syncProgress.logoAltText "&brandShortName; logo">
+<!ENTITY syncProgress.diffText "&brandShortName; will now automatically sync in the background. You can close this tab and continue using &brandShortName;.">
+
diff --git a/locales/en-US/chrome/browser/syncQuota.dtd b/locales/en-US/chrome/browser/syncQuota.dtd
new file mode 100644
index 0000000..71174f0
--- /dev/null
+++ b/locales/en-US/chrome/browser/syncQuota.dtd
@@ -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/. -->
+
+<!ENTITY quota.dialogTitle.label "Server Quota">
+<!ENTITY quota.retrievingInfo.label "Retrieving quota information…">
+<!ENTITY quota.typeColumn.label "Type">
+<!ENTITY quota.sizeColumn.label "Size">
diff --git a/locales/en-US/chrome/browser/syncQuota.properties b/locales/en-US/chrome/browser/syncQuota.properties
new file mode 100644
index 0000000..0e1b857
--- /dev/null
+++ b/locales/en-US/chrome/browser/syncQuota.properties
@@ -0,0 +1,42 @@
+# 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/.
+
+collection.addons.label = Add-ons
+collection.bookmarks.label = Bookmarks
+collection.history.label = History
+collection.passwords.label = Passwords
+collection.prefs.label = Preferences
+collection.tabs.label = Tabs
+
+# LOCALIZATION NOTE (quota.usageNoQuota.label): %1$S and %2$S are numeric value
+# and unit (as defined in the download manager) of the amount of space occupied
+# on the server
+quota.usageNoQuota.label = You are currently using %1$S %2$S.
+# LOCALIZATION NOTE (quota.usagePercentage.label):
+# %1$S is the percentage of space used,
+# %2$S and %3$S numeric value and unit (as defined in the download manager)
+# of the amount of space used,
+# %3$S and %4$S numeric value and unit (as defined in the download manager)
+# of the total space available.
+quota.usagePercentage.label = You are using %1$S%% (%2$S %3$S) of your allowed %4$S %5$S.
+quota.usageError.label = Could not retrieve quota information.
+quota.retrieving.label = Retrieving…
+# LOCALIZATION NOTE (quota.sizeValueUnit.label): %1$S is the amount of space
+# occupied by the engine, %2$K the corresponding unit (e.g. kB) as defined in
+# the download manager.
+quota.sizeValueUnit.label = %1$S %2$S
+quota.remove.label = Remove
+quota.treeCaption.label = Uncheck items to stop syncing them and free up space on the server.
+# LOCALIZATION NOTE (quota.removal.label): %S is a list of engines that will be
+# disabled and whose data will be removed once the user confirms.
+quota.removal.label = Sync will remove the following data: %S.
+# LOCALIZATION NOTE (quota.list.separator): This is the separator string used
+# for the list of engines (incl. spaces where appropriate)
+quota.list.separator = ,\u0020
+# LOCALIZATION NOTE (quota.freeup.label): %1$S and %2$S are numeric value
+# and unit (as defined in the download manager) of the amount of space freed
+# up by disabling the unchecked engines. If displayed this string is
+# concatenated directly to quota.removal.label and may need to start off with
+# whitespace.
+quota.freeup.label = \u0020This will free up %1$S %2$S.
diff --git a/locales/en-US/chrome/browser/syncSetup.dtd b/locales/en-US/chrome/browser/syncSetup.dtd
new file mode 100644
index 0000000..7ee938e
--- /dev/null
+++ b/locales/en-US/chrome/browser/syncSetup.dtd
@@ -0,0 +1,116 @@
+<!-- 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/. -->
+
+<!ENTITY accountSetupTitle.label "&syncBrand.fullName.label; Setup">
+
+<!-- First page of the wizard -->
+
+<!ENTITY setup.pickSetupType.description2 "Welcome! If you've never used &syncBrand.fullName.label; before, you will need to create a new account.">
+<!ENTITY button.createNewAccount.label "Create a New Account">
+<!ENTITY button.haveAccount.label "I Have an Account">
+
+<!ENTITY setup.choicePage.title.label "Have you used &syncBrand.fullName.label; before?">
+<!ENTITY setup.choicePage.new.label "I've never used &syncBrand.shortName.label; before">
+<!ENTITY setup.choicePage.existing2.label "I'm already using &syncBrand.shortName.label; on another device">
+
+<!-- New Account AND Existing Account -->
+<!ENTITY server.label "Server">
+<!ENTITY serverType.default.label "Default: &syncBrand.fullName.label; server">
+<!ENTITY serverType.custom2.label "Use a custom server…">
+<!ENTITY signIn.account2.label "Account">
+<!ENTITY signIn.account2.accesskey "A">
+<!ENTITY signIn.password.label "Password">
+<!ENTITY signIn.password.accesskey "P">
+<!ENTITY signIn.recoveryKey.label "Recovery Key">
+<!ENTITY signIn.recoveryKey.accesskey "K">
+
+<!-- New Account Page 1: Basic Account Info -->
+<!ENTITY setup.newAccountDetailsPage.title.label "Account Details">
+<!ENTITY setup.emailAddress.label "Email Address">
+<!ENTITY setup.emailAddress.accesskey "E">
+<!ENTITY setup.choosePassword.label "Choose a Password">
+<!ENTITY setup.choosePassword.accesskey "P">
+<!ENTITY setup.confirmPassword.label "Confirm Password">
+<!ENTITY setup.confirmPassword.accesskey "m">
+<!ENTITY setup.setupMetro.label "Sync with Windows 8 style &brandShortName;">
+<!ENTITY setup.setupMetro.accesskey "S">
+
+<!-- LOCALIZATION NOTE: tosAgree1, tosLink, tosAgree2, ppLink, tosAgree3 are
+ joined with implicit white space, so spaces in the strings aren't necessary -->
+<!ENTITY setup.tosAgree1.label "I agree to the">
+<!ENTITY setup.tosAgree1.accesskey "a">
+<!ENTITY setup.tosLink.label "Terms of Service">
+<!ENTITY setup.tosAgree2.label "and the">
+<!ENTITY setup.ppLink.label "Privacy Policy">
+<!ENTITY setup.tosAgree3.label "">
+<!ENTITY setup.tosAgree2.accesskey "">
+
+<!-- My Recovery Key dialog -->
+<!ENTITY setup.newRecoveryKeyPage.title.label "&brandShortName; Cares About Your Privacy">
+<!ENTITY setup.newRecoveryKeyPage.description.label "To ensure your total privacy, all of your data is encrypted prior to being uploaded. The Recovery Key which is necessary to decrypt your data is not uploaded.">
+<!ENTITY recoveryKeyEntry.label "Your Recovery Key">
+<!ENTITY recoveryKeyEntry.accesskey "K">
+<!ENTITY syncGenerateNewKey.label "Generate a new key">
+<!ENTITY recoveryKeyBackup.description "Your Recovery Key is required to access &syncBrand.fullName.label; on other machines. Please create a backup copy. We cannot help you recover your Recovery Key.">
+
+<!ENTITY button.syncKeyBackup.print.label "Print…">
+<!ENTITY button.syncKeyBackup.print.accesskey "P">
+<!ENTITY button.syncKeyBackup.save.label "Save…">
+<!ENTITY button.syncKeyBackup.save.accesskey "S">
+
+<!-- Existing Account Page 1: Pair a Device (incl. Pair a Device dialog strings) -->
+<!ENTITY pairDevice.title.label "Pair a Device">
+<!ENTITY addDevice.showMeHow.label "Show me how.">
+<!ENTITY addDevice.dontHaveDevice.label "I don't have the device with me">
+<!ENTITY pairDevice.setup.description.label "To activate, select &#x0022;Pair a Device&#x0022; on your other device.">
+<!ENTITY addDevice.setup.enterCode.label "Then, enter this code:">
+<!ENTITY pairDevice.dialog.description.label "To activate your new device, select &#x0022;Set Up Sync&#x0022; on the device.">
+<!ENTITY addDevice.dialog.enterCode.label "Enter the code that the device provides:">
+<!ENTITY addDevice.dialog.tryAgain.label "Please try again.">
+<!ENTITY addDevice.dialog.successful.label "The device has been successfully added. The initial synchronization can take several minutes and will finish in the background.">
+<!ENTITY addDevice.dialog.recoveryKey.label "To activate your device you will need to enter your Recovery Key. Please print or save this key and take it with you.">
+<!ENTITY addDevice.dialog.connected.label "Device Connected">
+
+<!-- Existing Account Page 2: Manual Login -->
+<!ENTITY setup.signInPage.title.label "Sign In">
+<!ENTITY existingRecoveryKey.description "You can get a copy of your Recovery Key by going to &syncBrand.shortName.label; Options on your other device, and selecting &#x0022;My Recovery Key&#x0022; under &#x0022;Manage Account&#x0022;.">
+<!ENTITY verifying.label "Verifying…">
+<!ENTITY resetPassword.label "Reset Password">
+<!ENTITY resetSyncKey.label "I have lost my other device.">
+
+<!-- Sync Options -->
+<!ENTITY setup.optionsPage.title "Sync Options">
+<!ENTITY syncDeviceName.label "Device Name:">
+<!ENTITY syncDeviceName.accesskey "c">
+
+<!ENTITY syncMy.label "Sync My">
+<!ENTITY engine.bookmarks.label "Bookmarks">
+<!ENTITY engine.bookmarks.accesskey "m">
+<!ENTITY engine.tabs.label "Tabs">
+<!ENTITY engine.tabs.accesskey "T">
+<!ENTITY engine.history.label "History">
+<!ENTITY engine.history.accesskey "r">
+<!ENTITY engine.passwords.label "Passwords">
+<!ENTITY engine.passwords.accesskey "P">
+<!ENTITY engine.prefs.label "Preferences">
+<!ENTITY engine.prefs.accesskey "S">
+<!ENTITY engine.addons.label "Add-ons">
+<!ENTITY engine.addons.accesskey "A">
+
+<!ENTITY choice2a.merge.main.label "Merge this device's data with my &syncBrand.shortName.label; data">
+<!ENTITY choice2.merge.recommended.label "Recommended:">
+<!ENTITY choice2a.client.main.label "Replace all data on this device with my &syncBrand.shortName.label; data">
+<!ENTITY choice2a.server.main.label "Replace all other devices with this device's data">
+
+<!-- Confirm Merge Options -->
+<!ENTITY setup.optionsConfirmPage.title "Confirm">
+<!ENTITY confirm.merge2.label "&syncBrand.fullName.label; will now merge all this device's browser data into your Sync account.">
+<!ENTITY confirm.client3.label "Warning: The following &brandShortName; data on this device will be deleted:">
+<!ENTITY confirm.client2.moreinfo.label "&brandShortName; will then copy your &syncBrand.fullName.label; data to this device.">
+<!ENTITY confirm.server2.label "Warning: The following devices will be overwritten with your local data:">
+
+<!-- New & Existing Account: Setup Complete -->
+<!ENTITY setup.successPage.title "Setup Complete">
+<!ENTITY changeOptions.label "You can change this preference by selecting Sync Options below.">
+<!ENTITY continueUsing.label "You may now continue using &brandShortName;.">
diff --git a/locales/en-US/chrome/browser/syncSetup.properties b/locales/en-US/chrome/browser/syncSetup.properties
new file mode 100644
index 0000000..8a5170a
--- /dev/null
+++ b/locales/en-US/chrome/browser/syncSetup.properties
@@ -0,0 +1,51 @@
+# 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/.
+
+button.syncOptions.label = Sync Options
+button.syncOptionsDone.label = Done
+button.syncOptionsCancel.label = Cancel
+
+invalidEmail.label = Invalid email address
+serverInvalid.label = Please enter a valid server URL
+usernameNotAvailable.label = Already in use
+
+verifying.label = Verifying…
+
+# LOCALIZATION NOTE (additionalClientCount.label):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is the number of additional clients (was %S for a short while, use #1 instead, even if both work)
+additionalClientCount.label = and #1 additional device;and #1 additional devices
+# LOCALIZATION NOTE (bookmarksCount.label):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is the number of bookmarks (was %S for a short while, use #1 instead, even if both work)
+bookmarksCount.label = #1 bookmark;#1 bookmarks
+# LOCALIZATION NOTE (historyDaysCount.label):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is the number of days (was %S for a short while, use #1 instead, even if both work)
+historyDaysCount.label = #1 day of history;#1 days of history
+# LOCALIZATION NOTE (passwordsCount.label):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is the number of passwords (was %S for a short while, use #1 instead, even if both work)
+passwordsCount.label = #1 password;#1 passwords
+# LOCALIZATION NOTE (addonsCount.label): Semicolon-separated list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is the number of add-ons, see the link above for forms
+addonsCount.label = #1 addon;#1 addons
+
+save.recoverykey.title = Save Recovery Key
+save.recoverykey.defaultfilename = Pale Moon Recovery Key.html
+
+newAccount.action.label = Sync is now set up to automatically sync all of your browser data.
+newAccount.change.label = You can choose exactly what to sync by selecting Sync Options below.
+resetClient.change2.label = Sync will now merge all this device's browser data into your Sync account.
+wipeClient.change2.label = Sync will now replace all of the browser data on this device with the data in your Sync account.
+wipeRemote.change2.label = Sync will now replace all of the browser data in your Sync account with the data on this device.
+existingAccount.change.label = You can change this preference by selecting Sync Options below.
+
+# Several other strings are used (via Weave.Status.login), but they come from
+# /services/sync
diff --git a/locales/en-US/chrome/browser/tabbrowser.dtd b/locales/en-US/chrome/browser/tabbrowser.dtd
new file mode 100644
index 0000000..6741ff8
--- /dev/null
+++ b/locales/en-US/chrome/browser/tabbrowser.dtd
@@ -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/. -->
+
+<!ENTITY closeTab.label "Close Tab">
+<!ENTITY newTabButton.tooltip "Open a new tab">
diff --git a/locales/en-US/chrome/browser/tabbrowser.properties b/locales/en-US/chrome/browser/tabbrowser.properties
new file mode 100644
index 0000000..a4a0be0
--- /dev/null
+++ b/locales/en-US/chrome/browser/tabbrowser.properties
@@ -0,0 +1,30 @@
+# 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/.
+
+# LOCALIZATION NOTE: the following strings can be used in the tab title or
+# location bar to represent various states as a web page loads:
+# tabs.connecting = Firefox is sending a HTTP connection request
+# tabs.encryptingConnection = Firefox is sending a HTTPS connection request
+# tabs.searching = Firefox is searching for something (Awesomebar or Web search)
+# tabs.loading = Firefox is loading the web page
+# tabs.waiting = Firefox is waiting for a web resource to load
+# tabs.downloading = Firefox is downloading a file for a helper application (PDF)
+tabs.connecting=Connecting…
+tabs.encryptingConnection=Securing connection…
+tabs.searching=Searching…
+tabs.loading=Loading…
+tabs.waiting=Waiting…
+tabs.downloading=Downloading…
+
+tabs.emptyTabTitle=New Tab
+tabs.closeTab=Close Tab
+tabs.close=Close
+tabs.closeWarningTitle=Confirm close
+tabs.closeWarningMultipleTabs=You are about to close %S tabs. Are you sure you want to continue?
+tabs.closeButtonMultiple=Close tabs
+tabs.closeWarningPromptMe=Warn me when I attempt to close multiple tabs
+
+tabs.muteAudio.tooltip=Mute tab
+tabs.unmuteAudio.tooltip=Unmute tab
+tabs.unblockAudio.tooltip=Play tab
diff --git a/locales/en-US/chrome/browser/taskbar.properties b/locales/en-US/chrome/browser/taskbar.properties
new file mode 100644
index 0000000..987d5cc
--- /dev/null
+++ b/locales/en-US/chrome/browser/taskbar.properties
@@ -0,0 +1,12 @@
+# 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/.
+
+taskbar.tasks.newTab.label=Open new tab
+taskbar.tasks.newTab.description=Open a new browser tab.
+taskbar.tasks.newWindow.label=Open new window
+taskbar.tasks.newWindow.description=Open a new browser window.
+taskbar.tasks.newPrivateWindow.label=New private window
+taskbar.tasks.newPrivateWindow.description=Open a new window in private browsing mode.
+taskbar.frequent.label=Frequent
+taskbar.recent.label=Recent
diff --git a/locales/en-US/chrome/overrides/appstrings.properties b/locales/en-US/chrome/overrides/appstrings.properties
new file mode 100644
index 0000000..28ce022
--- /dev/null
+++ b/locales/en-US/chrome/overrides/appstrings.properties
@@ -0,0 +1,37 @@
+# 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/.
+
+malformedURI=The URL is not valid and cannot be loaded.
+fileNotFound=Pale Moon can't find the file at %S.
+dnsNotFound=Pale Moon can't find the server at %S.
+unknownProtocolFound=Pale Moon doesn't know how to open this address, because the protocol (%S) isn't associated with any program.
+connectionFailure=Pale Moon can't establish a connection to the server at %S.
+netInterrupt=The connection to %S was interrupted while the page was loading.
+netTimeout=The server at %S is taking too long to respond.
+redirectLoop=Pale Moon has detected that the server is redirecting the request for this address in a way that will never complete.
+## LOCALIZATION NOTE (confirmRepostPrompt): In this item, don't translate "%S"
+confirmRepostPrompt=To display this page, %S must send information that will repeat any action (such as a search or order confirmation) that was performed earlier.
+resendButton.label=Resend
+unknownSocketType=Pale Moon doesn't know how to communicate with the server.
+netReset=The connection to the server was reset while the page was loading.
+notCached=This document is no longer available.
+netOffline=Pale Moon is currently in offline mode and can't browse the Web.
+isprinting=The document cannot change while Printing or in Print Preview.
+deniedPortAccess=This address uses a network port which is normally used for purposes other than Web browsing. Pale Moon has canceled the request for your protection.
+proxyResolveFailure=Pale Moon is configured to use a proxy server that can't be found.
+proxyConnectFailure=Pale Moon is configured to use a proxy server that is refusing connections.
+contentEncodingError=The page you are trying to view cannot be shown because it uses an invalid or unsupported form of compression.
+unsafeContentType=The page you are trying to view cannot be shown because it is contained in a file type that may not be safe to open. Please contact the website owners to inform them of this problem.
+externalProtocolTitle=External Protocol Request
+externalProtocolPrompt=An external application must be launched to handle %1$S: links.\n\n\nRequested link:\n\n%2$S\n\nApplication: %3$S\n\n\nIf you were not expecting this request it may be an attempt to exploit a weakness in that other program. Cancel this request unless you are sure it is not malicious.\n
+#LOCALIZATION NOTE (externalProtocolUnknown): The following string is shown if the application name can't be determined
+externalProtocolUnknown=<Unknown>
+externalProtocolChkMsg=Remember my choice for all links of this type.
+externalProtocolLaunchBtn=Launch application
+cspBlocked=This page has a content security policy that prevents it from being embedded in this way.
+xssBlockMode=This page contains an XSS attack that has been blocked for your security.
+corruptedContentError=The page you are trying to view cannot be shown because an error in the data transmission was detected.
+remoteXUL=This page uses an unsupported technology that is no longer available by default in Pale Moon.
+## LOCALIZATION NOTE (sslv3Used) - Do not translate "%S".
+sslv3Used=Pale Moon cannot guarantee the safety of your data on %S because it uses SSLv3, a broken security protocol.
diff --git a/locales/en-US/chrome/overrides/netError.dtd b/locales/en-US/chrome/overrides/netError.dtd
new file mode 100644
index 0000000..9e5cbc7
--- /dev/null
+++ b/locales/en-US/chrome/overrides/netError.dtd
@@ -0,0 +1,254 @@
+<!-- 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/. -->
+
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+
+<!ENTITY loadError.label "Problem loading page">
+<!ENTITY retry.label "Try Again">
+<!ENTITY returnToPreviousPage.label "Go Back">
+<!ENTITY advanced.label "Advanced">
+
+<!-- Specific error messages -->
+
+<!ENTITY connectionFailure.title "Unable to connect">
+<!ENTITY connectionFailure.longDesc "&sharedLongDesc;">
+
+<!ENTITY deniedPortAccess.title "This address is restricted">
+<!ENTITY deniedPortAccess.longDesc "">
+
+<!ENTITY dnsNotFound.title "Server not found">
+<!ENTITY dnsNotFound.longDesc "
+<ul>
+ <li>Check the address for typing errors such as
+ <strong>ww</strong>.example.com instead of
+ <strong>www</strong>.example.com</li>
+ <li>If you are unable to load any pages, check your computer’s network
+ connection.</li>
+ <li>If your computer or network is protected by a firewall or proxy, make sure
+ that &brandShortName; is permitted to access the Web.</li>
+</ul>
+">
+
+<!ENTITY fileNotFound.title "File not found">
+<!ENTITY fileNotFound.longDesc "
+<ul>
+ <li>Check the file name for capitalization or other typing errors.</li>
+ <li>Check to see if the file was moved, renamed or deleted.</li>
+</ul>
+">
+
+<!ENTITY fileAccessDenied.title "Access to the file was denied">
+<!ENTITY fileAccessDenied.longDesc "
+<ul>
+ <li>It may have been removed, moved, or file permissions may be preventing access.</li>
+</ul>
+">
+
+<!ENTITY generic.title "Oops.">
+<!ENTITY generic.longDesc "
+<p>&brandShortName; can’t load this page for some reason.</p>
+">
+
+<!ENTITY captivePortal.title "Login to network">
+<!ENTITY captivePortal.longDesc "
+<p>This network may require you to login to access the internet.</p>
+">
+
+<!ENTITY openPortalLoginPage.label "Open Login Page">
+
+<!ENTITY malformedURI.title "The address isn't valid">
+<!ENTITY malformedURI.longDesc "
+<ul>
+ <li>Web addresses are usually written like
+ <strong>http://www.example.com/</strong></li>
+ <li>Make sure that you’re using forward slashes (i.e.
+ <strong>/</strong>).</li>
+</ul>
+">
+
+<!ENTITY netInterrupt.title "The connection was interrupted">
+<!ENTITY netInterrupt.longDesc "&sharedLongDesc;">
+
+<!ENTITY notCached.title "Document Expired">
+<!ENTITY notCached.longDesc "<p>The requested document is not available in &brandShortName;’s cache.</p><ul><li>As a security precaution, &brandShortName; does not automatically re-request sensitive documents.</li><li>Click Try Again to re-request the document from the website.</li></ul>">
+
+<!ENTITY netOffline.title "Offline mode">
+<!ENTITY netOffline.longDesc2 "
+<ul>
+ <li>Press &quot;Try Again&quot; to switch to online mode and reload the page.</li>
+</ul>
+">
+
+<!ENTITY contentEncodingError.title "Content Encoding Error">
+<!ENTITY contentEncodingError.longDesc "
+<ul>
+ <li>Please contact the website owners to inform them of this problem.</li>
+</ul>
+">
+
+<!ENTITY unsafeContentType.title "Unsafe File Type">
+<!ENTITY unsafeContentType.longDesc "
+<ul>
+ <li>Please contact the website owners to inform them of this problem.</li>
+</ul>
+">
+
+<!ENTITY netReset.title "The connection was reset">
+<!ENTITY netReset.longDesc "&sharedLongDesc;">
+
+<!ENTITY netTimeout.title "The connection has timed out">
+<!ENTITY netTimeout.longDesc "&sharedLongDesc;">
+
+<!ENTITY unknownProtocolFound.title "The address wasn’t understood">
+<!ENTITY unknownProtocolFound.longDesc "
+<ul>
+ <li>You might need to install other software to open this address.</li>
+</ul>
+">
+
+<!ENTITY proxyConnectFailure.title "The proxy server is refusing connections">
+<!ENTITY proxyConnectFailure.longDesc "
+<ul>
+ <li>Check the proxy settings to make sure that they are correct.</li>
+ <li>Contact your network administrator to make sure the proxy server is
+ working.</li>
+</ul>
+">
+
+<!ENTITY proxyResolveFailure.title "Unable to find the proxy server">
+<!ENTITY proxyResolveFailure.longDesc "
+<ul>
+ <li>Check the proxy settings to make sure that they are correct.</li>
+ <li>Check to make sure your computer has a working network connection.</li>
+ <li>If your computer or network is protected by a firewall or proxy, make sure
+ that &brandShortName; is permitted to access the Web.</li>
+</ul>
+">
+
+<!ENTITY redirectLoop.title "The page isn’t redirecting properly">
+<!ENTITY redirectLoop.longDesc "
+<ul>
+ <li>This problem can sometimes be caused by disabling or refusing to accept
+ cookies.</li>
+</ul>
+">
+
+<!ENTITY unknownSocketType.title "Unexpected response from server">
+<!ENTITY unknownSocketType.longDesc "
+<ul>
+ <li>Check to make sure your system has the Personal Security Manager
+ installed.</li>
+ <li>This might be due to a non-standard configuration on the server.</li>
+</ul>
+">
+
+<!ENTITY nssFailure2.title "Secure Connection Failed">
+<!ENTITY nssFailure2.longDesc2 "
+<ul>
+ <li>The page you are trying to view cannot be shown because the authenticity of the received data could not be verified.</li>
+ <li>Please contact the website owners to inform them of this problem.</li>
+</ul>
+">
+
+<!ENTITY nssBadCert.title "Secure Connection Failed">
+<!ENTITY nssBadCert.longDesc2 "
+<ul>
+ <li>This could be a problem with the server's configuration, or it could be
+someone trying to impersonate the server.</li>
+ <li>If you have connected to this server successfully in the past, the error may
+be temporary, and you can try again later.</li>
+</ul>
+">
+<!ENTITY certerror.longpagetitle1 "Your connection is not secure">
+<!-- Localization note (certerror.introPara) - The text content of the span tag
+will be replaced at runtime with the name of the server to which the user
+was trying to connect. -->
+<!ENTITY certerror.introPara "The owner of <span class='hostname'/> has configured their website improperly. To protect your information from being stolen, &brandShortName; has not connected to this website.">
+
+<!ENTITY sharedLongDesc "
+<ul>
+ <li>The site could be temporarily unavailable or too busy. Try again in a few
+ moments.</li>
+ <li>If you are unable to load any pages, check your computer’s network
+ connection.</li>
+ <li>If your computer or network is protected by a firewall or proxy, make sure
+ that &brandShortName; is permitted to access the Web.</li>
+</ul>
+">
+
+<!ENTITY cspBlocked.title "Blocked by Content Security Policy">
+<!ENTITY cspBlocked.longDesc "<p>&brandShortName; prevented this page from loading in this way because the page has a content security policy that disallows it.</p>">
+
+<!ENTITY xssBlockMode.title "Blocked by XSS Filter">
+<!ENTITY xssBlockMode.longDesc "
+<ul>
+ <li>&brandShortName; blocked further actions on this page, because it contains
+ injected JavaScript code.</li>
+ <li>Loading of this page has been suspended because of either the explicit request
+ by the website to block this page in case of XSS attacks, or because &brandShortName;
+ has been configured to block pages in that situation.</li>
+</ul>
+">
+
+<!ENTITY corruptedContentError.title "Corrupted Content Error">
+<!ENTITY corruptedContentError.longDesc "<p>The page you are trying to view cannot be shown because an error in the data transmission was detected.</p><ul><li>Please contact the website owners to inform them of this problem.</li></ul>">
+<!ENTITY corruptedContentErrorv2.title "Corrupted Content Error">
+<!ENTITY corruptedContentErrorv2.longDesc "<p>The page you are trying to view cannot be shown because an error in the data transmission was detected.</p><ul><li>Please contact the website owners to inform them of this problem.</li></ul>">
+
+
+<!ENTITY securityOverride.linkText "Or you can add an exception…">
+<!ENTITY securityOverride.getMeOutOfHereButton "Get me out of here!">
+<!ENTITY securityOverride.exceptionButtonLabel "Add Exception…">
+
+<!-- LOCALIZATION NOTE (securityOverride.warningContent) - Do not translate the
+contents of the <button> tags. It uses strings already defined above. The
+button is included here (instead of netError.xhtml) because it exposes
+functionality specific to firefox. -->
+
+<!ENTITY securityOverride.warningContent "
+<p>You should not add an exception if you are using an internet connection that you do not trust completely or if you are not used to seeing a warning for this server.</p>
+
+<button id='getMeOutOfHereButton'>&securityOverride.getMeOutOfHereButton;</button>
+<button id='exceptionDialogButton'>&securityOverride.exceptionButtonLabel;</button>
+">
+
+<!ENTITY remoteXUL.title "Remote XUL">
+<!ENTITY remoteXUL.longDesc "<p><ul><li>Please contact the website owners to inform them of this problem.</li></ul></p>">
+
+<!ENTITY sslv3Used.title "Unable to Connect Securely">
+<!-- LOCALIZATION NOTE (sslv3Used.longDesc) - Do not translate
+ "ssl_error_unsupported_version". -->
+<!ENTITY sslv3Used.longDesc "Advanced info: ssl_error_unsupported_version">
+<!ENTITY sslv3Used.learnMore "Learn More…">
+<!-- LOCALIZATION NOTE (sslv3Used.longDesc2) - Do not translate
+ "SSL_ERROR_UNSUPPORTED_VERSION". -->
+<!ENTITY sslv3Used.longDesc2 "Advanced info: SSL_ERROR_UNSUPPORTED_VERSION">
+
+<!ENTITY weakCryptoUsed.title "Your connection is not secure">
+<!-- LOCALIZATION NOTE (weakCryptoUsed.longDesc2) - Do not translate
+ "SSL_ERROR_NO_CYPHER_OVERLAP". -->
+<!ENTITY weakCryptoUsed.longDesc2 "Advanced info: SSL_ERROR_NO_CYPHER_OVERLAP">
+<!ENTITY weakCryptoAdvanced.title "Advanced">
+<!ENTITY weakCryptoAdvanced.longDesc "<span class='hostname'></span> uses security technology that is outdated and vulnerable to attack. An attacker could easily reveal information which you thought to be safe.">
+<!ENTITY weakCryptoAdvanced.override "(Not secure) Try loading <span class='hostname'></span> using outdated security">
+
+<!-- LOCALIZATION NOTE (certerror.wrongSystemTime) - The <span id='..' /> tags will be injected with actual values,
+ please leave them unchanged. -->
+<!ENTITY certerror.wrongSystemTime "<p>A secure connection to <span id='wrongSystemTime_URL'/> isn’t possible because your clock appears to show the wrong time.</p> <p>Your computer thinks it is <span id='wrongSystemTime_systemDate'/>. To fix this problem, change your date and time settings to match the correct time.</p>">
+
+<!ENTITY certerror.pagetitle1 "Insecure Connection">
+<!ENTITY certerror.whatShouldIDo.badStsCertExplanation "This site uses HTTP
+Strict Transport Security (HSTS) to specify that &brandShortName; may only connect
+to it securely. As a result, it is not possible to add an exception for this
+certificate.">
+<!ENTITY certerror.copyToClipboard.label "Copy text to clipboard">
+
+<!ENTITY inadequateSecurityError.title "Your connection is not secure">
+<!-- LOCALIZATION NOTE (inadequateSecurityError.longDesc) - Do not translate
+ "NS_ERROR_NET_INADEQUATE_SECURITY". -->
+<!ENTITY inadequateSecurityError.longDesc "<p><span class='hostname'></span> uses security technology that is outdated and vulnerable to attack. An attacker could easily reveal information which you thought to be safe. The website administrator will need to fix the server first before you can visit the site.</p><p>Error code: NS_ERROR_NET_INADEQUATE_SECURITY</p>">
+
+<!ENTITY prefReset.longDesc "It looks like your network security settings might be causing this. Do you want the default settings to be restored?">
+<!ENTITY prefReset.label "Restore default settings">
diff --git a/locales/en-US/chrome/overrides/settingsChange.dtd b/locales/en-US/chrome/overrides/settingsChange.dtd
new file mode 100644
index 0000000..efbdf4e
--- /dev/null
+++ b/locales/en-US/chrome/overrides/settingsChange.dtd
@@ -0,0 +1,7 @@
+<!-- -*- 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/. -->
+
+<!ENTITY settingsChangePreferences.label "Settings can be changed using the Applications tab in &brandShortName;'s Preferences.">
+<!ENTITY settingsChangeOptions.label "Settings can be changed using the Applications tab in &brandShortName;'s Preferences.">
diff --git a/locales/en-US/crashreporter/crashreporter-override.ini b/locales/en-US/crashreporter/crashreporter-override.ini
new file mode 100644
index 0000000..3345d76
--- /dev/null
+++ b/locales/en-US/crashreporter/crashreporter-override.ini
@@ -0,0 +1,9 @@
+; 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 in the UTF-8 encoding
+[Strings]
+# LOCALIZATION NOTE (CrashReporterProductErrorText2): The %s is replaced with a string containing detailed information.
+CrashReporterProductErrorText2=Firefox had a problem and crashed. We'll try to restore your tabs and windows when it restarts.\n\nUnfortunately the crash reporter is unable to submit a crash report.\n\nDetails: %s
+CrashReporterDescriptionText2=Firefox had a problem and crashed. We'll try to restore your tabs and windows when it restarts.\n\nTo help us diagnose and fix the problem, you can send us a crash report.
diff --git a/locales/en-US/defines.inc b/locales/en-US/defines.inc
new file mode 100644
index 0000000..539b809
--- /dev/null
+++ b/locales/en-US/defines.inc
@@ -0,0 +1,12 @@
+# 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 emptyLines
+
+#define MOZ_LANGPACK_CREATOR palemoon.org
+
+# If non-English locales wish to credit multiple contributors, uncomment this
+# variable definition and use the format specified.
+# #define MOZ_LANGPACK_CONTRIBUTORS <em:contributor>Joe Solon</em:contributor> <em:contributor>Suzy Solon</em:contributor>
+
+#unfilter emptyLines
diff --git a/locales/en-US/installer/custom.properties b/locales/en-US/installer/custom.properties
new file mode 100644
index 0000000..8f95a19
--- /dev/null
+++ b/locales/en-US/installer/custom.properties
@@ -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/.
+
+# LOCALIZATION NOTE:
+
+# This file must be saved as UTF8
+
+# Accesskeys are defined by prefixing the letter that is to be used for the
+# accesskey with an ampersand (e.g. &).
+
+# Do not replace $BrandShortName, $BrandFullName, or $BrandFullNameDA with a
+# custom string and always use the same one as used by the en-US files.
+# $BrandFullNameDA allows the string to contain an ampersand (e.g. DA stands
+# for double ampersand) and prevents the letter following the ampersand from
+# being used as an accesskey.
+
+# You can use \n to create a newline in the string but only when the string
+# from en-US contains a \n.
+
+REG_APP_DESC=$BrandShortName delivers safe, easy web browsing. A familiar user interface, enhanced security features including protection from online identity theft, and integrated search let you get the most out of the web.
+CONTEXT_OPTIONS=$BrandShortName &Options
+CONTEXT_SAFE_MODE=$BrandShortName &Safe Mode
+OPTIONS_PAGE_TITLE=Setup Type
+OPTIONS_PAGE_SUBTITLE=Choose setup options
+SHORTCUTS_PAGE_TITLE=Set Up Shortcuts
+SHORTCUTS_PAGE_SUBTITLE=Create Program Icons
+COMPONENTS_PAGE_TITLE=Set Up Optional Components
+COMPONENTS_PAGE_SUBTITLE=Optional Recommended Components
+SUMMARY_PAGE_TITLE=Summary
+SUMMARY_PAGE_SUBTITLE=Ready to start installing $BrandShortName
+SUMMARY_INSTALLED_TO=$BrandShortName will be installed to the following location:
+SUMMARY_REBOOT_REQUIRED_INSTALL=A restart of your computer may be required to complete the installation.
+SUMMARY_REBOOT_REQUIRED_UNINSTALL=A restart of your computer may be required to complete the uninstall.
+SUMMARY_TAKE_DEFAULTS=U&se $BrandShortName as my default web browser
+SUMMARY_INSTALL_CLICK=Click Install to continue.
+SUMMARY_UPGRADE_CLICK=Click Upgrade to continue.
+SURVEY_TEXT=&Tell us what you thought of $BrandShortName
+LAUNCH_TEXT=&Launch $BrandShortName now
+CREATE_ICONS_DESC=Create icons for $BrandShortName:
+ICONS_DESKTOP=On my &Desktop
+ICONS_STARTMENU=In my &Start Menu Programs folder
+ICONS_QUICKLAUNCH=In my &Quick Launch bar
+WARN_MANUALLY_CLOSE_APP_INSTALL=$BrandShortName must be closed to proceed with the installation.\n\nPlease close $BrandShortName to continue.
+WARN_MANUALLY_CLOSE_APP_UNINSTALL=$BrandShortName must be closed to proceed with the uninstall.\n\nPlease close $BrandShortName to continue.
+WARN_MANUALLY_CLOSE_APP_LAUNCH=$BrandShortName is already running.\n\nPlease close $BrandShortName prior to launching the version you have just installed.
+WARN_WRITE_ACCESS=You don't have access to write to the installation directory.\n\nClick OK to select a different directory.
+WARN_DISK_SPACE=You don't have sufficient disk space to install to this location.\n\nClick OK to select a different location.
+WARN_MIN_SUPPORTED_OSVER_MSG=Sorry, $BrandShortName can't be installed. This version of $BrandShortName requires ${MinSupportedVer} or newer.
+WARN_MIN_SUPPORTED_CPU_MSG=Sorry, $BrandShortName can't be installed. This version of $BrandShortName requires a processor with ${MinSupportedCPU} support.
+WARN_MIN_SUPPORTED_OSVER_CPU_MSG=Sorry, $BrandShortName can't be installed. This version of $BrandShortName requires ${MinSupportedVer} or newer and a processor with ${MinSupportedCPU} support.
+WARN_RESTART_REQUIRED_UNINSTALL=Your computer must be restarted to complete a previous uninstall of $BrandShortName. Do you want to reboot now?
+WARN_RESTART_REQUIRED_UPGRADE=Your computer must be restarted to complete a previous upgrade of $BrandShortName. Do you want to reboot now?
+ERROR_CREATE_DIRECTORY_PREFIX=Error creating directory:
+ERROR_CREATE_DIRECTORY_SUFFIX=Click Cancel to stop the installation or\nRetry to try again.
+
+UN_CONFIRM_PAGE_TITLE=Uninstall $BrandFullName
+UN_CONFIRM_PAGE_SUBTITLE=Remove $BrandFullName from your computer.
+UN_CONFIRM_UNINSTALLED_FROM=$BrandShortName will be uninstalled from the following location:
+UN_CONFIRM_CLICK=Click Uninstall to continue.
+UN_REMOVE_PROFILES=&Remove my $BrandShortName personal data and customizations
+UN_REMOVE_PROFILES_DESC=This will permanently remove your bookmarks, saved passwords, cookies and customizations. You may wish to keep this information if you plan on installing another version of $BrandShortName in the future.
+
+BANNER_CHECK_EXISTING=Checking existing installation…
+
+STATUS_INSTALL_APP=Installing $BrandShortName…
+STATUS_INSTALL_LANG=Installing Language Files (${AB_CD})…
+STATUS_UNINSTALL_MAIN=Uninstalling $BrandShortName…
+STATUS_CLEANUP=A Little Housekeeping…
+
+# _DESC strings support approximately 65 characters per line.
+# One line
+OPTIONS_SUMMARY=Choose the type of setup you prefer, then click Next.
+# One line
+OPTION_STANDARD_DESC=$BrandShortName will be installed with the most common options.
+OPTION_STANDARD_RADIO=&Standard
+# Two lines
+OPTION_CUSTOM_DESC=You may choose individual options to be installed. Recommended for experienced users.
+OPTION_CUSTOM_RADIO=&Custom
+
+# LOCALIZATION NOTE:
+# The following text replaces the Install button text on the summary page.
+# Verify that the access key for InstallBtn (in override.properties) and
+# UPGRADE_BUTTON is not already used by SUMMARY_TAKE_DEFAULTS.
+UPGRADE_BUTTON=&Upgrade
diff --git a/locales/en-US/installer/mui.properties b/locales/en-US/installer/mui.properties
new file mode 100644
index 0000000..c786dbb
--- /dev/null
+++ b/locales/en-US/installer/mui.properties
@@ -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/.
+
+# To make the l10n tinderboxen see changes to this file you can change a value
+# name by adding - to the end of the name followed by chars (e.g. Branding-2).
+
+# LOCALIZATION NOTE:
+
+# This file must be saved as UTF8
+
+# Accesskeys are defined by prefixing the letter that is to be used for the
+# accesskey with an ampersand (e.g. &).
+
+# Do not replace $BrandShortName, $BrandFullName, or $BrandFullNameDA with a
+# custom string and always use the same one as used by the en-US files.
+# $BrandFullNameDA allows the string to contain an ampersand (e.g. DA stands
+# for double ampersand) and prevents the letter following the ampersand from
+# being used as an accesskey.
+
+# You can use \n to create a newline in the string but only when the string
+# from en-US contains a \n.
+
+MUI_TEXT_WELCOME_INFO_TITLE=Welcome to the $BrandFullNameDA Setup Wizard
+MUI_TEXT_WELCOME_INFO_TEXT=This wizard will guide you through the installation of $BrandFullNameDA.\n\nIt is recommended that you close all other applications before starting Setup. This will make it possible to update relevant system files without having to reboot your computer.\n\n$_CLICK
+MUI_TEXT_COMPONENTS_TITLE=Choose Components
+MUI_TEXT_COMPONENTS_SUBTITLE=Choose which features of $BrandFullNameDA you want to install.
+MUI_INNERTEXT_COMPONENTS_DESCRIPTION_TITLE=Description
+MUI_INNERTEXT_COMPONENTS_DESCRIPTION_INFO=Position your mouse over a component to see its description.
+MUI_TEXT_DIRECTORY_TITLE=Choose Install Location
+MUI_TEXT_DIRECTORY_SUBTITLE=Choose the folder in which to install $BrandFullNameDA.
+MUI_TEXT_INSTALLING_TITLE=Installing
+MUI_TEXT_INSTALLING_SUBTITLE=Please wait while $BrandFullNameDA is being installed.
+MUI_TEXT_FINISH_TITLE=Installation Complete
+MUI_TEXT_FINISH_SUBTITLE=Setup was completed successfully.
+MUI_TEXT_ABORT_TITLE=Installation Aborted
+MUI_TEXT_ABORT_SUBTITLE=Setup was not completed successfully.
+MUI_BUTTONTEXT_FINISH=&Finish
+MUI_TEXT_FINISH_INFO_TITLE=Completing the $BrandFullNameDA Setup Wizard
+MUI_TEXT_FINISH_INFO_TEXT=$BrandFullNameDA has been installed on your computer.\n\nClick Finish to close this wizard.
+MUI_TEXT_FINISH_INFO_REBOOT=Your computer must be restarted in order to complete the installation of $BrandFullNameDA. Do you want to reboot now?
+MUI_TEXT_FINISH_REBOOTNOW=Reboot now
+MUI_TEXT_FINISH_REBOOTLATER=I want to manually reboot later
+MUI_TEXT_STARTMENU_TITLE=Choose Start Menu Folder
+MUI_TEXT_STARTMENU_SUBTITLE=Choose a Start Menu folder for the $BrandFullNameDA shortcuts.
+MUI_INNERTEXT_STARTMENU_TOP=Select the Start Menu folder in which you would like to create the program's shortcuts. You can also enter a name to create a new folder.
+MUI_TEXT_ABORTWARNING=Are you sure you want to quit $BrandFullName Setup?
+MUI_UNTEXT_WELCOME_INFO_TITLE=Welcome to the $BrandFullNameDA Uninstall Wizard
+MUI_UNTEXT_WELCOME_INFO_TEXT=This wizard will guide you through the uninstallation of $BrandFullNameDA.\n\nBefore starting the uninstallation, make sure $BrandFullNameDA is not running.\n\n$_CLICK
+MUI_UNTEXT_CONFIRM_TITLE=Uninstall $BrandFullNameDA
+MUI_UNTEXT_CONFIRM_SUBTITLE=Remove $BrandFullNameDA from your computer.
+MUI_UNTEXT_UNINSTALLING_TITLE=Uninstalling
+MUI_UNTEXT_UNINSTALLING_SUBTITLE=Please wait while $BrandFullNameDA is being uninstalled.
+MUI_UNTEXT_FINISH_TITLE=Uninstallation Complete
+MUI_UNTEXT_FINISH_SUBTITLE=Uninstall was completed successfully.
+MUI_UNTEXT_ABORT_TITLE=Uninstallation Aborted
+MUI_UNTEXT_ABORT_SUBTITLE=Uninstall was not completed successfully.
+MUI_UNTEXT_FINISH_INFO_TITLE=Completing the $BrandFullNameDA Uninstall Wizard
+MUI_UNTEXT_FINISH_INFO_TEXT=$BrandFullNameDA has been uninstalled from your computer.\n\nClick Finish to close this wizard.
+MUI_UNTEXT_FINISH_INFO_REBOOT=Your computer must be restarted in order to complete the uninstallation of $BrandFullNameDA. Do you want to reboot now?
+MUI_UNTEXT_ABORTWARNING=Are you sure you want to quit $BrandFullName Uninstall?
diff --git a/locales/en-US/installer/override.properties b/locales/en-US/installer/override.properties
new file mode 100644
index 0000000..288f674
--- /dev/null
+++ b/locales/en-US/installer/override.properties
@@ -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/.
+
+# LOCALIZATION NOTE:
+
+# This file must be saved as UTF8
+
+# Accesskeys are defined by prefixing the letter that is to be used for the
+# accesskey with an ampersand (e.g. &).
+
+# Do not replace $BrandShortName, $BrandFullName, or $BrandFullNameDA with a
+# custom string and always use the same one as used by the en-US files.
+# $BrandFullNameDA allows the string to contain an ampersand (e.g. DA stands
+# for double ampersand) and prevents the letter following the ampersand from
+# being used as an accesskey.
+
+# You can use \n to create a newline in the string but only when the string
+# from en-US contains a \n.
+
+# Strings that require a space at the end should be enclosed with double
+# quotes and the double quotes will be removed. To add quotes to the beginning
+# and end of a strong enclose the add and additional double quote to the
+# beginning and end of the string (e.g. ""This will include quotes"").
+
+SetupCaption=$BrandFullName Setup
+UninstallCaption=$BrandFullName Uninstall
+BackBtn=< &Back
+NextBtn=&Next >
+AcceptBtn=I &accept the terms in the License Agreement
+DontAcceptBtn=I &do not accept the terms in the License Agreement
+InstallBtn=&Install
+UninstallBtn=&Uninstall
+CancelBtn=Cancel
+CloseBtn=&Close
+BrowseBtn=B&rowse…
+ShowDetailsBtn=Show &details
+ClickNext=Click Next to continue.
+ClickInstall=Click Install to start the installation.
+ClickUninstall=Click Uninstall to start the uninstallation.
+Completed=Completed
+LicenseTextRB=Please review the license agreement before installing $BrandFullNameDA. If you accept all terms of the agreement, select the first option below. $_CLICK
+ComponentsText=Check the components you want to install and uncheck the components you don't want to install. $_CLICK
+ComponentsSubText2_NoInstTypes=Select components to install:
+DirText=Setup will install $BrandFullNameDA in the following folder. To install in a different folder, click Browse and select another folder. $_CLICK
+DirSubText=Destination Folder
+DirBrowseText=Select the folder to install $BrandFullNameDA in:
+SpaceAvailable="Space available: "
+SpaceRequired="Space required: "
+UninstallingText=$BrandFullNameDA will be uninstalled from the following folder. $_CLICK
+UninstallingSubText=Uninstalling from:
+FileError=Error opening file for writing: \r\n\r\n$0\r\n\r\nClick Abort to stop the installation,\r\nRetry to try again, or\r\nIgnore to skip this file.
+FileError_NoIgnore=Error opening file for writing: \r\n\r\n$0\r\n\r\nClick Retry to try again, or\r\nCancel to stop the installation.
+CantWrite="Can't write: "
+CopyFailed=Copy failed
+CopyTo="Copy to "
+Registering="Registering: "
+Unregistering="Unregistering: "
+SymbolNotFound="Could not find symbol: "
+CouldNotLoad="Could not load: "
+CreateFolder="Create folder: "
+CreateShortcut="Create shortcut: "
+CreatedUninstaller="Created uninstaller: "
+Delete="Delete file: "
+DeleteOnReboot="Delete on reboot: "
+ErrorCreatingShortcut="Error creating shortcut: "
+ErrorCreating="Error creating: "
+ErrorDecompressing=Error decompressing data! Corrupted installer?
+ErrorRegistering=Error registering DLL
+ExecShell="ExecShell: "
+Exec="Execute: "
+Extract="Extract: "
+ErrorWriting="Extract: error writing to file "
+InvalidOpcode=Installer corrupted: invalid opcode
+NoOLE="No OLE for: "
+OutputFolder="Output folder: "
+RemoveFolder="Remove folder: "
+RenameOnReboot="Rename on reboot: "
+Rename="Rename: "
+Skipped="Skipped: "
+CopyDetails=Copy Details To Clipboard
+LogInstall=Log install process
+Byte=B
+Kilo=K
+Mega=M
+Giga=G
diff --git a/locales/en-US/palemoon-l10n.js b/locales/en-US/palemoon-l10n.js
new file mode 100644
index 0000000..642ad65
--- /dev/null
+++ b/locales/en-US/palemoon-l10n.js
@@ -0,0 +1,7 @@
+# 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
+
+pref("general.useragent.locale", "@AB_CD@");
diff --git a/locales/en-US/profile/bookmarks.inc b/locales/en-US/profile/bookmarks.inc
new file mode 100644
index 0000000..d2a701e
--- /dev/null
+++ b/locales/en-US/profile/bookmarks.inc
@@ -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/.
+#filter emptyLines
+
+# LOCALIZATION NOTE: The 'en-US' strings in the URLs will be replaced with
+# your locale code, and link to your translated pages as soon as they're
+# live.
+
+#define bookmarks_title Bookmarks
+#define bookmarks_heading Bookmarks
+
+#define bookmarks_toolbarfolder Bookmarks Toolbar Folder
+#define bookmarks_toolbarfolder_description Add bookmarks to this folder to see them displayed on the Bookmarks Toolbar
+
+# LOCALIZATION NOTE (getting_started):
+# link title for https://www.mozilla.org/en-US/firefox/central/
+#define getting_started Getting Started
+
+# LOCALIZATION NOTE (firefox_heading):
+# Firefox links folder name
+#define firefox_heading Mozilla Firefox
+
+# LOCALIZATION NOTE (firefox_help):
+# link title for https://www.mozilla.org/en-US/firefox/help/
+#define firefox_help Help and Tutorials
+
+# LOCALIZATION NOTE (firefox_customize):
+# link title for https://www.mozilla.org/en-US/firefox/customize/
+#define firefox_customize Customize Firefox
+
+# LOCALIZATION NOTE (firefox_community):
+# link title for https://www.mozilla.org/en-US/contribute/
+#define firefox_community Get Involved
+
+# LOCALIZATION NOTE (firefox_about):
+# link title for https://www.mozilla.org/en-US/about/
+#define firefox_about About Us
+
+#unfilter emptyLines
diff --git a/locales/en-US/profile/chrome/userChrome-example.css b/locales/en-US/profile/chrome/userChrome-example.css
new file mode 100644
index 0000000..2495795
--- /dev/null
+++ b/locales/en-US/profile/chrome/userChrome-example.css
@@ -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/. */
+
+/*
+ * Edit this file and copy it as userChrome.css into your
+ * profile-directory/chrome/
+ */
+
+/*
+ * This file can be used to customize the look of Mozilla's user interface
+ * You should consider using !important on rules which you want to
+ * override default settings.
+ */
+
+/*
+ * Do not remove the @namespace line -- it's required for correct functioning
+ */
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); /* set default namespace to XUL */
+
+
+/*
+ * Some possible accessibility enhancements:
+ */
+/*
+ * Make all the default font sizes 20 pt:
+ *
+ * * {
+ * font-size: 20pt !important
+ * }
+ */
+/*
+ * Make menu items in particular 15 pt instead of the default size:
+ *
+ * menupopup > * {
+ * font-size: 15pt !important
+ * }
+ */
+/*
+ * Give the Location (URL) Bar a fixed-width font
+ *
+ * #urlbar {
+ * font-family: monospace !important;
+ * }
+ */
+
+/*
+ * For more examples see http://www.mozilla.org/unix/customizing.html
+ */
+
diff --git a/locales/en-US/profile/chrome/userContent-example.css b/locales/en-US/profile/chrome/userContent-example.css
new file mode 100644
index 0000000..a90694d
--- /dev/null
+++ b/locales/en-US/profile/chrome/userContent-example.css
@@ -0,0 +1,32 @@
+/* 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/. */
+
+/*
+ * Edit this file and copy it as userContent.css into your
+ * profile-directory/chrome/
+ */
+
+/*
+ * This file can be used to apply a style to all web pages you view
+ * Rules without !important are overruled by author rules if the
+ * author sets any. Rules with !important overrule author rules.
+ */
+
+/*
+ * example: give all tables a 2px border
+ *
+ * table { border: 2px solid; }
+ */
+
+/*
+ * example: turn off "marquee" element
+ *
+ * marquee { -moz-binding: none; }
+ *
+ */
+
+/*
+ * For more examples see http://www.mozilla.org/unix/customizing.html
+ */
+
diff --git a/locales/en-US/searchplugins/amazondotcom.xml b/locales/en-US/searchplugins/amazondotcom.xml
new file mode 100644
index 0000000..56362e8
--- /dev/null
+++ b/locales/en-US/searchplugins/amazondotcom.xml
@@ -0,0 +1,15 @@
+<!-- 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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Amazon.com</ShortName>
+<Description>Amazon.com Search</Description>
+<InputEncoding>ISO-8859-1</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAC0AQAAJgAAACAgAAAAAAAA6QIAANoBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAF7SURBVDjLlZPLasJAFIaFRF+iVV+h6hO0GF+gVB9AaHwDt64qCG03tQgtdCFIuyhUelmGli66MXThSt24kNiFBUlAYi6ezjnNxSuawB/ITP7v/HNmJgQAEaZzpgHs/gwcTyTEXuXl2U6nA8ViEbK5HKler28CVRAwnB9ptVrAh8MrQuCaZ4iA8fzIqSgCxwzpTIaSuN/RWGwdYLwCUBQFZFkGSZLgqdmEE7YEN8VOAKyaSKUW4nNBAFmnYiKZpDRX1WqwBBzP089n5f/NEQsFL4WqqtsBWJlzDAJr5PwSMM1awEzzdxIbGI3Hvc6jCZeVFgRQRwpY7Qcw3ktgfpR8wLRxCPaot/X4GS95MppfF6DX9n2A3f+kAZycaT8bAZjU6r6B/duD6d3BYg9wQq/tkYzHY1blEiz5lmQyGc95mrO6r2CxgpjCBXgNsJVviolpXJiraeOIjJRE10juUa4sR8V+mO17VvmGqtuOcdNlwut8zTQJcJ0njifyB2bgTdKh6w4BAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACsElEQVRYw71XQWsTURBe2LQgeNKLB+tVemt6txcteNSD/QGC6VEIGDx5s+eKPQqFgJhLNdFLBWMP7cU0oSAWjB70koC9WHbVQ5SO8+XtS14mr7svyaYDH9m87Jv55puZt1nPi4yIzjMeMj7T9OwjI88455nGC1cZX+nsDESumJmPFDwIAqrX6z00Gg1qt9vjkJgFgUeuO16Vy3RjeZkyMzM9+MY1fsM9I9h9zyV7ZAznZrA4FAoFVwJ1z+WuOysrg1lnMolkHJX4k0igzI5sARYWF7vEZEk0rvO6iyUSuJfLJUqM7zYSqRDIra4OOUZPmNZsNrsl8UVTpkJAjh1GzmaSpJ8mAWmYeZB5urHRhW5SNOfUCCDo47W1bvPZsp2qAhipy3Nz1kaLG8dUCEBqM5AvpgElqFar01NgIZsdco7Zb7VasU2YigIYL5tjqCL7Q5YkFQXKlcqQ7DbHthIALk/IWAKor82xPIhshxWABCYioDMz51sexcVi0XoG4DPLIyvJjkTArK3scDQnRvO0MdTrUHGiKZCP4tNgO6BAEI08EQH9Z2Qow0hyPypJGIa9p6JWKCn4SA8jSKmJIDgyRvPJkcRxjfUwNGr/i8+Mo32iHzWiThBD4NM60bet9P77/ubA728RlTjMiwiH6zEEfvIrwdZFtQmMJ7W/ofIDBZD5m3mVZGwJcOP2kmILIlCkE45HoPWurwCSg0+UQRD4ZyXxId+T7gQb9+4q9sioY5ltrOG3L5vqXiiJffDx/aUi83ZJ7jr2ohcEu8Hh6/m+I7OWGiVxbWKHsz+O3vSOakqFQdsFgQeJUiKD7Wv9YKXBgCeSUC3v2kM5EJhlHDh3NcgcPlG1BXZu98sDmTuBa4fsMnz9fniJUaGzs+eMC540XuR0aDO2L8Y3qPyMcdOM+R/8XcqRA3qp9gAAAABJRU5ErkJggg==</Image>
+<Url type="text/html" method="GET" template="http://www.amazon.com/exec/obidos/external-search/">
+ <Param name="field-keywords" value="{searchTerms}"/>
+ <Param name="mode" value="blended"/>
+</Url>
+<SearchForm>http://www.amazon.com/</SearchForm>
+</SearchPlugin>
diff --git a/locales/en-US/searchplugins/answers.xml b/locales/en-US/searchplugins/answers.xml
new file mode 100644
index 0000000..b219e61
--- /dev/null
+++ b/locales/en-US/searchplugins/answers.xml
@@ -0,0 +1,16 @@
+<!-- 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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Answers.com</ShortName>
+<Description>Dictionary Search on Answers.com</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAABMLAAATCwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////K////4f////E////5f///+n////P////mv///0EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD///8E////fv///+//////////////////////7NnP/+LFtv/////+////of///xYAAAAAAAAAAAAAAAD///8D////lf////////////j0//bi1v/OlXf/tGU9/6FCEv/OmH3////////////////D////FgAAAAAAAAAA////avPm4P/evaz/8NbI//7r3//23M3/xYRi/5kzAP/Df1z//u/l//749P/v4dn/+PPw/////6j///8B////GP///+W/f1//smM7//bczf/+69///uvf/9ytlP+ZMwD/5se3/+/f1//AgmP/nj0N/927qv/////+////QP///2z/////8NvQ/8WCYP/+69///uvf//7r3/+7ckz/pUkb/9m1ov+ePQ3/okUW/8+fh//38O3//////////5r///+t//////7y6v/Cflv/58Cr//DRwP/mwKv/okQU/8ODYv/cuqj/yZN4//Tq5f/+9e///vDn///////////Q////yf/////+7+b/05yA/65ZLv+9dVD/sF40/5kzAP/kvKb//vTu//Tr5v/7+Pb//vfz//707f//////////6f///8X//////vDm/+K4ov/KjGz//ure/8uNbf+jRBX/+OTX/+3b0v+jSBr/pk0h/717Wv/Wrpr//Pn4/////+b///+i//////7z7f/02Mj/wn5b//vl2P+uWS7/vXhU//v49//48u//1q6a/717Wv+oUSb/tWxH//jz8P/////K////V///////+/j//ure/8aFZP/fs5v/oEAQ/9q1o/+zaEL/1ayX//718P/+9/P/+PHu//jz8P//////////h////wr////O///////38v/YpYr/tGQ7/6ZLHf/06eX/s2dB/549Df/x49z//vDn//7x6f//////////8////yoAAAAA////R/////v/////7dXI/5kzAP+7cUv//vHp/+vYzv+bNwX/vHlY//38/P///////////////30AAAAAAAAAAAAAAAD///9n////+/z5+P++e1n/3LGc//7w5//++PT/0KKL/8OIa//9/Pv//////////5X///8GAAAAAAAAAAAAAAAAAAAAAP///0n////K///////////////////////////+/v7/////5v///2z///8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////Cv///1f///+g////xP///8n///+r////bP///xoAAAAAAAAAAAAAAAAAAAAA+B////AH///AA///wAH//4AB//+AAP//AAD//wAA//8AAP//AAD//4AA//+AAf//wAP//+AD///wD////D///w==</Image>
+<Url type="text/html" method="GET" template="http://www.answers.com/main/ntquery">
+ <Param name="s" value="{searchTerms}"/>
+</Url>
+<Url type="application/x-suggestions+json" method="GET"
+ template="http://www.answers.com/main/startswith?output=json&amp;client=firefox&amp;s={searchTerms}"/>
+<SearchForm>http://www.answers.com/</SearchForm>
+</SearchPlugin>
diff --git a/locales/en-US/searchplugins/bing.xml b/locales/en-US/searchplugins/bing.xml
new file mode 100644
index 0000000..22019ce
--- /dev/null
+++ b/locales/en-US/searchplugins/bing.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+ <ShortName>Bing</ShortName>
+ <Description>Bing. Search by Microsoft.</Description>
+ <InputEncoding>UTF-8</InputEncoding>
+ <Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEACADaCwAAJgAAACAgAAABAAgAlAIAAAAMAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIAgAAAJCRaDYAAAAJcEhZcwAACxMAAAsTAQCanBgAAApPaUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAAeNqdU2dUU+kWPffe9EJLiICUS29SFQggUkKLgBSRJiohCRBKiCGh2RVRwRFFRQQbyKCIA46OgIwVUSwMigrYB+Qhoo6Do4iKyvvhe6Nr1rz35s3+tdc+56zznbPPB8AIDJZIM1E1gAypQh4R4IPHxMbh5C5AgQokcAAQCLNkIXP9IwEA+H48PCsiwAe+AAF40wsIAMBNm8AwHIf/D+pCmVwBgIQBwHSROEsIgBQAQHqOQqYAQEYBgJ2YJlMAoAQAYMtjYuMAUC0AYCd/5tMAgJ34mXsBAFuUIRUBoJEAIBNliEQAaDsArM9WikUAWDAAFGZLxDkA2C0AMElXZkgAsLcAwM4QC7IACAwAMFGIhSkABHsAYMgjI3gAhJkAFEbyVzzxK64Q5yoAAHiZsjy5JDlFgVsILXEHV1cuHijOSRcrFDZhAmGaQC7CeZkZMoE0D+DzzAAAoJEVEeCD8/14zg6uzs42jrYOXy3qvwb/ImJi4/7lz6twQAAA4XR+0f4sL7MagDsGgG3+oiXuBGheC6B194tmsg9AtQCg6dpX83D4fjw8RaGQudnZ5eTk2ErEQlthyld9/mfCX8BX/Wz5fjz89/XgvuIkgTJdgUcE+ODCzPRMpRzPkgmEYtzmj0f8twv//B3TIsRJYrlYKhTjURJxjkSajPMypSKJQpIpxSXS/2Ti3yz7Az7fNQCwaj4Be5EtqF1jA/ZLJxBYdMDi9wAA8rtvwdQoCAOAaIPhz3f/7z/9R6AlAIBmSZJxAABeRCQuVMqzP8cIAABEoIEqsEEb9MEYLMAGHMEF3MEL/GA2hEIkxMJCEEIKZIAccmAprIJCKIbNsB0qYC/UQB00wFFohpNwDi7CVbgOPXAP+mEInsEovIEJBEHICBNhIdqIAWKKWCOOCBeZhfghwUgEEoskIMmIFFEiS5E1SDFSilQgVUgd8j1yAjmHXEa6kTvIADKC/Ia8RzGUgbJRPdQMtUO5qDcahEaiC9BkdDGajxagm9BytBo9jDah59CraA/ajz5DxzDA6BgHM8RsMC7Gw0KxOCwJk2PLsSKsDKvGGrBWrAO7ifVjz7F3BBKBRcAJNgR3QiBhHkFIWExYTthIqCAcJDQR2gk3CQOEUcInIpOoS7QmuhH5xBhiMjGHWEgsI9YSjxMvEHuIQ8Q3JBKJQzInuZACSbGkVNIS0kbSblIj6SypmzRIGiOTydpka7IHOZQsICvIheSd5MPkM+Qb5CHyWwqdYkBxpPhT4ihSympKGeUQ5TTlBmWYMkFVo5pS3aihVBE1j1pCraG2Uq9Rh6gTNHWaOc2DFklLpa2ildMaaBdo92mv6HS6Ed2VHk6X0FfSy+lH6JfoA/R3DA2GFYPHiGcoGZsYBxhnGXcYr5hMphnTixnHVDA3MeuY55kPmW9VWCq2KnwVkcoKlUqVJpUbKi9Uqaqmqt6qC1XzVctUj6leU32uRlUzU+OpCdSWq1WqnVDrUxtTZ6k7qIeqZ6hvVD+kfln9iQZZw0zDT0OkUaCxX+O8xiALYxmzeCwhaw2rhnWBNcQmsc3ZfHYqu5j9HbuLPaqpoTlDM0ozV7NS85RmPwfjmHH4nHROCecop5fzforeFO8p4ikbpjRMuTFlXGuqlpeWWKtIq1GrR+u9Nq7tp52mvUW7WfuBDkHHSidcJ0dnj84FnedT2VPdpwqnFk09OvWuLqprpRuhu0R3v26n7pievl6Ankxvp955vef6HH0v/VT9bfqn9UcMWAazDCQG2wzOGDzFNXFvPB0vx9vxUUNdw0BDpWGVYZfhhJG50Tyj1UaNRg+MacZc4yTjbcZtxqMmBiYhJktN6k3umlJNuaYppjtMO0zHzczNos3WmTWbPTHXMueb55vXm9+3YFp4Wiy2qLa4ZUmy5FqmWe62vG6FWjlZpVhVWl2zRq2drSXWu627pxGnuU6TTque1mfDsPG2ybaptxmw5dgG2662bbZ9YWdiF2e3xa7D7pO9k326fY39PQcNh9kOqx1aHX5ztHIUOlY63prOnO4/fcX0lukvZ1jPEM/YM+O2E8spxGmdU5vTR2cXZ7lzg/OIi4lLgssulz4umxvG3ci95Ep09XFd4XrS9Z2bs5vC7ajbr+427mnuh9yfzDSfKZ5ZM3PQw8hD4FHl0T8Ln5Uwa9+sfk9DT4FntecjL2MvkVet17C3pXeq92HvFz72PnKf4z7jPDfeMt5ZX8w3wLfIt8tPw2+eX4XfQ38j/2T/ev/RAKeAJQFnA4mBQYFbAvv4enwhv44/Ottl9rLZ7UGMoLlBFUGPgq2C5cGtIWjI7JCtIffnmM6RzmkOhVB+6NbQB2HmYYvDfgwnhYeFV4Y/jnCIWBrRMZc1d9HcQ3PfRPpElkTem2cxTzmvLUo1Kj6qLmo82je6NLo/xi5mWczVWJ1YSWxLHDkuKq42bmy+3/zt84fineIL43sXmC/IXXB5oc7C9IWnFqkuEiw6lkBMiE44lPBBECqoFowl8hN3JY4KecIdwmciL9E20YjYQ1wqHk7ySCpNepLskbw1eSTFM6Us5bmEJ6mQvEwNTN2bOp4WmnYgbTI9Or0xg5KRkHFCqiFNk7Zn6mfmZnbLrGWFsv7Fbou3Lx6VB8lrs5CsBVktCrZCpuhUWijXKgeyZ2VXZr/Nico5lqueK83tzLPK25A3nO+f/+0SwhLhkralhktXLR1Y5r2sajmyPHF52wrjFQUrhlYGrDy4irYqbdVPq+1Xl65+vSZ6TWuBXsHKgsG1AWvrC1UK5YV969zX7V1PWC9Z37Vh+oadGz4ViYquFNsXlxV/2CjceOUbh2/Kv5nclLSpq8S5ZM9m0mbp5t4tnlsOlqqX5pcObg3Z2rQN31a07fX2Rdsvl80o27uDtkO5o788uLxlp8nOzTs/VKRU9FT6VDbu0t21Ydf4btHuG3u89jTs1dtbvPf9Psm+21UBVU3VZtVl+0n7s/c/romq6fiW+21drU5tce3HA9ID/QcjDrbXudTVHdI9VFKP1ivrRw7HH77+ne93LQ02DVWNnMbiI3BEeeTp9wnf9x4NOtp2jHus4QfTH3YdZx0vakKa8ppGm1Oa+1tiW7pPzD7R1ureevxH2x8PnDQ8WXlK81TJadrpgtOTZ/LPjJ2VnX1+LvncYNuitnvnY87fag9v77oQdOHSRf+L5zu8O85c8rh08rLb5RNXuFearzpfbep06jz+k9NPx7ucu5quuVxrue56vbV7ZvfpG543zt30vXnxFv/W1Z45Pd2983pv98X39d8W3X5yJ/3Oy7vZdyfurbxPvF/0QO1B2UPdh9U/W/7c2O/cf2rAd6Dz0dxH9waFg8/+kfWPD0MFj5mPy4YNhuueOD45OeI/cv3p/KdDz2TPJp4X/qL+y64XFi9++NXr187RmNGhl/KXk79tfKX96sDrGa/bxsLGHr7JeDMxXvRW++3Bd9x3He+j3w9P5Hwgfyj/aPmx9VPQp/uTGZOT/wQDmPP8YzMt2wAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAABBUlEQVR42mL8v4OBJMAEZ/0nTgMLnLXtitKRO9JmCi9cNR/wsP8mrOHfP8YbL4RvvBBWFXuvI/WGsJMYSHUSMujbY8LN/ttM4bmO1BtW5n+ENdipPmndbrHjqiIn6x9DuZc2yk8tlZ7hc5Kx/AtzxecMDAzff7Mcuys9/7gOAT8wMjAUOZ9x0XhI2A98HL+Eub/vuSG/8ozGmy+cEEF+zp/YNYjxfvPTv9O63fLpBx6ICCvz32DD24EGt7Fo4Gb/zcX2Z84RPbiIqfyLZJtL4rzfsDvJUf3R91+sC09o//7LJMn/NdXmkqHsSyzeQ0t8j9/znn8s7ql9Dy34cWogIbUSCQADAJ+jWQrH9LCsAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAIAAAD8GO2jAAACW0lEQVR4nGP8v5OBpoCJtsbTwQIWTKGUxe5mCi9M5V/oSL9mZf5HoQWMmHEQOD0AwuBg/WMo+8pB/ZGZwguyLcDiAzj48Zvl+D2pTz/YKLGAcBxcfSZCtulEWUAhGDQW3HstcOOF8P//jKRagC+SkQE/58/0pa7c7L9N5V+aKTw3kH3FxvKXmhYI83y3VXl64Jbs3htye2/IsbH8NZB9Zabw3FT+JR/nTypYwMDAEGBw+8AtWQj71x/mU/clT92XZGT8ry7+zlzxhbnic0n+LxRZIC/8yUju5blH4siC//8z3nghfOOF8MLj2jKCnydH7EXTRVoqCjC4g0f2yXteTEHSLNCVft0WcNhM4QXxiYmEIIIATcm3mpJvn37gmX7Q8OozYYLqycloTz/wLDulRYzpDMT4QFf6NZz95gvnyjMa+27I/SM6xxGwQJj7R6rtJQYGhk8/2NaeU9t+RfH3X2ZcihWEP5Fmgazg53qfY9zsv1ed0dh4UeXbL5yKudl/R5tdd9O6T4IFGhJvyz1OHbkts/qc2qfv7LiUMTIwOGk8irW4yo8jP2O3wEzxubHcy7I1Dq+/cOIymoGBQVn0Q5rtRTXx93jUYLFAX+b1sw88p+5L4tHGy/Er2uy6m9YDRsb/eJRht8BS+emCY7q4NDAyMLhpPYixuMbD/gu/0VD1WBtezz7w9O81vvNKEE1cTfxdmu0lZdEPxBiNzwIGBoa//xhXndFYfU4NUsnwcf6Ms7jmpPGQ1BoHpwUQcOOF0OT9RoayryJNr3Oz/ybRcCIsoBwMmkp/8FoAADmgy6ulKggYAAAAAElFTkSuQmCC</Image>
+ <Url type="application/x-suggestions+json" template="https://www.bing.com/osjson.aspx">
+ <Param name="query" value="{searchTerms}"/>
+ <Param name="form" value="OSDJAS"/>
+ </Url>
+ <Url type="text/html" method="GET" template="https://www.bing.com/search">
+ <Param name="q" value="{searchTerms}"/>
+ </Url>
+ <SearchForm>https://www.bing.com/search</SearchForm>
+</SearchPlugin>
diff --git a/locales/en-US/searchplugins/creativecommons.xml b/locales/en-US/searchplugins/creativecommons.xml
new file mode 100644
index 0000000..d9ac67e
--- /dev/null
+++ b/locales/en-US/searchplugins/creativecommons.xml
@@ -0,0 +1,14 @@
+<!-- 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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Creative Commons</ShortName>
+<Description>Find photos, movies, music, and text to rip, sample, mash, and share.</Description>
+<InputEncoding>utf-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAJUSURBVDiNjZO9S1thGMXPvTfJFbnkipNkLLS0ksFg0M0lf4CboNCEgIIg/RiKtEOn0qGWTtbVoBYcIji10I9J0ApWCjp0kRaXdhHjTW4+uGnur4NJ8GOwZ3nf4TnnfZ5z3scAdBGGYdyVdN+yrGHTNNOtVqsVhuG+pO+S3gE/LtV3BIxzPDJNc8FxHGN0dNRKpVIGoJ2dndr+/r5Vr9cl6bmkN0AoSQIEyHXdj5KYnZ3F932uolKpkM/nK5KQ9FmSCZwLOI7zQBLr6+vXiFdRLBaDtsiTTve3YrFYkM/nbyR3MDU1dSKpLumO+vr6Xruui+d5AFSrVVZWVtjY2KDRaABwdHTE4uIie3t7AJTLZaLRaFXSCyUSid1MJgOA53n09/eTTqdJJpPMzc2xurqKbduMj48Tj8fZ3d0FYHBw8FjSezmOU56fnwdgeXkZ27ap1WpUKhWazSZjY2Nks1kASqVSd4zp6eljSX/MtiHdRDpnEATyfb+bkiSVSqXu3TCM8xgHBga+dkY4OzvDdV2GhoZIJBLMzMxQKBSIRqNkMhlisRhbW1sAJJPJn5I+KB6Pv7poou/7rK2tsbm5SRAEXROXlpY4ODgAoFarYdu2J+llN8ZcLvffMeZyud+SGpLuCVBPT89jSRQKhRvJxWKxISmU9JTOT5Rk9Pb2fpHE5OQkJycn14inp6dMTEx4bdM/SbKAy8sk6WEkElmwLCuSSqUYGRmxgHB7e7t+eHgYazabgaRnkt7SeZnr63xbUtYwjGHTNNNhGP4F9iR9a6/zr4v1/wDE1D9XlC4rrAAAAABJRU5ErkJggg==</Image>
+<Url type="text/html" method="GET" template="http://search.creativecommons.org/">
+ <Param name="q" value="{searchTerms}"/>
+</Url>
+<SearchForm>http://search.creativecommons.org/</SearchForm>
+</SearchPlugin>
diff --git a/locales/en-US/searchplugins/duckduckgo-palemoon.xml b/locales/en-US/searchplugins/duckduckgo-palemoon.xml
new file mode 100644
index 0000000..57395e3
--- /dev/null
+++ b/locales/en-US/searchplugins/duckduckgo-palemoon.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
+ <ShortName>DuckDuckGo</ShortName>
+ <Description>Search DuckDuckGo</Description>
+ <InputEncoding>UTF-8</InputEncoding>
+ <LongName>Search Plugin for DuckDuckGo (HTTPS version)</LongName>
+ <Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAEAAATCwAAEwsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA11RgALs6oACbQ9wAj0v8AI9L/ACfQ9wAu0agANdUYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAzzN4CNdL/oK/z//////////////////////+jsPv/BDXX/wAz0t4AAAAAAAAAAAAAAAAAAAAAAAAAAAAyzvNSduD//////8jK/v+P+Lf/IbQL/17RPP+J3Y//wOKX//////9YeuX/ADLO8wAAAAAAAAAAAAAAAAAw091piOX/8/X9/1Fx5P9xhu//WOWZ/0W9Lv9Lwjn/J8BB/xyDAP9bdfL/9fP//2mI5v8AMNPdAAAAAAc610YRQ9f//////0Zr4P8AGdD/sb32////////////wrv//wAh1/8MPab/ACPc/05r4///////EkPX/wc610YANtWkrr/y/6S48P8AJ9L/AB3R/+/w/v///////////3+D7f8AQeL/AYTw/wFr5/8AMNb/p7Tv/6698v8AM9WkADLW//////8yXt//AC3V/wAw1/////////////z///8A0P7/AKb1/wWI7P8AuPf/AJ3w/zZW3P//////ADHV/wAx2P//////AzrZ/wAu1/84ZOL////////////e////AND//wC1+f8Atff/AZbv/wY62f8ELNf//////wAw1/8AMtn//////wAw2f8ALNn/kKrz////+//cwbH////////////R////Rcb8/wDO/f8A/P//AHzo//////8AMNj/ADXa//////8vXuL/ACna/4yq9///79T/jUkg/9i+r///////r2Q0/7Cozv8BKdr/AirY/zdZ4P//////ADTa/wI72tOuv/T/prr0/wAl2v+JqPb//7yW/+bUxv/9+/n////u//W+n/+Op/L/ADPd/wAv2v+ru/T/r7/0/wI72tMLQd1DEEjg//////9Cbef/ADng///////////////////////R3///AC3g/wAy3v9SeOn//////xFI4P8LQd1DAAAAAAM64PNmiuz/9/j//2mN7f/m7P3///////////9Cb+n/ACXd/wAt3v9rju3//////2iL7P8DOuDzAAAAAAAAAAAAAAAAAT3g/0p16f//////3OT8/3OS7v8AKt3/ACPc/zhn5/+xw/b//////0956v8CPeD/AAAAAAAAAAAAAAAAAAAAAAAAAAAEPODzBUDh/5uz8//7/f7/////////////////prz0/wtF4v8FQeDzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtF5kYDQOOkADrj/wA44v8AOeP/ADzk/wVB46QPReZGAAAAAAAAAAAAAAAAAAAAAPAPAADgBwAAwAMAAIABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIABAADAAwAA4AcAAPAPAAAoAAAAIAAAAEAAAAABACAAAAAAAAAQAAATCwAAEwsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAChIzyAnRNFwJ0TQryND0d8nRNH/J0TR/ydE0f8nRNH/I0PR3ydE0K8nRNFwKEjPIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAChE00AlRdK/J0XS/ydF0v8nRdL/XXPd/11z3f94i+P/k6Lp/5Oi6f9rf+D/NVDV/ydF0v8nRdL/JUXSvyhE00AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACBAzxAnRNOvJ0XT/ydF0/8lRdK/KEXSYOvu+6/+/v6//v7+v/39/c////////////7+/r/J0fOAKEXSYCVF0r8nRdP/J0XT/ydE068gQM8QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlRdUwJ0bT7ydG0/8nRtHPKETTQAAAAADHx8dA2vHhn5TYpN/o9+z/////////////////8PL83ydG0o8lRdUwAAAAAChE00AnRtHPJ0bT/ydG0+8lRdUwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKEXVYCdG1P8nRtT/KEbTgAAAAAAmRtZQI0PU38jIyP/F6s//Rrtk/0a7ZP9/yIr/c796/4vLkv+JpNf/M3Kq/zyWh/8zeKTfJkbWUAAAAAAoRtOAJ0bU/ydG1P8oRdVgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACVF1TAnR9X/J0fV/yhF1WAgQM8QJ0fTrydH1f9CW8//2tra/6Pdsv9Gu2T/Rrtk/0WzWv9Gu2T/Rrtk/0a7ZP9Gu2T/Rrtk/z6egP8nR9X/J0fTryBAzxAoRdVgJ0fV/ydH1f8lRdUwAAAAAAAAAAAAAAAAAAAAAAAAAAAgQM8QJ0fV7ydH1f8oSNVgIEDPECdH1c8nR9X/J0fV/1xwyf/t7e3/o92y/0a7ZP9Gu2T/Ra5U/0a7ZP9Gu2T/Rrtk/0a7ZP9Gu2T/Pp6A/ydH1f8nR9X/J0fVzyBAzxAoSNVgJ0fV/ydH1e8gQM8QAAAAAAAAAAAAAAAAAAAAACdH1q8nR9b/KEjVgCBQzxAnR9bPJ0fW/ydH1v8nR9b/gIzB//r6+v+j3bL/Rrtk/13Ed/+i26//ruG7/z6egf8+noH/Rrtk/0a7ZP86kI//J0fW/ydH1v8nR9b/J0fWzyBQzxAoSNWAJ0fW/ydH1q8AAAAAAAAAAAAAAAAoSNdAJkjW/yZH1s8AAAAAJEfWryZI1v8mSNb/JkjW/yZI1v+jqsT//////+j37P/R7tj////////////W3ff/JkjW/yZI1v8uZbr/PJeI/zJzrP8mSNb/JkjW/yZI1v8mSNb/JEfWrwAAAAAmR9bPJkjW/yhI10AAAAAAAAAAACVI1r8mSNf/KEjXQCZJ1lAmSNf/JkjX/yZI1/8mSNf/JkjX/9HR0f///////////////////////////5Ok6/8mSNf/JkjX/yZI1/8mSNf/JkjX/yZI1/8mSNf/JkjX/yZI1/8mSNf/JknWUChI10AmSNf/JUjWvwAAAAAoSNcgJknY/yZH2M8AAAAAI0nY3yZJ2P8mSdj/JknY/yZJ2P9KZM//39/f////////////////////////////XHfi/yZJ2P8mSdj/JknY/yZJ2P8mSdj/JknY/yZJ2P8mSdj/JknY/yZJ2P8jSdjfAAAAACZH2M8mSdj/KEjXICdJ2HAmSdj/JUjXYCVK2jAmSdj/JknY/yZJ2P8mSdj/JknY/2V4yf/t7e3///////////////////////////9cd+L/HXTj/xSf7/8Nwfj/CdL8/wnS/P8J0vz/ELDz/xt85v8mSdj/JknY/yZJ2P8lStowJUjXYCZJ2P8nSdhwJErZryZK2f8oSNcgJUnajyZK2f8mStn/JkrZ/yZK2f8mStn/iJPA////////////////////////////0ff+/xjV/P8J0vz/Drn1/xiO6/8Yjuv/GI7r/xCw8/8Lyvr/CdL8/xmF6P8mStn/JkrZ/yVJ2o8oSNcgJkrZ/yRK2a8jStrfI0rZ3wAAAAAlSdq/Jkra/yZK2v8mStr/Jkra/yZK2v+xtsf///////////////////////////8o2Pz/CdL8/wvK+v8mStr/Jkra/yZK2v8mStr/Jkra/yZK2v8iW97/Jkra/yZK2v8mStr/JUnavwAAAAAjStnfI0ra3yZK2v8lSdq/AAAAACZH2O8mStr/Jkra/yZK2v8mStr/L1HY/9HR0f///////////////////////////yjY/P8J0vz/CdL8/xCw9P8QsPT/ELD0/xSf7/8ddeX/Jkra/yZK2v8mStr/Jkra/yZK2v8mR9jvAAAAACVJ2r8mStr/Jkvb/yVJ2r8AAAAAJkvb/yZL2/8mS9v/Jkvb/yZL2/9KZtL/4+Pj////////////////////////////4Pn//0fd/f8J0vz/CdL8/wnS/P8J0vz/CdL8/wnS/P8Lyvr/Fpfu/yJc3/8mS9v/Jkvb/yZL2/8AAAAAJUnavyZL2/8mS9z/JUncvwAAAAAmS9z/Jkvc/yZL3P8mS9z/Jkvc/26AyP/x8fH//////////////////////////////////////9H3/v/C9P7/o+7+/2fa+/8Oufb/CdL8/wnS/P8J0vz/CdL8/xiP7P8mS9z/Jkvc/wAAAAAlSdy/Jkvc/yZM3P8lTNy/AAAAACZJ2e8mTNz/Jkzc/yZM3P8mTNz/iJTB////////////qnth/5VaOf/x6eX///////////////////////Hp5f/x6eX/ydL2/yZM3P8kVN7/G37o/xKo8v8QsfT/HXbm/yZM3P8mSdnvAAAAACVM3L8mTNz/I0vc3yZJ2u8AAAAAJUzevyZM3f8mTN3/Jkzd/yZM3f+fqc3///////////+VWjn/v5yI/+re1///////////////////////jk8s/7iRe//J0vb/Jkzd/yZM3f8mTN3/Jkzd/yZM3f8mTN3/Jkzd/yVM3r8AAAAAI0vc3yNL3N8kTd2vJk3d/yhQ3yAlTd2PJk3d/yZN3f8mTd3/Jk3d/6St0v////////////Hp5f/q3tf///////////////////////////+xhm7/49PK/6Cx8P8mTd3/Jk3d/yZN3f8mTd3/Jk3d/yZN3f8mTd3/JU3djyhQ3yAmTd3/JE3drydN33AmTd7/J03fcCVK3zAmTd7/Jk3e/yZN3v8mTd7/pK7S///////Sp5r/////////////////////////////////////////////////T27k/yZN3v8mTd7/Jk3e/yZN3v8mTd7/Jk3e/yZN3v8lSt8wJ03fcCZN3v8nTd9wKFDfICZO3/8mTt3PAAAAACVN3r8mTt//Jk7f/yZO3/+EltX//////+fRyv/SqaD/59LO///////////////////////at63/vIBy/7Glxf8mTt//Jk7f/yZO3/8mTt//Jk7f/yZO3/8mTt//JU3evwAAAAAmTt3PJk7f/yhQ3yAAAAAAJE/dryZO3/8oUN9AKFDfQCZO3/8mTt//Jk7f/zhb2v/o6/T/////////////////////////////////////////////////XHrn/yZO3/8mTt//Jk7f/yZO3/8mTt//Jk7f/yZO3/8oUN9AKFDfQCZO3/8kT92vAAAAAAAAAAAoUN9AJk7g/yZO4M8AAAAAJk/hnyZO4P8mTuD/Jk7g/05v5v/k6fv//////////////////////////////////////3eR7P8mTuD/Jk7g/yZO4P8mTuD/Jk7g/yZO4P8mTuD/Jk/hnwAAAAAmTuDPJk7g/yhQ30AAAAAAAAAAAAAAAAAjT+GfJU/h/yVO4Y8gUN8QIk7gzyVP4f8lT+H/SWnW/0lp1v+bq+H/8fHx/////////////////6Cy8v9OcOb/JU/h/yVP4f8lT+H/JU/h/yVP4f8lT+H/JU/h/yJO4M8gUN8QJU7hjyVP4f8jT+GfAAAAAAAAAAAAAAAAAAAAACBQ3xAlTOHvJU/h/yVQ4mAgUN8QIk7hzyVP4f+ktOv///////////////////////H0/f9phur/JU/h/yVP4f8lT+H/JU/h/yVP4f8lT+H/JU/h/yVP4f8iTuHPIFDfECVQ4mAlT+H/JUzh7yBQ3xAAAAAAAAAAAAAAAAAAAAAAAAAAACVQ3zAlUOLvJVDi/yVQ4mAgUN8QI1Din4mb2//J0/j/ydP4/6299P93ku3/M1vk/yVQ4v8lUOL/JVDi/yVQ4v8lUOL/JVDi/yVQ4v8lUOL/I1DinyBQ3xAlUOJgJVDi/yVQ4u8lUN8wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACVQ5DAlUOLvJVDi/yVQ4o8AAAAAJFDjQCVQ4r8lUOL/JVDi/yVQ4v8lUOL/JVDi/yVQ4v8lUOL/JVDi/yVQ4v8lUOL/JVDivyRQ40AAAAAAJVDijyVQ4v8lUOLvJVDkMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACVQ5DAjUeTfJVHj/yNR5N8kUONAAAAAACVQ5DAmUuOAJVHivyNR5N8lUeP/JVHj/yNR5N8lUeK/JlLjgCVQ5DAAAAAAJFDjQCNR5N8lUeP/I1Hk3yVQ5DAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACBQ3xAjUuSfJVHk/yVR5P8jUeTfJFLkcChQ5yAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAoUOcgJFLkcCNR5N8lUeT/JVHk/yNS5J8gUN8QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkUONAI1LknyVS5P8lUuT/JVLk/yVS5O8lUeS/JVHkvyVR5L8lUeS/JVLk7yVS5P8lUuT/JVLk/yRS468kUONAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIFDfECVS5GAjUuWfIlPlzyVS5f8lUuX/JVLl/yVS5f8iU+XPI1LlnyVS5GAgUN8QAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP/AA///AAD//AAAP/ggBB/wgAEP4AAAB8AAAAPAAAADiAAAEYAAAAEQAAAIAAAAAAAAAAAgAAAEIAAABCAAAAQgAAAEIAAABCAAAAQAAAAAAAAAABAAAAiAAAABiAAAEcAAAAPAAAAD4AAAB/CAAQ/4IAQf/AfgP/8AAP//wAP/</Image>
+ <Url type="text/html" method="get" template="https://duckduckgo.com/">
+ <Param name="t" value="palemoon"/>
+ <Param name="q" value="{searchTerms}"/>
+ </Url>
+ <Url type="application/x-suggestions+json" method="GET" template="https://duckduckgo.com/ac/">
+ <Param name="type" value="list"/>
+ <Param name="q" value="{searchTerms}"/>
+ </Url>
+</OpenSearchDescription>
diff --git a/locales/en-US/searchplugins/eBay.xml b/locales/en-US/searchplugins/eBay.xml
new file mode 100644
index 0000000..74af34c
--- /dev/null
+++ b/locales/en-US/searchplugins/eBay.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>eBay</ShortName>
+<Description>eBay - Online auctions</Description>
+<InputEncoding>ISO-8859-1</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAB6AQAAJgAAACAgAAAAAAAAQgMAAKABAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAFBSURBVDjLtZPdK0MBGIf3J5Babhx3rinFBWuipaUskX9DYvkopqgV90q5UJpyp0OKrUWM2VrRsS9D0zZKHGaOnW1nj4vtypVtPPe/533r9746QAAOAJXfo5Yzgg44pHrcugon/6Sgo0b+XuAOZ2iZiVQmyPoDpIwmUkYTzqM7GsdDdC7F6Lbf8pzOkfWOouzqeZem2b+2AqAV8zjD8yVBqqcf2b7C66yNiMGMfixIQSvi8Mp0LEbR5ADq1QSKWM+Gx0RC9nOZ2GLzwlIWdPWiuNzk4w/EpThNkyEAXKEP2ud8KGId2sspilhPMrmNwzfCuqePr/xbSfC5I/I0MMSj2YJ3z49gDdO2cEOrLUowJpE9G0QRG1ClKbR0EIdvmOPYcnUtnN+vsnZiQC1k/qnGagQ1n3LNzySUJZVskitnmr8BlQG7T2hvgxsAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAMJSURBVFjD7ZddSFNhGMeHXXQTZFFCWfR1pRhUECQlBdWVToo+6KYu1KigtDASG5qUfZgFZvahEDosECPDktKZS1FL+1DRnEvdUptjug91X2dnZzv/3vO6OZbWnR4v9sADL+fs7P97/s/znu2VAJD4UkpSSdKG+QubTyPBr+sXz8XCR64fIAHihVTis0SsUAoAVhEBrBKIHCGAEMB/ARi3F5LkbpS2WMRzYEEBXC2tsD6T03R9agsCGLNyqPw6CXmrBT06JvhbPHZwmkdwtR0B138PPKOHgzXD5jLAy3tmibo4K9weZwDAazJj/FQKRnfugfHMeRiTz0K3Ixam1HQKcPC+Fisu9NK1P08Uj4DleHgMdXC+WQ7nu3UEOhFMfTQcVUvQ1H4IN2sj8H2k7K+2TqCc3GseyA8AmDOzMBq7D9bS8sAr6nEJdNt3UbHVF1XQGtmZew8bTPT6tWoD3KpsUvlR8NxUoEICMvl6KQo+xqCwcRs4T8Ax5c8bFExjbAgAjO7aS8VsLypgq3g5nWStjztAhWRVhqAqeB6IuKTClkw1eNYEbrCQQBwD8yGGOsAooogLYejQPKBi7UPF9DkH+ezd+o141ZkUPAOC+L9SAMivNc7q46YMNSLTe4n1kaQF4XD3ZIDTPgU3XEYciKcAHrsGJS1xKFBGgyVzouiT4VbdGhjt/cEA5isyKsaz7jl3we7bg7Rqf6j0LoSldON4wWcqJDgQNGTN++l13vELA+MK6kKd6iryFOvxtidt9i5gO7owdjKJQliflNAU1pas6xQgnAzg1ux+lJEdILixNr0Pq9JUUA8NwVG9DM73G0jlcnh+V4BpjIWzJmIGQIjnXw5TiDuKSEwxurm3ITc8DNO51BnrLbIcsrW0dNA6RxgUKU1UdGVqLy5X6qGzTLvlnewiBZyGs3Yz6X8UeaYI3olvZDhzwLumZ+eHvooCCC0Q5VUsb4unwycM4YIDqA01tPqmgbzQr2EIYPECiPm33LYoDiZSsY9moh9O/Znoa4d9HkXtPg2pX/cPKCoRQ+ocZa4AAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="http://anywhere.ebay.com/services/suggest/">
+ <Param name="s" value="0"/>
+ <Param name="q" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="http://rover.ebay.com/rover/1/711-47294-18009-3/4">
+ <Param name="mpre" value="http://shop.ebay.com/?_nkw={searchTerms}"/>
+</Url>
+<SearchForm>http://search.ebay.com/</SearchForm>
+</SearchPlugin>
+
diff --git a/locales/en-US/searchplugins/ecosia.xml b/locales/en-US/searchplugins/ecosia.xml
new file mode 100644
index 0000000..04c8d22
--- /dev/null
+++ b/locales/en-US/searchplugins/ecosia.xml
@@ -0,0 +1,12 @@
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
+ <ShortName>Ecosia</ShortName>
+ <Description>Search Ecosia</Description>
+ <InputEncoding>UTF-8</InputEncoding>
+ <Contact>info@ecosia.org</Contact>
+ <LongName>Ecosia Search</LongName>
+ <Image width="16" height="16">
+ data:image/x-icon;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAAAACMuAAAjLgAAAAAAAAAAAAAAAAAAAAAAAAAAAAC8qzQBuaw3UrmsN6u5rDfruaw37bmsN+25rDfSuaw3fLmsNyAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC2rTokrLFGurqsNv+5rDf/uaw3/7msN/+5rDf/uaw3/7urNP/AqS7suqw2aAAAAAAAAAAAAAAAAAAAAAC/qjApkbpn4mvJlf/EqCr/uaw3/7msN/+5rDf/uaw3/7urNP+rsUj/ib5x/7qsNv+9qzKBAAAAAAAAAAC5rDcLwKkvzom9cf813Nb/lrlh/8KoLP+5rDf/uaw3/7msN//BqS3/eMSF/yXj6v+BwHv/lbli/7atO1IAAAAAuaw3bsCqL/+Rumb/K+Di/z3ZzP+dtln/vqox/7msN/+5rDf/waku/23Ikv8s4OH/ONvS/5m4Xv+7qzXZuaw3CbmsN9DBqS7/hL93/zDe3f8v393/RdbD/7OuPv+7qzX/uqw2/8WoKf99wn//Lt/e/y/e3f99wn//v6ow/7msN0+7qzT7s64+/0bWwf8y3tn/L97d/03TuP+usET/vKoz/7isOP+vr0P/XM6n/zDe3P813Nb/L97d/5O6Zf/EpymOu6s0/7OuPv8+2cv/J+Hn/1HStP+0rjz/vasy/76qMP9zxYr/NtzV/zTd1/823NX/NtzV/zLd2f9I1b//mbheqsGpLf+gtVX/bseR/3fEhv+wr0L/vaoy/7msN/+/qjD/Wc+q/yvg4/813Nb/Md7b/zfc1P833NT/Mt7a/zbc1aqHvnT6bMiT/522WP+wr0L/vqox/7msN/+5rDf/vaoy/6C1VP8/2cr/N9zT/2vJlf9hzKD/NtzU/zbc1f813NaONdzWz3HGjv9ky53/prNN/8SoKv+8qzT/uaw3/7msOP/EqCr/ecOE/0HYx/9V0K//N9vT/zXc1v823NX/NtzVTjXc120w3tz/Lt/e/0zUu/+Fv3X/rrBF/7msN/+7qzX/vaoy/6qxSf9G1sH/L9/d/zPd2P8x3tv/L9/e2C/f3Qk23NUKNtzVzDbc1v823NX/OdvQ/0nVvv+xr0H/ta07/7+qL/+7qzT/r69D/2LMoP823NX/VNGx/2TLnVEAAAAAAAAAADbc1Sc03dfgQNnJ/2bKm/862tD/pLRP/1vOqf9S0rP/ib1x/8CpL/+4rDj/qLJM/7qsNn4AAAAAAAAAAAAAAAAAAAAAM93YI0vUvLtux5H/VdGw/3DHj/9Zz6r/Xc2m/3rDgv+5rDf/u6s1672rM2gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAyaYjUburNaytsUbZuK056cGpLuS/qjDGuaw3gLmsNx4AAAAAAAAAAAAAAAAAAAAA+D8AAOAPAADAAwAAgAMAAIABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAABAACAAQAAgAMAAMAHAADgDwAA+B8AAA==
+ </Image>
+ <Url type="text/html" method="get" template="https://www.ecosia.org/search?q={searchTerms}&amp;addon=opensearch"/>
+ <Url type="application/x-suggestions+json" template="https://ac.ecosia.org/autocomplete/?q={searchTerms}&amp;type=list"/>
+</OpenSearchDescription>
diff --git a/locales/en-US/searchplugins/list.txt b/locales/en-US/searchplugins/list.txt
new file mode 100644
index 0000000..2f2ca32
--- /dev/null
+++ b/locales/en-US/searchplugins/list.txt
@@ -0,0 +1,6 @@
+duckduckgo-palemoon
+bing
+ecosia
+twitter
+wikipedia
+yahoo
diff --git a/locales/en-US/searchplugins/twitter.xml b/locales/en-US/searchplugins/twitter.xml
new file mode 100644
index 0000000..e1909e7
--- /dev/null
+++ b/locales/en-US/searchplugins/twitter.xml
@@ -0,0 +1,15 @@
+<!-- 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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Twitter</ShortName>
+<Description>Realtime Twitter Search</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAALAgAAJgAAACAgAAAAAAAAQQQAADECAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAHSSURBVDjLfVO/axRREP6SS0ihlv4FQpoYWxHyHxgCsUxhmToBS4tF7NLZWaiIEHLv7d5dJNEQ0tgYEAmIjeAfYC57++7MGbzLjxu/Nxt27zZrBoadnZnvm3kz7wEqMpZqmdAfBBMIZBzGVGCkorbXq4kFp/yPtBgPZCJzZvYQuNZ+jKhVo75G2LyHVz9uoeFmc6bILaHaepv9v/w6iRcypXbonmNPBB9OBbv82tYfmPgn7NHTnKAaP8E+g8Ztot68k/nXDm8ooNEVhHEP1vXROBbUf/M/Wc0Jwl8zsEkHOxcMtE6Y+A42nodpLtLfR9QWRMmA9jm2eh78aXQY4eF9VvjCgKDWEewM2PIZQc4nD9Sf6hk+sohNqorzW0kN91BBdYKtO9dE/00JZITA50XxsxTHlWarqMYBh/O3UPGqrednx7ox3o+UhbIRT7O1BO9PfIsXI5V9J34WvrqN94buwFi+b3N0k0nrCjAFAp1+Nz1Czd3N2y8Tm6ywYk9JTHKqk/cD9cM17YW89WExbo6Ja1zhvgK3+4Itql+rt8PkO1f6oBysBN3brLrMVhs84wHJvpHoMwnf8CI9ygZd3nbJgymrMvxeSoLjmlCsIJf+a17lP6juZmUWkMzvAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAECElEQVRYw+1XS2gTURS9ia0/EMSCCC4siCIKrly4UVwIrgRR3KgbXQmCCiIiCha7EQRR6cIupHShSeZjf6lttdWFgqBRBPGDKIqF1mQ+1Wp/SZvnvXfmTWfMpJ3U0pUPHplJ3rv33HO/Afi/5nPViTjuKm8rYlFlAoSI8a500R1SGLYUZRH+Hvee6+qqygvxW1KJ1XLd+74aUuY+0OzjoHw/BElj4zQQHxv0HDBUvjR9WQlNYmnJhSjK9dxFUC0LOsYE3M8LSI8LUKw8AmoG5ecqPtP8vgZ04zKeO+jqjUtBDi2qWY+71xPaKKpnpF0uzUpCnxDQOixQ+BSo9iR/3vsh4CF+r5of8D3F75r9udQAKUwx++AJXkhZCe9QBkGEuUSC1swTjhK0VrUmEYzw7SKCyUPrLwEP8IxufQN9aCfKPwCJwdq/lCPluvUG2n4jhQW8bD6Hu+amABvSd/LOTbEENOMjdIyicLuA1om/ADhbMqIZn/DMOMq+7iqPBanUzWfQOUGUjUFnnj5H0WdnQOlfFoxssdix3tjGgnWbrCuGKvdA4LmuSQJ6s9SN0zFwg6lS7Qmms+WngK4pEvAVg+w83MltCAAhAHSGFMwEgGQRq2quwZe2cShJkaYvtey39hEHMfsQL7cjxd1F4dBnPWUKVeMwaLmTkRggGd1oiJI9GxLcLg2NA8s5DTXzCKePbjtBJAVoVoGjmFxEwsgiCi5tFuoDDFinHcb9xWg6CBcjpS9ReReCeF9GOL5TsCEYhRkSs26HmSLXh2R2f8DlJS7Q7BZOwyhWzeZ3P2hijtJRG9wSXuQkIj27m1OKKKPUiQIiCv1ptF4x33rUh/Yb+aVqN8BjIf07HwDy0EPFzU2/ck0rgCpln8MgtOcBgOOiFizRSWt7aQCWA0GHdesSBuOAm+fFOQIoOFXVeBRqaGnvlsGIed5LwWiO/LP11KASg7tmt97fnRoz1Xj5NXc4WQP0Sliw6d4EN6mUdStyew8cbB6uQWGdXL+pPHMrjQLCVe40tHdo1NJoQ47X5XBnfKUykduBQ0U9+jEbKR5IOTc0IwfJb+tnjvwoq+3DCmxEV1iwvzyHtlzbSTnVHMRKuTm6crK8AcelDrGc/a/0r4WUsRcF3ebWzC4YCqt8Rbdo5XmO4IZj9IGSW+PFUuSVyu7BeeABIrdZMNFIfiSrCQQFouwDNHxQLyBQdK6nSPOfgftUSXWtaOlD67BkXkCLXrFV1IYJBAUifcpnspQHFjyjmi+4y8nBs6KIL1cJuRZkt6K1xzCYrvFAqdtptDCNz3dxX8V91Gsufqvn8r/CByJemd/kvJiprui/RAQkMaZR/r1i4W6a0rP/N/gXi6OvmDvBLoiyBV1/AN29Cs9hVFoUAAAAAElFTkSuQmCC</Image>
+<SearchForm>https://twitter.com/search/</SearchForm>
+<Url type="text/html" method="GET" template="https://twitter.com/search">
+ <Param name="q" value="{searchTerms}"/>
+ <Param name="source" value="desktop-search"/>
+</Url>
+</SearchPlugin>
diff --git a/locales/en-US/searchplugins/wikipedia.xml b/locales/en-US/searchplugins/wikipedia.xml
new file mode 100644
index 0000000..6bfb0cc
--- /dev/null
+++ b/locales/en-US/searchplugins/wikipedia.xml
@@ -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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Wikipedia (en)</ShortName>
+<Description>Wikipedia, the free encyclopedia</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAA4AQAAJgAAACAgAAAAAAAAJAMAAGQBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAEFSURBVDjLxZPRDYJAEESJoQjpgBoM/9IBtoAl4KcUQQlSAjYgJWAH0gPmyNtkzEEuxkQTPzawc3Ozc3MQTc/JfVPR/wW6a+eKQ+Hyfe54B2wvrfXVqXLDfTCMd3j0VHksrTcH9bl2aZq+BCgEwCCPj9E4TdPYGj0C9CYAKdkmBrIIxiIYbvpbb2sSl8AiA+ywAbJE5YLpCImLU/WRDyIAWRgu4k1s4v50ODru4haYSCk4ntkuM0wcMAINXiPKTJQ9CfgB40phBr8DyFjGKkKEhYhCY4iCDgpAYAM2EZBlhJnsZxQUYBNkSkfBvjDd0ttPeR0mxREQ+OhfYOJ6EmL+l/qzn2kGli9cAF3BOfkAAAAASUVORK5CYIKJUE5HDQoaCgAAAA1JSERSAAAAIAAAACAIBgAAAHN6evQAAAIKSURBVFjD7ZdBSgNRDIYLguAB7FLwAkXwBl0JgiDYjQcY8ARduBJKu3I5C0EoWDxAT9AL9AK9QBeCIHQlCM/3DZOSmeZNZ2r1bQyEGV7yXv7kJZlJq6XIOXfs+crzwPPTnvnR863n05ZFufDD/T595Q4eauM37u/pWYwfeX53cegcABcuHg0AkEQE8AKAu4gAXv8BrAEMh0PXbrddt9t1vV4v406nk62laeqm02n2LjKYIuK5WCyyfeiLDF32yLn6TJ5mBFarlev3+9nBMMqsabkYhmezWcEd2ctTE/tYBwhgt14BhtmAV2VaLpdrAHioCW+VdwWy9IMAUBQjJcQFTwGqvcTD+Xy+oc8askZJyAYrnKEokCeWLpQkSSZvBIANYgSDVVEQQJaeyHQu1QIgiQNb6AmrTtaQ9+RFSLa1D4iXgfsrVITloeSFFZlaAEjAUMaXo2DJWQtVRe1OKF5aJUkf0NdglXO5VzQGoI2USwwD3LEl590CtdO3QBoT5WSFV+Q63Oha17ITgMlkslGSGBWPdeNiDR2SL1B6zQFINmOAkFOW5eTSURCdvX6OdUlapaWjsKX0dgOg26/VWHSUKhrPz35ISKwq76R9Wx+kKgC1f0o5mISsypUG3kPj2L/lDzKYvEUwzoh2JtPRdQQAo1jD6afne88H1oTMeH6ZK+x7PB/lQ/CJtvkNEgDh1dr/bVYAAAAASUVORK5CYII=</Image>
+<Url type="application/x-suggestions+json" method="GET" template="http://en.wikipedia.org/w/api.php">
+ <Param name="action" value="opensearch"/>
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<Url type="text/html" method="GET" template="http://en.wikipedia.org/wiki/Special:Search">
+ <Param name="search" value="{searchTerms}"/>
+</Url>
+<SearchForm>http://en.wikipedia.org/wiki/Special:Search</SearchForm>
+</SearchPlugin>
diff --git a/locales/en-US/searchplugins/yahoo.xml b/locales/en-US/searchplugins/yahoo.xml
new file mode 100644
index 0000000..244e85f
--- /dev/null
+++ b/locales/en-US/searchplugins/yahoo.xml
@@ -0,0 +1,17 @@
+<!-- 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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Yahoo</ShortName>
+<Description>Yahoo Search</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAEACAA8DQAAJgAAACAgAAABAAgAowsAAGINAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAAJcEhZcwAACxMAAAsTAQCanBgAAApPaUNDUFBob3Rvc2hvcCBJQ0MgcHJvZmlsZQAAeNqdU2dUU+kWPffe9EJLiICUS29SFQggUkKLgBSRJiohCRBKiCGh2RVRwRFFRQQbyKCIA46OgIwVUSwMigrYB+Qhoo6Do4iKyvvhe6Nr1rz35s3+tdc+56zznbPPB8AIDJZIM1E1gAypQh4R4IPHxMbh5C5AgQokcAAQCLNkIXP9IwEA+H48PCsiwAe+AAF40wsIAMBNm8AwHIf/D+pCmVwBgIQBwHSROEsIgBQAQHqOQqYAQEYBgJ2YJlMAoAQAYMtjYuMAUC0AYCd/5tMAgJ34mXsBAFuUIRUBoJEAIBNliEQAaDsArM9WikUAWDAAFGZLxDkA2C0AMElXZkgAsLcAwM4QC7IACAwAMFGIhSkABHsAYMgjI3gAhJkAFEbyVzzxK64Q5yoAAHiZsjy5JDlFgVsILXEHV1cuHijOSRcrFDZhAmGaQC7CeZkZMoE0D+DzzAAAoJEVEeCD8/14zg6uzs42jrYOXy3qvwb/ImJi4/7lz6twQAAA4XR+0f4sL7MagDsGgG3+oiXuBGheC6B194tmsg9AtQCg6dpX83D4fjw8RaGQudnZ5eTk2ErEQlthyld9/mfCX8BX/Wz5fjz89/XgvuIkgTJdgUcE+ODCzPRMpRzPkgmEYtzmj0f8twv//B3TIsRJYrlYKhTjURJxjkSajPMypSKJQpIpxSXS/2Ti3yz7Az7fNQCwaj4Be5EtqF1jA/ZLJxBYdMDi9wAA8rtvwdQoCAOAaIPhz3f/7z/9R6AlAIBmSZJxAABeRCQuVMqzP8cIAABEoIEqsEEb9MEYLMAGHMEF3MEL/GA2hEIkxMJCEEIKZIAccmAprIJCKIbNsB0qYC/UQB00wFFohpNwDi7CVbgOPXAP+mEInsEovIEJBEHICBNhIdqIAWKKWCOOCBeZhfghwUgEEoskIMmIFFEiS5E1SDFSilQgVUgd8j1yAjmHXEa6kTvIADKC/Ia8RzGUgbJRPdQMtUO5qDcahEaiC9BkdDGajxagm9BytBo9jDah59CraA/ajz5DxzDA6BgHM8RsMC7Gw0KxOCwJk2PLsSKsDKvGGrBWrAO7ifVjz7F3BBKBRcAJNgR3QiBhHkFIWExYTthIqCAcJDQR2gk3CQOEUcInIpOoS7QmuhH5xBhiMjGHWEgsI9YSjxMvEHuIQ8Q3JBKJQzInuZACSbGkVNIS0kbSblIj6SypmzRIGiOTydpka7IHOZQsICvIheSd5MPkM+Qb5CHyWwqdYkBxpPhT4ihSympKGeUQ5TTlBmWYMkFVo5pS3aihVBE1j1pCraG2Uq9Rh6gTNHWaOc2DFklLpa2ildMaaBdo92mv6HS6Ed2VHk6X0FfSy+lH6JfoA/R3DA2GFYPHiGcoGZsYBxhnGXcYr5hMphnTixnHVDA3MeuY55kPmW9VWCq2KnwVkcoKlUqVJpUbKi9Uqaqmqt6qC1XzVctUj6leU32uRlUzU+OpCdSWq1WqnVDrUxtTZ6k7qIeqZ6hvVD+kfln9iQZZw0zDT0OkUaCxX+O8xiALYxmzeCwhaw2rhnWBNcQmsc3ZfHYqu5j9HbuLPaqpoTlDM0ozV7NS85RmPwfjmHH4nHROCecop5fzforeFO8p4ikbpjRMuTFlXGuqlpeWWKtIq1GrR+u9Nq7tp52mvUW7WfuBDkHHSidcJ0dnj84FnedT2VPdpwqnFk09OvWuLqprpRuhu0R3v26n7pievl6Ankxvp955vef6HH0v/VT9bfqn9UcMWAazDCQG2wzOGDzFNXFvPB0vx9vxUUNdw0BDpWGVYZfhhJG50Tyj1UaNRg+MacZc4yTjbcZtxqMmBiYhJktN6k3umlJNuaYppjtMO0zHzczNos3WmTWbPTHXMueb55vXm9+3YFp4Wiy2qLa4ZUmy5FqmWe62vG6FWjlZpVhVWl2zRq2drSXWu627pxGnuU6TTque1mfDsPG2ybaptxmw5dgG2662bbZ9YWdiF2e3xa7D7pO9k326fY39PQcNh9kOqx1aHX5ztHIUOlY63prOnO4/fcX0lukvZ1jPEM/YM+O2E8spxGmdU5vTR2cXZ7lzg/OIi4lLgssulz4umxvG3ci95Ep09XFd4XrS9Z2bs5vC7ajbr+427mnuh9yfzDSfKZ5ZM3PQw8hD4FHl0T8Ln5Uwa9+sfk9DT4FntecjL2MvkVet17C3pXeq92HvFz72PnKf4z7jPDfeMt5ZX8w3wLfIt8tPw2+eX4XfQ38j/2T/ev/RAKeAJQFnA4mBQYFbAvv4enwhv44/Ottl9rLZ7UGMoLlBFUGPgq2C5cGtIWjI7JCtIffnmM6RzmkOhVB+6NbQB2HmYYvDfgwnhYeFV4Y/jnCIWBrRMZc1d9HcQ3PfRPpElkTem2cxTzmvLUo1Kj6qLmo82je6NLo/xi5mWczVWJ1YSWxLHDkuKq42bmy+3/zt84fineIL43sXmC/IXXB5oc7C9IWnFqkuEiw6lkBMiE44lPBBECqoFowl8hN3JY4KecIdwmciL9E20YjYQ1wqHk7ySCpNepLskbw1eSTFM6Us5bmEJ6mQvEwNTN2bOp4WmnYgbTI9Or0xg5KRkHFCqiFNk7Zn6mfmZnbLrGWFsv7Fbou3Lx6VB8lrs5CsBVktCrZCpuhUWijXKgeyZ2VXZr/Nico5lqueK83tzLPK25A3nO+f/+0SwhLhkralhktXLR1Y5r2sajmyPHF52wrjFQUrhlYGrDy4irYqbdVPq+1Xl65+vSZ6TWuBXsHKgsG1AWvrC1UK5YV969zX7V1PWC9Z37Vh+oadGz4ViYquFNsXlxV/2CjceOUbh2/Kv5nclLSpq8S5ZM9m0mbp5t4tnlsOlqqX5pcObg3Z2rQN31a07fX2Rdsvl80o27uDtkO5o788uLxlp8nOzTs/VKRU9FT6VDbu0t21Ydf4btHuG3u89jTs1dtbvPf9Psm+21UBVU3VZtVl+0n7s/c/romq6fiW+21drU5tce3HA9ID/QcjDrbXudTVHdI9VFKP1ivrRw7HH77+ne93LQ02DVWNnMbiI3BEeeTp9wnf9x4NOtp2jHus4QfTH3YdZx0vakKa8ppGm1Oa+1tiW7pPzD7R1ureevxH2x8PnDQ8WXlK81TJadrpgtOTZ/LPjJ2VnX1+LvncYNuitnvnY87fag9v77oQdOHSRf+L5zu8O85c8rh08rLb5RNXuFearzpfbep06jz+k9NPx7ucu5quuVxrue56vbV7ZvfpG543zt30vXnxFv/W1Z45Pd2983pv98X39d8W3X5yJ/3Oy7vZdyfurbxPvF/0QO1B2UPdh9U/W/7c2O/cf2rAd6Dz0dxH9waFg8/+kfWPD0MFj5mPy4YNhuueOD45OeI/cv3p/KdDz2TPJp4X/qL+y64XFi9++NXr187RmNGhl/KXk79tfKX96sDrGa/bxsLGHr7JeDMxXvRW++3Bd9x3He+j3w9P5Hwgfyj/aPmx9VPQp/uTGZOT/wQDmPP8YzMt2wAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAACZ0lEQVR42mzSP4icZRTF4ee+38xOkp2sG5cQxVJIIaaKkICxTkqJjQhpJFYiop2F1YKFQqoUVpEoCBYSS7dfOxVFWGIsokUE/0TEye7OzPe977XYNWk83b0cDoffvXHWGxkKYjt0N1fi+FaJIzNIFSJ0kDXn0z5nF1O9Sp5PzaizamLD2NELo5W4sOwXqqX/04o1R2wg9PYs/GXUmTjqpGNxwvWdFzz19Akvjj+XUkYTggylFLfml93due+tZ7+y577BrkJnbNWke8yHmzvgi/4lq+WU1XjCsThl2p1ya3GZ4KNrt03KuhXH0SkkkbTOL5+u2PnuZ/D8axtGMTaKsbOvrINP3v/W3Y9XhCJjQCrUWRedVpaq3nvn7oHXrz8jD8PfvnEGbL0716LXytIoxqizkups4R/VwhB7hpi7sXkbXNo86bkrazK5sXnbEHND7BvMLcykOotz3vlxvZw+faRb08VEiVC64rPdSw/pZ/Ly9EutNi3TkHOLOvN3u3OnHNx7MFio5qq5Ifdce/WHhwEfXPnekPuq/UPPQhrAKOV0MFdyRFQFRefr7Z9wRrb0zfYd1aCpGmr2BvtSTkcp1wZLnX0tx4oQjeHX+UF97P75QGspM7VMqTfopVwb0aY1F4ZWlFK1SCVDHQKUEvphj0ztkEdrvZoLtOkoNS2XlkHJIlroIky7Jw8atDSJdQ/aPTUdtJBaLqVmlJpqQataCZKhY/L4HwcEI/Qbv1v8tivbIdVG1UtNnPVmFmPEoT9l/Dc9Ujp42Mx4uGl6I5pmgdjGzaLbopsdJqZHWZnqtKkXcZU8D/8OAPAMQ4kD8KK1AAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAByVJREFUWAm1l1uIldcVx9d3ruMZZzRaay+pCjFJH6LSRqxQqA1NH0pBiH3Qp774kEAg4EOkxKdQSCjUFvpm6YsNVNoSaGjFtmga2yZgCIIawdv04g2kM7Uz6lzO+c758v/t/9lzTB/61Oxhn7332muv9V+3vb8pnooDVRkzZ4oY/LmK6mQZa05frX6yFJ9Ae7x4qd2IuV1FFM9WMfhaI9Z+pQBAL+aiEZ0QgNBm2YuZmxHF9VZMXqmivFaLweUyuteWYvHGVPWr2f+F7YvF/ola9DZGVJsHUXs8YvBEK1ZrXt9URDwqxY1BdGMQvWjGqkgA+iLUtazHuADUoowHYugKTilaR7SIpZjWqOMRfY090RbasS4JglpFtzWIcqwZa+pSqnWVcLLXijXpZCFpvbgb/VhMe8huMLPylWkci8/oSD8xJq7hj4WUWvXrlbqVrUyKtBYdpX3Bh9YbzsdErwRgbZKyFP+KdqxPssu4l2hDAOOxIj6bCHigKWRNCcpMCHHHB4TJLc+TXxKHnC51Ct+Qgxl/TZ0qE5Be/EdWTwjqQuJJAPIB8qAZk4kZoXJnvHH+27Hq0+0YX12PH+w7E3/8zbWkitN2M8pS7kCKZ761OV55c2fcm+nG7J1e7N/+e3m2nbyKQcAhnHWZLC86B1rxiFRvSIkIgJHFVWzZ+qk4fG5HEr4wV8buVb+Vuv5QeVZsi/HeW//eHZ1HbNfLT5+Jc2dndBav9KXugfqc+pLsv6Xxvk6kVheumnpDnXlTVMZWfHh+Li6cdOKvmGzEC69+WTskzwr1SfUJ9ZWp7z/0pWXlF9+ejQtnUdCWnAxQ+al5Tdz80lIVEP8x9eZQWCQwOTAhNc34Re+rUW8U0S+r2Ns8nWzBKgONBOeX3V3RaCpPRN7XeFcO7yYl+InML2U3VdBVHszHzbSXYLBJkuTSQzBuphoYZ7X/u8O30gFAHHxzi+Yop8ETcfDXW5JyKMd/fFuO9l3mYuwLAl5gbMg8QuKdYQg4Zjcxo7HikMeIn37vcizes9Ide9bGhs9NLPN9YX0ndnzHpbZ4vx9HXr6kc6Sobo2hIkuzOnIh0xMFRlvc0waWL+p3UePCQ/Myjjx/JSnl59CJbUkJgl75g+ZD/D978Yrc7EuMPe4ESo6OYsaasiiX7tADAyny5cGtyMHsDxzFnP0Tx6Z0SfsW27B1PHZ+c13seGZdbNo2Lo6Iu7e7cfznfxc/8ggNQBhZI9dSs2c5k+rFaHBXmZhd32xTGdlZPvzDvefj9XddlgeObYVpuf1o3zkpyrEnCJwBDjlmr9i7XP3jgrYkDamhEqRA8UOBxZ53tcOtBbgyzr53M65f8DU6sVZ1o067cfFBvP+XGzrDOa5s+JkTShIc+dBtlLOLlRpqAUDc+yqQMnViNq81edDVnPixno/vP/dXjn2svbbnPa1RiqXEHVkYQ06RWygnFEtpbZDLAJws2X1OHgfCv+hiRkZU8Y+pmbjwzjTE1D48PR1TV+5IMErgsjex2A8TJrqCHH9Cw6U0BGBkPUWrKTZnPq4L9WqIOFvEO8ml+vbRvyUB/Jw6OiUa9GydM58qQl6lTrNHyiENrwyTkOvXLziVkMlOOsesVKyIFtZB1zfDAGvdyj4xtkD7yHQ8Ynn4hCrwvYA+DOJCSlXAZl3MjNQobNzVPK7gJm0AiPsQyEg0c6s1cbEB5X08AmDz1TTLucApzHHyJgADvUqVysJMKOSicLRQl+emOIvbnaw+ot2pSTzl5zzJVjPaZ6ix7zCSN4E1shOAWnqbyYH8bOqd1h9AGJ0qtl6LRBubcBKxbo6xh60kWlbLjgG4NJ2ETkwqbl7SeUXVSCq+BF1C2bWEgEO4CxBGvOydGmu3ooXv7AEogLFqn2JtWKO8yc9xAmDxjhGiWMOQXe63zCvHtIjOpGOIwvGJlhRQepyzaiu0MQ4MnFhuT7CiJQC+sUg4jtOYO+1IH9OdCwgBSmOkP2r60CarHeXMjxw3PGyvOBnN670EgOPOc1yEYgDYCxbqTPDXki1srChi4R6lpQ+uDmVFDtkA5GH1qJEvQFgacqCFT37pyP+Y+DMJs0Y54NgbiIVn61jhEUrNARuNIi3vOQf8iUeQuNzILe4b/jFZ7RDYJhTbVRaJTxyWh8PgO93hQJCBsSa2GQyyoLlBzWDxgnm9l0JgADgNgVxElCH22xs4NCsaieSUyzWXaSTLDAPlGQB0Kt6JaqpzYjkJQT9id60aNwqZjVqlz9Kqp+JcfDjOAqhirNoCI6MelpVPAjZ/CbFv45Y9YNcicqDMKm/Xo/FPJdMlqZ9SIK7qSrrci9mbl6q3/DGQ5f7XuK347rgKeuMgiicEfLPmT0rGY1K5SdI/ryritlMbJrr/PZ8+I8qf9PF8qhMrT39QHfHLkhj/fz/bi+eb83F/VxX1b6jWvt6KdTs/AvvCmqXE235jAAAAAElFTkSuQmCC</Image>
+<Url type="application/x-suggestions+json" method="GET"
+ template="https://ff.search.yahoo.com/gossip?output=fxjson&amp;command={searchTerms}" />
+<Url type="text/html" method="GET" template="https://search.yahoo.com/search">
+ <Param name="p" value="{searchTerms}"/>
+ <Param name="ei" value="UTF-8"/>
+</Url>
+<SearchForm>https://search.yahoo.com/</SearchForm>
+</SearchPlugin>
diff --git a/locales/en-US/updater/updater.ini b/locales/en-US/updater/updater.ini
new file mode 100644
index 0000000..6bc731f
--- /dev/null
+++ b/locales/en-US/updater/updater.ini
@@ -0,0 +1,10 @@
+; 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 in the UTF-8 encoding
+; All strings must be less than 600 chars.
+[Strings]
+TitleText=%MOZ_APP_DISPLAYNAME% Update
+InfoText=%MOZ_APP_DISPLAYNAME% is installing your updates and will start in a few moments…
+MozillaMaintenanceDescription=The Mozilla Maintenance Service ensures that you have the latest and most secure version of Mozilla Firefox on your computer. Keeping Firefox up to date is very important for your online security, and Mozilla strongly recommends that you keep this service enabled.
diff --git a/locales/filter.py b/locales/filter.py
new file mode 100644
index 0000000..8e097db
--- /dev/null
+++ b/locales/filter.py
@@ -0,0 +1,37 @@
+# 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/.
+
+def test(mod, path, entity = None):
+ import re
+ # ignore anything but Firefox
+ if mod not in ("netwerk", "dom", "toolkit", "security/manager",
+ "browser", "browser/metro", "webapprt",
+ "extensions/reporter", "extensions/spellcheck",
+ "other-licenses/branding/firefox",
+ "browser/branding/official",
+ "services/sync"):
+ return False
+ if mod != "browser" and mod != "extensions/spellcheck":
+ # we only have exceptions for browser and extensions/spellcheck
+ return True
+ if not entity:
+ if mod == "extensions/spellcheck":
+ return False
+ # browser
+ return not (re.match(r"searchplugins\/.+\.xml", path) or
+ re.match(r"chrome\/help\/images\/[A-Za-z-_]+\.png", path))
+ if mod == "extensions/spellcheck":
+ # l10n ships en-US dictionary or something, do compare
+ return True
+ if path == "defines.inc":
+ return entity != "MOZ_LANGPACK_CONTRIBUTORS"
+
+ if path != "chrome/browser-region/region.properties":
+ # only region.properties exceptions remain, compare all others
+ return True
+
+ return not (re.match(r"browser\.search\.order\.[1-9]", entity) or
+ re.match(r"browser\.contentHandlers\.types\.[0-5]", entity) or
+ re.match(r"goanna\.handlerService\.schemes\.", entity) or
+ re.match(r"goanna\.handlerService\.defaultHandlersVersion", entity))
diff --git a/locales/generic/extract-bookmarks.py b/locales/generic/extract-bookmarks.py
new file mode 100644
index 0000000..7bf711f
--- /dev/null
+++ b/locales/generic/extract-bookmarks.py
@@ -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/.
+
+import sys
+import re
+import codecs
+try:
+ from Mozilla.Parser import getParser
+except ImportError:
+ sys.exit('''extract-bookmarks needs compare-locales
+
+Find that on http://pypi.python.org/pypi/compare-locales.
+This script has been tested with version 0.6, and might work with future
+versions.''')
+
+ll=re.compile('\.(title|a|dd|h[0-9])$')
+
+p = getParser(sys.argv[1])
+p.readFile(sys.argv[1])
+
+template = '''#filter emptyLines
+
+# LOCALIZATION NOTE: The 'en-US' strings in the URLs will be replaced with
+# your locale code, and link to your translated pages as soon as they're
+# live.
+
+#define bookmarks_title %s
+#define bookmarks_heading %s
+
+#define bookmarks_toolbarfolder %s
+#define bookmarks_toolbarfolder_description %s
+
+# LOCALIZATION NOTE (getting_started):
+# link title for https://www.mozilla.org/en-US/firefox/central/
+#define getting_started %s
+
+# LOCALIZATION NOTE (firefox_heading):
+# Firefox links folder name
+#define firefox_heading %s
+
+# LOCALIZATION NOTE (firefox_help):
+# link title for https://www.mozilla.org/en-US/firefox/help/
+#define firefox_help %s
+
+# LOCALIZATION NOTE (firefox_customize):
+# link title for https://www.mozilla.org/en-US/firefox/customize/
+#define firefox_customize %s
+
+# LOCALIZATION NOTE (firefox_community):
+# link title for https://www.mozilla.org/en-US/contribute/
+#define firefox_community %s
+
+# LOCALIZATION NOTE (firefox_about):
+# link title for https://www.mozilla.org/en-US/about/
+#define firefox_about %s
+
+#unfilter emptyLines'''
+
+strings = tuple(e.val for e in p if ll.search(e.key))
+
+print codecs.utf_8_encode(template % strings)[0]
diff --git a/locales/generic/profile/bookmarks.html.in b/locales/generic/profile/bookmarks.html.in
new file mode 100644
index 0000000..90e3adf
--- /dev/null
+++ b/locales/generic/profile/bookmarks.html.in
@@ -0,0 +1,19 @@
+#filter substitution
+<!DOCTYPE NETSCAPE-Bookmark-file-1>
+<!-- This is an automatically generated file.
+ It will be read and overwritten.
+ DO NOT EDIT! -->
+<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
+<TITLE>@bookmarks_title@</TITLE>
+<H1>@bookmarks_heading@</H1>
+
+<DL><p>
+ <DT><H3 PERSONAL_TOOLBAR_FOLDER="true" ID="rdf:#$FvPhC3">@bookmarks_toolbarfolder@</H3>
+<DD>@bookmarks_toolbarfolder_description@
+ <DL><p>
+ <DT><A HREF="http://www.palemoon.org/" ICON_URI="http://www.palemoon.org/favicon.ico" ICON="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAFXRFWHRDcmVhdGlvbiBUaW1lAAfjBQQNKi3s6zvVAAAAB3RJTUUH4wUFDiY4QniqfgAAAAlwSFlzAAAK8AAACvABQqw0mAAAAn1JREFUeNpVU9FKW0EUnLO7NzdFY5uqRSMSoSj1F/paP0EEoU/2H/pWIkiLX+GzH5HnQj9BENQqpSgWkdbeu/fubudsCKQ3CSS7M3PmzDmRb5f3mH1SShsGcmAF7wTYaGMC35cQjBNwYoxczeJlKpASrAFGHYOP3cKUJMO3Cb4JMPwhIggJdZNwLEaOKBiU52bIp73S7jp+iyyJFNG2/JBlSBYC6KrsWhnR1XYQ2Sc1GuRKcTTXMbudwqpcfokxUPtamV/heGWdgaVKYWXPpvRp0sLF/ZB3Z72uKx0B3ocswis8VQ0CRf7yTMWflRZTh00bq1awpS18IK8kXltBQWDbBroK2X7Byxc9m9tIk7Qyju66EThwjHhHyS3DQpG0dSYVEELM9rWdyKskmmDMEpaphjpAnOw4VhuayAMGBi85cX0UzF7xVEfUdJNyV5Ltu5wNYIJsOK1GPji6TFKBkm3wJxq6iLQkMWVXhkmqkAktHHF/qgDnfXsZUhi03mTVqm7gFKiDzuPUXWg4FabPqbdRRVWcZOcuiJXx02P1VgjU8HJM2rtWysFOzh1dcXSaPizolEKdue5Yvp7fDn+efz+LtS81Bt2YIHn1Jn0SqLO31ubwtLzlGfekerm5vuWss1fzz3vHtxc/RpE9q4cmkcCKUW2yGc2EFLQpTdcei8OVL8a567zKCyuLR78f6u37m7s9iS0rE1HrhD1qilXC6vyIddCr/mr/dH516bNy8ypz3mHwZrD/anPt0JuyqkOBqtJlKrhMFk2dUPkID1MtvV45XNsevCcn/vdvnD6+btbvbn4dPNw+7lSVH+pZOde96i8vjJcH/ZNO6a5n8f8A5KRcUpQlS3kAAAAASUVORK5CYII=">Pale Moon</A>
+ <DT><A HREF="https://forum.palemoon.org/index.php" ICON_URI="https://forum.palemoon.org/favicon.ico" ICON="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAFXRFWHRDcmVhdGlvbiBUaW1lAAfjBQQNKi3s6zvVAAAAB3RJTUUH4wUFDiY4QniqfgAAAAlwSFlzAAAK8AAACvABQqw0mAAAAn1JREFUeNpVU9FKW0EUnLO7NzdFY5uqRSMSoSj1F/paP0EEoU/2H/pWIkiLX+GzH5HnQj9BENQqpSgWkdbeu/fubudsCKQ3CSS7M3PmzDmRb5f3mH1SShsGcmAF7wTYaGMC35cQjBNwYoxczeJlKpASrAFGHYOP3cKUJMO3Cb4JMPwhIggJdZNwLEaOKBiU52bIp73S7jp+iyyJFNG2/JBlSBYC6KrsWhnR1XYQ2Sc1GuRKcTTXMbudwqpcfokxUPtamV/heGWdgaVKYWXPpvRp0sLF/ZB3Z72uKx0B3ocswis8VQ0CRf7yTMWflRZTh00bq1awpS18IK8kXltBQWDbBroK2X7Byxc9m9tIk7Qyju66EThwjHhHyS3DQpG0dSYVEELM9rWdyKskmmDMEpaphjpAnOw4VhuayAMGBi85cX0UzF7xVEfUdJNyV5Ltu5wNYIJsOK1GPji6TFKBkm3wJxq6iLQkMWVXhkmqkAktHHF/qgDnfXsZUhi03mTVqm7gFKiDzuPUXWg4FabPqbdRRVWcZOcuiJXx02P1VgjU8HJM2rtWysFOzh1dcXSaPizolEKdue5Yvp7fDn+efz+LtS81Bt2YIHn1Jn0SqLO31ubwtLzlGfekerm5vuWss1fzz3vHtxc/RpE9q4cmkcCKUW2yGc2EFLQpTdcei8OVL8a567zKCyuLR78f6u37m7s9iS0rE1HrhD1qilXC6vyIddCr/mr/dH516bNy8ypz3mHwZrD/anPt0JuyqkOBqtJlKrhMFk2dUPkID1MtvV45XNsevCcn/vdvnD6+btbvbn4dPNw+7lSVH+pZOde96i8vjJcH/ZNO6a5n8f8A5KRcUpQlS3kAAAAASUVORK5CYII=" LAST_CHARSET="UTF-8">Pale Moon forum</A>
+ <DT><A HREF="http://www.palemoon.org/faq.shtml" ICON_URI="http://www.palemoon.org/favicon.ico" ICON="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAFXRFWHRDcmVhdGlvbiBUaW1lAAfjBQQNKi3s6zvVAAAAB3RJTUUH4wUFDiY4QniqfgAAAAlwSFlzAAAK8AAACvABQqw0mAAAAn1JREFUeNpVU9FKW0EUnLO7NzdFY5uqRSMSoSj1F/paP0EEoU/2H/pWIkiLX+GzH5HnQj9BENQqpSgWkdbeu/fubudsCKQ3CSS7M3PmzDmRb5f3mH1SShsGcmAF7wTYaGMC35cQjBNwYoxczeJlKpASrAFGHYOP3cKUJMO3Cb4JMPwhIggJdZNwLEaOKBiU52bIp73S7jp+iyyJFNG2/JBlSBYC6KrsWhnR1XYQ2Sc1GuRKcTTXMbudwqpcfokxUPtamV/heGWdgaVKYWXPpvRp0sLF/ZB3Z72uKx0B3ocswis8VQ0CRf7yTMWflRZTh00bq1awpS18IK8kXltBQWDbBroK2X7Byxc9m9tIk7Qyju66EThwjHhHyS3DQpG0dSYVEELM9rWdyKskmmDMEpaphjpAnOw4VhuayAMGBi85cX0UzF7xVEfUdJNyV5Ltu5wNYIJsOK1GPji6TFKBkm3wJxq6iLQkMWVXhkmqkAktHHF/qgDnfXsZUhi03mTVqm7gFKiDzuPUXWg4FabPqbdRRVWcZOcuiJXx02P1VgjU8HJM2rtWysFOzh1dcXSaPizolEKdue5Yvp7fDn+efz+LtS81Bt2YIHn1Jn0SqLO31ubwtLzlGfekerm5vuWss1fzz3vHtxc/RpE9q4cmkcCKUW2yGc2EFLQpTdcei8OVL8a567zKCyuLR78f6u37m7s9iS0rE1HrhD1qilXC6vyIddCr/mr/dH516bNy8ypz3mHwZrD/anPt0JuyqkOBqtJlKrhMFk2dUPkID1MtvV45XNsevCcn/vdvnD6+btbvbn4dPNw+7lSVH+pZOde96i8vjJcH/ZNO6a5n8f8A5KRcUpQlS3kAAAAASUVORK5CYII=">F.A.Q.</A>
+ <DT><A HREF="http://www.palemoon.org/releasenotes.shtml" ICON_URI="http://www.palemoon.org/favicon.ico" ICON="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAFXRFWHRDcmVhdGlvbiBUaW1lAAfjBQQNKi3s6zvVAAAAB3RJTUUH4wUFDiY4QniqfgAAAAlwSFlzAAAK8AAACvABQqw0mAAAAn1JREFUeNpVU9FKW0EUnLO7NzdFY5uqRSMSoSj1F/paP0EEoU/2H/pWIkiLX+GzH5HnQj9BENQqpSgWkdbeu/fubudsCKQ3CSS7M3PmzDmRb5f3mH1SShsGcmAF7wTYaGMC35cQjBNwYoxczeJlKpASrAFGHYOP3cKUJMO3Cb4JMPwhIggJdZNwLEaOKBiU52bIp73S7jp+iyyJFNG2/JBlSBYC6KrsWhnR1XYQ2Sc1GuRKcTTXMbudwqpcfokxUPtamV/heGWdgaVKYWXPpvRp0sLF/ZB3Z72uKx0B3ocswis8VQ0CRf7yTMWflRZTh00bq1awpS18IK8kXltBQWDbBroK2X7Byxc9m9tIk7Qyju66EThwjHhHyS3DQpG0dSYVEELM9rWdyKskmmDMEpaphjpAnOw4VhuayAMGBi85cX0UzF7xVEfUdJNyV5Ltu5wNYIJsOK1GPji6TFKBkm3wJxq6iLQkMWVXhkmqkAktHHF/qgDnfXsZUhi03mTVqm7gFKiDzuPUXWg4FabPqbdRRVWcZOcuiJXx02P1VgjU8HJM2rtWysFOzh1dcXSaPizolEKdue5Yvp7fDn+efz+LtS81Bt2YIHn1Jn0SqLO31ubwtLzlGfekerm5vuWss1fzz3vHtxc/RpE9q4cmkcCKUW2yGc2EFLQpTdcei8OVL8a567zKCyuLR78f6u37m7s9iS0rE1HrhD1qilXC6vyIddCr/mr/dH516bNy8ypz3mHwZrD/anPt0JuyqkOBqtJlKrhMFk2dUPkID1MtvV45XNsevCcn/vdvnD6+btbvbn4dPNw+7lSVH+pZOde96i8vjJcH/ZNO6a5n8f8A5KRcUpQlS3kAAAAASUVORK5CYII=">Release notes</A>
+ </DL><p>
+</DL><p>
diff --git a/locales/generic/profile/localstore.rdf b/locales/generic/profile/localstore.rdf
new file mode 100644
index 0000000..cc4b3b5
--- /dev/null
+++ b/locales/generic/profile/localstore.rdf
@@ -0,0 +1,9 @@
+<?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/. -->
+
+<RDF:RDF
+ xmlns:NC="http://home.netscape.com/NC-rdf#"
+ xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+</RDF:RDF>
diff --git a/locales/generic/profile/mimeTypes.rdf b/locales/generic/profile/mimeTypes.rdf
new file mode 100644
index 0000000..8407940
--- /dev/null
+++ b/locales/generic/profile/mimeTypes.rdf
@@ -0,0 +1,17 @@
+<?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/. -->
+
+
+<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:NC="http://home.netscape.com/NC-rdf#"
+ xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+
+ <Description about="urn:mimetypes">
+ <NC:MIME-types>
+ <Seq about="urn:mimetypes:root">
+ </Seq>
+ </NC:MIME-types>
+ </Description>
+</RDF>
diff --git a/locales/jar.mn b/locales/jar.mn
new file mode 100644
index 0000000..5fcee24
--- /dev/null
+++ b/locales/jar.mn
@@ -0,0 +1,97 @@
+#filter substitution
+# 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/.
+
+
+@AB_CD@.jar:
+% locale browser @AB_CD@ %locale/browser/
+ locale/browser/aboutCertError.dtd (%chrome/browser/aboutCertError.dtd)
+ locale/browser/aboutDialog.dtd (%chrome/browser/aboutDialog.dtd)
+ locale/browser/aboutPrivateBrowsing.dtd (%chrome/browser/aboutPrivateBrowsing.dtd)
+* locale/browser/aboutHome.dtd (%chrome/browser/aboutHome.dtd)
+ locale/browser/aboutSessionRestore.dtd (%chrome/browser/aboutSessionRestore.dtd)
+#ifdef MOZ_SERVICES_SYNC
+ locale/browser/syncProgress.dtd (%chrome/browser/syncProgress.dtd)
+ locale/browser/aboutSyncTabs.dtd (%chrome/browser/aboutSyncTabs.dtd)
+#endif
+* locale/browser/browser.dtd (%chrome/browser/browser.dtd)
+ locale/browser/baseMenuOverlay.dtd (%chrome/browser/baseMenuOverlay.dtd)
+ locale/browser/charsetOverlay.dtd (%chrome/browser/charsetOverlay.dtd)
+ locale/browser/browser.properties (%chrome/browser/browser.properties)
+ locale/browser/charsetMenu.properties (%chrome/browser/charsetMenu.properties)
+ locale/browser/charsetMenu.dtd (%chrome/browser/charsetMenu.dtd)
+ locale/browser/newTab.dtd (%chrome/browser/newTab.dtd)
+ locale/browser/newTab.properties (%chrome/browser/newTab.properties)
+ locale/browser/openLocation.dtd (%chrome/browser/openLocation.dtd)
+ locale/browser/openLocation.properties (%chrome/browser/openLocation.properties)
+ locale/browser/pageInfo.dtd (%chrome/browser/pageInfo.dtd)
+ locale/browser/pageInfo.properties (%chrome/browser/pageInfo.properties)
+ locale/browser/palemoon.dtd (%chrome/browser/palemoon.dtd)
+ locale/browser/quitDialog.properties (%chrome/browser/quitDialog.properties)
+ locale/browser/safeMode.dtd (%chrome/browser/safeMode.dtd)
+ locale/browser/sanitize.dtd (%chrome/browser/sanitize.dtd)
+ locale/browser/search.properties (%chrome/browser/search.properties)
+ locale/browser/searchbar.dtd (%chrome/browser/searchbar.dtd)
+ locale/browser/engineManager.dtd (%chrome/browser/engineManager.dtd)
+ locale/browser/engineManager.properties (%chrome/browser/engineManager.properties)
+ locale/browser/setDesktopBackground.dtd (%chrome/browser/setDesktopBackground.dtd)
+ locale/browser/shellservice.properties (%chrome/browser/shellservice.properties)
+ locale/browser/statusbar/statusbar-overlay.dtd (%chrome/browser/statusbar/statusbar-overlay.dtd)
+ locale/browser/statusbar/statusbar-prefs.dtd (%chrome/browser/statusbar/statusbar-prefs.dtd)
+ locale/browser/statusbar/meta.properties (%chrome/browser/statusbar/meta.properties)
+ locale/browser/statusbar/overlay.properties (%chrome/browser/statusbar/overlay.properties)
+ locale/browser/statusbar/prefs.properties (%chrome/browser/statusbar/prefs.properties)
+ locale/browser/tabbrowser.dtd (%chrome/browser/tabbrowser.dtd)
+ locale/browser/tabbrowser.properties (%chrome/browser/tabbrowser.properties)
+ locale/browser/taskbar.properties (%chrome/browser/taskbar.properties)
+ locale/browser/downloads/downloads.dtd (%chrome/browser/downloads/downloads.dtd)
+ locale/browser/downloads/downloads.properties (%chrome/browser/downloads/downloads.properties)
+ locale/browser/places/places.dtd (%chrome/browser/places/places.dtd)
+ locale/browser/places/places.properties (%chrome/browser/places/places.properties)
+ locale/browser/places/editBookmarkOverlay.dtd (%chrome/browser/places/editBookmarkOverlay.dtd)
+ locale/browser/places/bookmarkProperties.properties (%chrome/browser/places/bookmarkProperties.properties)
+ locale/browser/preferences/selectBookmark.dtd (%chrome/browser/preferences/selectBookmark.dtd)
+ locale/browser/places/moveBookmarks.dtd (%chrome/browser/places/moveBookmarks.dtd)
+ locale/browser/feeds/subscribe.dtd (%chrome/browser/feeds/subscribe.dtd)
+ locale/browser/feeds/subscribe.properties (%chrome/browser/feeds/subscribe.properties)
+ locale/browser/permissions/aboutPermissions.dtd (%chrome/browser/permissions/aboutPermissions.dtd)
+ locale/browser/permissions/aboutPermissions.properties (%chrome/browser/permissions/aboutPermissions.properties)
+ locale/browser/preferences/advanced.dtd (%chrome/browser/preferences/advanced.dtd)
+ locale/browser/preferences/applicationManager.dtd (%chrome/browser/preferences/applicationManager.dtd)
+ locale/browser/preferences/applicationManager.properties (%chrome/browser/preferences/applicationManager.properties)
+ locale/browser/preferences/colors.dtd (%chrome/browser/preferences/colors.dtd)
+ locale/browser/preferences/cookies.dtd (%chrome/browser/preferences/cookies.dtd)
+ locale/browser/preferences/content.dtd (%chrome/browser/preferences/content.dtd)
+ locale/browser/preferences/connection.dtd (%chrome/browser/preferences/connection.dtd)
+ locale/browser/preferences/applications.dtd (%chrome/browser/preferences/applications.dtd)
+ locale/browser/preferences/fonts.dtd (%chrome/browser/preferences/fonts.dtd)
+ locale/browser/preferences/main.dtd (%chrome/browser/preferences/main.dtd)
+ locale/browser/preferences/languages.dtd (%chrome/browser/preferences/languages.dtd)
+ locale/browser/preferences/permissions.dtd (%chrome/browser/preferences/permissions.dtd)
+ locale/browser/preferences/preferences.dtd (%chrome/browser/preferences/preferences.dtd)
+ locale/browser/preferences/preferences.properties (%chrome/browser/preferences/preferences.properties)
+ locale/browser/preferences/privacy.dtd (%chrome/browser/preferences/privacy.dtd)
+ locale/browser/preferences/security.dtd (%chrome/browser/preferences/security.dtd)
+#ifdef MOZ_SERVICES_SYNC
+ locale/browser/preferences/sync.dtd (%chrome/browser/preferences/sync.dtd)
+#endif
+ locale/browser/preferences/tabs.dtd (%chrome/browser/preferences/tabs.dtd)
+#ifdef MOZ_SERVICES_SYNC
+ locale/browser/syncBrand.dtd (%chrome/browser/syncBrand.dtd)
+ locale/browser/syncSetup.dtd (%chrome/browser/syncSetup.dtd)
+ locale/browser/syncSetup.properties (%chrome/browser/syncSetup.properties)
+ locale/browser/syncGenericChange.properties (%chrome/browser/syncGenericChange.properties)
+ locale/browser/syncKey.dtd (%chrome/browser/syncKey.dtd)
+ locale/browser/syncQuota.dtd (%chrome/browser/syncQuota.dtd)
+ locale/browser/syncQuota.properties (%chrome/browser/syncQuota.properties)
+#endif
+% locale browser-region @AB_CD@ %locale/browser-region/
+ locale/browser-region/region.properties (%chrome/browser-region/region.properties)
+# the following files are browser-specific overrides
+ locale/browser/netError.dtd (%chrome/overrides/netError.dtd)
+ locale/browser/appstrings.properties (%chrome/overrides/appstrings.properties)
+ locale/browser/downloads/settingsChange.dtd (%chrome/overrides/settingsChange.dtd)
+% override chrome://global/locale/netError.dtd chrome://browser/locale/netError.dtd
+% override chrome://global/locale/appstrings.properties chrome://browser/locale/appstrings.properties
+% override chrome://mozapps/locale/downloads/settingsChange.dtd chrome://browser/locale/downloads/settingsChange.dtd
diff --git a/locales/l10n.ini b/locales/l10n.ini
new file mode 100644
index 0000000..9a466b6
--- /dev/null
+++ b/locales/l10n.ini
@@ -0,0 +1,22 @@
+# 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/.
+
+[general]
+depth = ../../..
+all = application/palemoon/locales/all-locales
+
+[compare]
+dirs = application/palemoon
+ extensions/reporter
+ other-licenses/branding/firefox
+ application/palemoon/branding/official
+
+[includes]
+# non-central apps might want to use %(topsrcdir)s here, or other vars
+# RFE: that needs to be supported by compare-locales, too, though
+toolkit = toolkit/locales/l10n.ini
+services_sync = services/sync/locales/l10n.ini
+
+[extras]
+dirs = extensions/spellcheck
diff --git a/locales/moz.build b/locales/moz.build
new file mode 100644
index 0000000..c97072b
--- /dev/null
+++ b/locales/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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/locales/shipped-locales b/locales/shipped-locales
new file mode 100644
index 0000000..96bd960
--- /dev/null
+++ b/locales/shipped-locales
@@ -0,0 +1,90 @@
+ach
+af
+ak
+ar
+as
+ast
+be
+bg
+bn-BD
+bn-IN
+br
+bs
+ca
+cs
+csb
+cy
+da
+de
+el
+en-GB
+en-US
+en-ZA
+eo
+es-AR
+es-CL
+es-ES
+es-MX
+et
+eu
+fa
+ff
+fi
+fr
+fy-NL
+ga-IE
+gd
+gl
+gu-IN
+he
+hi-IN
+hr
+hu
+hy-AM
+id
+is
+it
+ja linux win32
+ja-JP-mac osx
+kk
+km
+kn
+ko
+ku
+lg
+lij
+lt
+lv
+mai
+mk
+ml
+mr
+nb-NO
+nl
+nn-NO
+nso
+or
+pa-IN
+pl
+pt-BR
+pt-PT
+rm
+ro
+ru
+si
+sk
+sl
+son
+sq
+sr
+sv-SE
+ta
+ta-LK
+te
+th
+tr
+uk
+vi
+zh-CN
+zh-TW
+zu
diff --git a/modules/AboutHomeUtils.jsm b/modules/AboutHomeUtils.jsm
new file mode 100644
index 0000000..72712e1
--- /dev/null
+++ b/modules/AboutHomeUtils.jsm
@@ -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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [ "AboutHomeUtils" ];
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+this.AboutHomeUtils = {
+ /**
+ * Returns an object containing the name and searchURL of the original default
+ * search engine.
+ */
+ get defaultSearchEngine() {
+ let defaultEngine = Services.search.defaultEngine;
+ let submission = defaultEngine.getSubmission("_searchTerms_", null, "homepage");
+
+ return Object.freeze({
+ name: defaultEngine.name,
+ searchURL: submission.uri.spec,
+ postDataString: submission.postDataString
+ });
+ },
+
+ /*
+ * showKnowYourRights - Determines if the user should be shown the
+ * about:rights notification. The notification should *not* be shown if
+ * we've already shown the current version, or if the override pref says to
+ * never show it. The notification *should* be shown if it's never been seen
+ * before, if a newer version is available, or if the override pref says to
+ * always show it.
+ */
+ get showKnowYourRights() {
+ // Look for an unconditional override pref. If set, do what it says.
+ // (true --> never show, false --> always show)
+ try {
+ return !Services.prefs.getBoolPref("browser.rights.override");
+ } catch (e) { }
+ // Ditto, for the legacy EULA pref.
+ try {
+ return !Services.prefs.getBoolPref("browser.EULA.override");
+ } catch (e) { }
+
+#ifndef MC_OFFICIAL
+ // Non-official builds shouldn't show the notification.
+ return false;
+#endif
+
+ // Look to see if the user has seen the current version or not.
+ var currentVersion = Services.prefs.getIntPref("browser.rights.version");
+ try {
+ return !Services.prefs.getBoolPref("browser.rights." + currentVersion + ".shown");
+ } catch (e) { }
+
+ // Legacy: If the user accepted a EULA, we won't annoy them with the
+ // equivalent about:rights page until the version changes.
+ try {
+ return !Services.prefs.getBoolPref("browser.EULA." + currentVersion + ".accepted");
+ } catch (e) { }
+
+ // We haven't shown the notification before, so do so now.
+ return true;
+ }
+};
diff --git a/modules/AutoCompletePopup.jsm b/modules/AutoCompletePopup.jsm
new file mode 100644
index 0000000..c3698f9
--- /dev/null
+++ b/modules/AutoCompletePopup.jsm
@@ -0,0 +1,293 @@
+/* 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 Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+this.EXPORTED_SYMBOLS = [ "AutoCompletePopup" ];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+// nsITreeView implementation that feeds the autocomplete popup
+// with the search data.
+var AutoCompleteTreeView = {
+ // nsISupports
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsITreeView,
+ Ci.nsIAutoCompleteController]),
+
+ // Private variables
+ treeBox: null,
+ results: [],
+
+ // nsITreeView
+ selection: null,
+
+ get rowCount() { return this.results.length; },
+ setTree: function(treeBox) { this.treeBox = treeBox; },
+ getCellText: function(idx, column) { return this.results[idx].value },
+ isContainer: function(idx) { return false; },
+ getCellValue: function(idx, column) { return false },
+ isContainerOpen: function(idx) { return false; },
+ isContainerEmpty: function(idx) { return false; },
+ isSeparator: function(idx) { return false; },
+ isSorted: function() { return false; },
+ isEditable: function(idx, column) { return false; },
+ canDrop: function(idx, orientation, dt) { return false; },
+ getLevel: function(idx) { return 0; },
+ getParentIndex: function(idx) { return -1; },
+ hasNextSibling: function(idx, after) { return idx < this.results.length - 1 },
+ toggleOpenState: function(idx) { },
+ getCellProperties: function(idx, column) { return this.results[idx].style || ""; },
+ getRowProperties: function(idx) { return ""; },
+ getImageSrc: function(idx, column) { return null; },
+ getProgressMode : function(idx, column) { },
+ cycleHeader: function(column) { },
+ cycleCell: function(idx, column) { },
+ selectionChanged: function() { },
+ performAction: function(action) { },
+ performActionOnCell: function(action, index, column) { },
+ getColumnProperties: function(column) { return ""; },
+
+ // nsIAutoCompleteController
+ get matchCount() {
+ return this.rowCount;
+ },
+
+ handleEnter: function(aIsPopupSelection) {
+ AutoCompletePopup.handleEnter(aIsPopupSelection);
+ },
+
+ stopSearch: function() {},
+
+ // Internal JS-only API
+ clearResults: function() {
+ this.results = [];
+ },
+
+ setResults: function(results) {
+ this.results = results;
+ },
+};
+
+this.AutoCompletePopup = {
+ MESSAGES: [
+ "FormAutoComplete:SelectBy",
+ "FormAutoComplete:GetSelectedIndex",
+ "FormAutoComplete:SetSelectedIndex",
+ "FormAutoComplete:MaybeOpenPopup",
+ "FormAutoComplete:ClosePopup",
+ "FormAutoComplete:Disconnect",
+ "FormAutoComplete:RemoveEntry",
+ "FormAutoComplete:Invalidate",
+ ],
+
+ init: function() {
+ for (let msg of this.MESSAGES) {
+ Services.mm.addMessageListener(msg, this);
+ }
+ },
+
+ uninit: function() {
+ for (let msg of this.MESSAGES) {
+ Services.mm.removeMessageListener(msg, this);
+ }
+ },
+
+ handleEvent: function(evt) {
+ switch (evt.type) {
+ case "popupshowing": {
+ this.sendMessageToBrowser("FormAutoComplete:PopupOpened");
+ break;
+ }
+
+ case "popuphidden": {
+ this.sendMessageToBrowser("FormAutoComplete:PopupClosed");
+ this.openedPopup = null;
+ this.weakBrowser = null;
+ evt.target.removeEventListener("popuphidden", this);
+ evt.target.removeEventListener("popupshowing", this);
+ break;
+ }
+ }
+ },
+
+ // Along with being called internally by the receiveMessage handler,
+ // this function is also called directly by the login manager, which
+ // uses a single message to fill in the autocomplete results. See
+ // "RemoteLogins:autoCompleteLogins".
+ showPopupWithResults: function({ browser, rect, dir, results }) {
+ if (!results.length || this.openedPopup) {
+ // We shouldn't ever be showing an empty popup, and if we
+ // already have a popup open, the old one needs to close before
+ // we consider opening a new one.
+ return;
+ }
+
+ let window = browser.ownerDocument.defaultView;
+ let tabbrowser = window.gBrowser;
+ if (Services.focus.activeWindow != window ||
+ tabbrowser.selectedBrowser != browser) {
+ // We were sent a message from a window or tab that went into the
+ // background, so we'll ignore it for now.
+ return;
+ }
+
+ this.weakBrowser = Cu.getWeakReference(browser);
+ this.openedPopup = browser.autoCompletePopup;
+ this.openedPopup.hidden = false;
+ // don't allow the popup to become overly narrow
+ this.openedPopup.setAttribute("width", Math.max(100, rect.width));
+ this.openedPopup.style.direction = dir;
+
+ AutoCompleteTreeView.setResults(results);
+ this.openedPopup.view = AutoCompleteTreeView;
+ this.openedPopup.selectedIndex = -1;
+ this.openedPopup.invalidate();
+
+ if (results.length) {
+ // Reset fields that were set from the last time the search popup was open
+ this.openedPopup.mInput = null;
+ this.openedPopup.showCommentColumn = false;
+ this.openedPopup.showImageColumn = false;
+ this.openedPopup.addEventListener("popuphidden", this);
+ this.openedPopup.addEventListener("popupshowing", this);
+ this.openedPopup.openPopupAtScreenRect("after_start", rect.left, rect.top,
+ rect.width, rect.height, false,
+ false);
+ } else {
+ this.closePopup();
+ }
+ },
+
+ invalidate(results) {
+ if (!this.openedPopup) {
+ return;
+ }
+
+ if (!results.length) {
+ this.closePopup();
+ } else {
+ AutoCompleteTreeView.setResults(results);
+ // We need to re-set the view in order for the
+ // tree to know the view has changed.
+ this.openedPopup.view = AutoCompleteTreeView;
+ this.openedPopup.invalidate();
+ }
+ },
+
+ closePopup() {
+ if (this.openedPopup) {
+ // Note that hidePopup() closes the popup immediately,
+ // so popuphiding or popuphidden events will be fired
+ // and handled during this call.
+ this.openedPopup.hidePopup();
+ }
+ AutoCompleteTreeView.clearResults();
+ },
+
+ removeLogin(login) {
+ Services.logins.removeLogin(login);
+ },
+
+ receiveMessage: function(message) {
+ if (!message.target.autoCompletePopup) {
+ // Returning false to pacify ESLint, but this return value is
+ // ignored by the messaging infrastructure.
+ return false;
+ }
+
+ switch (message.name) {
+ case "FormAutoComplete:SelectBy": {
+ this.openedPopup.selectBy(message.data.reverse, message.data.page);
+ break;
+ }
+
+ case "FormAutoComplete:GetSelectedIndex": {
+ if (this.openedPopup) {
+ return this.openedPopup.selectedIndex;
+ }
+ // If the popup was closed, then the selection
+ // has not changed.
+ return -1;
+ }
+
+ case "FormAutoComplete:SetSelectedIndex": {
+ let { index } = message.data;
+ if (this.openedPopup) {
+ this.openedPopup.selectedIndex = index;
+ }
+ break;
+ }
+
+ case "FormAutoComplete:MaybeOpenPopup": {
+ let { results, rect, dir } = message.data;
+ this.showPopupWithResults({ browser: message.target, rect, dir,
+ results });
+ break;
+ }
+
+ case "FormAutoComplete:Invalidate": {
+ let { results } = message.data;
+ this.invalidate(results);
+ break;
+ }
+
+ case "FormAutoComplete:ClosePopup": {
+ this.closePopup();
+ break;
+ }
+
+ case "FormAutoComplete:Disconnect": {
+ // The controller stopped controlling the current input, so clear
+ // any cached data. This is necessary cause otherwise we'd clear data
+ // only when starting a new search, but the next input could not support
+ // autocomplete and it would end up inheriting the existing data.
+ AutoCompleteTreeView.clearResults();
+ break;
+ }
+ }
+ // Returning false to pacify ESLint, but this return value is
+ // ignored by the messaging infrastructure.
+ return false;
+ },
+
+ /**
+ * Despite its name, this handleEnter is only called when the user clicks on
+ * one of the items in the popup since the popup is rendered in the parent process.
+ * The real controller's handleEnter is called directly in the content process
+ * for other methods of completing a selection (e.g. using the tab or enter
+ * keys) since the field with focus is in that process.
+ */
+ handleEnter(aIsPopupSelection) {
+ if (this.openedPopup) {
+ this.sendMessageToBrowser("FormAutoComplete:HandleEnter", {
+ selectedIndex: this.openedPopup.selectedIndex,
+ isPopupSelection: aIsPopupSelection,
+ });
+ }
+ },
+
+ /**
+ * If a browser exists that AutoCompletePopup knows about,
+ * sends it a message. Otherwise, this is a no-op.
+ *
+ * @param {string} msgName
+ * The name of the message to send.
+ * @param {object} data
+ * The optional data to send with the message.
+ */
+ sendMessageToBrowser(msgName, data) {
+ let browser = this.weakBrowser ? this.weakBrowser.get()
+ : null;
+ if (browser) {
+ browser.messageManager.sendAsyncMessage(msgName, data);
+ }
+ },
+
+ stopSearch: function() {}
+}
diff --git a/modules/BrowserNewTabPreloader.jsm b/modules/BrowserNewTabPreloader.jsm
new file mode 100644
index 0000000..778698f
--- /dev/null
+++ b/modules/BrowserNewTabPreloader.jsm
@@ -0,0 +1,436 @@
+/* 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";
+
+this.EXPORTED_SYMBOLS = ["BrowserNewTabPreloader"];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const XUL_PAGE = "data:application/vnd.mozilla.xul+xml;charset=utf-8,<window%20id='win'/>";
+const NEWTAB_URL = "about:newtab";
+const PREF_BRANCH = "browser.newtab.";
+
+// The interval between swapping in a preload docShell and kicking off the
+// next preload in the background.
+const PRELOADER_INTERVAL_MS = 600;
+// The initial delay before we start preloading our first new tab page. The
+// timer is started after the first 'browser-delayed-startup' has been sent.
+const PRELOADER_INIT_DELAY_MS = 5000;
+// The number of miliseconds we'll wait after we received a notification that
+// causes us to update our list of browsers and tabbrowser sizes. This acts as
+// kind of a damper when too many events are occuring in quick succession.
+const PRELOADER_UPDATE_DELAY_MS = 3000;
+
+const TOPIC_TIMER_CALLBACK = "timer-callback";
+const TOPIC_DELAYED_STARTUP = "browser-delayed-startup-finished";
+const TOPIC_XUL_WINDOW_CLOSED = "xul-window-destroyed";
+
+function createTimer(obj, delay) {
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.init(obj, delay, Ci.nsITimer.TYPE_ONE_SHOT);
+ return timer;
+}
+
+function clearTimer(timer) {
+ if (timer) {
+ timer.cancel();
+ }
+ return null;
+}
+
+this.BrowserNewTabPreloader = {
+ init: function Preloader_init() {
+ Initializer.start();
+ },
+
+ uninit: function Preloader_uninit() {
+ Initializer.stop();
+ HostFrame.destroy();
+ Preferences.uninit();
+ HiddenBrowsers.uninit();
+ },
+
+ newTab: function Preloader_newTab(aTab) {
+ let win = aTab.ownerDocument.defaultView;
+ if (win.gBrowser) {
+ let utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+
+ let {width, height} = utils.getBoundsWithoutFlushing(win.gBrowser);
+ let hiddenBrowser = HiddenBrowsers.get(width, height)
+ if (hiddenBrowser) {
+ return hiddenBrowser.swapWithNewTab(aTab);
+ }
+ }
+
+ return false;
+ }
+};
+
+Object.freeze(BrowserNewTabPreloader);
+
+var Initializer = {
+ _timer: null,
+ _observing: false,
+
+ start: function Initializer_start() {
+ Services.obs.addObserver(this, TOPIC_DELAYED_STARTUP, false);
+ this._observing = true;
+ },
+
+ stop: function Initializer_stop() {
+ this._timer = clearTimer(this._timer);
+
+ if (this._observing) {
+ Services.obs.removeObserver(this, TOPIC_DELAYED_STARTUP);
+ this._observing = false;
+ }
+ },
+
+ observe: function Initializer_observe(aSubject, aTopic, aData) {
+ if (aTopic == TOPIC_DELAYED_STARTUP) {
+ Services.obs.removeObserver(this, TOPIC_DELAYED_STARTUP);
+ this._observing = false;
+ this._startTimer();
+ } else if (aTopic == TOPIC_TIMER_CALLBACK) {
+ this._timer = null;
+ this._startPreloader();
+ }
+ },
+
+ _startTimer: function Initializer_startTimer() {
+ this._timer = createTimer(this, PRELOADER_INIT_DELAY_MS);
+ },
+
+ _startPreloader: function Initializer_startPreloader() {
+ Preferences.init();
+ if (Preferences.enabled) {
+ HiddenBrowsers.init();
+ }
+ }
+};
+
+var Preferences = {
+ _enabled: null,
+ _branch: null,
+
+ get enabled() {
+ if (this._enabled === null) {
+ this._enabled = this._branch.getBoolPref("preload") &&
+ !this._branch.prefHasUserValue("url");
+ }
+
+ return this._enabled;
+ },
+
+ init: function Preferences_init() {
+ this._branch = Services.prefs.getBranch(PREF_BRANCH);
+ this._branch.addObserver("", this, false);
+ },
+
+ uninit: function Preferences_uninit() {
+ if (this._branch) {
+ this._branch.removeObserver("", this);
+ this._branch = null;
+ }
+ },
+
+ observe: function Preferences_observe() {
+ let prevEnabled = this._enabled;
+ this._enabled = null;
+
+ if (prevEnabled && !this.enabled) {
+ HiddenBrowsers.uninit();
+ } else if (!prevEnabled && this.enabled) {
+ HiddenBrowsers.init();
+ }
+ },
+};
+
+var HiddenBrowsers = {
+ _browsers: null,
+ _updateTimer: null,
+
+ _topics: [
+ TOPIC_DELAYED_STARTUP,
+ TOPIC_XUL_WINDOW_CLOSED
+ ],
+
+ init: function () {
+ this._browsers = new Map();
+ this._updateBrowserSizes();
+ this._topics.forEach(t => Services.obs.addObserver(this, t, false));
+ },
+
+ uninit: function () {
+ if (this._browsers) {
+ this._topics.forEach(t => Services.obs.removeObserver(this, t, false));
+ this._updateTimer = clearTimer(this._updateTimer);
+
+ for (let [key, browser] of this._browsers) {
+ browser.destroy();
+ }
+ this._browsers = null;
+ }
+ },
+
+ get: function (width, height) {
+ // We haven't been initialized, yet.
+ if (!this._browsers) {
+ return null;
+ }
+
+ let key = width + "x" + height;
+ if (!this._browsers.has(key)) {
+ // Update all browsers' sizes if we can't find a matching one.
+ this._updateBrowserSizes();
+ }
+
+ // We should now have a matching browser.
+ if (this._browsers.has(key)) {
+ return this._browsers.get(key);
+ }
+
+ // We should never be here. Return the first browser we find.
+ Cu.reportError("NewTabPreloader: no matching browser found after updating");
+ for (let [size, browser] of this._browsers) {
+ return browser;
+ }
+
+ // We should really never be here.
+ Cu.reportError("NewTabPreloader: not even a single browser was found?");
+ return null;
+ },
+
+ observe: function (subject, topic, data) {
+ if (topic === TOPIC_TIMER_CALLBACK) {
+ this._updateTimer = null;
+ this._updateBrowserSizes();
+ } else {
+ this._updateTimer = clearTimer(this._updateTimer);
+ this._updateTimer = createTimer(this, PRELOADER_UPDATE_DELAY_MS);
+ }
+ },
+
+ _updateBrowserSizes: function () {
+ let sizes = this._collectTabBrowserSizes();
+ let toRemove = [];
+
+ // Iterate all browsers and check that they
+ // each can be assigned to one of the sizes.
+ for (let [key, browser] of this._browsers) {
+ if (sizes.has(key)) {
+ // We already have a browser for that size, great!
+ sizes.delete(key);
+ } else {
+ // This browser is superfluous or needs to be resized.
+ toRemove.push(browser);
+ this._browsers.delete(key);
+ }
+ }
+
+ // Iterate all sizes that we couldn't find a browser for.
+ for (let [key, {width, height}] of sizes) {
+ let browser;
+ if (toRemove.length) {
+ // Let's just resize one of the superfluous
+ // browsers and put it back into the map.
+ browser = toRemove.shift();
+ browser.resize(width, height);
+ } else {
+ // No more browsers to reuse, create a new one.
+ browser = new HiddenBrowser(width, height);
+ }
+
+ this._browsers.set(key, browser);
+ }
+
+ // Finally, remove all browsers we don't need anymore.
+ toRemove.forEach(b => b.destroy());
+ },
+
+ _collectTabBrowserSizes: function () {
+ let sizes = new Map();
+
+ function tabBrowserBounds() {
+ let wins = Services.ww.getWindowEnumerator("navigator:browser");
+ while (wins.hasMoreElements()) {
+ let win = wins.getNext();
+ if (win.gBrowser) {
+ let utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ yield utils.getBoundsWithoutFlushing(win.gBrowser);
+ }
+ }
+ }
+
+ // Collect the sizes of all <tabbrowser>s out there.
+ for (let {width, height} of tabBrowserBounds()) {
+ if (width > 0 && height > 0) {
+ let key = width + "x" + height;
+ if (!sizes.has(key)) {
+ sizes.set(key, {width: width, height: height});
+ }
+ }
+ }
+
+ return sizes;
+ }
+};
+
+function HiddenBrowser(width, height) {
+ this.resize(width, height);
+
+ HostFrame.get().then(aFrame => {
+ let doc = aFrame.document;
+ this._browser = doc.createElementNS(XUL_NS, "browser");
+ this._browser.setAttribute("type", "content");
+ this._browser.setAttribute("src", NEWTAB_URL);
+ this._applySize();
+ doc.getElementById("win").appendChild(this._browser);
+ });
+}
+
+HiddenBrowser.prototype = {
+ _width: null,
+ _height: null,
+ _timer: null,
+ _needsFrameScripts: true,
+
+ get isPreloaded() {
+ return this._browser &&
+ this._browser.contentDocument &&
+ this._browser.contentDocument.readyState === "complete" &&
+ this._browser.currentURI.spec === NEWTAB_URL;
+ },
+
+ swapWithNewTab: function (aTab) {
+ if (!this.isPreloaded || this._timer) {
+ return false;
+ }
+
+ let win = aTab.ownerDocument.defaultView;
+ let tabbrowser = win.gBrowser;
+
+ if (!tabbrowser) {
+ return false;
+ }
+
+ // Swap docShells.
+ tabbrowser.swapNewTabWithBrowser(aTab, this._browser);
+
+ // Load all default frame scripts.
+ if (this._needsFrameScripts) {
+ this._needsFrameScripts = false;
+
+ let mm = aTab.linkedBrowser.messageManager;
+ mm.loadFrameScript("chrome://browser/content/content.js", true);
+ mm.loadFrameScript("chrome://browser/content/content-sessionStore.js", true);
+
+ if ("TabView" in win) {
+ mm.loadFrameScript("chrome://browser/content/tabview-content.js", true);
+ }
+ }
+
+ // Start a timer that will kick off preloading the next newtab page.
+ this._timer = createTimer(this, PRELOADER_INTERVAL_MS);
+
+ // Signal that we swapped docShells.
+ return true;
+ },
+
+ observe: function () {
+ this._timer = null;
+
+ // Start pre-loading the new tab page.
+ this._browser.loadURI(NEWTAB_URL);
+ },
+
+ resize: function (width, height) {
+ this._width = width;
+ this._height = height;
+ this._applySize();
+ },
+
+ _applySize: function () {
+ if (this._browser) {
+ this._browser.style.width = this._width + "px";
+ this._browser.style.height = this._height + "px";
+ }
+ },
+
+ destroy: function () {
+ if (this._browser) {
+ this._browser.remove();
+ this._browser = null;
+ }
+
+ this._timer = clearTimer(this._timer);
+ }
+};
+
+var HostFrame = {
+ _frame: null,
+ _deferred: null,
+
+ get hiddenDOMDocument() {
+ return Services.appShell.hiddenDOMWindow.document;
+ },
+
+ get isReady() {
+ return this.hiddenDOMDocument.readyState === "complete";
+ },
+
+ get: function () {
+ if (!this._deferred) {
+ this._deferred = Promise.defer();
+ this._create();
+ }
+
+ return this._deferred.promise;
+ },
+
+ destroy: function () {
+ if (this._frame) {
+ if (!Cu.isDeadWrapper(this._frame)) {
+ this._frame.removeEventListener("load", this, true);
+ this._frame.remove();
+ }
+
+ this._frame = null;
+ this._deferred = null;
+ }
+ },
+
+ handleEvent: function () {
+ let contentWindow = this._frame.contentWindow;
+ if (contentWindow.location.href === XUL_PAGE) {
+ this._frame.removeEventListener("load", this, true);
+ this._deferred.resolve(contentWindow);
+ } else {
+ contentWindow.location = XUL_PAGE;
+ }
+ },
+
+ _create: function () {
+ if (this.isReady) {
+ let doc = this.hiddenDOMDocument;
+ this._frame = doc.createElementNS(HTML_NS, "iframe");
+ this._frame.addEventListener("load", this, true);
+ doc.documentElement.appendChild(this._frame);
+ } else {
+ let flags = Ci.nsIThread.DISPATCH_NORMAL;
+ Services.tm.currentThread.dispatch(() => this._create(), flags);
+ }
+ }
+};
diff --git a/modules/CharsetMenu.jsm b/modules/CharsetMenu.jsm
new file mode 100644
index 0000000..f973088
--- /dev/null
+++ b/modules/CharsetMenu.jsm
@@ -0,0 +1,160 @@
+/* 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.EXPORTED_SYMBOLS = [ "CharsetMenu" ];
+
+const { classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyGetter(this, "gBundle", function() {
+ const kUrl = "chrome://browser/locale/charsetMenu.properties";
+ return Services.strings.createBundle(kUrl);
+});
+/**
+ * This set contains encodings that are in the Encoding Standard, except:
+ * - XSS-dangerous encodings (except ISO-2022-JP which is assumed to be
+ * too common not to be included).
+ * - x-user-defined, which practically never makes sense as an end-user-chosen
+ * override.
+ * - Encodings that IE11 doesn't have in its correspoding menu.
+ */
+const kEncodings = new Set([
+ // Globally relevant
+ "UTF-8",
+ "windows-1252",
+ // Arabic
+ "windows-1256",
+ "ISO-8859-6",
+ // Baltic
+ "windows-1257",
+ "ISO-8859-4",
+ // "ISO-8859-13", // Hidden since not in menu in IE11
+ // Central European
+ "windows-1250",
+ "ISO-8859-2",
+ // Chinese, Simplified
+ "gbk",
+ "gb18030",
+ // Chinese, Traditional
+ "Big5",
+ // Cyrillic
+ "windows-1251",
+ "ISO-8859-5",
+ "KOI8-R",
+ "KOI8-U",
+ "IBM866", // Not in menu in Chromium. Maybe drop this?
+ // "x-mac-cyrillic", // Not in menu in IE11 or Chromium.
+ // Greek
+ "windows-1253",
+ "ISO-8859-7",
+ // Hebrew
+ "windows-1255",
+ "ISO-8859-8-I",
+ "ISO-8859-8",
+ // Japanese
+ "Shift_JIS",
+ "EUC-JP",
+ "ISO-2022-JP",
+ // Korean
+ "EUC-KR",
+ // Thai
+ "windows-874",
+ // Turkish
+ "windows-1254",
+ // Vietnamese
+ "windows-1258",
+ // Hiding rare European encodings that aren't in the menu in IE11 and would
+ // make the menu messy by sorting all over the place
+ // "ISO-8859-3",
+ // "ISO-8859-10",
+ // "ISO-8859-14",
+ // "ISO-8859-15",
+ // "ISO-8859-16",
+ // "macintosh"
+]);
+
+// Always at the start of the menu, in this order, followed by a separator.
+const kPinned = [
+ "UTF-8",
+ "windows-1252"
+];
+
+this.CharsetMenu = Object.freeze({
+ build: function BuildCharsetMenu(event) {
+ let parent = event.target;
+ if (parent.lastChild.localName != "menuseparator") {
+ // Detector menu or charset menu already built
+ return;
+ }
+ let doc = parent.ownerDocument;
+
+ function createItem(encoding) {
+ let menuItem = doc.createElement("menuitem");
+ menuItem.setAttribute("type", "radio");
+ menuItem.setAttribute("name", "charsetGroup");
+ try {
+ menuItem.setAttribute("label", gBundle.GetStringFromName(encoding));
+ } catch (e) {
+ // Localization error but put *something* in the menu to recover.
+ menuItem.setAttribute("label", encoding);
+ }
+ try {
+ menuItem.setAttribute("accesskey",
+ gBundle.GetStringFromName(encoding + ".key"));
+ } catch (e) {
+ // Some items intentionally don't have an accesskey
+ }
+ menuItem.setAttribute("id", "charset." + encoding);
+ return menuItem;
+ }
+
+ // Clone the set in order to be able to remove the pinned encodings from
+ // the cloned set.
+ let encodings = new Set(kEncodings);
+ for (let encoding of kPinned) {
+ encodings.delete(encoding);
+ parent.appendChild(createItem(encoding));
+ }
+ parent.appendChild(doc.createElement("menuseparator"));
+ let list = [];
+ for (let encoding of encodings) {
+ list.push(createItem(encoding));
+ }
+
+ list.sort(function (a, b) {
+ let titleA = a.getAttribute("label");
+ let titleB = b.getAttribute("label");
+ // Normal sorting sorts the part in parenthesis in an order that
+ // happens to make the less frequently-used items first.
+ let index;
+ if ((index = titleA.indexOf("(")) > -1) {
+ titleA = titleA.substring(0, index);
+ }
+ if ((index = titleB.indexOf("(")) > -1) {
+ titleA = titleB.substring(0, index);
+ }
+ let comp = titleA.localeCompare(titleB);
+ if (comp) {
+ return comp;
+ }
+ // secondarily reverse sort by encoding name to sort "windows" or
+ // "shift_jis" first. This works regardless of localization, because
+ // the ids aren't localized.
+ let idA = a.getAttribute("id");
+ let idB = b.getAttribute("id");
+ if (idA < idB) {
+ return 1;
+ }
+ if (idB < idA) {
+ return -1;
+ }
+ return 0;
+ });
+
+ for (let item of list) {
+ parent.appendChild(item);
+ }
+ },
+}); \ No newline at end of file
diff --git a/modules/FormSubmitObserver.jsm b/modules/FormSubmitObserver.jsm
new file mode 100644
index 0000000..6b2ea3c
--- /dev/null
+++ b/modules/FormSubmitObserver.jsm
@@ -0,0 +1,235 @@
+/* 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/. */
+
+/*
+ * Handles the validation callback from nsIFormFillController and
+ * the display of the help panel on invalid elements.
+ */
+
+"use strict";
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+var HTMLInputElement = Ci.nsIDOMHTMLInputElement;
+var HTMLTextAreaElement = Ci.nsIDOMHTMLTextAreaElement;
+var HTMLSelectElement = Ci.nsIDOMHTMLSelectElement;
+var HTMLButtonElement = Ci.nsIDOMHTMLButtonElement;
+
+this.EXPORTED_SYMBOLS = [ "FormSubmitObserver" ];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/BrowserUtils.jsm");
+
+function FormSubmitObserver(aWindow, aTabChildGlobal) {
+ this.init(aWindow, aTabChildGlobal);
+}
+
+FormSubmitObserver.prototype =
+{
+ _validationMessage: "",
+ _content: null,
+ _element: null,
+
+ /*
+ * Public apis
+ */
+
+ init: function(aWindow, aTabChildGlobal)
+ {
+ this._content = aWindow;
+ this._tab = aTabChildGlobal;
+ this._mm =
+ this._content.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .sameTypeRootTreeItem
+ .QueryInterface(Ci.nsIDocShell)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIContentFrameMessageManager);
+
+ // nsIFormSubmitObserver callback about invalid forms. See HTMLFormElement
+ // for details.
+ Services.obs.addObserver(this, "invalidformsubmit", false);
+ this._tab.addEventListener("pageshow", this, false);
+ this._tab.addEventListener("unload", this, false);
+ },
+
+ uninit: function()
+ {
+ Services.obs.removeObserver(this, "invalidformsubmit");
+ this._content.removeEventListener("pageshow", this, false);
+ this._content.removeEventListener("unload", this, false);
+ this._mm = null;
+ this._element = null;
+ this._content = null;
+ this._tab = null;
+ },
+
+ /*
+ * Events
+ */
+
+ handleEvent: function (aEvent) {
+ switch (aEvent.type) {
+ case "pageshow":
+ if (this._isRootDocumentEvent(aEvent)) {
+ this._hidePopup();
+ }
+ break;
+ case "unload":
+ this.uninit();
+ break;
+ case "input":
+ this._onInput(aEvent);
+ break;
+ case "blur":
+ this._onBlur(aEvent);
+ break;
+ }
+ },
+
+ /*
+ * nsIFormSubmitObserver
+ */
+
+ 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;
+ }
+
+ // Ensure that this is the FormSubmitObserver associated with the
+ // element / window this notification is about.
+ let element = aInvalidElements.queryElementAt(0, Ci.nsISupports);
+ if (this._content != element.ownerGlobal.top.document.defaultView) {
+ return;
+ }
+
+ if (!(element instanceof HTMLInputElement ||
+ element instanceof HTMLTextAreaElement ||
+ element instanceof HTMLSelectElement ||
+ element instanceof HTMLButtonElement)) {
+ return;
+ }
+
+ // Update validation message before showing notification
+ this._validationMessage = element.validationMessage;
+
+ // Don't connect up to the same element more than once.
+ if (this._element == element) {
+ this._showPopup(element);
+ return;
+ }
+ this._element = element;
+
+ element.focus();
+
+ // Watch for input changes which may change the validation message.
+ element.addEventListener("input", this, false);
+
+ // Watch for focus changes so we can disconnect our listeners and
+ // hide the popup.
+ element.addEventListener("blur", this, false);
+
+ this._showPopup(element);
+ },
+
+ /*
+ * Internal
+ */
+
+ /*
+ * Handles input changes on the form element we've associated a popup
+ * with. Updates the validation message or closes the popup if form data
+ * becomes valid.
+ */
+ _onInput: function (aEvent) {
+ let element = aEvent.originalTarget;
+
+ // If the form input is now valid, hide the popup.
+ if (element.validity.valid) {
+ this._hidePopup();
+ return;
+ }
+
+ // If the element is still invalid for a new reason, we should update
+ // the popup error message.
+ if (this._validationMessage != element.validationMessage) {
+ this._validationMessage = element.validationMessage;
+ this._showPopup(element);
+ }
+ },
+
+ /*
+ * Blur event handler in which we disconnect from the form element and
+ * hide the popup.
+ */
+ _onBlur: function (aEvent) {
+ aEvent.originalTarget.removeEventListener("input", this, false);
+ aEvent.originalTarget.removeEventListener("blur", this, false);
+ this._element = null;
+ this._hidePopup();
+ },
+
+ /*
+ * Send the show popup message to chrome with appropriate position
+ * information. Can be called repetitively to update the currently
+ * displayed popup position and text.
+ */
+ _showPopup: function (aElement) {
+ // Collect positional information and show the popup
+ let panelData = {};
+
+ panelData.message = this._validationMessage;
+
+ // Note, this is relative to the browser and needs to be translated
+ // in chrome.
+ panelData.contentRect = BrowserUtils.getElementBoundingRect(aElement);
+
+ // 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;
+
+ if (aElement.tagName == 'INPUT' &&
+ (aElement.type == 'radio' || aElement.type == 'checkbox')) {
+ panelData.position = "bottomcenter topleft";
+ } else {
+ let win = aElement.ownerGlobal;
+ let style = win.getComputedStyle(aElement, null);
+ if (style.direction == 'rtl') {
+ offset = parseInt(style.paddingRight) + parseInt(style.borderRightWidth);
+ } else {
+ offset = parseInt(style.paddingLeft) + parseInt(style.borderLeftWidth);
+ }
+ let zoomFactor = this._getWindowUtils().fullZoom;
+ panelData.offset = Math.round(offset * zoomFactor);
+ panelData.position = "after_start";
+ }
+ this._mm.sendAsyncMessage("FormValidation:ShowPopup", panelData);
+ },
+
+ _hidePopup: function () {
+ this._mm.sendAsyncMessage("FormValidation:HidePopup", {});
+ },
+
+ _getWindowUtils: function () {
+ return this._content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+ },
+
+ _isRootDocumentEvent: function (aEvent) {
+ if (this._content == null) {
+ return true;
+ }
+ let target = aEvent.originalTarget;
+ return (target == this._content.document ||
+ (target.ownerDocument && target.ownerDocument == this._content.document));
+ },
+
+ QueryInterface : XPCOMUtils.generateQI([Ci.nsIFormSubmitObserver])
+};
diff --git a/modules/FormValidationHandler.jsm b/modules/FormValidationHandler.jsm
new file mode 100644
index 0000000..387c221
--- /dev/null
+++ b/modules/FormValidationHandler.jsm
@@ -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/. */
+
+/*
+ * Chrome side handling of form validation popup.
+ */
+
+"use strict";
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+this.EXPORTED_SYMBOLS = [ "FormValidationHandler" ];
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+var FormValidationHandler =
+{
+ _panel: null,
+ _anchor: null,
+
+ /*
+ * Public apis
+ */
+
+ init: function () {
+ let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
+ mm.addMessageListener("FormValidation:ShowPopup", this);
+ mm.addMessageListener("FormValidation:HidePopup", this);
+ },
+
+ uninit: function () {
+ let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
+ mm.removeMessageListener("FormValidation:ShowPopup", this);
+ mm.removeMessageListener("FormValidation:HidePopup", this);
+ this._panel = null;
+ this._anchor = null;
+ },
+
+ hidePopup: function () {
+ this._hidePopup();
+ },
+
+ /*
+ * Events
+ */
+
+ receiveMessage: function (aMessage) {
+ let window = aMessage.target.ownerDocument.defaultView;
+ let json = aMessage.json;
+ let tabBrowser = window.gBrowser;
+ switch (aMessage.name) {
+ case "FormValidation:ShowPopup":
+ // target is the <browser>, make sure we're receiving a message
+ // from the foreground tab.
+ if (tabBrowser && aMessage.target != tabBrowser.selectedBrowser) {
+ return;
+ }
+ this._showPopup(window, json);
+ break;
+ case "FormValidation:HidePopup":
+ this._hidePopup();
+ break;
+ }
+ },
+
+ observe: function (aSubject, aTopic, aData) {
+ this._hidePopup();
+ },
+
+ handleEvent: function (aEvent) {
+ switch (aEvent.type) {
+ case "FullZoomChange":
+ case "TextZoomChange":
+ case "ZoomChangeUsingMouseWheel":
+ case "scroll":
+ this._hidePopup();
+ break;
+ case "popuphiding":
+ this._onPopupHiding(aEvent);
+ break;
+ }
+ },
+
+ /*
+ * Internal
+ */
+
+ _onPopupHiding: function (aEvent) {
+ aEvent.originalTarget.removeEventListener("popuphiding", this, true);
+ let tabBrowser = aEvent.originalTarget.ownerDocument.getElementById("content");
+ tabBrowser.selectedBrowser.removeEventListener("scroll", this, true);
+ tabBrowser.selectedBrowser.removeEventListener("FullZoomChange", this, false);
+ tabBrowser.selectedBrowser.removeEventListener("TextZoomChange", this, false);
+ tabBrowser.selectedBrowser.removeEventListener("ZoomChangeUsingMouseWheel", this, false);
+
+ this._panel.hidden = true;
+ this._panel = null;
+ this._anchor.hidden = true;
+ this._anchor = null;
+ },
+
+ /*
+ * Shows the form validation popup at a specified position or updates the
+ * messaging and position if the popup is already displayed.
+ *
+ * @aWindow - the chrome window
+ * @aPanelData - Object that contains popup information
+ * aPanelData stucture detail:
+ * contentRect - the bounding client rect of the target element. If
+ * content is remote, this is relative to the browser, otherwise its
+ * relative to the window.
+ * position - popup positional string constants.
+ * message - the form element validation message text.
+ */
+ _showPopup: function (aWindow, aPanelData) {
+ let previouslyShown = !!this._panel;
+ this._panel = aWindow.document.getElementById("invalid-form-popup");
+ this._panel.firstChild.textContent = aPanelData.message;
+ this._panel.hidden = false;
+
+ let tabBrowser = aWindow.gBrowser;
+ this._anchor = tabBrowser.popupAnchor;
+ this._anchor.left = aPanelData.contentRect.left;
+ this._anchor.top = aPanelData.contentRect.top;
+ this._anchor.width = aPanelData.contentRect.width;
+ this._anchor.height = aPanelData.contentRect.height;
+ this._anchor.hidden = false;
+
+ // Display the panel if it isn't already visible.
+ if (!previouslyShown) {
+ // Cleanup after the popup is hidden
+ this._panel.addEventListener("popuphiding", this, true);
+
+ // Hide if the user scrolls the page
+ tabBrowser.selectedBrowser.addEventListener("scroll", this, true);
+ tabBrowser.selectedBrowser.addEventListener("FullZoomChange", this, false);
+ tabBrowser.selectedBrowser.addEventListener("TextZoomChange", this, false);
+ tabBrowser.selectedBrowser.addEventListener("ZoomChangeUsingMouseWheel", this, false);
+
+ // Open the popup
+ this._panel.openPopup(this._anchor, aPanelData.position, 0, 0, false);
+ }
+ },
+
+ /*
+ * Hide the popup if currently displayed. Will fire an event to onPopupHiding
+ * above if visible.
+ */
+ _hidePopup: function () {
+ if (this._panel) {
+ this._panel.hidePopup();
+ }
+ }
+};
diff --git a/modules/NetworkPrioritizer.jsm b/modules/NetworkPrioritizer.jsm
new file mode 100644
index 0000000..23d688a
--- /dev/null
+++ b/modules/NetworkPrioritizer.jsm
@@ -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/. */
+
+/*
+ * This module adjusts network priority for tabs in a way that gives 'important'
+ * tabs a higher priority. There are 3 levels of priority. Each is listed below
+ * with the priority adjustment used.
+ *
+ * Highest (-10): Selected tab in the focused window.
+ * Medium (0): Background tabs in the focused window.
+ * Selected tab in background windows.
+ * Lowest (+10): Background tabs in background windows.
+ */
+
+this.EXPORTED_SYMBOLS = ["trackBrowserWindow"];
+
+const Ci = Components.interfaces;
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+
+// Lazy getters
+XPCOMUtils.defineLazyServiceGetter(this, "_focusManager",
+ "@mozilla.org/focus-manager;1",
+ "nsIFocusManager");
+
+
+// Constants
+const TAB_EVENTS = ["TabOpen", "TabSelect"];
+const WINDOW_EVENTS = ["activate", "unload"];
+// PRIORITY DELTA is -10 because lower priority value is actually a higher priority
+const PRIORITY_DELTA = -10;
+
+
+// Variables
+var _lastFocusedWindow = null;
+var _windows = [];
+
+
+// Exported symbol
+this.trackBrowserWindow = function trackBrowserWindow(aWindow) {
+ WindowHelper.addWindow(aWindow);
+}
+
+
+// Global methods
+function _handleEvent(aEvent) {
+ switch (aEvent.type) {
+ case "TabOpen":
+ BrowserHelper.onOpen(aEvent.target.linkedBrowser);
+ break;
+ case "TabSelect":
+ BrowserHelper.onSelect(aEvent.target.linkedBrowser);
+ break;
+ case "activate":
+ WindowHelper.onActivate(aEvent.target);
+ break;
+ case "unload":
+ WindowHelper.removeWindow(aEvent.currentTarget);
+ break;
+ }
+}
+
+
+// Methods that impact a browser. Put into single object for organization.
+var BrowserHelper = {
+ onOpen: function NP_BH_onOpen(aBrowser) {
+ // If the tab is in the focused window, leave priority as it is
+ if (aBrowser.ownerDocument.defaultView != _lastFocusedWindow)
+ this.decreasePriority(aBrowser);
+ },
+
+ onSelect: function NP_BH_onSelect(aBrowser) {
+ let windowEntry = WindowHelper.getEntry(aBrowser.ownerDocument.defaultView);
+ if (windowEntry.lastSelectedBrowser)
+ this.decreasePriority(windowEntry.lastSelectedBrowser);
+ this.increasePriority(aBrowser);
+
+ windowEntry.lastSelectedBrowser = aBrowser;
+ },
+
+ increasePriority: function NP_BH_increasePriority(aBrowser) {
+ aBrowser.adjustPriority(PRIORITY_DELTA);
+ },
+
+ decreasePriority: function NP_BH_decreasePriority(aBrowser) {
+ aBrowser.adjustPriority(PRIORITY_DELTA * -1);
+ }
+};
+
+
+// Methods that impact a window. Put into single object for organization.
+var WindowHelper = {
+ addWindow: function NP_WH_addWindow(aWindow) {
+ // Build internal data object
+ _windows.push({ window: aWindow, lastSelectedBrowser: null });
+
+ // Add event listeners
+ TAB_EVENTS.forEach(function(event) {
+ aWindow.gBrowser.tabContainer.addEventListener(event, _handleEvent, false);
+ });
+ WINDOW_EVENTS.forEach(function(event) {
+ aWindow.addEventListener(event, _handleEvent, false);
+ });
+
+ // This gets called AFTER activate event, so if this is the focused window
+ // we want to activate it. Otherwise, deprioritize it.
+ if (aWindow == _focusManager.activeWindow)
+ this.handleFocusedWindow(aWindow);
+ else
+ this.decreasePriority(aWindow);
+
+ // Select the selected tab
+ BrowserHelper.onSelect(aWindow.gBrowser.selectedBrowser);
+ },
+
+ removeWindow: function NP_WH_removeWindow(aWindow) {
+ if (aWindow == _lastFocusedWindow)
+ _lastFocusedWindow = null;
+
+ // Delete this window from our tracking
+ _windows.splice(this.getEntryIndex(aWindow), 1);
+
+ // Remove the event listeners
+ TAB_EVENTS.forEach(function(event) {
+ aWindow.gBrowser.tabContainer.removeEventListener(event, _handleEvent, false);
+ });
+ WINDOW_EVENTS.forEach(function(event) {
+ aWindow.removeEventListener(event, _handleEvent, false);
+ });
+ },
+
+ onActivate: function NP_WH_onActivate(aWindow, aHasFocus) {
+ // If this window was the last focused window, we don't need to do anything
+ if (aWindow == _lastFocusedWindow)
+ return;
+
+ // handleFocusedWindow will deprioritize the current window
+ this.handleFocusedWindow(aWindow);
+
+ // Lastly we should increase priority for this window
+ this.increasePriority(aWindow);
+ },
+
+ handleFocusedWindow: function NP_WH_handleFocusedWindow(aWindow) {
+ // If we have a last focused window, we need to deprioritize it first
+ if (_lastFocusedWindow)
+ this.decreasePriority(_lastFocusedWindow);
+
+ // aWindow is now focused
+ _lastFocusedWindow = aWindow;
+ },
+
+ // Auxiliary methods
+ increasePriority: function NP_WH_increasePriority(aWindow) {
+ aWindow.gBrowser.browsers.forEach(function(aBrowser) {
+ BrowserHelper.increasePriority(aBrowser);
+ });
+ },
+
+ decreasePriority: function NP_WH_decreasePriority(aWindow) {
+ aWindow.gBrowser.browsers.forEach(function(aBrowser) {
+ BrowserHelper.decreasePriority(aBrowser);
+ });
+ },
+
+ getEntry: function NP_WH_getEntry(aWindow) {
+ return _windows[this.getEntryIndex(aWindow)];
+ },
+
+ getEntryIndex: function NP_WH_getEntryAtIndex(aWindow) {
+ // Assumes that every object has a unique window & it's in the array
+ for (let i = 0; i < _windows.length; i++)
+ if (_windows[i].window == aWindow)
+ return i;
+ }
+};
+
diff --git a/modules/PageMenu.jsm b/modules/PageMenu.jsm
new file mode 100644
index 0000000..d01f626
--- /dev/null
+++ b/modules/PageMenu.jsm
@@ -0,0 +1,238 @@
+/* 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.EXPORTED_SYMBOLS = ["PageMenu"];
+
+this.PageMenu = function PageMenu() {
+}
+
+PageMenu.prototype = {
+ PAGEMENU_ATTR: "pagemenu",
+ GENERATEDITEMID_ATTR: "generateditemid",
+
+ _popup: null,
+ _builder: null,
+
+ // Given a target node, get the context menu for it or its ancestor.
+ getContextMenu: function(aTarget) {
+ let target = aTarget;
+ while (target) {
+ let contextMenu = target.contextMenu;
+ if (contextMenu) {
+ return contextMenu;
+ }
+ target = target.parentNode;
+ }
+
+ return null;
+ },
+
+ // Given a target node, generate a JSON object for any context menu
+ // associated with it, or null if there is no context menu.
+ maybeBuild: function(aTarget) {
+ let pageMenu = this.getContextMenu(aTarget);
+ if (!pageMenu) {
+ return null;
+ }
+
+ pageMenu.QueryInterface(Components.interfaces.nsIHTMLMenu);
+ pageMenu.sendShowEvent();
+ // the show event is not cancelable, so no need to check a result here
+
+ this._builder = pageMenu.createBuilder();
+ if (!this._builder) {
+ return null;
+ }
+
+ pageMenu.build(this._builder);
+
+ // This serializes then parses again, however this could be avoided in
+ // the single-process case with further improvement.
+ let menuString = this._builder.toJSONString();
+ if (!menuString) {
+ return null;
+ }
+
+ return JSON.parse(menuString);
+ },
+
+ // Given a JSON menu object and popup, add the context menu to the popup.
+ buildAndAttachMenuWithObject: function(aMenu, aBrowser, aPopup) {
+ if (!aMenu) {
+ return false;
+ }
+
+ let insertionPoint = this.getInsertionPoint(aPopup);
+ if (!insertionPoint) {
+ return false;
+ }
+
+ let fragment = aPopup.ownerDocument.createDocumentFragment();
+ this.buildXULMenu(aMenu, fragment);
+
+ let pos = insertionPoint.getAttribute(this.PAGEMENU_ATTR);
+ if (pos == "start") {
+ insertionPoint.insertBefore(fragment,
+ insertionPoint.firstChild);
+ } else if (pos.startsWith("#")) {
+ insertionPoint.insertBefore(fragment, insertionPoint.querySelector(pos));
+ } else {
+ insertionPoint.appendChild(fragment);
+ }
+
+ this._popup = aPopup;
+
+ this._popup.addEventListener("command", this);
+ this._popup.addEventListener("popuphidden", this);
+
+ return true;
+ },
+
+ // Construct the XUL menu structure for a given JSON object.
+ buildXULMenu: function(aNode, aElementForAppending) {
+ let document = aElementForAppending.ownerDocument;
+
+ let children = aNode.children;
+ for (let child of children) {
+ let menuitem;
+ switch (child.type) {
+ case "menuitem":
+ if (!child.id) {
+ continue; // Ignore children without ids
+ }
+
+ menuitem = document.createElement("menuitem");
+ if (child.checkbox) {
+ menuitem.setAttribute("type", "checkbox");
+ if (child.checked) {
+ menuitem.setAttribute("checked", "true");
+ }
+ }
+
+ if (child.label) {
+ menuitem.setAttribute("label", child.label);
+ }
+ if (child.icon) {
+ menuitem.setAttribute("image", child.icon);
+ menuitem.className = "menuitem-iconic";
+ }
+ if (child.disabled) {
+ menuitem.setAttribute("disabled", true);
+ }
+
+ break;
+
+ case "separator":
+ menuitem = document.createElement("menuseparator");
+ break;
+
+ case "menu":
+ menuitem = document.createElement("menu");
+ if (child.label) {
+ menuitem.setAttribute("label", child.label);
+ }
+
+ let menupopup = document.createElement("menupopup");
+ menuitem.appendChild(menupopup);
+
+ this.buildXULMenu(child, menupopup);
+ break;
+ }
+
+ menuitem.setAttribute(this.GENERATEDITEMID_ATTR, child.id ? child.id : 0);
+ aElementForAppending.appendChild(menuitem);
+ }
+ },
+
+ // Called when the generated menuitem is executed.
+ handleEvent: function(event) {
+ let type = event.type;
+ let target = event.target;
+ if (type == "command" && target.hasAttribute(this.GENERATEDITEMID_ATTR)) {
+ // If a builder is assigned, call click on it directly. Otherwise, this is
+ // likely a menu with data from another process, so send a message to the
+ // browser to execute the menuitem.
+ if (this._builder) {
+ this._builder.click(target.getAttribute(this.GENERATEDITEMID_ATTR));
+ }
+ } else if (type == "popuphidden" && this._popup == target) {
+ this.removeGeneratedContent(this._popup);
+
+ this._popup.removeEventListener("popuphidden", this);
+ this._popup.removeEventListener("command", this);
+
+ this._popup = null;
+ this._builder = null;
+ }
+ },
+
+ // Get the first child of the given element with the given tag name.
+ getImmediateChild: function(element, tag) {
+ let child = element.firstChild;
+ while (child) {
+ if (child.localName == tag) {
+ return child;
+ }
+ child = child.nextSibling;
+ }
+ return null;
+ },
+
+ // Return the location where the generated items should be inserted into the
+ // given popup. They should be inserted as the next sibling of the returned
+ // element.
+ getInsertionPoint: function(aPopup) {
+ if (aPopup.hasAttribute(this.PAGEMENU_ATTR))
+ return aPopup;
+
+ let element = aPopup.firstChild;
+ while (element) {
+ if (element.localName == "menu") {
+ let popup = this.getImmediateChild(element, "menupopup");
+ if (popup) {
+ let result = this.getInsertionPoint(popup);
+ if (result) {
+ return result;
+ }
+ }
+ }
+ element = element.nextSibling;
+ }
+
+ return null;
+ },
+
+ // Returns true if custom menu items were present.
+ maybeBuildAndAttachMenu: function(aTarget, aPopup) {
+ let menuObject = this.maybeBuild(aTarget);
+ if (!menuObject) {
+ return false;
+ }
+
+ return this.buildAndAttachMenuWithObject(menuObject, null, aPopup);
+ },
+
+ // Remove the generated content from the given popup.
+ removeGeneratedContent: function(aPopup) {
+ let ungenerated = [];
+ ungenerated.push(aPopup);
+
+ let count;
+ while (0 != (count = ungenerated.length)) {
+ let last = count - 1;
+ let element = ungenerated[last];
+ ungenerated.splice(last, 1);
+
+ let i = element.childNodes.length;
+ while (i-- > 0) {
+ let child = element.childNodes[i];
+ if (!child.hasAttribute(this.GENERATEDITEMID_ATTR)) {
+ ungenerated.push(child);
+ continue;
+ }
+ element.removeChild(child);
+ }
+ }
+ }
+}
diff --git a/modules/PopupNotifications.jsm b/modules/PopupNotifications.jsm
new file mode 100644
index 0000000..0cb9702
--- /dev/null
+++ b/modules/PopupNotifications.jsm
@@ -0,0 +1,994 @@
+/* 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.EXPORTED_SYMBOLS = ["PopupNotifications"];
+
+var Cc = Components.classes, Ci = Components.interfaces, Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const NOTIFICATION_EVENT_DISMISSED = "dismissed";
+const NOTIFICATION_EVENT_REMOVED = "removed";
+const NOTIFICATION_EVENT_SHOWING = "showing";
+const NOTIFICATION_EVENT_SHOWN = "shown";
+const NOTIFICATION_EVENT_SWAPPING = "swapping";
+
+const ICON_SELECTOR = ".notification-anchor-icon";
+const ICON_ATTRIBUTE_SHOWING = "showing";
+
+const PREF_SECURITY_DELAY = "security.notification_enable_delay";
+
+var popupNotificationsMap = new WeakMap();
+var gNotificationParents = new WeakMap;
+
+function getAnchorFromBrowser(aBrowser) {
+ let anchor = aBrowser.getAttribute("popupnotificationanchor") ||
+ aBrowser.popupnotificationanchor;
+ if (anchor) {
+ if (anchor instanceof Ci.nsIDOMXULElement) {
+ return anchor;
+ }
+ return aBrowser.ownerDocument.getElementById(anchor);
+ }
+ return null;
+}
+
+function getNotificationFromElement(aElement) {
+ // Need to find the associated notification object, which is a bit tricky
+ // since it isn't associated with the element directly - this is kind of
+ // gross and very dependent on the structure of the popupnotification
+ // binding's content.
+ let notificationEl;
+ let parent = aElement;
+ while (parent && (parent = aElement.ownerDocument.getBindingParent(parent)))
+ notificationEl = parent;
+ return notificationEl;
+}
+
+/**
+ * Notification object describes a single popup notification.
+ *
+ * @see PopupNotifications.show()
+ */
+function Notification(id, message, anchorID, mainAction, secondaryActions,
+ browser, owner, options) {
+ this.id = id;
+ this.message = message;
+ this.anchorID = anchorID;
+ this.mainAction = mainAction;
+ this.secondaryActions = secondaryActions || [];
+ this.browser = browser;
+ this.owner = owner;
+ this.options = options || {};
+}
+
+Notification.prototype = {
+
+ id: null,
+ message: null,
+ anchorID: null,
+ mainAction: null,
+ secondaryActions: null,
+ browser: null,
+ owner: null,
+ options: null,
+ timeShown: null,
+
+ /**
+ * Removes the notification and updates the popup accordingly if needed.
+ */
+ remove: function Notification_remove() {
+ this.owner.remove(this);
+ },
+
+ get anchorElement() {
+ let iconBox = this.owner.iconBox;
+
+ let anchorElement = getAnchorFromBrowser(this.browser);
+
+ if (!iconBox)
+ return anchorElement;
+
+ if (!anchorElement && this.anchorID)
+ anchorElement = iconBox.querySelector("#"+this.anchorID);
+
+ // Use a default anchor icon if it's available
+ if (!anchorElement)
+ anchorElement = iconBox.querySelector("#default-notification-icon") ||
+ iconBox;
+
+ return anchorElement;
+ },
+
+ reshow: function() {
+ this.owner._reshowNotifications(this.anchorElement, this.browser);
+ }
+};
+
+/**
+ * The PopupNotifications object manages popup notifications for a given browser
+ * window.
+ * @param tabbrowser
+ * window's <xul:tabbrowser/>. Used to observe tab switching events and
+ * for determining the active browser element.
+ * @param panel
+ * The <xul:panel/> element to use for notifications. The panel is
+ * populated with <popupnotification> children and displayed it as
+ * needed.
+ * @param iconBox
+ * Reference to a container element that should be hidden or
+ * unhidden when notifications are hidden or shown. It should be the
+ * parent of anchor elements whose IDs are passed to show().
+ * It is used as a fallback popup anchor if notifications specify
+ * invalid or non-existent anchor IDs.
+ */
+this.PopupNotifications = function PopupNotifications(tabbrowser, panel, iconBox) {
+ if (!(tabbrowser instanceof Ci.nsIDOMXULElement))
+ throw "Invalid tabbrowser";
+ if (iconBox && !(iconBox instanceof Ci.nsIDOMXULElement))
+ throw "Invalid iconBox";
+ if (!(panel instanceof Ci.nsIDOMXULElement))
+ throw "Invalid panel";
+
+ this.window = tabbrowser.ownerDocument.defaultView;
+ this.panel = panel;
+ this.tabbrowser = tabbrowser;
+ this.iconBox = iconBox;
+ this.buttonDelay = Services.prefs.getIntPref(PREF_SECURITY_DELAY);
+
+ this.panel.addEventListener("popuphidden", this, true);
+
+ this.window.addEventListener("activate", this, true);
+ if (this.tabbrowser.tabContainer)
+ this.tabbrowser.tabContainer.addEventListener("TabSelect", this, true);
+}
+
+PopupNotifications.prototype = {
+
+ window: null,
+ panel: null,
+ tabbrowser: null,
+
+ _iconBox: null,
+ set iconBox(iconBox) {
+ // Remove the listeners on the old iconBox, if needed
+ if (this._iconBox) {
+ this._iconBox.removeEventListener("click", this, false);
+ this._iconBox.removeEventListener("keypress", this, false);
+ }
+ this._iconBox = iconBox;
+ if (iconBox) {
+ iconBox.addEventListener("click", this, false);
+ iconBox.addEventListener("keypress", this, false);
+ }
+ },
+ get iconBox() {
+ return this._iconBox;
+ },
+
+ /**
+ * Retrieve a Notification object associated with the browser/ID pair.
+ * @param id
+ * The Notification ID to search for.
+ * @param browser
+ * The browser whose notifications should be searched. If null, the
+ * currently selected browser's notifications will be searched.
+ *
+ * @returns the corresponding Notification object, or null if no such
+ * notification exists.
+ */
+ getNotification: function PopupNotifications_getNotification(id, browser) {
+ let n = null;
+ let notifications = this._getNotificationsForBrowser(browser || this.tabbrowser.selectedBrowser);
+ notifications.some(function(x) x.id == id && (n = x));
+ return n;
+ },
+
+ /**
+ * Adds a new popup notification.
+ * @param browser
+ * The <xul:browser> element associated with the notification. Must not
+ * be null.
+ * @param id
+ * A unique ID that identifies the type of notification (e.g.
+ * "geolocation"). Only one notification with a given ID can be visible
+ * at a time. If a notification already exists with the given ID, it
+ * will be replaced.
+ * @param message
+ * The text to be displayed in the notification.
+ * @param anchorID
+ * The ID of the element that should be used as this notification
+ * popup's anchor. May be null, in which case the notification will be
+ * anchored to the iconBox.
+ * @param mainAction
+ * A JavaScript object literal describing the notification button's
+ * action. If present, it must have the following properties:
+ * - label (string): the button's label.
+ * - accessKey (string): the button's accessKey.
+ * - callback (function): a callback to be invoked when the button is
+ * pressed, is passed an object that contains the following fields:
+ * - checkboxChecked: (boolean) If the optional checkbox is checked.
+ * If null, the notification will not have a button, and
+ * secondaryActions will be ignored.
+ * @param secondaryActions
+ * An optional JavaScript array describing the notification's alternate
+ * actions. The array should contain objects with the same properties
+ * as mainAction. These are used to populate the notification button's
+ * dropdown menu.
+ * @param options
+ * An options JavaScript object holding additional properties for the
+ * notification. The following properties are currently supported:
+ * persistence: An integer. The notification will not automatically
+ * dismiss for this many page loads.
+ * timeout: A time in milliseconds. The notification will not
+ * automatically dismiss before this time.
+ * persistWhileVisible:
+ * A boolean. If true, a visible notification will always
+ * persist across location changes.
+ * dismissed: Whether the notification should be added as a dismissed
+ * notification. Dismissed notifications can be activated
+ * by clicking on their anchorElement.
+ * eventCallback:
+ * Callback to be invoked when the notification changes
+ * state. The callback's first argument is a string
+ * identifying the state change:
+ * "dismissed": notification has been dismissed by the
+ * user (e.g. by clicking away or switching
+ * tabs)
+ * "removed": notification has been removed (due to
+ * location change or user action)
+ * "showing": notification is about to be shown
+ * (this can be fired multiple times as
+ * notifications are dismissed and re-shown)
+ * "shown": notification has been shown (this can be fired
+ * multiple times as notifications are dismissed
+ * and re-shown)
+ * "swapping": the docshell of the browser that created
+ * the notification is about to be swapped to
+ * another browser. A second parameter contains
+ * the browser that is receiving the docshell,
+ * so that the event callback can transfer stuff
+ * specific to this notification.
+ * If the callback returns true, the notification
+ * will be moved to the new browser.
+ * If the callback isn't implemented, returns false,
+ * or doesn't return any value, the notification
+ * will be removed.
+ * neverShow: Indicate that no popup should be shown for this
+ * notification. Useful for just showing the anchor icon.
+ * removeOnDismissal:
+ * Notifications with this parameter set to true will be
+ * removed when they would have otherwise been dismissed
+ * (i.e. any time the popup is closed due to user
+ * interaction).
+ * checkbox: An object that allows you to add a checkbox and
+ * control its behavior with these fields:
+ * label:
+ * (required) Label to be shown next to the checkbox.
+ * checked:
+ * (optional) Whether the checkbox should be checked
+ * by default. Defaults to false.
+ * checkedState:
+ * (optional) An object that allows you to customize
+ * the notification state when the checkbox is checked.
+ * disableMainAction:
+ * (optional) Whether the mainAction is disabled.
+ * Defaults to false.
+ * warningLabel:
+ * (optional) A (warning) text that is shown below the
+ * checkbox. Pass null to hide.
+ * uncheckedState:
+ * (optional) An object that allows you to customize
+ * the notification state when the checkbox is not checked.
+ * Has the same attributes as checkedState.
+ * popupIconURL:
+ * A string. URL of the image to be displayed in the popup.
+ * Normally specified in CSS using list-style-image and the
+ * .popup-notification-icon[popupid=...] selector.
+ * learnMoreURL:
+ * A string URL. Setting this property will make the
+ * prompt display a "Learn More" link that, when clicked,
+ * opens the URL in a new tab.
+ * @returns the Notification object corresponding to the added notification.
+ */
+ show: function PopupNotifications_show(browser, id, message, anchorID,
+ mainAction, secondaryActions, options) {
+ function isInvalidAction(a) {
+ return !a || !(typeof(a.callback) == "function") || !a.label || !a.accessKey;
+ }
+
+ if (!browser)
+ throw "PopupNotifications_show: invalid browser";
+ if (!id)
+ throw "PopupNotifications_show: invalid ID";
+ if (mainAction && isInvalidAction(mainAction))
+ throw "PopupNotifications_show: invalid mainAction";
+ if (secondaryActions && secondaryActions.some(isInvalidAction))
+ throw "PopupNotifications_show: invalid secondaryActions";
+
+ let notification = new Notification(id, message, anchorID, mainAction,
+ secondaryActions, browser, this, options);
+
+ if (options && options.dismissed)
+ notification.dismissed = true;
+
+ let existingNotification = this.getNotification(id, browser);
+ if (existingNotification)
+ this._remove(existingNotification);
+
+ let notifications = this._getNotificationsForBrowser(browser);
+ notifications.push(notification);
+
+ let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
+ if (browser.docShell.isActive && fm.activeWindow == this.window) {
+ // show panel now
+ this._update(notifications, notification.anchorElement, true);
+ } else {
+ // Otherwise, update() will display the notification the next time the
+ // relevant tab/window is selected.
+
+ // If the tab is selected but the window is in the background, let the OS
+ // tell the user that there's a notification waiting in that window.
+ // At some point we might want to do something about background tabs here
+ // too. When the user switches to this window, we'll show the panel if
+ // this browser is a tab (thus showing the anchor icon). For
+ // non-tabbrowser browsers, we need to make the icon visible now or the
+ // user will not be able to open the panel.
+ if (!notification.dismissed && browser.docShell.isActive) {
+ this.window.getAttention();
+ if (notification.anchorElement.parentNode != this.iconBox) {
+ notification.anchorElement.setAttribute(ICON_ATTRIBUTE_SHOWING, "true");
+ }
+ }
+
+ // Notify observers that we're not showing the popup (useful for testing)
+ this._notify("backgroundShow");
+ }
+
+ return notification;
+ },
+
+ /**
+ * Returns true if the notification popup is currently being displayed.
+ */
+ get isPanelOpen() {
+ let panelState = this.panel.state;
+
+ return panelState == "showing" || panelState == "open";
+ },
+
+ /**
+ * Called by the consumer to indicate that a browser's location has changed,
+ * so that we can update the active notifications accordingly.
+ */
+ locationChange: function PopupNotifications_locationChange(aBrowser) {
+ if (!aBrowser)
+ throw "PopupNotifications_locationChange: invalid browser";
+
+ let notifications = this._getNotificationsForBrowser(aBrowser);
+
+ notifications = notifications.filter(function (notification) {
+ // The persistWhileVisible option allows an open notification to persist
+ // across location changes
+ if (notification.options.persistWhileVisible &&
+ this.isPanelOpen) {
+ if ("persistence" in notification.options &&
+ notification.options.persistence)
+ notification.options.persistence--;
+ return true;
+ }
+
+ // The persistence option allows a notification to persist across multiple
+ // page loads
+ if ("persistence" in notification.options &&
+ notification.options.persistence) {
+ notification.options.persistence--;
+ return true;
+ }
+
+ // The timeout option allows a notification to persist until a certain time
+ if ("timeout" in notification.options &&
+ Date.now() <= notification.options.timeout) {
+ return true;
+ }
+
+ this._fireCallback(notification, NOTIFICATION_EVENT_REMOVED);
+ return false;
+ }, this);
+
+ this._setNotificationsForBrowser(aBrowser, notifications);
+
+ if (aBrowser.docShell.isActive) {
+ // get the anchor element if the browser has defined one so it will
+ // _update will handle both the tabs iconBox and non-tab permission
+ // anchors.
+ let anchorElement = notifications.length > 0 ? notifications[0].anchorElement : null;
+ if (!anchorElement)
+ anchorElement = getAnchorFromBrowser(aBrowser);
+ this._update(notifications, anchorElement);
+ }
+ },
+
+ /**
+ * Removes a Notification.
+ * @param notification
+ * The Notification object to remove.
+ */
+ remove: function PopupNotifications_remove(notification) {
+ this._remove(notification);
+
+ if (notification.browser.docShell.isActive) {
+ let notifications = this._getNotificationsForBrowser(notification.browser);
+ this._update(notifications, notification.anchorElement);
+ }
+ },
+
+ handleEvent: function (aEvent) {
+ switch (aEvent.type) {
+ case "popuphidden":
+ this._onPopupHidden(aEvent);
+ break;
+ case "activate":
+ case "TabSelect":
+ let self = this;
+ // setTimeout(..., 0) needed, otherwise openPopup from "activate" event
+ // handler results in the popup being hidden again for some reason...
+ this.window.setTimeout(function () {
+ self._update();
+ }, 0);
+ break;
+ case "click":
+ case "keypress":
+ this._onIconBoxCommand(aEvent);
+ break;
+ }
+ },
+
+////////////////////////////////////////////////////////////////////////////////
+// Utility methods
+////////////////////////////////////////////////////////////////////////////////
+
+ _ignoreDismissal: null,
+ _currentAnchorElement: null,
+
+ /**
+ * Gets notifications for the currently selected browser.
+ */
+ get _currentNotifications() {
+ return this.tabbrowser.selectedBrowser ? this._getNotificationsForBrowser(this.tabbrowser.selectedBrowser) : [];
+ },
+
+ _remove: function PopupNotifications_removeHelper(notification) {
+ // This notification may already be removed, in which case let's just fail
+ // silently.
+ let notifications = this._getNotificationsForBrowser(notification.browser);
+ if (!notifications)
+ return;
+
+ var index = notifications.indexOf(notification);
+ if (index == -1)
+ return;
+
+ if (notification.browser.docShell.isActive)
+ notification.anchorElement.removeAttribute(ICON_ATTRIBUTE_SHOWING);
+
+ // remove the notification
+ notifications.splice(index, 1);
+ this._fireCallback(notification, NOTIFICATION_EVENT_REMOVED);
+ },
+
+ /**
+ * Dismisses the notification without removing it.
+ */
+ _dismiss: function PopupNotifications_dismiss() {
+ let browser = this.panel.firstChild &&
+ this.panel.firstChild.notification.browser;
+ if (typeof this.panel.hidePopup === "function") {
+ this.panel.hidePopup();
+ }
+ if (browser)
+ browser.focus();
+ },
+
+ /**
+ * Hides the notification popup.
+ */
+ _hidePanel: function PopupNotifications_hide() {
+ this._ignoreDismissal = true;
+ if (typeof this.panel.hidePopup === "function") {
+ this.panel.hidePopup();
+ }
+ this._ignoreDismissal = false;
+ },
+
+ /**
+ * Removes all notifications from the notification popup.
+ */
+ _clearPanel: function () {
+ let popupnotification;
+ while ((popupnotification = this.panel.lastChild)) {
+ this.panel.removeChild(popupnotification);
+
+ // If this notification was provided by the chrome document rather than
+ // created ad hoc, move it back to where we got it from.
+ let originalParent = gNotificationParents.get(popupnotification);
+ if (originalParent) {
+ popupnotification.notification = null;
+
+ // Remove nodes dynamically added to the notification's menu button
+ // in _refreshPanel. Keep popupnotificationcontent nodes; they are
+ // provided by the chrome document.
+ let contentNode = popupnotification.lastChild;
+ while (contentNode) {
+ let previousSibling = contentNode.previousSibling;
+ if (contentNode.nodeName != "popupnotificationcontent")
+ popupnotification.removeChild(contentNode);
+ contentNode = previousSibling;
+ }
+
+ // Re-hide the notification such that it isn't rendered in the chrome
+ // document. _refreshPanel will unhide it again when needed.
+ popupnotification.hidden = true;
+
+ originalParent.appendChild(popupnotification);
+ }
+ }
+ },
+
+ _refreshPanel: function PopupNotifications_refreshPanel(notificationsToShow) {
+ this._clearPanel();
+
+ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ notificationsToShow.forEach(function (n) {
+ let doc = this.window.document;
+
+ // Append "-notification" to the ID to try to avoid ID conflicts with other stuff
+ // in the document.
+ let popupnotificationID = n.id + "-notification";
+
+ // If the chrome document provides a popupnotification with this id, use
+ // that. Otherwise create it ad-hoc.
+ let popupnotification = doc.getElementById(popupnotificationID);
+ if (popupnotification)
+ gNotificationParents.set(popupnotification, popupnotification.parentNode);
+ else
+ popupnotification = doc.createElementNS(XUL_NS, "popupnotification");
+
+ popupnotification.setAttribute("label", n.message);
+ popupnotification.setAttribute("id", popupnotificationID);
+ popupnotification.setAttribute("popupid", n.id);
+ popupnotification.setAttribute("closebuttoncommand", "PopupNotifications._dismiss();");
+ if (n.mainAction) {
+ popupnotification.setAttribute("buttonlabel", n.mainAction.label);
+ popupnotification.setAttribute("buttonaccesskey", n.mainAction.accessKey);
+ popupnotification.setAttribute("buttoncommand", "PopupNotifications._onButtonCommand(event);");
+ popupnotification.setAttribute("menucommand", "PopupNotifications._onMenuCommand(event);");
+ popupnotification.setAttribute("closeitemcommand", "PopupNotifications._dismiss();event.stopPropagation();");
+ } else {
+ popupnotification.removeAttribute("buttonlabel");
+ popupnotification.removeAttribute("buttonaccesskey");
+ popupnotification.removeAttribute("buttoncommand");
+ popupnotification.removeAttribute("menucommand");
+ popupnotification.removeAttribute("closeitemcommand");
+ }
+
+ if (n.options.popupIconURL)
+ popupnotification.setAttribute("icon", n.options.popupIconURL);
+ if (n.options.learnMoreURL)
+ popupnotification.setAttribute("learnmoreurl", n.options.learnMoreURL);
+ else
+ popupnotification.removeAttribute("learnmoreurl");
+
+ popupnotification.notification = n;
+
+ if (n.secondaryActions) {
+ n.secondaryActions.forEach(function (a) {
+ let item = doc.createElementNS(XUL_NS, "menuitem");
+ item.setAttribute("label", a.label);
+ item.setAttribute("accesskey", a.accessKey);
+ item.notification = n;
+ item.action = a;
+
+ popupnotification.appendChild(item);
+ }, this);
+
+ if (n.secondaryActions.length) {
+ let closeItemSeparator = doc.createElementNS(XUL_NS, "menuseparator");
+ popupnotification.appendChild(closeItemSeparator);
+ }
+ }
+
+ let checkbox = n.options.checkbox;
+ if (checkbox && checkbox.label) {
+ let checked = n._checkboxChecked != null ? n._checkboxChecked : !!checkbox.checked;
+
+ popupnotification.setAttribute("checkboxhidden", "false");
+ popupnotification.setAttribute("checkboxchecked", checked);
+ popupnotification.setAttribute("checkboxlabel", checkbox.label);
+
+ popupnotification.setAttribute("checkboxcommand", "PopupNotifications._onCheckboxCommand(event);");
+
+ if (checked) {
+ this._setNotificationUIState(popupnotification, checkbox.checkedState);
+ } else {
+ this._setNotificationUIState(popupnotification, checkbox.uncheckedState);
+ }
+ } else {
+ popupnotification.setAttribute("checkboxhidden", "true");
+ }
+
+ this.panel.appendChild(popupnotification);
+
+ // The popupnotification may be hidden if we got it from the chrome
+ // document rather than creating it ad hoc.
+ popupnotification.hidden = false;
+ }, this);
+ },
+
+ _setNotificationUIState(notification, state={}) {
+ notification.setAttribute("mainactiondisabled", state.disableMainAction || "false");
+
+ if (state.warningLabel) {
+ notification.setAttribute("warninglabel", state.warningLabel);
+ notification.setAttribute("warninghidden", "false");
+ } else {
+ notification.setAttribute("warninghidden", "true");
+ }
+ },
+
+ _onCheckboxCommand(event) {
+ let notificationEl = getNotificationFromElement(event.originalTarget);
+ let checked = notificationEl.checkbox.checked;
+ let notification = notificationEl.notification;
+
+ // Save checkbox state to be able to persist it when re-opening the doorhanger.
+ notification._checkboxChecked = checked;
+
+ if (checked) {
+ this._setNotificationUIState(notificationEl, notification.options.checkbox.checkedState);
+ } else {
+ this._setNotificationUIState(notificationEl, notification.options.checkbox.uncheckedState);
+ }
+ },
+
+ _showPanel: function PopupNotifications_showPanel(notificationsToShow, anchorElement) {
+ this.panel.hidden = false;
+
+ notificationsToShow.forEach(function (n) {
+ this._fireCallback(n, NOTIFICATION_EVENT_SHOWING);
+ }, this);
+ this._refreshPanel(notificationsToShow);
+
+ if (this.isPanelOpen && this._currentAnchorElement == anchorElement)
+ return;
+
+ // If the panel is already open but we're changing anchors, we need to hide
+ // it first. Otherwise it can appear in the wrong spot. (_hidePanel is
+ // safe to call even if the panel is already hidden.)
+ this._hidePanel();
+
+ // If the anchor element is hidden or null, use the tab as the anchor. We
+ // only ever show notifications for the current browser, so we can just use
+ // the current tab.
+ let selectedTab = this.tabbrowser.selectedTab;
+ if (anchorElement) {
+ let bo = anchorElement.boxObject;
+ if (bo.height == 0 && bo.width == 0)
+ anchorElement = selectedTab; // hidden
+ } else {
+ anchorElement = selectedTab; // null
+ }
+
+ this._currentAnchorElement = anchorElement;
+
+ // On OS X and Linux we need a different panel arrow color for
+ // click-to-play plugins, so copy the popupid and use css.
+ this.panel.setAttribute("popupid", this.panel.firstChild.getAttribute("popupid"));
+ notificationsToShow.forEach(function (n) {
+ // Remember the time the notification was shown for the security delay.
+ n.timeShown = this.window.performance.now();
+ }, this);
+ this.panel.openPopup(anchorElement, "bottomcenter topleft");
+ notificationsToShow.forEach(function (n) {
+ this._fireCallback(n, NOTIFICATION_EVENT_SHOWN);
+ }, this);
+ },
+
+ /**
+ * Updates the notification state in response to window activation or tab
+ * selection changes.
+ *
+ * @param notifications an array of Notification instances. if null,
+ * notifications will be retrieved off the current
+ * browser tab
+ * @param anchor is a XUL element that the notifications panel will be
+ * anchored to
+ * @param dismissShowing if true, dismiss any currently visible notifications
+ * if there are no notifications to show. Otherwise,
+ * currently displayed notifications will be left alone.
+ */
+ _update: function PopupNotifications_update(notifications, anchor, dismissShowing = false) {
+ let useIconBox = this.iconBox && (!anchor || anchor.parentNode == this.iconBox);
+ if (useIconBox) {
+ // hide icons of the previous tab.
+ this._hideIcons();
+ }
+
+ let anchorElement = anchor, notificationsToShow = [];
+ if (!notifications)
+ notifications = this._currentNotifications;
+ let haveNotifications = notifications.length > 0;
+ if (haveNotifications) {
+ // Only show the notifications that have the passed-in anchor (or the
+ // first notification's anchor, if none was passed in). Other
+ // notifications will be shown once these are dismissed.
+ anchorElement = anchor || notifications[0].anchorElement;
+
+ if (useIconBox) {
+ this._showIcons(notifications);
+ this.iconBox.hidden = false;
+ } else if (anchorElement) {
+ anchorElement.setAttribute(ICON_ATTRIBUTE_SHOWING, "true");
+ // use the anchorID as a class along with the default icon class as a
+ // fallback if anchorID is not defined in CSS. We always use the first
+ // notifications icon, so in the case of multiple notifications we'll
+ // only use the default icon
+ if (anchorElement.classList.contains("notification-anchor-icon")) {
+ // remove previous icon classes
+ let className = anchorElement.className.replace(/([-\w]+-notification-icon\s?)/g,"")
+ className = "default-notification-icon " + className;
+ if (notifications.length == 1) {
+ className = notifications[0].anchorID + " " + className;
+ }
+ anchorElement.className = className;
+ }
+ }
+
+ // Also filter out notifications that have been dismissed.
+ notificationsToShow = notifications.filter(function (n) {
+ return !n.dismissed && n.anchorElement == anchorElement &&
+ !n.options.neverShow;
+ });
+ }
+
+ if (notificationsToShow.length > 0) {
+ this._showPanel(notificationsToShow, anchorElement);
+ } else {
+ // Notify observers that we're not showing the popup (useful for testing)
+ this._notify("updateNotShowing");
+
+ // Close the panel if there are no notifications to show.
+ // When called from PopupNotifications.show() we should never close the
+ // panel, however. It may just be adding a dismissed notification, in
+ // which case we want to continue showing any existing notifications.
+ if (!dismissShowing)
+ this._dismiss();
+
+ // Only hide the iconBox if we actually have no notifications (as opposed
+ // to not having any showable notifications)
+ if (!haveNotifications) {
+ if (useIconBox)
+ this.iconBox.hidden = true;
+ else if (anchorElement)
+ anchorElement.removeAttribute(ICON_ATTRIBUTE_SHOWING);
+ }
+ }
+ },
+
+ _showIcons: function PopupNotifications_showIcons(aCurrentNotifications) {
+ for (let notification of aCurrentNotifications) {
+ let anchorElm = notification.anchorElement;
+ if (anchorElm) {
+ anchorElm.setAttribute(ICON_ATTRIBUTE_SHOWING, "true");
+ }
+ }
+ },
+
+ _hideIcons: function PopupNotifications_hideIcons() {
+ let icons = this.iconBox.querySelectorAll(ICON_SELECTOR);
+ for (let icon of icons) {
+ icon.removeAttribute(ICON_ATTRIBUTE_SHOWING);
+ }
+ },
+
+ /**
+ * Gets and sets notifications for the browser.
+ */
+ _getNotificationsForBrowser: function PopupNotifications_getNotifications(browser) {
+ let notifications = popupNotificationsMap.get(browser);
+ if (!notifications) {
+ // Initialize the WeakMap for the browser so callers can reference/manipulate the array.
+ notifications = [];
+ popupNotificationsMap.set(browser, notifications);
+ }
+ return notifications;
+ },
+ _setNotificationsForBrowser: function PopupNotifications_setNotifications(browser, notifications) {
+ popupNotificationsMap.set(browser, notifications);
+ return notifications;
+ },
+
+ _onIconBoxCommand: function PopupNotifications_onIconBoxCommand(event) {
+ // Left click, space or enter only
+ let type = event.type;
+ if (type == "click" && event.button != 0)
+ return;
+
+ if (type == "keypress" &&
+ !(event.charCode == Ci.nsIDOMKeyEvent.DOM_VK_SPACE ||
+ event.keyCode == Ci.nsIDOMKeyEvent.DOM_VK_RETURN))
+ return;
+
+ if (this._currentNotifications.length == 0)
+ return;
+
+ // Get the anchor that is the immediate child of the icon box
+ let anchor = event.target;
+ while (anchor && anchor.parentNode != this.iconBox)
+ anchor = anchor.parentNode;
+
+ this._reshowNotifications(anchor);
+ },
+
+ _reshowNotifications: function PopupNotifications_reshowNotifications(anchor, browser) {
+ // Mark notifications anchored to this anchor as un-dismissed
+ let notifications = this._getNotificationsForBrowser(browser || this.tabbrowser.selectedBrowser);
+ notifications.forEach(function (n) {
+ if (n.anchorElement == anchor)
+ n.dismissed = false;
+ });
+
+ // ...and then show them.
+ this._update(notifications, anchor);
+ },
+
+ _swapBrowserNotifications: function PopupNotifications_swapBrowserNoficications(ourBrowser, otherBrowser) {
+ // When swaping browser docshells (e.g. dragging tab to new window) we need
+ // to update our notification map.
+
+ let ourNotifications = this._getNotificationsForBrowser(ourBrowser);
+ let other = otherBrowser.ownerDocument.defaultView.PopupNotifications;
+ if (!other) {
+ if (ourNotifications.length > 0)
+ Cu.reportError("unable to swap notifications: otherBrowser doesn't support notifications");
+ return;
+ }
+ let otherNotifications = other._getNotificationsForBrowser(otherBrowser);
+ if (ourNotifications.length < 1 && otherNotifications.length < 1) {
+ // No notification to swap.
+ return;
+ }
+
+ otherNotifications = otherNotifications.filter(n => {
+ if (this._fireCallback(n, NOTIFICATION_EVENT_SWAPPING, ourBrowser)) {
+ n.browser = ourBrowser;
+ n.owner = this;
+ return true;
+ }
+ other._fireCallback(n, NOTIFICATION_EVENT_REMOVED);
+ return false;
+ });
+
+ ourNotifications = ourNotifications.filter(n => {
+ if (this._fireCallback(n, NOTIFICATION_EVENT_SWAPPING, otherBrowser)) {
+ n.browser = otherBrowser;
+ n.owner = other;
+ return true;
+ }
+ this._fireCallback(n, NOTIFICATION_EVENT_REMOVED);
+ return false;
+ });
+
+ this._setNotificationsForBrowser(otherBrowser, ourNotifications);
+ other._setNotificationsForBrowser(ourBrowser, otherNotifications);
+
+ if (otherNotifications.length > 0)
+ this._update(otherNotifications, otherNotifications[0].anchorElement);
+ if (ourNotifications.length > 0)
+ other._update(ourNotifications, ourNotifications[0].anchorElement);
+ },
+
+ _fireCallback: function PopupNotifications_fireCallback(n, event, ...args) {
+ try {
+ if (n.options.eventCallback)
+ return n.options.eventCallback.call(n, event, ...args);
+ } catch (error) {
+ Cu.reportError(error);
+ }
+ return undefined;
+ },
+
+ _onPopupHidden: function PopupNotifications_onPopupHidden(event) {
+ if (event.target != this.panel || this._ignoreDismissal)
+ return;
+
+ let browser = this.panel.firstChild &&
+ this.panel.firstChild.notification.browser;
+ if (!browser)
+ return;
+
+ let notifications = this._getNotificationsForBrowser(browser);
+ // Mark notifications as dismissed and call dismissal callbacks
+ Array.forEach(this.panel.childNodes, function (nEl) {
+ let notificationObj = nEl.notification;
+ // Never call a dismissal handler on a notification that's been removed.
+ if (notifications.indexOf(notificationObj) == -1)
+ return;
+
+ // Do not mark the notification as dismissed or fire NOTIFICATION_EVENT_DISMISSED
+ // if the notification is removed.
+ if (notificationObj.options.removeOnDismissal)
+ this._remove(notificationObj);
+ else {
+ notificationObj.dismissed = true;
+ this._fireCallback(notificationObj, NOTIFICATION_EVENT_DISMISSED);
+ }
+ }, this);
+
+ this._clearPanel();
+
+ this._update();
+ },
+
+ _onButtonCommand: function PopupNotifications_onButtonCommand(event) {
+ let notificationEl = getNotificationFromElement(event.originalTarget);
+
+ if (!notificationEl)
+ throw "PopupNotifications_onButtonCommand: couldn't find notification element";
+
+ if (!notificationEl.notification)
+ throw "PopupNotifications_onButtonCommand: couldn't find notification";
+
+ let notification = notificationEl.notification;
+ let timeSinceShown = this.window.performance.now() - notification.timeShown;
+
+ // Only report the first time mainAction is triggered and remember that this occurred.
+ if (!notification.timeMainActionFirstTriggered) {
+ notification.timeMainActionFirstTriggered = timeSinceShown;
+ }
+
+ if (timeSinceShown < this.buttonDelay) {
+ Services.console.logStringMessage("PopupNotifications_onButtonCommand: " +
+ "Button click happened before the security delay: " +
+ timeSinceShown + "ms");
+ return;
+ }
+
+ try {
+ notification.mainAction.callback.call(undefined, {
+ checkboxChecked: notificationEl.checkbox.checked
+ });
+ } catch (error) {
+ Cu.reportError(error);
+ }
+
+ this._remove(notification);
+ this._update();
+ },
+
+ _onMenuCommand: function PopupNotifications_onMenuCommand(event) {
+ let target = event.originalTarget;
+ if (!target.action || !target.notification)
+ throw "menucommand target has no associated action/notification";
+
+ let notificationEl = target.parentElement;
+ event.stopPropagation();
+
+ try {
+ target.action.callback.call(undefined, {
+ checkboxChecked: notificationEl.checkbox.checked
+ });
+ } catch (error) {
+ Cu.reportError(error);
+ }
+
+ this._remove(target.notification);
+ this._update();
+ },
+
+ _notify: function PopupNotifications_notify(topic) {
+ Services.obs.notifyObservers(null, "PopupNotifications-" + topic, "");
+ },
+};
diff --git a/modules/QuotaManager.jsm b/modules/QuotaManager.jsm
new file mode 100644
index 0000000..48cfe88
--- /dev/null
+++ b/modules/QuotaManager.jsm
@@ -0,0 +1,51 @@
+/* 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.EXPORTED_SYMBOLS = ["QuotaManagerHelper"];
+
+Components.utils.import('resource://gre/modules/Services.jsm');
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cu = Components.utils;
+
+this.QuotaManagerHelper = {
+ clear: function(isShutDown) {
+ try {
+ var stord = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ stord.append("storage");
+ if (stord.exists() && stord.isDirectory()) {
+ var doms = {};
+ for (var stor of ["default", "permanent", "temporary"]) {
+ var storsubd = stord.clone();
+ storsubd.append(stor);
+ if (storsubd.exists() && storsubd.isDirectory()) {
+ var entries = storsubd.directoryEntries;
+ while(entries.hasMoreElements()) {
+ var host, entry = entries.getNext();
+ entry.QueryInterface(Ci.nsIFile);
+ if ((host = /^(https?|file)\+\+\+(.+)$/.exec(entry.leafName)) !== null) {
+ if (isShutDown) {
+ entry.remove(true);
+ } else {
+ doms[host[1] + "://" + host[2]] = true;
+ }
+ }
+ }
+ }
+ }
+ var qm = Cc["@mozilla.org/dom/quota-manager-service;1"]
+ .getService(Ci.nsIQuotaManagerService);
+ for (var dom in doms) {
+ var uri = Services.io.newURI(dom, null, null);
+ let principal = Services.scriptSecurityManager
+ .createCodebasePrincipal(uri, {});
+ qm.clearStoragesForPrincipal(principal);
+ }
+ }
+ } catch(er) {
+ Cu.reportError(er);
+ }
+ }
+};
diff --git a/modules/RecentWindow.jsm b/modules/RecentWindow.jsm
new file mode 100644
index 0000000..0018b50
--- /dev/null
+++ b/modules/RecentWindow.jsm
@@ -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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["RecentWindow"];
+
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+#ifndef XP_WIN
+#define BROKEN_WM_Z_ORDER
+#endif
+
+this.RecentWindow = {
+ /*
+ * Get the most recent browser window.
+ *
+ * @param aOptions an object accepting the arguments for the search.
+ * * private: true to restrict the search to private windows
+ * only, false to restrict the search to non-private only.
+ * Omit the property to search in both groups.
+ * * allowPopups: true if popup windows are permissable.
+ */
+ getMostRecentBrowserWindow: function RW_getMostRecentBrowserWindow(aOptions) {
+ let checkPrivacy = typeof aOptions == "object" &&
+ "private" in aOptions;
+
+ let allowPopups = typeof aOptions == "object" && !!aOptions.allowPopups;
+
+ function isSuitableBrowserWindow(win) {
+ return (!win.closed &&
+ (allowPopups || win.toolbar.visible) &&
+ (!checkPrivacy ||
+ PrivateBrowsingUtils.permanentPrivateBrowsing ||
+ PrivateBrowsingUtils.isWindowPrivate(win) == aOptions.private));
+ }
+
+#ifdef BROKEN_WM_Z_ORDER
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+
+ // if we're lucky, this isn't a popup, and we can just return this
+ if (win && !isSuitableBrowserWindow(win)) {
+ win = null;
+ let windowList = Services.wm.getEnumerator("navigator:browser");
+ // this is oldest to newest, so this gets a bit ugly
+ while (windowList.hasMoreElements()) {
+ let nextWin = windowList.getNext();
+ if (isSuitableBrowserWindow(nextWin))
+ win = nextWin;
+ }
+ }
+ return win;
+#else
+ let windowList = Services.wm.getZOrderDOMWindowEnumerator("navigator:browser", true);
+ while (windowList.hasMoreElements()) {
+ let win = windowList.getNext();
+ if (isSuitableBrowserWindow(win))
+ return win;
+ }
+ return null;
+#endif
+ }
+};
+
diff --git a/modules/SharedFrame.jsm b/modules/SharedFrame.jsm
new file mode 100644
index 0000000..b9d59bf
--- /dev/null
+++ b/modules/SharedFrame.jsm
@@ -0,0 +1,221 @@
+/* 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";
+
+this.EXPORTED_SYMBOLS = [ "SharedFrame" ];
+
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+/**
+ * The purpose of this module is to create and group various iframe
+ * elements that are meant to all display the same content and only
+ * one at a time. This makes it possible to have the content loaded
+ * only once, while the other iframes can be kept as placeholders to
+ * quickly move the content to them through the swapFrameLoaders function
+ * when another one of the placeholder is meant to be displayed.
+ * */
+
+var Frames = new Map();
+
+/**
+ * The Frames map is the main data structure that holds information
+ * about the groups being tracked. Each entry's key is the group name,
+ * and the object holds information about what is the URL being displayed
+ * on that group, and what is the active element on the group (the frame that
+ * holds the loaded content).
+ * The reference to the activeFrame is a weak reference, which allows the
+ * frame to go away at any time, and when that happens the module considers that
+ * there are no active elements in that group. The group can be reactivated
+ * by changing the URL, calling preload again or adding a new element.
+ *
+ *
+ * Frames = {
+ * "messages-panel": {
+ * url: string,
+ * activeFrame: weakref
+ * }
+ * }
+ *
+ * Each object on the map is called a _SharedFrameGroup, which is an internal
+ * class of this module which does not automatically keep track of its state. This
+ * object should not be used externally, and all control should be handled by the
+ * module's functions.
+ */
+
+function UNLOADED_URL(aStr) "data:text/html;charset=utf-8,<!-- Unloaded frame " + aStr + " -->";
+
+
+this.SharedFrame = {
+ /**
+ * Creates an iframe element and track it as part of the specified group
+ * The module must create the iframe itself because it needs to do some special
+ * handling for the element's src attribute.
+ *
+ * @param aGroupName the name of the group to which this frame belongs
+ * @param aParent the parent element to which the frame will be appended to
+ * @param aFrameAttributes an object with a list of attributes to set in the iframe
+ * before appending it to the DOM. The "src" attribute has
+ * special meaning here and if it's not blank it specifies
+ * the URL that will be initially assigned to this group
+ * @param aPreload optional, tells if the URL specified in the src attribute
+ * should be preloaded in the frame being created, in case
+ * it's not yet preloaded in any other frame of the group.
+ * This parameter has no meaning if src is blank.
+ */
+ createFrame: function (aGroupName, aParent, aFrameAttributes, aPreload = true) {
+ let frame = aParent.ownerDocument.createElement("iframe");
+
+ for (let [key, val] of Iterator(aFrameAttributes)) {
+ frame.setAttribute(key, val);
+ }
+
+ let src = aFrameAttributes.src;
+ if (!src) {
+ aPreload = false;
+ }
+
+ let group = Frames.get(aGroupName);
+
+ if (group) {
+ // If this group has already been created
+
+ if (aPreload && !group.isAlive) {
+ // If aPreload is set and the group is not already loaded, load it.
+ // This can happen if:
+ // - aPreload was not used while creating the previous frames of this group, or
+ // - the previously active frame went dead in the meantime
+ group.url = src;
+ this.preload(aGroupName, frame);
+ } else {
+ // If aPreload is not set, or the group is already loaded in a different frame,
+ // there's not much that we need to do here: just create this frame as an
+ // inactivate placeholder
+ frame.setAttribute("src", UNLOADED_URL(aGroupName));
+ }
+
+ } else {
+ // This is the first time we hear about this group, so let's start tracking it,
+ // and also preload it if the src attribute was set and aPreload = true
+ group = new _SharedFrameGroup(src);
+ Frames.set(aGroupName, group);
+
+ if (aPreload) {
+ this.preload(aGroupName, frame);
+ } else {
+ frame.setAttribute("src", UNLOADED_URL(aGroupName));
+ }
+ }
+
+ aParent.appendChild(frame);
+ return frame;
+
+ },
+
+ /**
+ * Function that moves the loaded content from one active frame to
+ * another one that is currently a placeholder. If there's no active
+ * frame in the group, the content is loaded/reloaded.
+ *
+ * @param aGroupName the name of the group
+ * @param aTargetFrame the frame element to which the content should
+ * be moved to.
+ */
+ setOwner: function (aGroupName, aTargetFrame) {
+ let group = Frames.get(aGroupName);
+ let frame = group.activeFrame;
+
+ if (frame == aTargetFrame) {
+ // nothing to do here
+ return;
+ }
+
+ if (group.isAlive) {
+ // Move document ownership to the desired frame, and make it the active one
+ frame.QueryInterface(Ci.nsIFrameLoaderOwner).swapFrameLoaders(aTargetFrame);
+ group.activeFrame = aTargetFrame;
+ } else {
+ // Previous owner was dead, reload the document at the new owner and make it the active one
+ aTargetFrame.setAttribute("src", group.url);
+ group.activeFrame = aTargetFrame;
+ }
+ },
+
+ /**
+ * Updates the current URL in use by this group, and loads it into the active frame.
+ *
+ * @param aGroupName the name of the group
+ * @param aURL the new url
+ */
+ updateURL: function (aGroupName, aURL) {
+ let group = Frames.get(aGroupName);
+ group.url = aURL;
+
+ if (group.isAlive) {
+ group.activeFrame.setAttribute("src", aURL);
+ }
+ },
+
+ /**
+ * Loads the group's url into a target frame, if the group doesn't have a currently
+ * active frame.
+ *
+ * @param aGroupName the name of the group
+ * @param aTargetFrame the frame element which should be made active and
+ * have the group's content loaded to
+ */
+ preload: function (aGroupName, aTargetFrame) {
+ let group = Frames.get(aGroupName);
+ if (!group.isAlive) {
+ aTargetFrame.setAttribute("src", group.url);
+ group.activeFrame = aTargetFrame;
+ }
+ },
+
+ /**
+ * Tells if a group currently have an active element.
+ *
+ * @param aGroupName the name of the group
+ */
+ isGroupAlive: function (aGroupName) {
+ return Frames.get(aGroupName).isAlive;
+ },
+
+ /**
+ * Forgets about this group. This function doesn't need to be used
+ * unless the group's name needs to be reused.
+ *
+ * @param aGroupName the name of the group
+ */
+ forgetGroup: function (aGroupName) {
+ Frames.delete(aGroupName);
+ }
+}
+
+
+function _SharedFrameGroup(aURL) {
+ this.url = aURL;
+ this._activeFrame = null;
+}
+
+_SharedFrameGroup.prototype = {
+ get isAlive() {
+ let frame = this.activeFrame;
+ return !!(frame &&
+ frame.contentDocument &&
+ frame.contentDocument.location);
+ },
+
+ get activeFrame() {
+ return this._activeFrame &&
+ this._activeFrame.get();
+ },
+
+ set activeFrame(aActiveFrame) {
+ this._activeFrame = aActiveFrame
+ ? Cu.getWeakReference(aActiveFrame)
+ : null;
+ }
+}
diff --git a/modules/Windows8WindowFrameColor.jsm b/modules/Windows8WindowFrameColor.jsm
new file mode 100644
index 0000000..e7a447d
--- /dev/null
+++ b/modules/Windows8WindowFrameColor.jsm
@@ -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/. */
+
+"use strict";
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+this.EXPORTED_SYMBOLS = ["Windows8WindowFrameColor"];
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/WindowsRegistry.jsm");
+
+var Windows8WindowFrameColor = {
+ _windowFrameColor: null,
+
+ get_win8: function() {
+ if (this._windowFrameColor)
+ return this._windowFrameColor;
+
+ const HKCU = Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER;
+ const dwmKey = "Software\\Microsoft\\Windows\\DWM";
+
+ // Window frame base color component values when Color Intensity is at 0.
+ let frameBaseColor = 217;
+
+ let windowFrameColor = WindowsRegistry.readRegKey(HKCU, dwmKey,
+ "ColorizationColor");
+ if (windowFrameColor == undefined) {
+ // Return the default color if unset or colorization not used
+ return this._windowFrameColor = [frameBaseColor, frameBaseColor, frameBaseColor];
+ }
+ // The color returned from the Registry is in decimal form.
+ let windowFrameColorHex = windowFrameColor.toString(16);
+ // Zero-pad the number just to make sure that it is 8 digits.
+ windowFrameColorHex = ("00000000" + windowFrameColorHex).substr(-8);
+ let windowFrameColorArray = windowFrameColorHex.match(/../g);
+ let [unused, fgR, fgG, fgB] = windowFrameColorArray.map(function(val) parseInt(val, 16));
+ let windowFrameColorBalance = WindowsRegistry.readRegKey(HKCU, dwmKey,
+ "ColorizationColorBalance");
+ // Default to balance=78 if reg key isn't defined
+ if (windowFrameColorBalance == undefined) {
+ windowFrameColorBalance = 78;
+ }
+ let alpha = windowFrameColorBalance / 100;
+
+ // Alpha-blend the foreground color with the frame base color.
+ let r = Math.round(fgR * alpha + frameBaseColor * (1 - alpha));
+ let g = Math.round(fgG * alpha + frameBaseColor * (1 - alpha));
+ let b = Math.round(fgB * alpha + frameBaseColor * (1 - alpha));
+ return this._windowFrameColor = [r, g, b];
+ }
+};
diff --git a/modules/WindowsJumpLists.jsm b/modules/WindowsJumpLists.jsm
new file mode 100644
index 0000000..e7f7855
--- /dev/null
+++ b/modules/WindowsJumpLists.jsm
@@ -0,0 +1,581 @@
+/* -*- 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/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+/**
+ * Constants
+ */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+// Stop updating jumplists after some idle time.
+const IDLE_TIMEOUT_SECONDS = 5 * 60;
+
+// Prefs
+const PREF_TASKBAR_BRANCH = "browser.taskbar.lists.";
+const PREF_TASKBAR_ENABLED = "enabled";
+const PREF_TASKBAR_ITEMCOUNT = "maxListItemCount";
+const PREF_TASKBAR_FREQUENT = "frequent.enabled";
+const PREF_TASKBAR_RECENT = "recent.enabled";
+const PREF_TASKBAR_TASKS = "tasks.enabled";
+const PREF_TASKBAR_REFRESH = "refreshInSeconds";
+
+// Hash keys for pendingStatements.
+const LIST_TYPE = {
+ FREQUENT: 0
+, RECENT: 1
+}
+
+/**
+ * Exports
+ */
+
+this.EXPORTED_SYMBOLS = [
+ "WinTaskbarJumpList",
+];
+
+/**
+ * Smart getters
+ */
+
+XPCOMUtils.defineLazyGetter(this, "_prefs", function() {
+ return Services.prefs.getBranch(PREF_TASKBAR_BRANCH);
+});
+
+XPCOMUtils.defineLazyGetter(this, "_stringBundle", function() {
+ return Services.strings
+ .createBundle("chrome://browser/locale/taskbar.properties");
+});
+
+XPCOMUtils.defineLazyGetter(this, "PlacesUtils", function() {
+ Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
+ return PlacesUtils;
+});
+
+XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
+ Components.utils.import("resource://gre/modules/NetUtil.jsm");
+ return NetUtil;
+});
+
+XPCOMUtils.defineLazyServiceGetter(this, "_idle",
+ "@mozilla.org/widget/idleservice;1",
+ "nsIIdleService");
+
+XPCOMUtils.defineLazyServiceGetter(this, "_taskbarService",
+ "@mozilla.org/windows-taskbar;1",
+ "nsIWinTaskbar");
+
+XPCOMUtils.defineLazyServiceGetter(this, "_winShellService",
+ "@mozilla.org/browser/shell-service;1",
+ "nsIWindowsShellService");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+/**
+ * Global functions
+ */
+
+function _getString(name) {
+ return _stringBundle.GetStringFromName(name);
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Task list configuration data object.
+
+var tasksCfg = [
+ /**
+ * Task configuration options: title, description, args, iconIndex, open, close.
+ *
+ * title - Task title displayed in the list. (strings in the table are temp fillers.)
+ * description - Tooltip description on the list item.
+ * args - Command line args to invoke the task.
+ * iconIndex - Optional win icon index into the main application for the
+ * list item.
+ * open - Boolean indicates if the command should be visible after the browser opens.
+ * close - Boolean indicates if the command should be visible after the browser closes.
+ */
+ // Open new tab
+ {
+ get title() _getString("taskbar.tasks.newTab.label"),
+ get description() _getString("taskbar.tasks.newTab.description"),
+ args: "-new-tab about:blank",
+ iconIndex: 3, // New window icon
+ open: true,
+ close: true, // The jump list already has an app launch icon, but
+ // we don't always update the list on shutdown.
+ // Thus true for consistency.
+ },
+
+ // Open new window
+ {
+ get title() _getString("taskbar.tasks.newWindow.label"),
+ get description() _getString("taskbar.tasks.newWindow.description"),
+ args: "-browser",
+ iconIndex: 2, // New tab icon
+ open: true,
+ close: true, // No point, but we don't always update the list on
+ // shutdown. Thus true for consistency.
+ },
+
+ // Open new private window
+ {
+ get title() _getString("taskbar.tasks.newPrivateWindow.label"),
+ get description() _getString("taskbar.tasks.newPrivateWindow.description"),
+ args: "-private-window",
+ iconIndex: 4, // Private browsing mode icon
+ open: true,
+ close: true, // No point, but we don't always update the list on
+ // shutdown. Thus true for consistency.
+ },
+];
+
+/////////////////////////////////////////////////////////////////////////////
+// Implementation
+
+this.WinTaskbarJumpList =
+{
+ _builder: null,
+ _tasks: null,
+ _shuttingDown: false,
+
+ /**
+ * Startup, shutdown, and update
+ */
+
+ startup: function WTBJL_startup() {
+ // exit if this isn't win7 or higher.
+ if (!this._initTaskbar())
+ return;
+
+ // Win shell shortcut maintenance. If we've gone through an update,
+ // this will update any pinned taskbar shortcuts. Not specific to
+ // jump lists, but this was a convienent place to call it.
+ try {
+ // dev builds may not have helper.exe, ignore failures.
+ this._shortcutMaintenance();
+ } catch (ex) {
+ }
+
+ // Store our task list config data
+ this._tasks = tasksCfg;
+
+ // retrieve taskbar related prefs.
+ this._refreshPrefs();
+
+ // observer for private browsing and our prefs branch
+ this._initObs();
+
+ // jump list refresh timer
+ this._updateTimer();
+ },
+
+ update: function WTBJL_update() {
+ // are we disabled via prefs? don't do anything!
+ if (!this._enabled)
+ return;
+
+ // do what we came here to do, update the taskbar jumplist
+ this._buildList();
+ },
+
+ _shutdown: function WTBJL__shutdown() {
+ this._shuttingDown = true;
+
+ // Correctly handle a clear history on shutdown. If there are no
+ // entries be sure to empty all history lists. Luckily Places caches
+ // this value, so it's a pretty fast call.
+ if (!PlacesUtils.history.hasHistoryEntries) {
+ this.update();
+ }
+
+ this._free();
+ },
+
+ _shortcutMaintenance: function WTBJL__maintenace() {
+ _winShellService.shortcutMaintenance();
+ },
+
+ /**
+ * List building
+ *
+ * @note Async builders must add their mozIStoragePendingStatement to
+ * _pendingStatements object, using a different LIST_TYPE entry for
+ * each statement. Once finished they must remove it and call
+ * commitBuild(). When there will be no more _pendingStatements,
+ * commitBuild() will commit for real.
+ */
+
+ _pendingStatements: {},
+ _hasPendingStatements: function WTBJL__hasPendingStatements() {
+ return Object.keys(this._pendingStatements).length > 0;
+ },
+
+ _buildList: function WTBJL__buildList() {
+ if (this._hasPendingStatements()) {
+ // We were requested to update the list while another update was in
+ // progress, this could happen at shutdown, idle or privatebrowsing.
+ // Abort the current list building.
+ for (let listType in this._pendingStatements) {
+ this._pendingStatements[listType].cancel();
+ delete this._pendingStatements[listType];
+ }
+ this._builder.abortListBuild();
+ }
+
+ // anything to build?
+ if (!this._showFrequent && !this._showRecent && !this._showTasks) {
+ // don't leave the last list hanging on the taskbar.
+ this._deleteActiveJumpList();
+ return;
+ }
+
+ if (!this._startBuild())
+ return;
+
+ if (this._showTasks)
+ this._buildTasks();
+
+ // Space for frequent items takes priority over recent.
+ if (this._showFrequent)
+ this._buildFrequent();
+
+ if (this._showRecent)
+ this._buildRecent();
+
+ this._commitBuild();
+ },
+
+ /**
+ * Taskbar api wrappers
+ */
+
+ _startBuild: function WTBJL__startBuild() {
+ var removedItems = Cc["@mozilla.org/array;1"].
+ createInstance(Ci.nsIMutableArray);
+ this._builder.abortListBuild();
+ if (this._builder.initListBuild(removedItems)) {
+ // Prior to building, delete removed items from history.
+ this._clearHistory(removedItems);
+ return true;
+ }
+ return false;
+ },
+
+ _commitBuild: function WTBJL__commitBuild() {
+ if (!this._hasPendingStatements() && !this._builder.commitListBuild()) {
+ this._builder.abortListBuild();
+ }
+ },
+
+ _buildTasks: function WTBJL__buildTasks() {
+ var items = Cc["@mozilla.org/array;1"].
+ createInstance(Ci.nsIMutableArray);
+ this._tasks.forEach(function (task) {
+ if ((this._shuttingDown && !task.close) || (!this._shuttingDown && !task.open))
+ return;
+ var item = this._getHandlerAppItem(task.title, task.description,
+ task.args, task.iconIndex, null);
+ items.appendElement(item, false);
+ }, this);
+
+ if (items.length > 0)
+ this._builder.addListToBuild(this._builder.JUMPLIST_CATEGORY_TASKS, items);
+ },
+
+ _buildCustom: function WTBJL__buildCustom(title, items) {
+ if (items.length > 0)
+ this._builder.addListToBuild(this._builder.JUMPLIST_CATEGORY_CUSTOMLIST, items, title);
+ },
+
+ _buildFrequent: function WTBJL__buildFrequent() {
+ // If history is empty, just bail out.
+ if (!PlacesUtils.history.hasHistoryEntries) {
+ return;
+ }
+
+ // Windows supports default frequent and recent lists,
+ // but those depend on internal windows visit tracking
+ // which we don't populate. So we build our own custom
+ // frequent and recent lists using our nav history data.
+
+ var items = Cc["@mozilla.org/array;1"].
+ createInstance(Ci.nsIMutableArray);
+ // track frequent items so that we don't add them to
+ // the recent list.
+ this._frequentHashList = [];
+
+ this._pendingStatements[LIST_TYPE.FREQUENT] = this._getHistoryResults(
+ Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_DESCENDING,
+ this._maxItemCount,
+ function (aResult) {
+ if (!aResult) {
+ delete this._pendingStatements[LIST_TYPE.FREQUENT];
+ // The are no more results, build the list.
+ this._buildCustom(_getString("taskbar.frequent.label"), items);
+ this._commitBuild();
+ return;
+ }
+
+ let title = aResult.title || aResult.uri;
+ let faviconPageUri = Services.io.newURI(aResult.uri, null, null);
+ let shortcut = this._getHandlerAppItem(title, title, aResult.uri, 1,
+ faviconPageUri);
+ items.appendElement(shortcut, false);
+ this._frequentHashList.push(aResult.uri);
+ },
+ this
+ );
+ },
+
+ _buildRecent: function WTBJL__buildRecent() {
+ // If history is empty, just bail out.
+ if (!PlacesUtils.history.hasHistoryEntries) {
+ return;
+ }
+
+ var items = Cc["@mozilla.org/array;1"].
+ createInstance(Ci.nsIMutableArray);
+ // Frequent items will be skipped, so we select a double amount of
+ // entries and stop fetching results at _maxItemCount.
+ var count = 0;
+
+ this._pendingStatements[LIST_TYPE.RECENT] = this._getHistoryResults(
+ Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING,
+ this._maxItemCount * 2,
+ function (aResult) {
+ if (!aResult) {
+ // The are no more results, build the list.
+ this._buildCustom(_getString("taskbar.recent.label"), items);
+ delete this._pendingStatements[LIST_TYPE.RECENT];
+ this._commitBuild();
+ return;
+ }
+
+ if (count >= this._maxItemCount) {
+ return;
+ }
+
+ // Do not add items to recent that have already been added to frequent.
+ if (this._frequentHashList &&
+ this._frequentHashList.indexOf(aResult.uri) != -1) {
+ return;
+ }
+
+ let title = aResult.title || aResult.uri;
+ let faviconPageUri = Services.io.newURI(aResult.uri, null, null);
+ let shortcut = this._getHandlerAppItem(title, title, aResult.uri, 1,
+ faviconPageUri);
+ items.appendElement(shortcut, false);
+ count++;
+ },
+ this
+ );
+ },
+
+ _deleteActiveJumpList: function WTBJL__deleteAJL() {
+ this._builder.deleteActiveList();
+ },
+
+ /**
+ * Jump list item creation helpers
+ */
+
+ _getHandlerAppItem: function WTBJL__getHandlerAppItem(name, description,
+ args, iconIndex,
+ faviconPageUri) {
+ var file = Services.dirsvc.get("XREExeF", Ci.nsILocalFile);
+
+ var handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"].
+ createInstance(Ci.nsILocalHandlerApp);
+ handlerApp.executable = file;
+ // handlers default to the leaf name if a name is not specified
+ if (name && name.length != 0)
+ handlerApp.name = name;
+ handlerApp.detailedDescription = description;
+ handlerApp.appendParameter(args);
+
+ var item = Cc["@mozilla.org/windows-jumplistshortcut;1"].
+ createInstance(Ci.nsIJumpListShortcut);
+ item.app = handlerApp;
+ item.iconIndex = iconIndex;
+ item.faviconPageUri = faviconPageUri;
+ return item;
+ },
+
+ _getSeparatorItem: function WTBJL__getSeparatorItem() {
+ var item = Cc["@mozilla.org/windows-jumplistseparator;1"].
+ createInstance(Ci.nsIJumpListSeparator);
+ return item;
+ },
+
+ /**
+ * Nav history helpers
+ */
+
+ _getHistoryResults:
+ function WTBLJL__getHistoryResults(aSortingMode, aLimit, aCallback, aScope) {
+ var options = PlacesUtils.history.getNewQueryOptions();
+ options.maxResults = aLimit;
+ options.sortingMode = aSortingMode;
+ var query = PlacesUtils.history.getNewQuery();
+
+ // Return the pending statement to the caller, to allow cancelation.
+ return PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
+ .asyncExecuteLegacyQueries([query], 1, options, {
+ handleResult: function (aResultSet) {
+ for (let row; (row = aResultSet.getNextRow());) {
+ try {
+ aCallback.call(aScope,
+ { uri: row.getResultByIndex(1)
+ , title: row.getResultByIndex(2)
+ });
+ } catch (e) {}
+ }
+ },
+ handleError: function (aError) {
+ Components.utils.reportError(
+ "Async execution error (" + aError.result + "): " + aError.message);
+ },
+ handleCompletion: function (aReason) {
+ aCallback.call(WinTaskbarJumpList, null);
+ },
+ });
+ },
+
+ _clearHistory: function WTBJL__clearHistory(items) {
+ if (!items)
+ return;
+ var URIsToRemove = [];
+ var e = items.enumerate();
+ while (e.hasMoreElements()) {
+ let oldItem = e.getNext().QueryInterface(Ci.nsIJumpListShortcut);
+ if (oldItem) {
+ try { // in case we get a bad uri
+ let uriSpec = oldItem.app.getParameter(0);
+ URIsToRemove.push(NetUtil.newURI(uriSpec));
+ } catch (err) { }
+ }
+ }
+ if (URIsToRemove.length > 0) {
+ PlacesUtils.bhistory.removePages(URIsToRemove, URIsToRemove.length, true);
+ }
+ },
+
+ /**
+ * Prefs utilities
+ */
+
+ _refreshPrefs: function WTBJL__refreshPrefs() {
+ this._enabled = _prefs.getBoolPref(PREF_TASKBAR_ENABLED);
+ this._showFrequent = _prefs.getBoolPref(PREF_TASKBAR_FREQUENT);
+ this._showRecent = _prefs.getBoolPref(PREF_TASKBAR_RECENT);
+ this._showTasks = _prefs.getBoolPref(PREF_TASKBAR_TASKS);
+ this._maxItemCount = _prefs.getIntPref(PREF_TASKBAR_ITEMCOUNT);
+ },
+
+ /**
+ * Init and shutdown utilities
+ */
+
+ _initTaskbar: function WTBJL__initTaskbar() {
+ this._builder = _taskbarService.createJumpListBuilder();
+ if (!this._builder || !this._builder.available)
+ return false;
+
+ return true;
+ },
+
+ _initObs: function WTBJL__initObs() {
+ // If the browser is closed while in private browsing mode, the "exit"
+ // notification is fired on quit-application-granted.
+ // History cleanup can happen at profile-change-teardown.
+ Services.obs.addObserver(this, "profile-before-change", false);
+ Services.obs.addObserver(this, "browser:purge-session-history", false);
+ _prefs.addObserver("", this, false);
+ },
+
+ _freeObs: function WTBJL__freeObs() {
+ Services.obs.removeObserver(this, "profile-before-change");
+ Services.obs.removeObserver(this, "browser:purge-session-history");
+ _prefs.removeObserver("", this);
+ },
+
+ _updateTimer: function WTBJL__updateTimer() {
+ if (this._enabled && !this._shuttingDown && !this._timer) {
+ this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this._timer.initWithCallback(this,
+ _prefs.getIntPref(PREF_TASKBAR_REFRESH)*1000,
+ this._timer.TYPE_REPEATING_SLACK);
+ }
+ else if ((!this._enabled || this._shuttingDown) && this._timer) {
+ this._timer.cancel();
+ delete this._timer;
+ }
+ },
+
+ _hasIdleObserver: false,
+ _updateIdleObserver: function WTBJL__updateIdleObserver() {
+ if (this._enabled && !this._shuttingDown && !this._hasIdleObserver) {
+ _idle.addIdleObserver(this, IDLE_TIMEOUT_SECONDS);
+ this._hasIdleObserver = true;
+ }
+ else if ((!this._enabled || this._shuttingDown) && this._hasIdleObserver) {
+ _idle.removeIdleObserver(this, IDLE_TIMEOUT_SECONDS);
+ this._hasIdleObserver = false;
+ }
+ },
+
+ _free: function WTBJL__free() {
+ this._freeObs();
+ this._updateTimer();
+ this._updateIdleObserver();
+ delete this._builder;
+ },
+
+ /**
+ * Notification handlers
+ */
+
+ notify: function WTBJL_notify(aTimer) {
+ // Add idle observer on the first notification so it doesn't hit startup.
+ this._updateIdleObserver();
+ this.update();
+ },
+
+ observe: function WTBJL_observe(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "nsPref:changed":
+ if (this._enabled == true && !_prefs.getBoolPref(PREF_TASKBAR_ENABLED))
+ this._deleteActiveJumpList();
+ this._refreshPrefs();
+ this._updateTimer();
+ this._updateIdleObserver();
+ this.update();
+ break;
+
+ case "profile-before-change":
+ this._shutdown();
+ break;
+
+ case "browser:purge-session-history":
+ this.update();
+ break;
+ case "idle":
+ if (this._timer) {
+ this._timer.cancel();
+ delete this._timer;
+ }
+ break;
+
+ case "back":
+ this._updateTimer();
+ break;
+ }
+ },
+};
diff --git a/modules/WindowsPreviewPerTab.jsm b/modules/WindowsPreviewPerTab.jsm
new file mode 100644
index 0000000..4b5030a
--- /dev/null
+++ b/modules/WindowsPreviewPerTab.jsm
@@ -0,0 +1,861 @@
+/* vim: se cin sw=2 ts=2 et filetype=javascript :
+ * 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 module implements the front end behavior for AeroPeek. The taskbar
+ * allows an application to expose its tabbed interface by showing thumbnail
+ * previews rather than the default window preview.
+ * Additionally, when a user hovers over a thumbnail (tab or window),
+ * they are shown a live preview of the window (or tab + its containing window).
+ *
+ * In Windows 7, a title, icon, close button and optional toolbar are shown for
+ * each preview. This feature does not make use of the toolbar. For window
+ * previews, the title is the window title and the icon the window icon. For
+ * tab previews, the title is the page title and the page's favicon. In both
+ * cases, the close button "does the right thing."
+ *
+ * The primary objects behind this feature are nsITaskbarTabPreview and
+ * nsITaskbarPreviewController. Each preview has a controller. The controller
+ * responds to the user's interactions on the taskbar and provides the required
+ * data to the preview for determining the size of the tab and thumbnail. The
+ * PreviewController class implements this interface. The preview will request
+ * the controller to provide a thumbnail or preview when the user interacts with
+ * the taskbar. To reduce the overhead of drawing the tab area, the controller
+ * implementation caches the tab's contents in a <canvas> element. If no
+ * previews or thumbnails have been requested for some time, the controller will
+ * discard its cached tab contents.
+ *
+ * Screen real estate is limited so when there are too many thumbnails to fit
+ * on the screen, the taskbar stops displaying thumbnails and instead displays
+ * just the title, icon and close button in a similar fashion to previous
+ * versions of the taskbar. If there are still too many previews to fit on the
+ * screen, the taskbar resorts to a scroll up and scroll down button pair to let
+ * the user scroll through the list of tabs. Since this is undoubtedly
+ * inconvenient for users with many tabs, the AeroPeek objects turns off all of
+ * the tab previews. This tells the taskbar to revert to one preview per window.
+ * If the number of tabs falls below this magic threshold, the preview-per-tab
+ * behavior returns. There is no reliable way to determine when the scroll
+ * buttons appear on the taskbar, so a magic pref-controlled number determines
+ * when this threshold has been crossed.
+ */
+this.EXPORTED_SYMBOLS = ["AeroPeek"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
+Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+// Pref to enable/disable preview-per-tab
+const TOGGLE_PREF_NAME = "browser.taskbar.previews.enable";
+// Pref to determine the magic auto-disable threshold
+const DISABLE_THRESHOLD_PREF_NAME = "browser.taskbar.previews.max";
+// Pref to control the time in seconds that tab contents live in the cache
+const CACHE_EXPIRATION_TIME_PREF_NAME = "browser.taskbar.previews.cachetime";
+
+const WINTASKBAR_CONTRACTID = "@mozilla.org/windows-taskbar;1";
+
+// Various utility properties
+XPCOMUtils.defineLazyServiceGetter(this, "imgTools",
+ "@mozilla.org/image/tools;1",
+ "imgITools");
+XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs",
+ "resource://gre/modules/PageThumbs.jsm");
+
+// nsIURI -> imgIContainer
+function _imageFromURI(uri, privateMode, callback) {
+ let channel = NetUtil.newChannel({
+ uri: uri,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE
+ });
+
+ try {
+ channel.QueryInterface(Ci.nsIPrivateBrowsingChannel);
+ channel.setPrivate(privateMode);
+ } catch (e) {
+ // Ignore channels which do not support nsIPrivateBrowsingChannel
+ }
+ NetUtil.asyncFetch(channel, function(inputStream, resultCode) {
+ if (!Components.isSuccessCode(resultCode))
+ return;
+ try {
+ let out_img = { value: null };
+ imgTools.decodeImageData(inputStream, channel.contentType, out_img);
+ callback(out_img.value);
+ } catch (e) {
+ // We failed, so use the default favicon (only if this wasn't the default
+ // favicon).
+ let defaultURI = PlacesUtils.favicons.defaultFavicon;
+ if (!defaultURI.equals(uri))
+ _imageFromURI(defaultURI, privateMode, callback);
+ }
+ });
+}
+
+// string? -> imgIContainer
+function getFaviconAsImage(iconurl, privateMode, callback) {
+ if (iconurl) {
+ _imageFromURI(NetUtil.newURI(iconurl), privateMode, callback);
+ } else {
+ _imageFromURI(PlacesUtils.favicons.defaultFavicon, privateMode, callback);
+ }
+}
+
+// Snaps the given rectangle to be pixel-aligned at the given scale
+function snapRectAtScale(r, scale) {
+ let x = Math.floor(r.x * scale);
+ let y = Math.floor(r.y * scale);
+ let width = Math.ceil((r.x + r.width) * scale) - x;
+ let height = Math.ceil((r.y + r.height) * scale) - y;
+
+ r.x = x / scale;
+ r.y = y / scale;
+ r.width = width / scale;
+ r.height = height / scale;
+}
+
+// PreviewController
+
+/*
+ * This class manages the behavior of thumbnails and previews. It has the following
+ * responsibilities:
+ * 1) Responding to requests from Windows taskbar for a thumbnail or window
+ * preview.
+ * 2) Listening for DOM events that result in a thumbnail or window preview needing
+ * to be refreshed, and communicating this to the taskbar.
+ * 3) Handling queryies and returning new thumbnail or window preview images to the
+ * taskbar through PageThumbs.
+ *
+ * @param win
+ * The TabWindow (see below) that owns the preview that this controls
+ * @param tab
+ * The <tab> that this preview is associated with
+ */
+function PreviewController(win, tab) {
+ this.win = win;
+ this.tab = tab;
+ this.linkedBrowser = tab.linkedBrowser;
+ this.preview = this.win.createTabPreview(this);
+
+ this.tab.addEventListener("TabAttrModified", this, false);
+
+ XPCOMUtils.defineLazyGetter(this, "canvasPreview", function () {
+ let canvas = PageThumbs.createCanvas();
+ canvas.mozOpaque = true;
+ return canvas;
+ });
+}
+
+PreviewController.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsITaskbarPreviewController,
+ Ci.nsIDOMEventListener]),
+
+ destroy: function () {
+ this.tab.removeEventListener("TabAttrModified", this, false);
+
+ // Break cycles, otherwise we end up leaking the window with everything
+ // attached to it.
+ delete this.win;
+ delete this.preview;
+ },
+
+ get wrappedJSObject() {
+ return this;
+ },
+
+ // Resizes the canvasPreview to 0x0, essentially freeing its memory.
+ resetCanvasPreview: function () {
+ this.canvasPreview.width = 0;
+ this.canvasPreview.height = 0;
+ },
+
+ /**
+ * Set the canvas dimensions.
+ */
+ resizeCanvasPreview: function (aRequestedWidth, aRequestedHeight) {
+ this.canvasPreview.width = aRequestedWidth;
+ this.canvasPreview.height = aRequestedHeight;
+ },
+
+
+ get zoom() {
+ // Note that winutils.fullZoom accounts for "quantization" of the zoom factor
+ // from nsIContentViewer due to conversion through appUnits.
+ // We do -not- want screenPixelsPerCSSPixel here, because that would -also-
+ // incorporate any scaling that is applied due to hi-dpi resolution options.
+ return this.tab.linkedBrowser.fullZoom;
+ },
+
+ get screenPixelsPerCSSPixel() {
+ let chromeWin = this.tab.ownerGlobal;
+ let windowUtils = chromeWin.getInterface(Ci.nsIDOMWindowUtils);
+ return windowUtils.screenPixelsPerCSSPixel;
+ },
+
+ get browserDims() {
+ return this.tab.linkedBrowser.getBoundingClientRect();
+ },
+
+ cacheBrowserDims: function () {
+ let dims = this.browserDims;
+ this._cachedWidth = dims.width;
+ this._cachedHeight = dims.height;
+ },
+
+ testCacheBrowserDims: function () {
+ let dims = this.browserDims;
+ return this._cachedWidth == dims.width &&
+ this._cachedHeight == dims.height;
+ },
+
+ /**
+ * Capture a new thumbnail image for this preview. Called by the controller
+ * in response to a request for a new thumbnail image.
+ */
+ updateCanvasPreview: function (aFullScale, aCallback) {
+ // Update our cached browser dims so that delayed resize
+ // events don't trigger another invalidation if this tab becomes active.
+ this.cacheBrowserDims();
+ PageThumbs.captureToCanvas(this.linkedBrowser, this.canvasPreview,
+ aCallback, { fullScale: aFullScale });
+ // If we're updating the canvas, then we're in the middle of a peek so
+ // don't discard the cache of previews.
+ AeroPeek.resetCacheTimer();
+ },
+
+ updateTitleAndTooltip: function () {
+ let title = this.win.tabbrowser.getWindowTitleForBrowser(this.linkedBrowser);
+ this.preview.title = title;
+ this.preview.tooltip = title;
+ },
+
+ // nsITaskbarPreviewController
+
+ // window width and height, not browser
+ get width() {
+ return this.win.width;
+ },
+
+ // window width and height, not browser
+ get height() {
+ return this.win.height;
+ },
+
+ get thumbnailAspectRatio() {
+ let browserDims = this.browserDims;
+ // Avoid returning 0
+ let tabWidth = browserDims.width || 1;
+ // Avoid divide by 0
+ let tabHeight = browserDims.height || 1;
+ return tabWidth / tabHeight;
+ },
+
+ /**
+ * Responds to taskbar requests for window previews. Returns the results asynchronously
+ * through updateCanvasPreview.
+ *
+ * @param aTaskbarCallback nsITaskbarPreviewCallback results callback
+ */
+ requestPreview: function (aTaskbarCallback) {
+ // Grab a high res content preview
+ this.resetCanvasPreview();
+ this.updateCanvasPreview(true, (aPreviewCanvas) => {
+ let winWidth = this.win.width;
+ let winHeight = this.win.height;
+
+ let composite = PageThumbs.createCanvas();
+
+ // Use transparency, Aero glass is drawn black without it.
+ composite.mozOpaque = false;
+
+ let ctx = composite.getContext('2d');
+ let scale = this.screenPixelsPerCSSPixel / this.zoom;
+
+ composite.width = winWidth * scale;
+ composite.height = winHeight * scale;
+
+ ctx.save();
+ ctx.scale(scale, scale);
+
+ // Draw chrome. Note we currently do not get scrollbars for remote frames
+ // in the image above.
+ ctx.drawWindow(this.win.win, 0, 0, winWidth, winHeight, "rgba(0,0,0,0)");
+
+ // Draw the content are into the composite canvas at the right location.
+ ctx.drawImage(aPreviewCanvas, this.browserDims.x, this.browserDims.y,
+ aPreviewCanvas.width, aPreviewCanvas.height);
+ ctx.restore();
+
+ // Deliver the resulting composite canvas to Windows
+ this.win.tabbrowser.previewTab(this.tab, function () {
+ aTaskbarCallback.done(composite, false);
+ });
+ });
+ },
+
+ /**
+ * Responds to taskbar requests for tab thumbnails. Returns the results asynchronously
+ * through updateCanvasPreview.
+ *
+ * Note Windows requests a specific width and height here, if the resulting thumbnail
+ * does not match these dimensions thumbnail display will fail.
+ *
+ * @param aTaskbarCallback nsITaskbarPreviewCallback results callback
+ * @param aRequestedWidth width of the requested thumbnail
+ * @param aRequestedHeight height of the requested thumbnail
+ */
+ requestThumbnail: function (aTaskbarCallback, aRequestedWidth, aRequestedHeight) {
+ this.resizeCanvasPreview(aRequestedWidth, aRequestedHeight);
+ this.updateCanvasPreview(false, (aThumbnailCanvas) => {
+ aTaskbarCallback.done(aThumbnailCanvas, false);
+ });
+ },
+
+ // Event handling
+
+ onClose: function () {
+ this.win.tabbrowser.removeTab(this.tab);
+ },
+
+ onActivate: function () {
+ this.win.tabbrowser.selectedTab = this.tab;
+
+ // Accept activation - this will restore the browser window
+ // if it's minimized
+ return true;
+ },
+
+ // nsIDOMEventListener
+ handleEvent: function (evt) {
+ switch (evt.type) {
+ case "TabAttrModified":
+ this.updateTitleAndTooltip();
+ break;
+ }
+ }
+};
+
+XPCOMUtils.defineLazyGetter(PreviewController.prototype, "canvasPreviewFlags",
+ function () { let canvasInterface = Ci.nsIDOMCanvasRenderingContext2D;
+ return canvasInterface.DRAWWINDOW_DRAW_VIEW
+ | canvasInterface.DRAWWINDOW_DRAW_CARET
+ | canvasInterface.DRAWWINDOW_ASYNC_DECODE_IMAGES
+ | canvasInterface.DRAWWINDOW_DO_NOT_FLUSH;
+});
+
+// TabWindow
+
+/*
+ * This class monitors a browser window for changes to its tabs
+ *
+ * @param win
+ * The nsIDOMWindow browser window
+ */
+function TabWindow(win) {
+ this.win = win;
+ this.tabbrowser = win.gBrowser;
+
+ this.previews = new Map();
+
+ for (let i = 0; i < this.tabEvents.length; i++)
+ this.tabbrowser.tabContainer.addEventListener(this.tabEvents[i], this, false);
+
+ for (let i = 0; i < this.winEvents.length; i++)
+ this.win.addEventListener(this.winEvents[i], this, false);
+
+ this.tabbrowser.addTabsProgressListener(this);
+
+ AeroPeek.windows.push(this);
+ let tabs = this.tabbrowser.tabs;
+ for (let i = 0; i < tabs.length; i++)
+ this.newTab(tabs[i]);
+
+ this.updateTabOrdering();
+ AeroPeek.checkPreviewCount();
+}
+
+TabWindow.prototype = {
+ _enabled: false,
+ _cachedWidth: 0,
+ _cachedHeight: 0,
+ tabEvents: ["TabOpen", "TabClose", "TabSelect", "TabMove"],
+ winEvents: ["resize"],
+
+ destroy: function () {
+ this._destroying = true;
+
+ let tabs = this.tabbrowser.tabs;
+
+ this.tabbrowser.removeTabsProgressListener(this);
+
+ for (let i = 0; i < this.winEvents.length; i++)
+ this.win.removeEventListener(this.winEvents[i], this, false);
+
+ for (let i = 0; i < this.tabEvents.length; i++)
+ this.tabbrowser.tabContainer.removeEventListener(this.tabEvents[i], this, false);
+
+ for (let i = 0; i < tabs.length; i++)
+ this.removeTab(tabs[i]);
+
+ let idx = AeroPeek.windows.indexOf(this.win.gTaskbarTabGroup);
+ AeroPeek.windows.splice(idx, 1);
+ AeroPeek.checkPreviewCount();
+ },
+
+ get width () {
+ return this.win.innerWidth;
+ },
+ get height () {
+ return this.win.innerHeight;
+ },
+
+ cacheDims: function () {
+ this._cachedWidth = this.width;
+ this._cachedHeight = this.height;
+ },
+
+ testCacheDims: function () {
+ return this._cachedWidth == this.width && this._cachedHeight == this.height;
+ },
+
+ // Invoked when the given tab is added to this window
+ newTab: function (tab) {
+ let controller = new PreviewController(this, tab);
+ // It's OK to add the preview now while the favicon still loads.
+ this.previews.set(tab, controller.preview);
+ AeroPeek.addPreview(controller.preview);
+ // updateTitleAndTooltip relies on having controller.preview which is lazily resolved.
+ // Now that we've updated this.previews, it will resolve successfully.
+ controller.updateTitleAndTooltip();
+ },
+
+ createTabPreview: function (controller) {
+ let docShell = this.win
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell);
+ let preview = AeroPeek.taskbar.createTaskbarTabPreview(docShell, controller);
+ preview.visible = AeroPeek.enabled;
+ preview.active = this.tabbrowser.selectedTab == controller.tab;
+ this.onLinkIconAvailable(controller.tab.linkedBrowser,
+ controller.tab.getAttribute("image"));
+ return preview;
+ },
+
+ // Invoked when the given tab is closed
+ removeTab: function (tab) {
+ let preview = this.previewFromTab(tab);
+ preview.active = false;
+ preview.visible = false;
+ preview.move(null);
+ preview.controller.wrappedJSObject.destroy();
+
+ this.previews.delete(tab);
+ AeroPeek.removePreview(preview);
+ },
+
+ get enabled () {
+ return this._enabled;
+ },
+
+ set enabled (enable) {
+ this._enabled = enable;
+ // Because making a tab visible requires that the tab it is next to be
+ // visible, it is far simpler to unset the 'next' tab and recreate them all
+ // at once.
+ for (let [, preview] of this.previews) {
+ preview.move(null);
+ preview.visible = enable;
+ }
+ this.updateTabOrdering();
+ },
+
+ previewFromTab: function (tab) {
+ return this.previews.get(tab);
+ },
+
+ updateTabOrdering: function () {
+ let previews = this.previews;
+ let tabs = this.tabbrowser.tabs;
+
+ // Previews are internally stored using a map, so we need to iterate the
+ // tabbrowser's array of tabs to retrieve previews in the same order.
+ let inorder = [];
+ for (let t of tabs) {
+ if (previews.has(t)) {
+ inorder.push(previews.get(t));
+ }
+ }
+
+ // Since the internal taskbar array has not yet been updated we must force
+ // on it the sorting order of our local array. To do so we must walk
+ // the local array backwards, otherwise we would send move requests in the
+ // wrong order. See bug 522610 for details.
+ for (let i = inorder.length - 1; i >= 0; i--) {
+ inorder[i].move(inorder[i + 1] || null);
+ }
+ },
+
+ // nsIDOMEventListener
+ handleEvent: function (evt) {
+ let tab = evt.originalTarget;
+ switch (evt.type) {
+ case "TabOpen":
+ this.newTab(tab);
+ this.updateTabOrdering();
+ break;
+ case "TabClose":
+ this.removeTab(tab);
+ this.updateTabOrdering();
+ break;
+ case "TabSelect":
+ this.previewFromTab(tab).active = true;
+ break;
+ case "TabMove":
+ this.updateTabOrdering();
+ break;
+ case "resize":
+ if (!AeroPeek._prefenabled)
+ return;
+ this.onResize();
+ break;
+ }
+ },
+
+ // Set or reset a timer that will invalidate visible thumbnails soon.
+ setInvalidationTimer: function () {
+ if (!this.invalidateTimer) {
+ this.invalidateTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ }
+ this.invalidateTimer.cancel();
+
+ // delay 1 second before invalidating
+ this.invalidateTimer.initWithCallback(() => {
+ // invalidate every preview. note the internal implementation of
+ // invalidate ignores thumbnails that aren't visible.
+ this.previews.forEach(function (aPreview) {
+ let controller = aPreview.controller.wrappedJSObject;
+ if (!controller.testCacheBrowserDims()) {
+ controller.cacheBrowserDims();
+ aPreview.invalidate();
+ }
+ });
+ }, 1000, Ci.nsITimer.TYPE_ONE_SHOT);
+ },
+
+ onResize: function () {
+ // Specific to a window.
+
+ // Call invalidate on each tab thumbnail so that Windows will request an
+ // updated image. However don't do this repeatedly across multiple resize
+ // events triggered during window border drags.
+
+ if (this.testCacheDims()) {
+ return;
+ }
+
+ // update the window dims on our TabWindow object.
+ this.cacheDims();
+
+ // invalidate soon
+ this.setInvalidationTimer();
+ },
+
+ invalidateTabPreview: function(aBrowser) {
+ for (let [tab, preview] of this.previews) {
+ if (aBrowser == tab.linkedBrowser) {
+ preview.invalidate();
+ break;
+ }
+ }
+ },
+
+ // Browser progress listener
+
+ onLocationChange: function (aBrowser) {
+ // I'm not sure we need this, onStateChange does a really good job
+ // of picking up page changes.
+ // this.invalidateTabPreview(aBrowser);
+ },
+
+ onStateChange: function (aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
+ if (aStateFlags & Ci.nsIWebProgressListener.STATE_STOP &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) {
+ this.invalidateTabPreview(aBrowser);
+ }
+ },
+
+ directRequestProtocols: new Set([
+ "file", "chrome", "resource", "about"
+ ]),
+ onLinkIconAvailable: function (aBrowser, aIconURL) {
+ let self = this;
+ let requestURL = null;
+ if (aIconURL) {
+ let shouldRequestFaviconURL = true;
+ try {
+ let urlObject = NetUtil.newURI(aIconURL);
+ shouldRequestFaviconURL =
+ !this.directRequestProtocols.has(urlObject.scheme);
+ } catch (ex) {}
+
+ requestURL = shouldRequestFaviconURL ?
+ "moz-anno:favicon:" + aIconURL :
+ aIconURL;
+ }
+ let isDefaultFavicon = !requestURL;
+ getFaviconAsImage(
+ requestURL,
+ PrivateBrowsingUtils.isWindowPrivate(self.win),
+ img => {
+ let index = self.tabbrowser.browsers.indexOf(aBrowser);
+ // Only add it if we've found the index and the URI is still the same.
+ // The tab could have closed, and there's no guarantee the icons
+ // will have finished fetching 'in order'.
+ if (index != -1) {
+ let tab = self.tabbrowser.tabs[index];
+ let preview = self.previews.get(tab);
+ if (tab.getAttribute("image") == aIconURL ||
+ (!preview.icon && isDefaultFavicon)) {
+ preview.icon = img;
+ }
+ }
+ }
+ );
+ }
+}
+
+// AeroPeek
+
+/*
+ * This object acts as global storage and external interface for this feature.
+ * It maintains the values of the prefs.
+ */
+this.AeroPeek = {
+ available: false,
+ // Does the pref say we're enabled?
+ __prefenabled: false,
+
+ _enabled: true,
+
+ initialized: false,
+
+ // nsITaskbarTabPreview array
+ previews: [],
+
+ // TabWindow array
+ windows: [],
+
+ // nsIWinTaskbar service
+ taskbar: null,
+
+ // Maximum number of previews
+ maxpreviews: 20,
+
+ // Length of time in seconds that previews are cached
+ cacheLifespan: 20,
+
+ initialize: function () {
+ if (!(WINTASKBAR_CONTRACTID in Cc))
+ return;
+ this.taskbar = Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar);
+ this.available = this.taskbar.available;
+ if (!this.available)
+ return;
+
+ this.prefs.addObserver(TOGGLE_PREF_NAME, this, true);
+ this.enabled = this._prefenabled = this.prefs.getBoolPref(TOGGLE_PREF_NAME);
+ this.initialized = true;
+ },
+
+ destroy: function destroy() {
+ this._enabled = false;
+
+ if (this.cacheTimer)
+ this.cacheTimer.cancel();
+ },
+
+ get enabled() {
+ return this._enabled;
+ },
+
+ set enabled(enable) {
+ if (this._enabled == enable)
+ return;
+
+ this._enabled = enable;
+
+ this.windows.forEach(function (win) {
+ win.enabled = enable;
+ });
+ },
+
+ get _prefenabled() {
+ return this.__prefenabled;
+ },
+
+ set _prefenabled(enable) {
+ if (enable == this.__prefenabled) {
+ return;
+ }
+ this.__prefenabled = enable;
+
+ if (enable) {
+ this.enable();
+ } else {
+ this.disable();
+ }
+ },
+
+ _observersAdded: false,
+
+ enable() {
+ if (!this._observersAdded) {
+ this.prefs.addObserver(DISABLE_THRESHOLD_PREF_NAME, this, true);
+ this.prefs.addObserver(CACHE_EXPIRATION_TIME_PREF_NAME, this, true);
+ PlacesUtils.history.addObserver(this, true);
+ this._observersAdded = true;
+ }
+
+ this.cacheLifespan = this.prefs.getIntPref(CACHE_EXPIRATION_TIME_PREF_NAME);
+
+ this.maxpreviews = this.prefs.getIntPref(DISABLE_THRESHOLD_PREF_NAME);
+
+ // If the user toggled us on/off while the browser was already up
+ // (rather than this code running on startup because the pref was
+ // already set to true), we must initialize previews for open windows:
+ if (this.initialized) {
+ let browserWindows = Services.wm.getEnumerator("navigator:browser");
+ while (browserWindows.hasMoreElements()) {
+ let win = browserWindows.getNext();
+ if (!win.closed) {
+ this.onOpenWindow(win);
+ }
+ }
+ }
+ },
+
+ disable() {
+ while (this.windows.length) {
+ // We can't call onCloseWindow here because it'll bail if we're not
+ // enabled.
+ let tabWinObject = this.windows[0];
+ tabWinObject.destroy(); // This will remove us from the array.
+ delete tabWinObject.win.gTaskbarTabGroup; // Tidy up the window.
+ }
+ },
+
+ addPreview: function (preview) {
+ this.previews.push(preview);
+ this.checkPreviewCount();
+ },
+
+ removePreview: function (preview) {
+ let idx = this.previews.indexOf(preview);
+ this.previews.splice(idx, 1);
+ this.checkPreviewCount();
+ },
+
+ checkPreviewCount: function () {
+ if (!this._prefenabled) {
+ return;
+ }
+ this.enabled = this.previews.length <= this.maxpreviews;
+ },
+
+ onOpenWindow: function (win) {
+ // This occurs when the taskbar service is not available (xp, vista)
+ if (!this.available || !this._prefenabled)
+ return;
+
+ win.gTaskbarTabGroup = new TabWindow(win);
+ },
+
+ onCloseWindow: function (win) {
+ // This occurs when the taskbar service is not available (xp, vista)
+ if (!this.available || !this._prefenabled)
+ return;
+
+ win.gTaskbarTabGroup.destroy();
+ delete win.gTaskbarTabGroup;
+
+ if (this.windows.length == 0)
+ this.destroy();
+ },
+
+ resetCacheTimer: function () {
+ this.cacheTimer.cancel();
+ this.cacheTimer.init(this, 1000*this.cacheLifespan, Ci.nsITimer.TYPE_ONE_SHOT);
+ },
+
+ // nsIObserver
+ observe: function (aSubject, aTopic, aData) {
+ if (aTopic == "nsPref:changed" && aData == TOGGLE_PREF_NAME) {
+ this._prefenabled = this.prefs.getBoolPref(TOGGLE_PREF_NAME);
+ }
+ if (!this._prefenabled) {
+ return;
+ }
+ switch (aTopic) {
+ case "nsPref:changed":
+ if (aData == CACHE_EXPIRATION_TIME_PREF_NAME)
+ break;
+
+ if (aData == DISABLE_THRESHOLD_PREF_NAME)
+ this.maxpreviews = this.prefs.getIntPref(DISABLE_THRESHOLD_PREF_NAME);
+ // Might need to enable/disable ourselves
+ this.checkPreviewCount();
+ break;
+ case "timer-callback":
+ this.previews.forEach(function (preview) {
+ let controller = preview.controller.wrappedJSObject;
+ controller.resetCanvasPreview();
+ });
+ break;
+ }
+ },
+
+ /* nsINavHistoryObserver implementation */
+ onBeginUpdateBatch() {},
+ onEndUpdateBatch() {},
+ onVisit() {},
+ onTitleChanged() {},
+ onFrecencyChanged() {},
+ onManyFrecenciesChanged() {},
+ onDeleteURI() {},
+ onClearHistory() {},
+ onDeleteVisits() {},
+ onPageChanged(uri, changedConst, newValue) {
+ if (this.enabled && changedConst == Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON) {
+ for (let win of this.windows) {
+ for (let [tab, ] of win.previews) {
+ if (tab.getAttribute("image") == newValue) {
+ win.onLinkIconAvailable(tab.linkedBrowser, newValue);
+ }
+ }
+ }
+ }
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([
+ Ci.nsISupportsWeakReference,
+ Ci.nsINavHistoryObserver,
+ Ci.nsIObserver
+ ]),
+};
+
+XPCOMUtils.defineLazyGetter(AeroPeek, "cacheTimer", () =>
+ Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer)
+);
+
+XPCOMUtils.defineLazyServiceGetter(AeroPeek, "prefs",
+ "@mozilla.org/preferences-service;1",
+ "nsIPrefBranch");
+
+AeroPeek.initialize();
diff --git a/modules/moz.build b/modules/moz.build
new file mode 100644
index 0000000..12a3ece
--- /dev/null
+++ b/modules/moz.build
@@ -0,0 +1,42 @@
+# -*- 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/.
+
+EXTRA_JS_MODULES += [
+ 'AutoCompletePopup.jsm',
+ 'BrowserNewTabPreloader.jsm',
+ 'CharsetMenu.jsm',
+ 'FormSubmitObserver.jsm',
+ 'FormValidationHandler.jsm',
+ 'NetworkPrioritizer.jsm',
+ 'offlineAppCache.jsm',
+ 'openLocationLastURL.jsm',
+ 'PageMenu.jsm',
+ 'PopupNotifications.jsm',
+ 'QuotaManager.jsm',
+ 'SharedFrame.jsm'
+]
+
+if CONFIG['MOZ_WEBRTC']:
+ EXTRA_JS_MODULES += ['webrtcUI.jsm']
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
+ EXTRA_JS_MODULES += [
+ 'Windows8WindowFrameColor.jsm',
+ 'WindowsJumpLists.jsm',
+ 'WindowsPreviewPerTab.jsm',
+ ]
+
+EXTRA_PP_JS_MODULES += [
+ 'AboutHomeUtils.jsm',
+ 'RecentWindow.jsm',
+]
+
+# Pass down 'official build' flags
+if CONFIG['MC_OFFICIAL']:
+ DEFINES['MC_OFFICIAL'] = 1
+
+if CONFIG['MOZILLA_OFFICIAL']:
+ DEFINES['MOZILLA_OFFICIAL'] = 1
diff --git a/modules/offlineAppCache.jsm b/modules/offlineAppCache.jsm
new file mode 100644
index 0000000..00ded09
--- /dev/null
+++ b/modules/offlineAppCache.jsm
@@ -0,0 +1,20 @@
+/* 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.EXPORTED_SYMBOLS = ["OfflineAppCacheHelper"];
+
+Components.utils.import('resource://gre/modules/LoadContextInfo.jsm');
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+this.OfflineAppCacheHelper = {
+ clear: function() {
+ var cacheService = Cc["@mozilla.org/netwerk/cache-storage-service;1"].getService(Ci.nsICacheStorageService);
+ var appCacheStorage = cacheService.appCacheStorage(LoadContextInfo.default, null);
+ try {
+ appCacheStorage.asyncEvictStorage(null);
+ } catch(er) {}
+ }
+};
diff --git a/modules/openLocationLastURL.jsm b/modules/openLocationLastURL.jsm
new file mode 100644
index 0000000..3f58db8
--- /dev/null
+++ b/modules/openLocationLastURL.jsm
@@ -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/. */
+
+const LAST_URL_PREF = "general.open_location.last_url";
+const nsISupportsString = Components.interfaces.nsISupportsString;
+const Ci = Components.interfaces;
+
+Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+this.EXPORTED_SYMBOLS = [ "OpenLocationLastURL" ];
+
+var prefSvc = Components.classes["@mozilla.org/preferences-service;1"]
+ .getService(Components.interfaces.nsIPrefBranch);
+var gOpenLocationLastURLData = "";
+
+var observer = {
+ QueryInterface: function (aIID) {
+ if (aIID.equals(Components.interfaces.nsIObserver) ||
+ aIID.equals(Components.interfaces.nsISupports) ||
+ aIID.equals(Components.interfaces.nsISupportsWeakReference))
+ return this;
+ throw Components.results.NS_NOINTERFACE;
+ },
+ observe: function (aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "last-pb-context-exited":
+ gOpenLocationLastURLData = "";
+ break;
+ case "browser:purge-session-history":
+ prefSvc.clearUserPref(LAST_URL_PREF);
+ gOpenLocationLastURLData = "";
+ break;
+ }
+ }
+};
+
+var os = Components.classes["@mozilla.org/observer-service;1"]
+ .getService(Components.interfaces.nsIObserverService);
+os.addObserver(observer, "last-pb-context-exited", true);
+os.addObserver(observer, "browser:purge-session-history", true);
+
+
+this.OpenLocationLastURL = function OpenLocationLastURL(aWindow) {
+ this.window = aWindow;
+}
+
+OpenLocationLastURL.prototype = {
+ isPrivate: function OpenLocationLastURL_isPrivate() {
+ // Assume not in private browsing mode, unless the browser window is
+ // in private mode.
+ if (!this.window)
+ return false;
+
+ return PrivateBrowsingUtils.isWindowPrivate(this.window);
+ },
+ get value() {
+ if (this.isPrivate())
+ return gOpenLocationLastURLData;
+ else {
+ try {
+ return prefSvc.getComplexValue(LAST_URL_PREF, nsISupportsString).data;
+ }
+ catch (e) {
+ return "";
+ }
+ }
+ },
+ set value(val) {
+ if (typeof val != "string")
+ val = "";
+ if (this.isPrivate())
+ gOpenLocationLastURLData = val;
+ else {
+ let str = Components.classes["@mozilla.org/supports-string;1"]
+ .createInstance(Components.interfaces.nsISupportsString);
+ str.data = val;
+ prefSvc.setComplexValue(LAST_URL_PREF, nsISupportsString, str);
+ }
+ },
+ reset: function() {
+ prefSvc.clearUserPref(LAST_URL_PREF);
+ gOpenLocationLastURLData = "";
+ }
+};
diff --git a/modules/webrtcUI.jsm b/modules/webrtcUI.jsm
new file mode 100644
index 0000000..819ca18
--- /dev/null
+++ b/modules/webrtcUI.jsm
@@ -0,0 +1,292 @@
+/* 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";
+
+this.EXPORTED_SYMBOLS = ["webrtcUI"];
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+ "resource://gre/modules/PluralForm.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "MediaManagerService",
+ "@mozilla.org/mediaManagerService;1",
+ "nsIMediaManagerService");
+
+this.webrtcUI = {
+ init: function () {
+ Services.obs.addObserver(handleRequest, "getUserMedia:request", false);
+ Services.obs.addObserver(updateIndicators, "recording-device-events", false);
+ Services.obs.addObserver(removeBrowserSpecificIndicator, "recording-window-ended", false);
+ },
+
+ uninit: function () {
+ Services.obs.removeObserver(handleRequest, "getUserMedia:request");
+ Services.obs.removeObserver(updateIndicators, "recording-device-events");
+ Services.obs.removeObserver(removeBrowserSpecificIndicator, "recording-window-ended");
+ },
+
+ showGlobalIndicator: false,
+
+ get activeStreams() {
+ let contentWindowSupportsArray = MediaManagerService.activeMediaCaptureWindows;
+ let count = contentWindowSupportsArray.Count();
+ let activeStreams = [];
+ for (let i = 0; i < count; i++) {
+ let contentWindow = contentWindowSupportsArray.GetElementAt(i);
+ let browser = getBrowserForWindow(contentWindow);
+ let browserWindow = browser.ownerDocument.defaultView;
+ let tab = browserWindow.gBrowser &&
+ browserWindow.gBrowser._getTabForContentWindow(contentWindow.top);
+ activeStreams.push({
+ uri: contentWindow.location.href,
+ tab: tab,
+ browser: browser
+ });
+ }
+ return activeStreams;
+ }
+}
+
+function getBrowserForWindowId(aWindowID) {
+ return getBrowserForWindow(Services.wm.getOuterWindowWithId(aWindowID));
+}
+
+function getBrowserForWindow(aContentWindow) {
+ return aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+}
+
+function handleRequest(aSubject, aTopic, aData) {
+ let {windowID: windowID, callID: callID} = JSON.parse(aData);
+
+ let params = aSubject.QueryInterface(Ci.nsIMediaStreamOptions);
+
+ Services.wm.getMostRecentWindow(null).navigator.mozGetUserMediaDevices(
+ function (devices) {
+ prompt(windowID, callID, params.audio, params.video || params.picture, devices);
+ },
+ function (error) {
+ // bug 827146 -- In the future, the UI should catch NO_DEVICES_FOUND
+ // and allow the user to plug in a device, instead of immediately failing.
+ denyRequest(callID, error);
+ }
+ );
+}
+
+function denyRequest(aCallID, aError) {
+ let msg = null;
+ if (aError) {
+ msg = Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
+ msg.data = aError;
+ }
+ Services.obs.notifyObservers(msg, "getUserMedia:response:deny", aCallID);
+}
+
+function prompt(aWindowID, aCallID, aAudioRequested, aVideoRequested, aDevices) {
+ let audioDevices = [];
+ let videoDevices = [];
+ for (let device of aDevices) {
+ device = device.QueryInterface(Ci.nsIMediaDevice);
+ switch (device.type) {
+ case "audio":
+ if (aAudioRequested)
+ audioDevices.push(device);
+ break;
+ case "video":
+ if (aVideoRequested)
+ videoDevices.push(device);
+ break;
+ }
+ }
+
+ let requestType;
+ if (audioDevices.length && videoDevices.length)
+ requestType = "CameraAndMicrophone";
+ else if (audioDevices.length)
+ requestType = "Microphone";
+ else if (videoDevices.length)
+ requestType = "Camera";
+ else {
+ denyRequest(aCallID, "NO_DEVICES_FOUND");
+ return;
+ }
+
+ let contentWindow = Services.wm.getOuterWindowWithId(aWindowID);
+ let host = contentWindow.document.documentURIObject.host;
+ let browser = getBrowserForWindow(contentWindow);
+ let chromeDoc = browser.ownerDocument;
+ let chromeWin = chromeDoc.defaultView;
+ let stringBundle = chromeWin.gNavigatorBundle;
+ let message = stringBundle.getFormattedString("getUserMedia.share" + requestType + ".message",
+ [ host ]);
+
+ let mainAction = {
+ label: PluralForm.get(requestType == "CameraAndMicrophone" ? 2 : 1,
+ stringBundle.getString("getUserMedia.shareSelectedDevices.label")),
+ accessKey: stringBundle.getString("getUserMedia.shareSelectedDevices.accesskey"),
+ // The real callback will be set during the "showing" event. The
+ // empty function here is so that PopupNotifications.show doesn't
+ // reject the action.
+ callback: function() {}
+ };
+
+ let secondaryActions = [{
+ label: stringBundle.getString("getUserMedia.denyRequest.label"),
+ accessKey: stringBundle.getString("getUserMedia.denyRequest.accesskey"),
+ callback: function () {
+ denyRequest(aCallID);
+ }
+ }];
+
+ let options = {
+ eventCallback: function(aTopic, aNewBrowser) {
+ if (aTopic == "swapping")
+ return true;
+
+ if (aTopic != "showing")
+ return false;
+
+ let chromeDoc = this.browser.ownerDocument;
+
+ function listDevices(menupopup, devices) {
+ while (menupopup.lastChild)
+ menupopup.removeChild(menupopup.lastChild);
+
+ let deviceIndex = 0;
+ for (let device of devices) {
+ addDeviceToList(menupopup, device.name, deviceIndex);
+ deviceIndex++;
+ }
+ }
+
+ function addDeviceToList(menupopup, deviceName, deviceIndex) {
+ let menuitem = chromeDoc.createElement("menuitem");
+ menuitem.setAttribute("value", deviceIndex);
+ menuitem.setAttribute("label", deviceName);
+ menuitem.setAttribute("tooltiptext", deviceName);
+ menupopup.appendChild(menuitem);
+ }
+
+ chromeDoc.getElementById("webRTC-selectCamera").hidden = !videoDevices.length;
+ chromeDoc.getElementById("webRTC-selectMicrophone").hidden = !audioDevices.length;
+
+ let camMenupopup = chromeDoc.getElementById("webRTC-selectCamera-menupopup");
+ let micMenupopup = chromeDoc.getElementById("webRTC-selectMicrophone-menupopup");
+ listDevices(camMenupopup, videoDevices);
+ listDevices(micMenupopup, audioDevices);
+ if (requestType == "CameraAndMicrophone") {
+ let stringBundle = chromeDoc.defaultView.gNavigatorBundle;
+ addDeviceToList(camMenupopup, stringBundle.getString("getUserMedia.noVideo.label"), "-1");
+ addDeviceToList(micMenupopup, stringBundle.getString("getUserMedia.noAudio.label"), "-1");
+ }
+
+ this.mainAction.callback = function() {
+ let allowedDevices = Cc["@mozilla.org/supports-array;1"]
+ .createInstance(Ci.nsISupportsArray);
+ if (videoDevices.length) {
+ let videoDeviceIndex = chromeDoc.getElementById("webRTC-selectCamera-menulist").value;
+ if (videoDeviceIndex != "-1")
+ allowedDevices.AppendElement(videoDevices[videoDeviceIndex]);
+ }
+ if (audioDevices.length) {
+ let audioDeviceIndex = chromeDoc.getElementById("webRTC-selectMicrophone-menulist").value;
+ if (audioDeviceIndex != "-1")
+ allowedDevices.AppendElement(audioDevices[audioDeviceIndex]);
+ }
+
+ if (allowedDevices.Count() == 0) {
+ denyRequest(aCallID);
+ return;
+ }
+
+ Services.obs.notifyObservers(allowedDevices, "getUserMedia:response:allow", aCallID);
+ };
+ return true;
+ }
+ };
+
+ chromeWin.PopupNotifications.show(browser, "webRTC-shareDevices", message,
+ "webRTC-shareDevices-notification-icon", mainAction,
+ secondaryActions, options);
+}
+
+function updateIndicators() {
+ webrtcUI.showGlobalIndicator =
+ MediaManagerService.activeMediaCaptureWindows.Count() > 0;
+
+ let e = Services.wm.getEnumerator("navigator:browser");
+ while (e.hasMoreElements())
+ e.getNext().WebrtcIndicator.updateButton();
+
+ for (let {browser: browser} of webrtcUI.activeStreams)
+ showBrowserSpecificIndicator(browser);
+}
+
+function showBrowserSpecificIndicator(aBrowser) {
+ let hasVideo = {};
+ let hasAudio = {};
+ MediaManagerService.mediaCaptureWindowState(aBrowser.contentWindow,
+ hasVideo, hasAudio);
+ let captureState;
+ if (hasVideo.value && hasAudio.value) {
+ captureState = "CameraAndMicrophone";
+ } else if (hasVideo.value) {
+ captureState = "Camera";
+ } else if (hasAudio.value) {
+ captureState = "Microphone";
+ } else {
+ Cu.reportError("showBrowserSpecificIndicator: got neither video nor audio access");
+ return;
+ }
+
+ let chromeWin = aBrowser.ownerDocument.defaultView;
+ let stringBundle = chromeWin.gNavigatorBundle;
+
+ let message = stringBundle.getString("getUserMedia.sharing" + captureState + ".message2");
+
+ let windowId = aBrowser.contentWindow
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .currentInnerWindowID;
+ let mainAction = {
+ label: "Continue Sharing", //stringBundle.getString("getUserMedia.continueSharing.label"),
+ accessKey: "C", //stringBundle.getString("getUserMedia.continueSharing.accesskey"),
+ callback: function () {},
+ dismiss: true
+ };
+ let secondaryActions = [{
+ label: "Stop Sharing", //stringBundle.getString("getUserMedia.stopSharing.label"),
+ accessKey: "S", //stringBundle.getString("getUserMedia.stopSharing.accesskey"),
+ callback: function () {
+ Services.obs.notifyObservers(null, "getUserMedia:revoke", windowId);
+ }
+ }];
+ let options = {
+ hideNotNow: true,
+ dismissed: true,
+ eventCallback: function(aTopic) aTopic == "swapping"
+ };
+ chromeWin.PopupNotifications.show(aBrowser, "webRTC-sharingDevices", message,
+ "webRTC-sharingDevices-notification-icon", mainAction,
+ secondaryActions, options);
+}
+
+function removeBrowserSpecificIndicator(aSubject, aTopic, aData) {
+ let browser = getBrowserForWindowId(aData);
+ let PopupNotifications = browser.ownerDocument.defaultView.PopupNotifications;
+ let notification = PopupNotifications &&
+ PopupNotifications.getNotification("webRTC-sharingDevices",
+ browser);
+ if (notification)
+ PopupNotifications.remove(notification);
+}
diff --git a/moz.build b/moz.build
new file mode 100644
index 0000000..72e3767
--- /dev/null
+++ b/moz.build
@@ -0,0 +1,24 @@
+# -*- 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/.
+
+CONFIGURE_SUBST_FILES += ['installer/Makefile']
+
+DIRS += [
+ 'base',
+ 'components',
+ 'fonts',
+ 'locales',
+ 'modules',
+ 'themes',
+]
+
+DIRS += ['app']
+
+if CONFIG['MAKENSISU']:
+ DIRS += ['installer/windows']
+
+DIST_SUBDIR = 'browser'
+export('DIST_SUBDIR') \ No newline at end of file
diff --git a/moz.configure b/moz.configure
new file mode 100644
index 0000000..7223625
--- /dev/null
+++ b/moz.configure
@@ -0,0 +1,7 @@
+# -*- Mode: python; 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/.
+
+include('../../toolkit/moz.configure')
diff --git a/mozconfig.example b/mozconfig.example
new file mode 100644
index 0000000..9b92b92
--- /dev/null
+++ b/mozconfig.example
@@ -0,0 +1,42 @@
+mk_add_options AUTOCLOBBER=1
+mk_add_options MOZ_OBJDIR=/home/$USER/build/wbbuild/
+ac_add_options --enable-application=webbrowser
+
+ac_add_options --enable-optimize="-O2 -march=skylake -fomit-frame-pointer -pipe"
+#ac_add_options --enable-optimize="-O2 -pipe"
+ac_add_options --enable-ccache
+
+#ac_add_options CC=clang
+#ac_add_options CXX=clang++
+
+# Please see https://www.palemoon.org/redist.shtml for restrictions when using the official branding.
+#ac_add_options --enable-official-branding
+ac_add_options --disable-official-branding
+#export MOZILLA_OFFICIAL=1
+
+ac_add_options --enable-default-toolkit=cairo-gtk2
+ac_add_options --enable-jemalloc
+ac_add_options --enable-strip
+ac_add_options --with-pthreads
+ac_add_options --with-system-libevent
+ac_add_options --enable-hardware-aec-ns
+
+ac_add_options --disable-tests
+ac_add_options --disable-eme
+ac_add_options --disable-parental-controls
+ac_add_options --disable-accessibility
+ac_add_options --disable-gamepad
+ac_add_options --disable-necko-wifi
+ac_add_options --disable-updater
+ac_add_options --disable-sync
+ac_add_options --disable-pulseaudio
+ac_add_options --x-libraries=/usr/lib
+
+ac_add_options --enable-private-build
+ac_add_options --enable-webrtc
+ac_add_options --enable-alsa
+ac_add_options --enable-jack
+
+ac_add_options --disable-mozril-geoloc
+
+ac_add_options --with-system-libvpx
diff --git a/themes/LICENSE b/themes/LICENSE
new file mode 100644
index 0000000..39d4f8f
--- /dev/null
+++ b/themes/LICENSE
@@ -0,0 +1,2 @@
+All files in this directory are assumed to be licensed under the MPL 2 license
+which is used throughout this codebase.
diff --git a/themes/linux/Geolocation-16.png b/themes/linux/Geolocation-16.png
new file mode 100644
index 0000000..082b177
--- /dev/null
+++ b/themes/linux/Geolocation-16.png
Binary files differ
diff --git a/themes/linux/Geolocation-64.png b/themes/linux/Geolocation-64.png
new file mode 100644
index 0000000..6e09ab9
--- /dev/null
+++ b/themes/linux/Geolocation-64.png
Binary files differ
diff --git a/themes/linux/Go-arrow.png b/themes/linux/Go-arrow.png
new file mode 100644
index 0000000..259c8a4
--- /dev/null
+++ b/themes/linux/Go-arrow.png
Binary files differ
diff --git a/themes/linux/Info.png b/themes/linux/Info.png
new file mode 100644
index 0000000..d144798
--- /dev/null
+++ b/themes/linux/Info.png
Binary files differ
diff --git a/themes/linux/KUI-close.png b/themes/linux/KUI-close.png
new file mode 100644
index 0000000..08eeb81
--- /dev/null
+++ b/themes/linux/KUI-close.png
Binary files differ
diff --git a/themes/linux/Makefile.in b/themes/linux/Makefile.in
new file mode 100644
index 0000000..3b81bbf
--- /dev/null
+++ b/themes/linux/Makefile.in
@@ -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/.
+
+ICON_FILES := icon.png
+ICON_DEST = $(FINAL_TARGET)/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}
+INSTALL_TARGETS += ICON
+
diff --git a/themes/linux/Privacy-16.png b/themes/linux/Privacy-16.png
new file mode 100644
index 0000000..013cdc4
--- /dev/null
+++ b/themes/linux/Privacy-16.png
Binary files differ
diff --git a/themes/linux/Privacy-32.png b/themes/linux/Privacy-32.png
new file mode 100644
index 0000000..f56dd2d
--- /dev/null
+++ b/themes/linux/Privacy-32.png
Binary files differ
diff --git a/themes/linux/Privacy-48.png b/themes/linux/Privacy-48.png
new file mode 100644
index 0000000..8dd0243
--- /dev/null
+++ b/themes/linux/Privacy-48.png
Binary files differ
diff --git a/themes/linux/Privacy-64.png b/themes/linux/Privacy-64.png
new file mode 100644
index 0000000..106afb4
--- /dev/null
+++ b/themes/linux/Privacy-64.png
Binary files differ
diff --git a/themes/linux/Secure.png b/themes/linux/Secure.png
new file mode 100644
index 0000000..5ee25e9
--- /dev/null
+++ b/themes/linux/Secure.png
Binary files differ
diff --git a/themes/linux/Security-broken.png b/themes/linux/Security-broken.png
new file mode 100644
index 0000000..1ec110b
--- /dev/null
+++ b/themes/linux/Security-broken.png
Binary files differ
diff --git a/themes/linux/Toolbar-small.png b/themes/linux/Toolbar-small.png
new file mode 100644
index 0000000..bcc8f63
--- /dev/null
+++ b/themes/linux/Toolbar-small.png
Binary files differ
diff --git a/themes/linux/Toolbar.png b/themes/linux/Toolbar.png
new file mode 100644
index 0000000..2851657
--- /dev/null
+++ b/themes/linux/Toolbar.png
Binary files differ
diff --git a/themes/linux/aboutCertError.css b/themes/linux/aboutCertError.css
new file mode 100644
index 0000000..dbb3530
--- /dev/null
+++ b/themes/linux/aboutCertError.css
@@ -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/. */
+
+
+html {
+ background: #833;
+}
+
+body {
+ margin: 0;
+ padding: 0 1em;
+ color: -moz-FieldText;
+ font: message-box;
+}
+
+h1 {
+ margin: 0 0 .6em 0;
+ border-bottom: 1px solid ThreeDLightShadow;
+ font-size: 160%;
+}
+
+h2 {
+ font-size: 130%;
+}
+
+#errorPageContainer {
+ position: relative;
+ min-width: 13em;
+ max-width: 52em;
+ margin: 4em auto;
+ border: 2px solid #DD0D09;
+ border-radius: 10px;
+ box-shadow: 0px 0px 8px red;
+ padding: 3em;
+ -moz-padding-start: 30px;
+ background: url("chrome://global/skin/icons/sslWarning.png") left 0 no-repeat -moz-Field;
+ background-origin: content-box;
+}
+
+#errorPageContainer:-moz-dir(rtl) {
+ background-position: right 0;
+}
+
+#errorTitle {
+ -moz-margin-start: 80px;
+}
+
+#errorLongContent {
+ -moz-margin-start: 80px;
+}
+
+.expander > button {
+ -moz-padding-start: 20px;
+ -moz-margin-start: -20px;
+ background: url("chrome://browser/skin/aboutCertError_sectionExpanded.png") left center no-repeat;
+ border: none;
+ font: inherit;
+ color: inherit;
+ cursor: pointer;
+}
+
+.expander > button:-moz-dir(rtl) {
+ background-position: right center;
+}
+
+.expander[collapsed] > button {
+ background-image: url("chrome://browser/skin/aboutCertError_sectionCollapsed.png");
+}
+
+.expander[collapsed] > button:-moz-dir(rtl) {
+ background-image: url("chrome://browser/skin/aboutCertError_sectionCollapsed-rtl.png");
+}
diff --git a/themes/linux/aboutCertError_sectionCollapsed-rtl.png b/themes/linux/aboutCertError_sectionCollapsed-rtl.png
new file mode 100644
index 0000000..84ba18c
--- /dev/null
+++ b/themes/linux/aboutCertError_sectionCollapsed-rtl.png
Binary files differ
diff --git a/themes/linux/aboutCertError_sectionCollapsed.png b/themes/linux/aboutCertError_sectionCollapsed.png
new file mode 100644
index 0000000..c9805f6
--- /dev/null
+++ b/themes/linux/aboutCertError_sectionCollapsed.png
Binary files differ
diff --git a/themes/linux/aboutCertError_sectionExpanded.png b/themes/linux/aboutCertError_sectionExpanded.png
new file mode 100644
index 0000000..128cef9
--- /dev/null
+++ b/themes/linux/aboutCertError_sectionExpanded.png
Binary files differ
diff --git a/themes/linux/aboutPrivateBrowsing.css b/themes/linux/aboutPrivateBrowsing.css
new file mode 100644
index 0000000..2bb39d2
--- /dev/null
+++ b/themes/linux/aboutPrivateBrowsing.css
@@ -0,0 +1,47 @@
+%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
+
+body.private > #errorPageContainer {
+ background-image: url("chrome://browser/skin/Privacy-48.png");
+}
+
+body.normal > #errorPageContainer {
+ background-image: url("moz-icon://stock/gtk-dialog-question?size=dialog");
+}
+
+#clearRecentHistoryDesc {
+ margin-top: 2em;
+}
+
+#clearRecentHistoryDesc > p {
+ font-size: 110%; /* to match the value set in chrome://global/skin/netError.css */
+}
+
+#startPrivateBrowsingDesc > button {
+ -moz-margin-start: 0;
+}
+
+#footerDesc > p {
+ font-size: 110%; /* to match the value set in chrome://global/skin/netError.css */
+}
+
+#moreInfo {
+ font-size: 110%; /* to match the value set in chrome://global/skin/netError.css */
+ -moz-padding-start: 25px;
+ background: url("moz-icon://stock/gtk-dialog-info?size=menu") no-repeat top left;
+}
+
+#moreInfo:-moz-dir(rtl) {
+ background-position: top right;
+}
+
+#moreInfoText {
+ margin-bottom: 0;
+}
+
+#moreInfoLinkContainer {
+ margin-top: 0.5em;
+}
diff --git a/themes/linux/aboutSessionRestore-window-icon.png b/themes/linux/aboutSessionRestore-window-icon.png
new file mode 100644
index 0000000..a998323
--- /dev/null
+++ b/themes/linux/aboutSessionRestore-window-icon.png
Binary files differ
diff --git a/themes/linux/aboutSessionRestore.css b/themes/linux/aboutSessionRestore.css
new file mode 100644
index 0000000..a32b975
--- /dev/null
+++ b/themes/linux/aboutSessionRestore.css
@@ -0,0 +1,90 @@
+%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 {
+ background: #f8ffd0;
+ height: 100%;
+}
+
+body {
+ height: 100%;
+ text-align: center;
+}
+
+#errorPageContainer {
+ background-image: url("moz-icon://stock/gtk-dialog-warning?size=dialog");
+ display: -moz-box;
+ width: -moz-available;
+ max-width: 85%;
+ height: 75%;
+ max-height: 85%;
+ -moz-box-orient: vertical;
+ text-align: start;
+ border: 2px solid #efc;
+ box-shadow: 0px 0px 8px #aaa;
+}
+
+#errorShortDesc > p {
+ margin-top: 0.4em;
+ margin-bottom: 0;
+}
+
+#errorLongContent, #errorTrailerDesc {
+ display: -moz-box;
+ -moz-box-flex: 1;
+ -moz-box-orient: vertical;
+}
+
+#tabList {
+ margin-top: 2.5em;
+ width: 100%;
+ min-height: 12em;
+}
+
+treechildren::-moz-tree-image(icon),
+treechildren::-moz-tree-image(noicon) {
+ padding-right: 2px;
+ margin: 0px 2px;
+ width: 16px;
+ height: 16px;
+}
+
+treechildren::-moz-tree-image(noicon) {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+treechildren::-moz-tree-image(container, noicon) {
+ list-style-image: url("chrome://browser/skin/aboutSessionRestore-window-icon.png");
+}
+treechildren::-moz-tree-image(checked) {
+ list-style-image: url("chrome://global/skin/checkbox/cbox-check.gif");
+}
+treechildren::-moz-tree-image(partial) {
+ list-style-image: url("chrome://global/skin/checkbox/cbox-check-dis.gif");
+}
+
+/* undo odd row highlighting from tree.css */
+treechildren::-moz-tree-row(odd) {
+ background-color: transparent;
+}
+treechildren::-moz-tree-row(odd, selected, focus) {
+ background-color: Highlight;
+}
+
+/* highlight "windows" instead */
+treechildren::-moz-tree-row(alternate) {
+ background-color: -moz-oddtreerow;
+}
+treechildren::-moz-tree-row(alternate, selected) {
+ background-color: Highlight;
+}
+
+#buttons {
+ -moz-margin-start: 80px; /* same as #errorLongContent in netError.css */
+}
+#buttons > button {
+ margin-top: 2em;
+ -moz-margin-start: 5px;
+}
diff --git a/themes/linux/aboutSyncTabs.css b/themes/linux/aboutSyncTabs.css
new file mode 100644
index 0000000..4f21a9d
--- /dev/null
+++ b/themes/linux/aboutSyncTabs.css
@@ -0,0 +1,101 @@
+/* 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/. */
+
+#tabs-display,
+#tabsList {
+ background-color: transparent;
+ -moz-appearance: none;
+ margin: 0;
+}
+
+#tabsList {
+ width: 100%;
+}
+
+#tabs-display {
+ background: #fff url(chrome://browser/skin/sync-bg.png) repeat-x center -80px;
+}
+
+#headers {
+ background: url(chrome://browser/skin/sync-32.png) no-repeat;
+ margin-top: 4px;
+ width: 45em;
+ height: 32px;
+ -moz-margin-start: 2em;
+ -moz-margin-end: 2em;
+}
+
+#tabsListHeading {
+ font-size: 140%;
+ font-weight: bold;
+ -moz-margin-start: 40px;
+}
+
+richlistitem {
+ -moz-margin-end: 2em;
+}
+
+richlistitem[selected="true"],
+richlistitem:focus {
+ outline-style: none;
+}
+
+richlistitem[type="tab"] {
+ min-height: 3em;
+ border: #999999 1px solid !important;
+ padding: 2px 5px;
+ margin-bottom: 4px;
+ -moz-margin-start: 4em;
+ border-radius: 6px;
+ background-color: menu;
+ width: 44em;
+ opacity: 0.9;
+ box-shadow:
+ inset rgba(255, 255, 255, 0.5) 0 1px 0px,
+ inset rgba(0, 0, 0, 0.1) 0 -2px 0px,
+ rgba(0, 0, 0, 0.1) 0px 1px 0px;
+}
+
+richlistitem[type="tab"][selected="true"] {
+ background-color: -moz-MenuHover;
+}
+
+richlistitem[type="client"] {
+ min-height: 2em;
+ color: #000000;
+ -moz-margin-start: 2em;
+ margin-top: 2px;
+ margin-bottom: 3px;
+ width: 42em;
+ border-radius: 6px;
+ background-color: transparent;
+ -moz-user-focus: ignore !important;
+}
+richlistitem.mobile[type="client"] {
+ list-style-image: url("chrome://browser/skin/sync-mobileIcon.png");
+}
+richlistitem.desktop[type="client"] {
+ list-style-image: url("chrome://browser/skin/sync-desktopIcon.png");
+}
+
+.title,
+.clientName {
+ color: #000000;
+ font-size: 1.1em;
+}
+
+.title[selected="true"],
+.url[selected="true"] {
+ color: inherit;
+}
+
+.url {
+ color: -moz-nativehyperlinktext;
+ font-size: 0.95em;
+}
+
+.tabIcon {
+ -moz-padding-start: 2px;
+ padding-top: 2px;
+}
diff --git a/themes/linux/actionicon-tab.png b/themes/linux/actionicon-tab.png
new file mode 100644
index 0000000..433c25e
--- /dev/null
+++ b/themes/linux/actionicon-tab.png
Binary files differ
diff --git a/themes/linux/autocomplete.css b/themes/linux/autocomplete.css
new file mode 100644
index 0000000..ab92685
--- /dev/null
+++ b/themes/linux/autocomplete.css
@@ -0,0 +1,210 @@
+/* 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/. */
+
+/* ===== autocomplete.css =================================================
+ == Styles used by the autocomplete widget.
+ ======================================================================= */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
+
+/* ::::: autocomplete ::::: */
+
+/* .padded is used by autocomplete widgets that don't have an icon. Gross. -dwh */
+textbox:not(.padded) {
+ cursor: default;
+ padding: 0;
+}
+
+textbox[enablehistory="true"] {
+ -moz-appearance: none;
+ border: 0;
+ background-color: transparent;
+}
+
+textbox[nomatch="true"][highlightnonmatches="true"] {
+ color: red;
+}
+
+.private-autocomplete-textbox-container {
+ -moz-box-align: center;
+}
+
+textbox[enablehistory="true"] > .autocomplete-textbox-container {
+ -moz-appearance: menulist-textfield;
+}
+
+textbox:not(.padded) .textbox-input-box {
+ margin: 0 3px;
+}
+
+.textbox-input-box {
+ -moz-box-align: center;
+}
+
+/* ::::: autocomplete popups ::::: */
+
+panel[type="private-autocomplete"],
+panel[type="private-autocomplete-richlistbox"],
+.private-autocomplete-history-popup {
+ border-width: 1px;
+ -moz-border-top-colors: ThreeDDarkShadow;
+ -moz-border-right-colors: ThreeDDarkShadow;
+ -moz-border-bottom-colors: ThreeDDarkShadow;
+ -moz-border-left-colors: ThreeDDarkShadow;
+ padding: 0;
+ background-color: -moz-Field;
+}
+
+.private-autocomplete-history-popup {
+ max-height: 180px;
+}
+
+/* ::::: tree ::::: */
+
+.private-autocomplete-tree {
+ -moz-appearance: none !important;
+ border: none !important;
+ background-color: transparent !important;
+ color: MenuText;
+}
+
+.private-autocomplete-treecol {
+ -moz-appearance: none !important;
+ margin: 0 !important;
+ border: none !important;
+ padding: 0 !important;
+}
+
+/* GTK calculates space for a sort arrow */
+.private-autocomplete-treecol > .treecol-sortdirection {
+ -moz-appearance: none !important;
+}
+
+.private-autocomplete-treebody::-moz-tree-cell-text {
+ -moz-padding-start: 8px;
+}
+
+treechildren.private-autocomplete-treebody::-moz-tree-row(selected) {
+ background-color: Highlight;
+}
+
+treechildren.private-autocomplete-treebody::-moz-tree-cell-text(selected) {
+ color: HighlightText !important;
+}
+
+.private-autocomplete-treebody::-moz-tree-image(treecolAutoCompleteValue) {
+ max-width: 16px;
+ height: 16px;
+}
+
+/* ::::: richlistbox autocomplete ::::: */
+
+.private-autocomplete-richlistbox {
+ -moz-appearance: none;
+ margin: 1px;
+ background-color: transparent;
+}
+
+.private-autocomplete-richlistitem[selected="true"] {
+ background-color: Highlight;
+ color: HighlightText;
+}
+
+.private-autocomplete-richlistitem {
+ padding: 6px 2px;
+ color: MenuText;
+}
+
+.ac-url-box {
+ /* When setting a vertical margin here, half of that needs to be added
+ .ac-title-box's translateY for when .ac-url-box is hidden (see below). */
+ margin-top: 1px;
+}
+
+.private-autocomplete-richlistitem[actiontype="keyword"] .ac-url-box,
+.private-autocomplete-richlistitem[actiontype="searchengine"] .ac-url-box,
+.private-autocomplete-richlistitem[actiontype="visiturl"] .ac-url-box,
+.private-autocomplete-richlistitem[type~="autofill"] .ac-url-box {
+ visibility: hidden;
+}
+
+.private-autocomplete-richlistitem[actiontype="keyword"] .ac-title-box,
+.private-autocomplete-richlistitem[actiontype="searchengine"] .ac-title-box,
+.private-autocomplete-richlistitem[actiontype="visiturl"] .ac-title-box,
+.private-autocomplete-richlistitem[type~="autofill"] .ac-title-box {
+ /* Center the title by moving it down by half of .ac-url-box's height,
+ including vertical margins (if any). */
+ transform: translateY(.5em);
+}
+
+.ac-site-icon {
+ width: 16px;
+ height: 16px;
+ margin-bottom: -2px;
+ -moz-margin-start: 3px;
+ -moz-margin-end: 6px;
+}
+
+.ac-type-icon {
+ width: 16px;
+ height: 16px;
+ -moz-margin-start: 6px;
+ -moz-margin-end: 4px;
+}
+
+.ac-extra > .ac-result-type-tag {
+ margin: 0 4px;
+}
+
+.ac-extra > .ac-comment {
+ padding-right: 4px;
+}
+
+.ac-ellipsis-after {
+ margin: 0 !important;
+ padding: 0;
+ min-width: 1em;
+}
+
+.ac-normal-text {
+ margin: 0 !important;
+ padding: 0;
+}
+
+.ac-normal-text > html|span {
+ margin: 0 !important;
+ padding: 0;
+}
+
+html|span.ac-emphasize-text {
+ box-shadow: inset 0 0 1px 1px rgba(0,0,0,0.1);
+ background-color: rgba(0,0,0,0.05);
+ border-radius: 2px;
+ text-shadow: 0 0 currentColor; /*faux bold effect*/
+}
+
+.ac-url-text > html|span.ac-emphasize-text,
+.ac-action-text > html|span.ac-emphasize-text {
+ box-shadow: none;
+}
+
+.ac-normal-text[selected="true"] > html|span.ac-emphasize-text {
+ box-shadow: inset 0 0 1px 1px rgba(255,255,255,0.3);
+ background-color: rgba(255,255,255,0.2);
+}
+
+.ac-title, .ac-url {
+ overflow: hidden;
+}
+
+/* ::::: textboxes inside toolbarpaletteitems ::::: */
+
+toolbarpaletteitem > toolbaritem > textbox > hbox > hbox > html|*.textbox-input {
+ visibility: hidden;
+}
+
+toolbarpaletteitem > toolbaritem > * > textbox > hbox > hbox > html|*.textbox-input {
+ visibility: hidden;
+}
diff --git a/themes/linux/browser.css b/themes/linux/browser.css
new file mode 100644
index 0000000..4933b40
--- /dev/null
+++ b/themes/linux/browser.css
@@ -0,0 +1,2210 @@
+%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
+
+@import url("chrome://global/skin/");
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
+
+%include ../shared/browser.inc
+%filter substitution
+%define toolbarHighlight rgba(255,255,255,.3)
+%define selectedTabHighlight rgba(255,255,255,.8) 1px, rgba(255,255,255,.5) 3px
+%define forwardTransitionLength 150ms
+%define conditionalForwardWithUrlbar window:not([chromehidden~=toolbar]) :-moz-any(#nav-bar[currentset*="unified-back-forward-button,urlbar-container"][mode=icons], #nav-bar:not([currentset])[mode=icons]) > #unified-back-forward-button
+%define conditionalForwardWithUrlbar_small window:not([chromehidden~=toolbar]) :-moz-any(#nav-bar[currentset*="unified-back-forward-button,urlbar-container"][mode=icons][iconsize=small],#nav-bar:not([currentset])[mode=icons][iconsize=small]) > #unified-back-forward-button
+
+#menubar-items {
+ -moz-box-orient: vertical; /* for flex hack */
+}
+
+#main-menubar {
+ -moz-box-flex: 1; /* make menu items expand to fill toolbar height */
+}
+
+#navigator-toolbox {
+ -moz-appearance: none;
+ background-color: transparent;
+ border-top: none;
+}
+
+#main-window:not([disablechrome]) #navigator-toolbox[tabsontop=true] {
+ border-bottom: 1px solid ThreeDShadow;
+}
+
+#navigator-toolbox[tabsontop=true] > toolbar:not(:-moz-lwtheme):not(#toolbar-menubar):not(#TabsToolbar),
+#navigator-toolbox[tabsontop=false] > toolbar:not(:-moz-lwtheme):not(#toolbar-menubar) {
+ -moz-appearance: none;
+ border-style: none;
+ background-color: -moz-Dialog;
+}
+
+#navigator-toolbox > toolbar:not(#toolbar-menubar):not(#TabsToolbar) {
+ padding-top: 1px;
+ padding-bottom: 1px;
+}
+
+#nav-bar:not(:-moz-lwtheme),
+#nav-bar[collapsed=true] + toolbar:not(:-moz-lwtheme),
+#nav-bar[collapsed=true] + #customToolbars + #PersonalToolbar:not(:-moz-lwtheme),
+#nav-bar[tabsontop=true],
+#nav-bar[tabsontop=true][collapsed=true]:not([customizing]) + toolbar,
+#nav-bar[tabsontop=true][collapsed=true]:not([customizing]) + #customToolbars + #PersonalToolbar {
+ background-image: linear-gradient(@toolbarHighlight@, rgba(255,255,255,0));
+}
+
+#nav-bar[tabsontop=true]:-moz-lwtheme,
+#nav-bar[tabsontop=true][collapsed=true]:not([customizing]):-moz-lwtheme + toolbar,
+#nav-bar[tabsontop=true][collapsed=true]:not([customizing]):-moz-lwtheme + #customToolbars + #PersonalToolbar {
+ background-image: linear-gradient(rgba(255,255,255,.8), rgba(255,255,255,0));
+}
+
+#nav-bar[tabsontop=true]:-moz-lwtheme-brighttext,
+#nav-bar[tabsontop=true][collapsed=true]:not([customizing]):-moz-lwtheme-brighttext + toolbar,
+#nav-bar[tabsontop=true][collapsed=true]:not([customizing]):-moz-lwtheme-brighttext + #customToolbars + #PersonalToolbar {
+ background-image: linear-gradient(rgba(32,32,32,.8), rgba(32,32,32,0));
+}
+
+#personal-bookmarks {
+ min-height: 29px;
+}
+
+#browser-bottombox {
+ /* opaque for layers optimization */
+ background-color: -moz-Dialog;
+}
+
+#urlbar:-moz-lwtheme:not([focused="true"]),
+.searchbar-textbox:-moz-lwtheme:not([focused="true"]) {
+ opacity: .85;
+}
+
+/* Places toolbar */
+toolbarbutton.bookmark-item {
+ margin: 0;
+ padding: 2px 3px;
+}
+
+toolbarbutton.bookmark-item:hover:active,
+toolbarbutton.bookmark-item[open="true"] {
+ padding-top: 3px;
+ padding-bottom: 1px;
+ -moz-padding-start: 4px;
+ -moz-padding-end: 2px;
+}
+
+.bookmark-item > .toolbarbutton-icon {
+ width: 16px;
+ height: 16px;
+}
+
+/* Prevent [mode="icons"] from hiding the label */
+.bookmark-item > .toolbarbutton-text {
+ display: -moz-box !important;
+}
+
+.bookmark-item > .toolbarbutton-menu-dropmarker {
+ display: none;
+}
+
+/* Dropmarker for folder bookmarks */
+.bookmark-item[container] > .toolbarbutton-menu-dropmarker {
+ display: -moz-box !important;
+}
+
+#wrapper-personal-bookmarks[place="palette"] > .toolbarpaletteitem-box {
+ background: url("chrome://browser/skin/places/bookmarksToolbar.png") no-repeat center;
+}
+
+.bookmarks-toolbar-customize {
+ max-width: 15em !important;
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png") !important;
+}
+
+/* Bookmark menus */
+menu.bookmark-item,
+menuitem.bookmark-item {
+ min-width: 0;
+ max-width: 32em;
+}
+
+.bookmark-item > .menu-iconic-left {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+.bookmark-item > .menu-iconic-left > .menu-iconic-icon {
+ -moz-padding-start: 0px;
+}
+
+/* Bookmark drag and drop styles */
+.bookmark-item[dragover-into="true"] {
+ background: Highlight !important;
+ color: HighlightText !important;
+}
+
+/* rules for menupopup drop indicators */
+.menupopup-drop-indicator-bar {
+ position: relative;
+ /* these two margins must together compensate the indicator's height */
+ margin-top: -1px;
+ margin-bottom: -1px;
+}
+
+.menupopup-drop-indicator {
+ list-style-image: none;
+ height: 2px;
+ -moz-margin-end: -4em;
+ background-color: Highlight;
+}
+
+/* Bookmarks toolbar */
+#PlacesToolbarDropIndicator {
+ list-style-image: url(chrome://browser/skin/places/toolbarDropMarker.png);
+}
+
+/* Bookmark items */
+.bookmark-item {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+
+.bookmark-item[container] {
+ list-style-image: url("moz-icon://stock/gtk-directory?size=menu");
+}
+
+.bookmark-item[container][livemark] {
+ list-style-image: url("chrome://browser/skin/feeds/feedIcon16.png");
+}
+
+.bookmark-item[container][livemark] .bookmark-item {
+ list-style-image: url("chrome://browser/skin/places/livemark-item.png");
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+}
+
+.bookmark-item[container][livemark] .bookmark-item[visited] {
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+.bookmark-item[container][query] {
+ list-style-image: url("chrome://browser/skin/places/query.png");
+}
+
+.bookmark-item[query][tagContainer] {
+ list-style-image: url("chrome://browser/skin/places/tag.png");
+}
+
+.bookmark-item[query][dayContainer] {
+ list-style-image: url("chrome://browser/skin/places/calendar.png");
+}
+
+.bookmark-item[query][hostContainer] {
+ list-style-image: url("moz-icon://stock/gtk-directory?size=menu");
+}
+
+.bookmark-item[query][hostContainer][open] {
+ list-style-image: url("moz-icon://stock/gtk-directory?size=menu");
+}
+
+.bookmark-item[cutting] > .toolbarbutton-icon,
+.bookmark-item[cutting] > .menu-iconic-left > .menu-iconic-icon {
+ opacity: 0.5;
+}
+
+.bookmark-item[cutting] > .toolbarbutton-text,
+.bookmark-item[cutting] > .menu-iconic-left > .menu-iconic-text {
+ opacity: 0.7;
+}
+
+/* Stock icons for the menu bar items */
+menuitem:not([type]):not(.menuitem-tooltip):not(.menuitem-iconic-tooltip) {
+ -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem-iconic");
+}
+
+#appmenu_newNavigator,
+#placesContext_open\:newwindow,
+#menu_newNavigator,
+#context-openlink,
+#context-openframe {
+ list-style-image: url("chrome://browser/skin/Toolbar-small.png");
+ -moz-image-region: rect(0px 80px 16px 64px);
+}
+
+#appmenu_newTab,
+#appmenu_newTab_popup,
+#placesContext_open\:newtab,
+#placesContext_openContainer\:tabs,
+#menu_newNavigatorTab,
+#context-openlinkintab,
+#context-openframeintab {
+ list-style-image: url("chrome://browser/skin/Toolbar-small.png");
+ -moz-image-region: rect(0px 64px 16px 48px);
+}
+
+#appmenu_openFile,
+#menu_openFile {
+ list-style-image: url("moz-icon://stock/gtk-open?size=menu");
+}
+
+#menu_close {
+ list-style-image: url("moz-icon://stock/gtk-close?size=menu");
+}
+
+#context-media-play {
+ list-style-image: url("moz-icon://stock/gtk-media-play?size=menu");
+}
+
+#context-media-pause {
+ list-style-image: url("moz-icon://stock/gtk-media-pause?size=menu");
+}
+
+#appmenu_savePage,
+#menu_savePage,
+#context-savelink,
+#context-saveimage,
+#context-savevideo,
+#context-saveaudio,
+#context-savepage,
+#context-saveframe {
+ list-style-image: url("moz-icon://stock/gtk-save-as?size=menu");
+}
+
+#appmenu_printPreview,
+#menu_printPreview {
+ list-style-image: url("moz-icon://stock/gtk-print-preview?size=menu");
+}
+
+#appmenu_print,
+#appmenu_print_popup,
+#menu_print,
+#context-printframe {
+ list-style-image: url("moz-icon://stock/gtk-print?size=menu");
+}
+
+#appmenu-quit,
+#menu_FileQuitItem {
+ list-style-image: url("moz-icon://stock/gtk-quit?size=menu");
+}
+
+#menu_undo,
+#context-undo {
+ list-style-image: url("moz-icon://stock/gtk-undo?size=menu");
+}
+
+#menu_undo[disabled],
+#context-undo[disabled] {
+ list-style-image: url("moz-icon://stock/gtk-undo?size=menu&state=disabled");
+}
+
+#menu_redo {
+ list-style-image: url("moz-icon://stock/gtk-redo?size=menu");
+}
+
+#menu_redo[disabled] {
+ list-style-image: url("moz-icon://stock/gtk-redo?size=menu&state=disabled");
+}
+
+#menu_cut,
+#placesContext_cut,
+#context-cut {
+ list-style-image: url("moz-icon://stock/gtk-cut?size=menu");
+}
+
+#menu_cut[disabled],
+#placesContext_cut[disabled],
+#context-cut[disabled] {
+ list-style-image: url("moz-icon://stock/gtk-cut?size=menu&state=disabled");
+}
+
+#menu_copy,
+#placesContext_copy,
+#context-copy,
+#context-copyimage,
+#context-copyvideourl,
+#context-copyaudiourl,
+#context-copylink,
+#context-copyemail {
+ list-style-image: url("moz-icon://stock/gtk-copy?size=menu");
+}
+
+#menu_copy[disabled],
+#placesContext_copy[disabled],
+#context-copy[disabled] {
+ list-style-image: url("moz-icon://stock/gtk-copy?size=menu&state=disabled");
+}
+
+#menu_paste,
+#placesContext_paste,
+#context-paste {
+ list-style-image: url("moz-icon://stock/gtk-paste?size=menu");
+}
+
+#menu_paste[disabled],
+#placesContext_paste[disabled],
+#context-paste[disabled] {
+ list-style-image: url("moz-icon://stock/gtk-paste?size=menu&state=disabled");
+}
+
+#menu_delete,
+#placesContext_delete,
+#placesContext_delete_history,
+#context-delete {
+ list-style-image: url("moz-icon://stock/gtk-delete?size=menu");
+}
+
+#menu_delete[disabled],
+#placesContext_delete[disabled],
+#placesContext_delete_history[disabled],
+#context-delete[disabled] {
+ list-style-image: url("moz-icon://stock/gtk-delete?size=menu&state=disabled");
+}
+
+#menu_selectAll,
+#context-selectall {
+ list-style-image: url("moz-icon://stock/gtk-select-all?size=menu");
+}
+
+#appmenu_find,
+#menu_find {
+ list-style-image: url("moz-icon://stock/gtk-find?size=menu");
+}
+
+#menu_find[disabled] {
+ list-style-image: url("moz-icon://stock/gtk-find?size=menu&state=disabled");
+}
+
+#appmenu_customize,
+#appmenu_preferences,
+#menu_preferences {
+ list-style-image: url("moz-icon://stock/gtk-preferences?size=menu");
+}
+
+#menu_stop,
+#context-stop {
+ list-style-image: url("moz-icon://stock/gtk-stop?size=menu");
+}
+
+#menu_stop[disabled],
+#context-stop[disabled] {
+ list-style-image: url("moz-icon://stock/gtk-stop?size=menu&state=disabled");
+}
+
+#menu_reload,
+#placesContext_reload,
+#context-reload,
+#context-reloadframe {
+ list-style-image: url("moz-icon://stock/gtk-refresh?size=menu");
+}
+
+#menu_reload[disabled],
+#context-reload[disabled] {
+ list-style-image: url("moz-icon://stock/gtk-refresh?size=menu&state=disabled");
+}
+
+#menu_zoomEnlarge {
+ list-style-image: url("moz-icon://stock/gtk-zoom-in?size=menu");
+}
+
+#menu_zoomReduce {
+ list-style-image: url("moz-icon://stock/gtk-zoom-out?size=menu");
+}
+
+#menu_zoomReset {
+ list-style-image: url("moz-icon://stock/gtk-zoom-100?size=menu");
+}
+
+#historyMenuBack,
+#context-back {
+ list-style-image: url("moz-icon://stock/gtk-go-back-ltr?size=menu");
+}
+
+#historyMenuBack[disabled],
+#context-back[disabled] {
+ list-style-image: url("moz-icon://stock/gtk-go-back-ltr?size=menu&state=disabled");
+}
+
+#historyMenuBack:-moz-locale-dir(rtl),
+#context-back:-moz-locale-dir(rtl) {
+ list-style-image: url("moz-icon://stock/gtk-go-back-rtl?size=menu");
+}
+
+#historyMenuBack[disabled]:-moz-locale-dir(rtl),
+#context-back[disabled]:-moz-locale-dir(rtl) {
+ list-style-image: url("moz-icon://stock/gtk-go-back-rtl?size=menu&state=disabled");
+}
+
+#historyMenuForward,
+#context-forward {
+ list-style-image: url("moz-icon://stock/gtk-go-forward-ltr?size=menu");
+}
+
+#historyMenuForward[disabled],
+#context-forward[disabled] {
+ list-style-image: url("moz-icon://stock/gtk-go-forward-ltr?size=menu&state=disabled");
+}
+
+#historyMenuForward:-moz-locale-dir(rtl),
+#context-forward:-moz-locale-dir(rtl) {
+ list-style-image: url("moz-icon://stock/gtk-go-forward-rtl?size=menu");
+}
+
+#historyMenuForward[disabled]:-moz-locale-dir(rtl),
+#context-forward[disabled]:-moz-locale-dir(rtl) {
+ list-style-image: url("moz-icon://stock/gtk-go-forward-rtl?size=menu&state=disabled");
+}
+
+#historyMenuHome {
+ list-style-image: url("moz-icon://stock/gtk-home?size=menu");
+}
+
+#appmenu_history,
+#appmenu_showAllHistory,
+#menu_showAllHistory,
+#HMB_showAllHistory {
+ list-style-image: url("chrome://browser/skin/Toolbar-small.png");
+ -moz-image-region: rect(0px 32px 16px 16px);
+}
+
+#appmenu_bookmarks,
+#appmenu_showAllBookmarks,
+#bookmarksShowAll,
+#BMB_bookmarksShowAll {
+ list-style-image: url("chrome://browser/skin/Toolbar-small.png");
+ -moz-image-region: rect(0px 48px 16px 32px);
+}
+
+#appmenu_subscribeToPage:not([disabled]),
+#appmenu_subscribeToPageMenu,
+#subscribeToPageMenuitem:not([disabled]),
+#subscribeToPageMenupopup,
+#BMB_subscribeToPageMenuitem:not([disabled]),
+#BMB_subscribeToPageMenupopup {
+ list-style-image: url("chrome://browser/skin/page-livemarks.png");
+}
+
+#appmenu_bookmarksToolbar,
+#bookmarksToolbarFolderMenu,
+#BMB_bookmarksToolbar {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png");
+}
+
+#appmenu_bookmarkThisPage,
+#menu_bookmarkThisPage,
+#BMB_bookmarkThisPage {
+ list-style-image: url("chrome://browser/skin/places/starPage.png");
+}
+
+#appmenu_unsortedBookmarks,
+#menu_unsortedBookmarks,
+#BMB_unsortedBookmarks {
+ list-style-image: url("chrome://browser/skin/places/unsortedBookmarks.png");
+}
+
+#appmenu_downloads,
+#menu_openDownloads {
+ list-style-image: url("chrome://browser/skin/Toolbar-small.png");
+ -moz-image-region: rect(0px 16px 16px 0px);
+}
+
+#appmenu_addons,
+#menu_openAddons {
+ list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric-16.png");
+}
+
+#menu_pageInfo,
+#context-viewinfo,
+#context-viewframeinfo {
+ list-style-image: url("moz-icon://stock/gtk-info?size=menu");
+}
+
+#appmenu_privateBrowsing,
+#appmenu_newPrivateWindow,
+#placesContext_open\:newprivatewindow,
+#privateBrowsingItem {
+ list-style-image: url("chrome://browser/skin/Privacy-16.png");
+}
+
+#placesContext_show\:info {
+ list-style-image: url("moz-icon://stock/gtk-properties?size=menu");
+}
+
+#appmenu_sanitizeHistory,
+#sanitizeItem,
+#HMB_sanitizeItem {
+ list-style-image: url("moz-icon://stock/gtk-clear?size=menu");
+}
+
+#appmenu_help,
+#appmenu_openHelp,
+#menu_openHelp {
+ list-style-image: url("moz-icon://stock/gtk-help?size=menu");
+}
+
+#appmenu_about,
+#aboutName {
+ list-style-image: url("moz-icon://stock/gtk-about?size=menu");
+}
+
+#javascriptConsole {
+ list-style-image: url("chrome://global/skin/console/console.png");
+}
+
+/* Primary toolbar buttons */
+.toolbarbutton-1:not([type="menu-button"]) {
+ -moz-box-orient: vertical;
+ min-width: 0;
+ list-style-image: url("chrome://browser/skin/Toolbar.png");
+}
+
+.toolbarbutton-1 > .toolbarbutton-icon {
+ -moz-margin-end: 0;
+}
+
+toolbar[mode="full"] .toolbarbutton-1:not([type="menu-button"]),
+toolbar[mode="full"] .toolbarbutton-1 > .toolbarbutton-menubutton-button {
+ min-width: 57px;
+}
+
+.toolbarbutton-1:not([type="menu-button"]),
+.toolbarbutton-1 > .toolbarbutton-menubutton-button {
+ padding: 5px;
+}
+
+.toolbarbutton-1[checked="true"] {
+ padding: 5px !important;
+}
+
+/* 24px primary toolbar buttons */
+#back-button {
+ list-style-image: url("moz-icon://stock/gtk-go-back-ltr?size=toolbar");
+}
+#back-button[disabled="true"] {
+ list-style-image: url("moz-icon://stock/gtk-go-back-ltr?size=toolbar&state=disabled");
+}
+
+#back-button:-moz-locale-dir(rtl) {
+ list-style-image: url("moz-icon://stock/gtk-go-back-rtl?size=toolbar");
+}
+#back-button[disabled="true"]:-moz-locale-dir(rtl) {
+ list-style-image: url("moz-icon://stock/gtk-go-back-rtl?size=toolbar&state=disabled");
+}
+
+#forward-button/*,
+ @conditionalForwardWithUrlbar@ > #forward-button */{
+ list-style-image: url("moz-icon://stock/gtk-go-forward-ltr?size=toolbar");
+}
+#forward-button:-moz-locale-dir(rtl)/*,
+@conditionalForwardWithUrlbar@ > #forward-button:-moz-locale-dir(rtl) */{
+ list-style-image: url("moz-icon://stock/gtk-go-forward-rtl?size=toolbar");
+}
+
+#forward-button[disabled] {
+ list-style-image: url("moz-icon://stock/gtk-go-forward-ltr?size=toolbar&state=disabled");
+}
+#forward-button[disabled]:-moz-locale-dir(rtl) {
+ list-style-image: url("moz-icon://stock/gtk-go-forward-rtl?size=toolbar&state=disabled");
+}
+
+/*@conditionalForwardWithUrlbar@:not([switchingtabs]) > #forward-button {
+ transition: @forwardTransitionLength@ ease-out;
+}
+
+@conditionalForwardWithUrlbar@ > #forward-button[disabled] {
+ transform: scale(0);
+ opacity: 0;
+ pointer-events: none;
+}*/
+
+#reload-button {
+ list-style-image: url("moz-icon://stock/gtk-refresh?size=toolbar");
+}
+#reload-button[disabled="true"] {
+ list-style-image: url("moz-icon://stock/gtk-refresh?size=toolbar&state=disabled");
+}
+
+#stop-button {
+ list-style-image: url("moz-icon://stock/gtk-stop?size=toolbar");
+}
+#stop-button[disabled="true"] {
+ list-style-image: url("moz-icon://stock/gtk-stop?size=toolbar&state=disabled");
+}
+
+#home-button {
+ list-style-image: url("moz-icon://stock/gtk-home?size=toolbar");
+}
+#home-button[disabled="true"] {
+ list-style-image: url("moz-icon://stock/gtk-home?size=toolbar&state=disabled");
+}
+
+#downloads-button {
+ -moz-image-region: rect(0px 24px 24px 0px);
+}
+
+#history-button,
+#history-menu-button {
+ -moz-image-region: rect(0px 48px 24px 24px);
+}
+
+#history-menu-button.toolbarbutton-1 {
+ -moz-box-orient: horizontal;
+}
+
+#bookmarks-button,
+#bookmarks-menu-button {
+ -moz-image-region: rect(0px 72px 24px 48px);
+}
+
+#bookmarks-menu-button.bookmark-item {
+ list-style-image: url("chrome://browser/skin/Toolbar-small.png");
+}
+
+#bookmarks-menu-button.toolbarbutton-1 {
+ -moz-box-orient: horizontal;
+}
+
+#print-button {
+ list-style-image: url("moz-icon://stock/gtk-print?size=toolbar");
+}
+#print-button[disabled="true"] {
+ list-style-image: url("moz-icon://stock/gtk-print?size=toolbar&state=disabled");
+}
+
+#new-tab-button {
+ -moz-image-region: rect(0px 96px 24px 72px);
+}
+
+#new-window-button {
+ -moz-image-region: rect(0px 120px 24px 96px);
+}
+
+#cut-button {
+ list-style-image: url("moz-icon://stock/gtk-cut?size=toolbar");
+}
+#cut-button[disabled="true"] {
+ list-style-image: url("moz-icon://stock/gtk-cut?size=toolbar&state=disabled");
+}
+
+#copy-button {
+ list-style-image: url("moz-icon://stock/gtk-copy?size=toolbar");
+}
+#copy-button[disabled="true"] {
+ list-style-image: url("moz-icon://stock/gtk-copy?size=toolbar&state=disabled");
+}
+
+#paste-button {
+ list-style-image: url("moz-icon://stock/gtk-paste?size=toolbar");
+}
+#paste-button[disabled="true"] {
+ list-style-image: url("moz-icon://stock/gtk-paste?size=toolbar&state=disabled");
+}
+
+#fullscreen-button {
+ list-style-image: url("moz-icon://stock/gtk-fullscreen?size=toolbar");
+}
+
+#zoom-out-button {
+ list-style-image: url("moz-icon://stock/gtk-zoom-out?size=toolbar");
+}
+
+#zoom-in-button {
+ list-style-image: url("moz-icon://stock/gtk-zoom-in?size=toolbar");
+}
+
+#sync-button {
+ -moz-image-region: rect(0px 144px 24px 120px);
+}
+#sync-button[status="active"] {
+ list-style-image: url("chrome://browser/skin/sync-24-throbber.png");
+ -moz-image-region: rect(0px 24px 24px 0px);
+}
+
+#feed-button {
+ -moz-image-region: rect(0px 168px 24px 144px);
+}
+
+#feed-button[disabled] > .toolbarbutton-icon {
+ opacity: .4;
+}
+
+%ifdef MOZ_WEBRTC
+#webrtc-status-button {
+ -moz-image-region: rect(0px 192px 24px 168px);
+}
+%endif
+
+/* 16px primary toolbar buttons */
+toolbar[iconsize="small"] .toolbarbutton-1:not([type="menu-button"]) {
+ -moz-box-orient: vertical;
+ min-width: 0;
+ list-style-image: url("chrome://browser/skin/Toolbar-small.png");
+}
+
+toolbar[iconsize="small"] .toolbarbutton-1[type="menu-button"] {
+ border: 0 !important;
+}
+
+toolbar[iconsize="small"] #back-button {
+ list-style-image: url("moz-icon://stock/gtk-go-back-ltr?size=menu");
+}
+.unified-nav-back[_moz-menuactive] {
+ list-style-image: url("moz-icon://stock/gtk-go-back-ltr?size=menu") !important;
+}
+toolbar[iconsize="small"] #back-button[disabled="true"] {
+ list-style-image: url("moz-icon://stock/gtk-go-back-ltr?size=menu&state=disabled");
+}
+
+toolbar[iconsize="small"] #back-button:-moz-locale-dir(rtl) {
+ list-style-image: url("moz-icon://stock/gtk-go-back-rtl?size=menu");
+}
+.unified-nav-back[_moz-menuactive]:-moz-locale-dir(rtl) {
+ list-style-image: url("moz-icon://stock/gtk-go-back-rtl?size=menu") !important;
+}
+toolbar[iconsize="small"] #back-button[disabled="true"]:-moz-locale-dir(rtl) {
+ list-style-image: url("moz-icon://stock/gtk-go-back-rtl?size=menu&state=disabled");
+}
+
+toolbar[iconsize=small] #forward-button,
+@conditionalForwardWithUrlbar_small@ > #forward-button {
+ list-style-image: url("moz-icon://stock/gtk-go-forward-ltr?size=menu");
+}
+.unified-nav-forward[_moz-menuactive] {
+ list-style-image: url("moz-icon://stock/gtk-go-forward-ltr?size=menu") !important;
+}
+toolbar[iconsize=small] #forward-button[disabled] {
+ list-style-image: url("moz-icon://stock/gtk-go-forward-ltr?size=menu&state=disabled");
+}
+
+toolbar[iconsize=small] #forward-button:-moz-locale-dir(rtl),
+@conditionalForwardWithUrlbar_small@ > #forward-button:-moz-locale-dir(rtl) {
+ list-style-image: url("moz-icon://stock/gtk-go-forward-rtl?size=menu");
+}
+.unified-nav-forward[_moz-menuactive]:-moz-locale-dir(rtl) {
+ list-style-image: url("moz-icon://stock/gtk-go-forward-rtl?size=menu") !important;
+}
+toolbar[iconsize=small] #forward-button[disabled]:-moz-locale-dir(rtl) {
+ list-style-image: url("moz-icon://stock/gtk-go-forward-rtl?size=menu&state=disabled");
+}
+
+toolbar[iconsize="small"] #stop-button {
+ list-style-image: url("moz-icon://stock/gtk-stop?size=menu");
+}
+toolbar[iconsize="small"] #stop-button[disabled="true"] {
+ list-style-image: url("moz-icon://stock/gtk-stop?size=menu&state=disabled");
+}
+
+toolbar[iconsize="small"] #reload-button {
+ list-style-image: url("moz-icon://stock/gtk-refresh?size=menu");
+}
+toolbar[iconsize="small"] #reload-button[disabled="true"] {
+ list-style-image: url("moz-icon://stock/gtk-refresh?size=menu&state=disabled");
+}
+
+toolbar[iconsize="small"] #home-button,
+#home-button.bookmark-item {
+ list-style-image: url("moz-icon://stock/gtk-home?size=menu");
+}
+toolbar[iconsize="small"] #home-button[disabled="true"],
+#home-button.bookmark-item[disabled="true"] {
+ list-style-image: url("moz-icon://stock/gtk-home?size=menu&state=disabled");
+}
+
+toolbar[iconsize="small"] #downloads-button {
+ -moz-image-region: rect(0px 16px 16px 0px);
+}
+
+%ifdef MOZ_WEBRTC
+toolbar[iconsize="small"] #webrtc-status-button /* temporary placeholder (bug 824825) */,
+%endif
+toolbar[iconsize="small"] #history-button,
+toolbar[iconsize="small"] #history-menu-button {
+ -moz-image-region: rect(0px 32px 16px 16px);
+}
+
+toolbar[iconsize="small"] #bookmarks-button,
+toolbar[iconsize="small"] #bookmarks-menu-button,
+#bookmarks-menu-button.bookmark-item {
+ -moz-image-region: rect(0px 48px 16px 32px);
+}
+
+toolbar[iconsize="small"] #print-button {
+ list-style-image: url("moz-icon://stock/gtk-print?size=menu");
+}
+toolbar[iconsize="small"] #print-button[disabled="true"] {
+ list-style-image: url("moz-icon://stock/gtk-print?size=menu&state=disabled");
+}
+
+toolbar[iconsize="small"] #new-tab-button {
+ -moz-image-region: rect(0px 64px 16px 48px);
+}
+
+toolbar[iconsize="small"] #new-window-button {
+ -moz-image-region: rect(0px 80px 16px 64px);
+}
+
+toolbar[iconsize="small"] #cut-button {
+ list-style-image: url("moz-icon://stock/gtk-cut?size=menu");
+}
+toolbar[iconsize="small"] #cut-button[disabled="true"] {
+ list-style-image: url("moz-icon://stock/gtk-cut?size=menu&state=disabled");
+}
+
+toolbar[iconsize="small"] #copy-button {
+ list-style-image: url("moz-icon://stock/gtk-copy?size=menu");
+}
+toolbar[iconsize="small"] #copy-button[disabled="true"] {
+ list-style-image: url("moz-icon://stock/gtk-copy?size=menu&state=disabled");
+}
+
+toolbar[iconsize="small"] #paste-button {
+ list-style-image: url("moz-icon://stock/gtk-paste?size=menu");
+}
+toolbar[iconsize="small"] #paste-button[disabled="true"] {
+ list-style-image: url("moz-icon://stock/gtk-paste?size=menu&state=disabled");
+}
+
+toolbar[iconsize="small"] #fullscreen-button {
+ list-style-image: url("moz-icon://stock/gtk-fullscreen?size=menu");
+}
+
+toolbar[iconsize="small"] #zoom-out-button {
+ list-style-image: url("moz-icon://stock/gtk-zoom-out?size=menu");
+}
+
+toolbar[iconsize="small"] #zoom-in-button {
+ list-style-image: url("moz-icon://stock/gtk-zoom-in?size=menu");
+}
+
+toolbar[iconsize="small"] #sync-button {
+ -moz-image-region: rect(0px 96px 16px 80px);
+}
+toolbar[iconsize="small"] #sync-button[status="active"] {
+ list-style-image: url("chrome://browser/skin/sync-16-throbber.png");
+ -moz-image-region: rect(0px 16px 16px 0px);
+}
+
+toolbar[iconsize="small"] #feed-button {
+ -moz-image-region: rect(0px 112px 16px 96px);
+}
+
+%ifdef MOZ_WEBRTC
+toolbar[iconsize="small"] #webrtc-status-button {
+ -moz-image-region: rect(0px 128px 16px 112px);
+}
+%endif
+
+/* Fullscreen window controls */
+#window-controls {
+ -moz-box-align: start;
+ -moz-margin-start: 10px;
+}
+
+#minimize-button {
+ list-style-image: url("chrome://global/skin/icons/Minimize.gif");
+}
+#restore-button {
+ list-style-image: url("chrome://global/skin/icons/Restore.gif");
+}
+#close-button {
+ list-style-image: url("chrome://global/skin/icons/Close.gif");
+}
+
+/* Location bar */
+#urlbar {
+ width: 7em;
+ -moz-appearance: textfield;
+ padding: 0;
+}
+
+.urlbar-textbox-container {
+ -moz-appearance: none;
+ -moz-box-align: stretch;
+}
+
+.urlbar-input-box {
+ -moz-margin-start: 0;
+ min-width: 4em;
+}
+
+.urlbar-history-dropmarker {
+ -moz-appearance: toolbarbutton-dropdown;
+}
+
+#urlbar-container {
+ -moz-box-orient: horizontal;
+ -moz-box-align: stretch;
+}
+
+#urlbar-icons {
+ -moz-box-align: center;
+}
+
+.urlbar-icon {
+ cursor: pointer;
+ padding: 0 3px;
+}
+
+#urlbar-search-splitter {
+ -moz-appearance: none;
+ width: 8px;
+ -moz-margin-start: -4px;
+}
+
+#urlbar-search-splitter + #urlbar-container > #urlbar ,
+#urlbar-search-splitter + #search-container > #searchbar > .searchbar-textbox {
+ -moz-margin-start: 0;
+}
+
+#urlbar-display-box {
+ margin-top: -1px;
+ margin-bottom: -1px;
+ -moz-border-end: 1px solid #AAA;
+ -moz-margin-end: 3px;
+}
+
+#urlbar-display {
+ margin-top: 0;
+ margin-bottom: 0;
+ -moz-margin-start: 0;
+ color: GrayText;
+}
+
+/* Favicon */
+#page-proxy-favicon {
+ width: 16px;
+ height: 16px;
+ margin-top: 2px;
+ margin-bottom: 2px;
+ -moz-margin-start: 4px;
+ -moz-margin-end: 3px;
+ list-style-image: url(chrome://browser/skin/identity-icons-generic.png);
+ -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+/* Since we already have a padlock, always use the generic icon until the favicon loads
+.verifiedDomain > #page-proxy-favicon[pageproxystate="valid"] {
+ list-style-image: url(chrome://browser/skin/identity-icons-https.png);
+}
+
+.verifiedIdentity > #page-proxy-favicon[pageproxystate="valid"] {
+ list-style-image: url(chrome://browser/skin/identity-icons-https-ev.png);
+}
+
+.mixedActiveContent > #page-proxy-favicon[pageproxystate="valid"] {
+ list-style-image: url(chrome://browser/skin/identity-icons-https-mixed-active.png);
+}
+*/
+
+#identity-box:hover > #page-proxy-favicon {
+ -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
+#identity-box:hover:active > #page-proxy-favicon,
+#identity-box[open=true] > #page-proxy-favicon {
+ -moz-image-region: rect(0, 48px, 16px, 32px);
+}
+
+#page-proxy-favicon[pageproxystate="invalid"] {
+ opacity: 0.3;
+}
+
+/* Identity indicator */
+#identity-box {
+ padding: 1px;
+ margin: -1px;
+ -moz-margin-end: 0;
+ font-size: .9em;
+}
+
+#identity-box:-moz-locale-dir(ltr) {
+ border-top-left-radius: 2.5px;
+ border-bottom-left-radius: 2.5px;
+}
+
+#identity-box:-moz-locale-dir(rtl) {
+ border-top-right-radius: 2.5px;
+ border-bottom-right-radius: 2.5px;
+}
+
+#identity-box:-moz-focusring {
+ outline: 1px dotted #000;
+ outline-offset: -3px;
+}
+
+#identity-icon-labels {
+ -moz-padding-start: 2px;
+ -moz-padding-end: 5px;
+}
+
+#urlbar[pageproxystate="valid"] > #identity-box.verifiedIdentity {
+ color: rgb(95,127,67);
+ -moz-margin-end: 4px;
+ background-image: linear-gradient(rgba(226,246,208,1),
+ rgba(203,235,177,1));
+ background-position: right;
+ background-repeat: no-repeat;
+}
+
+#urlbar[pageproxystate="valid"] > #identity-box.verifiedDomain {
+ color: rgb(51,87,137);
+ -moz-margin-end: 4px;
+ background-image: linear-gradient(rgba(220,231,245,1),
+ rgba(207,221,242,1));
+ background-position: right;
+ background-repeat: no-repeat;
+}
+
+#identity-box.verifiedIdentity:-moz-locale-dir(rtl) {
+ background-position: left;
+}
+
+/* Address bar shading for SSL */
+
+#urlbar[https_color="all"][security_level="broken"],
+#urlbar[https_color="all"][security_level="low"] {
+ box-shadow: inset 0 0 4px rgb(168,0,0);
+}
+
+#urlbar[https_color="all"][security_level="mixed"],
+#urlbar[https_color="secure-mixed"][security_level="mixed"] {
+ box-shadow: inset 0 0 4px rgb(168,79,0);
+}
+
+#urlbar[https_color="all"][security_level="high"],
+#urlbar[https_color="secure-mixed"][security_level="high"],
+#urlbar[https_color="secure-only"][security_level="high"] {
+ box-shadow: inset 0 0 4px rgb(0,79,168);
+}
+
+#urlbar[https_color="all"][security_level="ev"],
+#urlbar[https_color="secure-mixed"][security_level="ev"],
+#urlbar[https_color="secure-only"][security_level="ev"] {
+ box-shadow: inset 0 0 4px rgb(0,168,0);
+}
+
+#urlbar[https_color="all"]:-moz-lwtheme-darktext,
+#urlbar[https_color="secure-mixed"]:-moz-lwtheme-darktext,
+#urlbar[https_color="secure-only"]:-moz-lwtheme-darktext {
+ box-shadow: inset 0 0 2px;
+}
+
+/* Identity popup icons */
+#identity-popup-icon {
+ height: 64px;
+ width: 64px;
+ padding: 0;
+ list-style-image: url("chrome://browser/skin/identity.png");
+ -moz-image-region: rect(0px, 64px, 64px, 0px);
+}
+
+#identity-popup.verifiedDomain > #identity-popup-container > #identity-popup-icon {
+ -moz-image-region: rect(64px, 64px, 128px, 0px);
+}
+
+#identity-popup.verifiedIdentity > #identity-popup-container > #identity-popup-icon {
+ -moz-image-region: rect(128px, 64px, 192px, 0px);
+}
+
+/* Identity popup body text */
+.identity-popup-description {
+ white-space: pre-wrap;
+ -moz-padding-start: 15px;
+ margin: 2px 0 4px;
+}
+
+.identity-popup-label {
+ white-space: pre-wrap;
+ -moz-padding-start: 15px;
+ margin: 0;
+}
+
+#identity-popup-content-host ,
+#identity-popup-content-owner {
+ font-weight: bold;
+ max-width: 300px;
+}
+
+#identity-popup-content-host ,
+#identity-popup-content-box.verifiedIdentity > #identity-popup-content-owner {
+ font-size: 140%;
+}
+
+#identity-popup-content-owner {
+ margin-bottom: 0 !important;
+}
+
+#identity-popup-content-verifier {
+ margin: 4px 0 2px;
+}
+
+#identity-popup-content-box.verifiedIdentity > #identity-popup-encryption ,
+#identity-popup-content-box.verifiedDomain > #identity-popup-encryption {
+ margin-top: 10px;
+ -moz-margin-start: -18px;
+}
+
+#identity-popup-content-box.verifiedIdentity > #identity-popup-encryption > vbox > #identity-popup-encryption-icon ,
+#identity-popup-content-box.verifiedDomain > #identity-popup-encryption > vbox > #identity-popup-encryption-icon {
+ list-style-image: url("chrome://browser/skin/Secure.png");
+}
+
+/* Identity popup bounding box */
+#identity-popup-container {
+ min-width: 280px;
+}
+
+/* Notification popup */
+#notification-popup {
+ min-width: 280px;
+}
+
+.popup-notification-icon {
+ width: 64px;
+ height: 64px;
+ -moz-margin-end: 10px;
+}
+
+.popup-notification-icon[popupid="geolocation"] {
+ list-style-image: url(chrome://browser/skin/Geolocation-64.png);
+}
+
+.popup-notification-icon[popupid="push"] {
+ list-style-image: url(chrome://browser/skin/Push-64.png);
+}
+
+.popup-notification-icon[popupid="xpinstall-disabled"],
+.popup-notification-icon[popupid="addon-progress"],
+.popup-notification-icon[popupid="addon-install-cancelled"],
+.popup-notification-icon[popupid="addon-install-blocked"],
+.popup-notification-icon[popupid="addon-install-origin-blocked"],
+.popup-notification-icon[popupid="addon-install-failed"],
+.popup-notification-icon[popupid="addon-install-complete"] {
+ list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric.png);
+ width: 32px;
+ height: 32px;
+}
+
+.popup-notification-icon[popupid="click-to-play-plugins"] {
+ list-style-image: url(chrome://mozapps/skin/plugins/pluginBlocked-64.png);
+}
+
+.popup-notification-icon[popupid="web-notifications"] {
+ list-style-image: url(chrome://browser/skin/notification-64.png);
+}
+
+.addon-progress-description {
+ width: 350px;
+ max-width: 350px;
+}
+
+.popup-progress-label,
+.popup-progress-meter {
+ -moz-margin-start: 0;
+ -moz-margin-end: 0;
+}
+
+.popup-progress-cancel {
+ -moz-appearance: none;
+ background: transparent;
+ border: none;
+ padding: 0;
+ margin: 0;
+ -moz-margin-start: 5px;
+ min-height: 0;
+ min-width: 0;
+ list-style-image: url("moz-icon://stock/gtk-cancel?size=menu");
+}
+
+.popup-notification-icon[popupid="indexedDB-permissions-prompt"],
+.popup-notification-icon[popupid="indexedDB-quota-prompt"],
+.popup-notification-icon[popupid*="offline-app-requested"],
+.popup-notification-icon[popupid="offline-app-usage"] {
+ list-style-image: url(chrome://global/skin/icons/question-64.png);
+}
+
+.popup-notification-icon[popupid="password"] {
+ list-style-image: url(chrome://mozapps/skin/passwordmgr/key-64.png);
+}
+
+.popup-notification-icon[popupid="mixed-content-blocked"] {
+ list-style-image: url(chrome://browser/skin/mixed-content-blocked-64.png);
+}
+
+%ifdef MOZ_WEBRTC
+.popup-notification-icon[popupid="webRTC-sharingDevices"],
+.popup-notification-icon[popupid="webRTC-shareDevices"] {
+ list-style-image: url(chrome://browser/skin/webRTC-shareDevice-64.png);
+}
+%endif
+
+.popup-notification-icon[popupid="pointerLock"] {
+ list-style-image: url(chrome://browser/skin/pointerLock-64.png);
+}
+
+/* Notification icon box */
+#notification-popup-box {
+ position: relative;
+ background-color: #fff;
+ background-clip: padding-box;
+ padding-left: 4px;
+ border-radius: 2.5px 0 0 2.5px;
+ border-width: 0 8px 0 0;
+ border-style: solid;
+ border-image: url("chrome://browser/skin/urlbar-arrow.png") 0 8 0 0 fill;
+ -moz-margin-end: -8px;
+ margin-top: -1px;
+ margin-bottom: -1px;
+}
+
+#notification-popup-box:not([hidden]) + #identity-box {
+ -moz-padding-start: 10px;
+ border-radius: 0;
+}
+
+#notification-popup-box:-moz-locale-dir(rtl),
+.notification-anchor-icon:-moz-locale-dir(rtl) {
+ transform: scaleX(-1);
+}
+
+.notification-anchor-icon {
+ width: 16px;
+ height: 16px;
+ margin: 0 2px;
+}
+
+.notification-anchor-icon:-moz-focusring {
+ outline: 1px dotted -moz-DialogText;
+}
+
+.default-notification-icon,
+#default-notification-icon {
+ list-style-image: url(chrome://global/skin/icons/information-16.png);
+}
+
+.geo-notification-icon,
+#geo-notification-icon {
+ list-style-image: url(chrome://browser/skin/Geolocation-16.png);
+}
+
+#addons-notification-icon {
+ list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric-16.png);
+}
+
+.indexedDB-notification-icon,
+#indexedDB-notification-icon {
+ list-style-image: url(chrome://global/skin/icons/question-16.png);
+}
+
+#password-notification-icon {
+ list-style-image: url(chrome://mozapps/skin/passwordmgr/key-16.png);
+}
+
+#plugins-notification-icon {
+ list-style-image: url(chrome://browser/skin/notification-pluginNormal.png);
+}
+
+#alert-plugins-notification-icon {
+ list-style-image: url(chrome://browser/skin/notification-pluginAlert.png);
+}
+
+#blocked-plugins-notification-icon {
+ list-style-image: url(chrome://browser/skin/notification-pluginBlocked.png);
+}
+
+#plugins-notification-icon,
+#alert-plugins-notification-icon,
+#blocked-plugins-notification-icon {
+ -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+#plugins-notification-icon:hover,
+#alert-plugins-notification-icon:hover,
+#blocked-plugins-notification-icon:hover {
+ -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
+#plugins-notification-icon:active,
+#alert-plugins-notification-icon:active,
+#blocked-plugins-notification-icon:active {
+ -moz-image-region: rect(0, 48px, 16px, 32px);
+}
+
+#notification-popup-box[hidden] {
+ /* Override display:none to make the pluginBlockedNotification animation work
+ when showing the notification repeatedly. */
+ display: -moz-box;
+ visibility: collapse;
+}
+
+#blocked-plugins-notification-icon[showing] {
+ animation: pluginBlockedNotification 500ms ease 0s 5 alternate both;
+}
+
+@keyframes pluginBlockedNotification {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
+.mixed-content-blocked-notification-icon,
+#mixed-content-blocked-notification-icon {
+ list-style-image: url(chrome://browser/skin/mixed-content-blocked-16.png);
+}
+
+%ifdef MOZ_WEBRTC
+.webRTC-shareDevices-notification-icon,
+#webRTC-shareDevices-notification-icon {
+ list-style-image: url(chrome://browser/skin/webRTC-shareDevice-16.png);
+}
+
+.webRTC-sharingDevices-notification-icon,
+#webRTC-sharingDevices-notification-icon {
+ list-style-image: url(chrome://browser/skin/webRTC-sharingDevice-16.png);
+}
+%endif
+
+.web-notifications-notification-icon,
+#web-notifications-notification-icon {
+ list-style-image: url(chrome://browser/skin/web-notifications-tray.svg);
+ -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+.web-notifications-notification-icon:hover,
+#web-notifications-notification-icon:hover {
+ -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
+.web-notifications-notification-icon:hover:active,
+#web-notifications-notification-icon:hover:active {
+ -moz-image-region: rect(0, 48px, 16px, 32px);
+}
+
+#pointerLock-notification-icon {
+ list-style-image: url(chrome://browser/skin/pointerLock-16.png);
+}
+#pointerLock-cancel {
+ margin: 0px;
+}
+
+/* Pale Moon: Feed icon */
+#ub-feed-button,
+#ub-feed-button > .button-box,
+#ub-feed-button:hover:active > .button-box {
+ padding: 0px;
+ margin: 0px;
+ border: 0px;
+ background-color: transparent !important;
+}
+
+#ub-feed-button {
+ -moz-appearance: none;
+ min-width: 0px;
+ margin-right: 1px !important;
+ list-style-image: url("chrome://browser/skin/page-livemarks.png");
+}
+
+
+#treecolAutoCompleteImage {
+ max-width : 36px;
+}
+
+.ac-result-type-bookmark,
+.autocomplete-treebody::-moz-tree-image(bookmark, treecolAutoCompleteImage) {
+ list-style-image: url("chrome://browser/skin/places/star-icons.png");
+ -moz-image-region: rect(0px 32px 16px 16px);
+ width: 16px;
+ height: 16px;
+}
+
+.ac-result-type-keyword,
+.autocomplete-treebody::-moz-tree-image(keyword, treecolAutoCompleteImage) {
+ list-style-image: url(moz-icon://stock/gtk-find?size=menu);
+ width: 16px;
+ height: 16px;
+}
+
+.ac-result-type-tag,
+.autocomplete-treebody::-moz-tree-image(tag, treecolAutoCompleteImage) {
+ list-style-image: url("chrome://browser/skin/places/tag.png");
+ width: 16px;
+ height: 16px;
+}
+
+.ac-comment {
+ font-size: 1.05em;
+}
+
+.ac-extra > .ac-comment {
+ font-size: inherit;
+}
+
+.ac-url-text,
+.ac-action-text {
+ color: -moz-nativehyperlinktext;
+ font-size: 0.9em;
+}
+
+richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-icon {
+ list-style-image: url("chrome://browser/skin/actionicon-tab.png");
+ padding: 0 3px;
+}
+
+.autocomplete-treebody::-moz-tree-cell-text(treecolAutoCompleteComment) {
+ color: GrayText;
+}
+
+.ac-comment[selected="true"],
+.ac-url-text[selected="true"],
+.ac-action-text[selected="true"] {
+ color: inherit !important;
+}
+
+.autocomplete-treebody::-moz-tree-cell-text(suggesthint, treecolAutoCompleteComment),
+.autocomplete-treebody::-moz-tree-cell-text(suggestfirst, treecolAutoCompleteComment) {
+ color: GrayText;
+ font-size: smaller;
+}
+
+.autocomplete-treebody::-moz-tree-cell(suggesthint) {
+ border-top: 1px solid GrayText;
+}
+
+/* Combined go/reload/stop button in location bar */
+
+#go-button {
+ padding-top: 2px;
+ padding-bottom: 2px;
+}
+
+#urlbar > toolbarbutton {
+ -moz-appearance: none;
+ padding: 0;
+ border: none;
+ cursor: pointer;
+ width: 22px;
+}
+
+#go-button,
+#urlbar-go-button {
+ list-style-image: url("chrome://browser/skin/Go-arrow.png");
+}
+
+#go-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
+#urlbar-go-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+ transform: scaleX(-1);
+}
+
+#urlbar-reload-button {
+ list-style-image: url("moz-icon://stock/gtk-refresh?size=menu");
+}
+
+#urlbar-stop-button {
+ list-style-image: url("moz-icon://stock/gtk-stop?size=menu");
+}
+
+/* Popup blocker button */
+#page-report-button {
+ list-style-image: url("chrome://browser/skin/Info.png");
+}
+
+/* Star button */
+#star-button {
+ list-style-image: url("chrome://browser/skin/places/starPage.png");
+}
+
+#star-button[starred="true"] {
+ list-style-image: url("chrome://browser/skin/places/pageStarred.png");
+}
+
+/* bookmarks menu-button */
+
+#bookmarks-menu-button[disabled] > .toolbarbutton-icon,
+#bookmarks-menu-button[disabled] > .toolbarbutton-menu-dropmarker,
+#bookmarks-menu-button[disabled] > .toolbarbutton-menubutton-dropmarker,
+#bookmarks-menu-button[disabled] > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
+#bookmarks-menu-button > .toolbarbutton-menubutton-button[disabled] > .toolbarbutton-icon {
+ opacity: .4;
+}
+
+/* history menu-button */
+
+#history-menu-button[disabled] > .toolbarbutton-icon,
+#history-menu-button[disabled] > .toolbarbutton-menu-dropmarker,
+#history-menu-button[disabled] > .toolbarbutton-menubutton-dropmarker,
+#history-menu-button[disabled] > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
+#history-menu-button > .toolbarbutton-menubutton-button[disabled] > .toolbarbutton-icon {
+ opacity: .4;
+}
+
+/* Bookmarking panel */
+#editBookmarkPanelStarIcon {
+ list-style-image: url("chrome://browser/skin/places/starred48.png");
+ width: 48px;
+ height: 48px;
+}
+
+#editBookmarkPanelStarIcon[unstarred] {
+ list-style-image: url("chrome://browser/skin/places/unstarred48.png");
+}
+
+#editBookmarkPanelTitle {
+ font-size: 130%;
+}
+
+#editBookmarkPanelHeader,
+#editBookmarkPanelContent {
+ margin-bottom: .5em;
+}
+
+/* Implements editBookmarkPanel resizing on folderTree un-collapse. */
+#editBMPanel_folderTree {
+ min-width: 27em;
+}
+
+/* Content area */
+#sidebar {
+ background-color: Window;
+}
+
+/* Throbber */
+#navigator-throbber {
+ width: 16px;
+ min-height: 16px;
+ margin: 0 3px;
+}
+
+#navigator-throbber[busy="true"] {
+ list-style-image: url("chrome://global/skin/icons/loading_16.png");
+}
+
+#navigator-throbber,
+#wrapper-navigator-throbber > #navigator-throbber {
+ list-style-image: url("chrome://global/skin/icons/notloading_16.png");
+}
+
+/* Tabstrip */
+
+#TabsToolbar {
+ min-height: 0;
+ padding: 0;
+}
+
+#TabsToolbar[tabsontop=true]:not(:-moz-lwtheme) {
+ -moz-appearance: menubar;
+ color: -moz-menubartext;
+ box-shadow: 0 -1px 0 rgba(0,0,0,.1) inset;
+}
+
+#TabsToolbar[tabsontop=true]:not(:-moz-lwtheme):-moz-system-metric(menubar-drag) {
+ -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbar-drag");
+}
+
+#TabsToolbar[tabsontop=false] {
+ background-image: linear-gradient(to top, rgba(0,0,0,.3) 1px, rgba(0,0,0,.05) 1px, transparent 50%);
+}
+
+/* When the tab bar is collapsed, show a 1px border in its place. */
+#TabsToolbar[tabsontop="false"][collapsed="true"]:not([customizing="true"]) {
+ visibility: visible;
+ height: 1px;
+ border-bottom-width: 1px;
+ /* !important here to override border-style: none on the toolbar */
+ border-bottom-style: solid !important;
+ border-bottom-color: ThreeDShadow;
+ overflow: hidden;
+}
+
+.tabbrowser-tab,
+.tabs-newtab-button {
+ position: static;
+ -moz-appearance: none;
+ background: linear-gradient(hsla(0,0%,100%,.2), hsla(0,0%,45%,.2) 2px, hsla(0,0%,32%,.2) 80%);
+ background-origin: border-box;
+ background-position: 1px 2px;
+ background-size: 100% calc(100% - 2px);
+ background-repeat: no-repeat;
+ color: inherit;
+ margin: 0;
+ padding: 0;
+ border-width: 4px 5px 3px 6px;
+ border-style: solid;
+ border-image: url(tabbrowser/tab.png) 4 5 3 6 fill repeat stretch;
+ border-radius: 10px 8px 0 0;
+ min-height: 25px; /* reserve space for the sometimes hidden close button */
+}
+
+.tabbrowser-tab:hover,
+.tabs-newtab-button:hover {
+ background-image: linear-gradient(hsla(0,0%,100%,.6), hsla(0,0%,100%,.2) 4px, hsla(0,0%,75%,.2) 80%);
+}
+
+.tabbrowser-tab[selected="true"] {
+ background-image: linear-gradient(@selectedTabHighlight@, @toolbarHighlight@ 32%),
+ linear-gradient(-moz-dialog, -moz-dialog);
+}
+
+.tabbrowser-tab[selected="true"]:not(:-moz-lwtheme) {
+ color: -moz-dialogtext;
+}
+
+#main-window[tabsontop=false]:not([disablechrome]) .tabbrowser-tab[selected=true]:not(:-moz-lwtheme) {
+ background-image: linear-gradient(to top, rgba(0,0,0,.3) 1px, transparent 1px),
+ linear-gradient(@selectedTabHighlight@, @toolbarHighlight@ 32%),
+ linear-gradient(-moz-dialog, -moz-dialog);
+}
+
+
+.tabbrowser-tab:-moz-lwtheme:not([selected="true"]) {
+ opacity: 0.9;
+}
+
+.tabbrowser-tab[selected="true"]:-moz-lwtheme {
+ background-image: linear-gradient(rgba(255,255,255,.6), rgba(255,255,255,.8) 50%);
+}
+
+.tabbrowser-tab[selected="true"]:-moz-lwtheme-brighttext {
+ background-image: linear-gradient(rgba(128,128,128,.9), rgba(32,32,32,.9) 50%, rgba(32,32,32,.9) 80%, rgba(32,32,32,.8) 100%);
+}
+
+.tabbrowser-tab:-moz-lwtheme-brighttext:not([selected="true"]),
+.tabs-newtab-button:-moz-lwtheme-brighttext {
+ background-image: linear-gradient(hsla(0,0%,25%,.4), hsla(0,0%,15%,.6) 80%);
+}
+
+.tabbrowser-tab:-moz-lwtheme-brighttext:not([selected="true"]):hover,
+.tabs-newtab-button:-moz-lwtheme-brighttext:hover {
+ background-image: linear-gradient(hsla(0,0%,60%,.4), hsla(0,0%,10%,.8) 80%);
+}
+
+.tabbrowser-tab:-moz-lwtheme-darktext:not([selected="true"]),
+.tabs-newtab-button:-moz-lwtheme-darktext {
+ background-image: linear-gradient(hsla(0,0%,75%,.4), hsla(0,0%,85%,.6) 80%);
+}
+
+.tabbrowser-tab:-moz-lwtheme-darktext:not([selected="true"]):hover,
+.tabs-newtab-button:-moz-lwtheme-darktext:hover {
+ background-image: linear-gradient(hsla(0,0%,60%,.4), hsla(0,0%,90%,.8) 80%);
+}
+
+.tabbrowser-tab[pinned][titlechanged]:not([selected="true"]) {
+ background-image: radial-gradient(circle farthest-corner at 50% 3px, rgba(233,242,252,1) 3%, rgba(172,206,255,.75) 40%, rgba(87,151,201,.5) 80%, rgba(87,151,201,0));
+}
+.tabbrowser-tab[pinned][titlechanged]:not([selected="true"]):hover {
+ background-image: linear-gradient(hsla(0,0%,100%,.8), hsla(0,0%,100%,.6) 2px, hsla(0,0%,75%,.2) 80%),
+ radial-gradient(circle farthest-corner at 50% 3px, rgba(233,242,252,1) 3%, rgba(172,206,255,.75) 40%, rgba(87,151,201,.5) 80%, rgba(87,151,201,0));
+}
+
+#tabbrowser-tabs[positionpinnedtabs] > .tabbrowser-tab > .tab-stack > .tab-content[pinned] {
+ min-height: 18px; /* corresponds to the max. height of non-textual tab contents, i.e. the tab close button */
+}
+
+.tabbrowser-tab:focus > .tab-stack {
+ outline: 1px dotted;
+}
+
+.tab-throbber,
+.tab-icon-image {
+ width: 16px;
+ height: 16px;
+ -moz-margin-end: 3px;
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+
+.tab-throbber {
+ list-style-image: url("chrome://browser/skin/tabbrowser/connecting.png");
+}
+
+.tab-throbber[progress] {
+ list-style-image: url("chrome://browser/skin/tabbrowser/loading.png");
+}
+
+.tab-throbber[pinned],
+.tab-icon-image[pinned],
+.tabs-newtab-button > .toolbarbutton-icon {
+ -moz-margin-start: 2px;
+ -moz-margin-end: 2px;
+}
+
+#context_reloadTab {
+ list-style-image: url("moz-icon://stock/gtk-refresh?size=menu");
+}
+
+#context_closeOtherTabs {
+ list-style-image: url("moz-icon://stock/gtk-clear?size=menu");
+}
+
+#context_closeOtherTabs[disabled] {
+ list-style-image: url("moz-icon://stock/gtk-clear?size=menu&state=disabled");
+}
+
+#context_undoCloseTab {
+ list-style-image: url("moz-icon://stock/gtk-undelete?size=menu");
+}
+
+#context_closeTab {
+ list-style-image: url("moz-icon://stock/gtk-close?size=menu");
+}
+
+/* Tab drag and drop */
+.tab-drop-indicator {
+ list-style-image: url(chrome://browser/skin/tabbrowser/tabDragIndicator.png);
+ margin-bottom: -11px;
+}
+
+/* In-tab close button */
+.tab-close-button > .toolbarbutton-icon {
+ /* XXX Buttons have padding in widget/ that we don't want here but can't override with good CSS, so we must
+ use evil CSS to give the impression of smaller content */
+ margin: -4px;
+}
+
+.tab-close-button {
+ padding: 0;
+ margin-top: -1px;
+ margin-bottom: -1px;
+ -moz-margin-end: -1px;
+}
+
+/* Tab sound indicator */
+.tab-icon-sound {
+ -moz-margin-start: 4px;
+ width: 16px;
+ height: 16px;
+ padding: 0;
+}
+
+.allTabs-endimage[soundplaying],
+.tab-icon-sound[soundplaying] {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio");
+}
+
+.allTabs-endimage[muted],
+.tab-icon-sound[muted] {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-muted");
+}
+
+.allTabs-endimage[blocked],
+.tab-icon-sound[blocked] {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-blocked");
+}
+
+#TabsToolbar:-moz-lwtheme-brighttext .tab-icon-sound[soundplaying],
+#TabsToolbar:-moz-lwtheme-brighttext .tab-icon-sound[blocked],
+#TabsToolbar:-moz-lwtheme-brighttext .tab-icon-sound[muted] {
+ filter: invert(1);
+}
+
+.tab-icon-sound[soundplaying-scheduledremoval]:not([muted]):not(:hover),
+.tab-icon-overlay[soundplaying-scheduledremoval]:not([muted]):not(:hover) {
+ transition: opacity .3s linear var(--soundplaying-removal-delay);
+ opacity: 0;
+}
+
+/* Tab icon overlay */
+.tab-icon-overlay {
+ width: 16px;
+ height: 16px;
+ margin-top: -8px;
+ margin-inline-start: -15px;
+ margin-inline-end: -1px;
+ position: relative;
+}
+
+.tab-icon-overlay[soundplaying],
+.tab-icon-overlay[muted]:not([crashed]),
+.tab-icon-overlay[blocked]:not([crashed]) {
+ border-radius: 10px;
+}
+
+.tab-icon-overlay[soundplaying]:hover,
+.tab-icon-overlay[muted]:not([crashed]):hover,
+.tab-icon-overlay[blocked]:not([crashed]):hover {
+ background-color: white;
+}
+
+.tab-icon-overlay[soundplaying] {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio");
+}
+
+.tab-icon-overlay[muted] {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-muted");
+}
+
+.tab-icon-overlay[blocked] {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-blocked");
+}
+
+#TabsToolbar:-moz-lwtheme-brighttext .tab-icon-overlay[soundplaying]:not([selected]):not(:hover),
+.tab-icon-overlay[soundplaying][selected]:-moz-lwtheme-brighttext:not(:hover) {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-white");
+}
+
+#TabsToolbar:-moz-lwtheme-brighttext .tab-icon-overlay[muted]:not([crashed]):not([selected]):not(:hover),
+.tab-icon-overlay[muted][selected]:-moz-lwtheme-brighttext:not(:hover) {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-white-muted");
+}
+
+#TabsToolbar:-moz-lwtheme-brighttext .tab-icon-overlay[blocked]:not([crashed]):not([selected]):not(:hover),
+.tab-icon-overlay[blocked][selected]:-moz-lwtheme-brighttext:not(:hover) {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-white-blocked");
+}
+
+/* Tabstrip new tab button */
+.tabs-newtab-button,
+#TabsToolbar > #new-tab-button ,
+#TabsToolbar > #wrapper-new-tab-button > #new-tab-button {
+ list-style-image: url("moz-icon://stock/gtk-add?size=menu");
+ -moz-image-region: auto;
+}
+
+/* Tabstrip close button */
+.tabs-closebutton > .toolbarbutton-icon {
+ /* XXX Buttons have padding in widget/ that we don't want here but can't override with good CSS, so we must
+ use evil CSS to give the impression of smaller content */
+ margin: -2px;
+}
+
+/* Tabbrowser arrowscrollbox arrows */
+.tabbrowser-arrowscrollbox > .scrollbutton-up,
+.tabbrowser-arrowscrollbox > .scrollbutton-down {
+ -moz-appearance: none;
+ margin: 0;
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-up {
+ -moz-border-start: 0;
+ -moz-border-end: 2px solid transparent;
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-down {
+ -moz-border-start: 2px solid transparent;
+ -moz-border-end: 0;
+ transition: 1s box-shadow ease-out;
+ border-radius: 4px;
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-down[notifybgtab] {
+ box-shadow: 0 0 5px 5px Highlight inset;
+ transition: none;
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-up:not([disabled]):-moz-locale-dir(ltr),
+.tabbrowser-arrowscrollbox > .scrollbutton-down:not([disabled]):-moz-locale-dir(rtl) {
+ border-width: 0 2px 0 0;
+ border-style: solid;
+ border-image: url("chrome://browser/skin/tabbrowser/tab-overflow-border.png") 0 2 0 2 fill;
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-down:not([disabled]):-moz-locale-dir(ltr),
+.tabbrowser-arrowscrollbox > .scrollbutton-up:not([disabled]):-moz-locale-dir(rtl) {
+ border-width: 0 0 0 2px;
+ border-style: solid;
+ border-image: url("chrome://browser/skin/tabbrowser/tab-overflow-border.png") 0 2 0 2 fill;
+}
+
+#TabsToolbar .toolbarbutton-1 > .toolbarbutton-icon,
+#TabsToolbar .toolbarbutton-1 > .toolbarbutton-menu-dropmarker,
+#TabsToolbar .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
+ margin-top: -2px;
+ margin-bottom: -2px;
+}
+
+#alltabs-button > .toolbarbutton-icon {
+ list-style-image: url("chrome://browser/skin/tabbrowser/alltabs.png");
+ margin: 2px 0 1px;
+}
+
+#alltabs-button[type="menu"] > .toolbarbutton-menu-dropmarker {
+ margin-bottom: -2px;
+}
+
+#alltabs-button[type="menu"] > .toolbarbutton-icon {
+ display: none;
+}
+
+/* All tabs menupopup */
+.alltabs-item > .menu-iconic-left > .menu-iconic-icon {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+
+.alltabs-item[selected="true"] {
+ font-weight: bold;
+}
+
+.alltabs-item[busy] > .menu-iconic-left > .menu-iconic-icon {
+ list-style-image: url("chrome://global/skin/icons/loading_16.png");
+}
+
+.alltabs-item[tabIsVisible] {
+ /* box-shadow instead of background-color to work around native styling */
+ box-shadow: inset -5px 0 ThreeDShadow;
+}
+
+/* Sidebar */
+#sidebar-header > .tabs-closebutton {
+ margin-bottom: 0px !important;
+ padding: 0px 2px 0px 2px !important;
+}
+
+#sidebar-throbber[loading="true"] {
+ list-style-image: url("chrome://global/skin/icons/loading_16.png");
+ -moz-margin-end: 4px;
+}
+
+toolbarbutton.chevron {
+ list-style-image: url("chrome://global/skin/toolbar/chevron.gif") !important;
+}
+
+toolbarbutton.chevron:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+ transform: scaleX(-1);
+}
+
+toolbarbutton.chevron > .toolbarbutton-text,
+toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
+ display: none;
+}
+
+toolbarbutton.chevron > .toolbarbutton-icon {
+ margin: 0;
+}
+
+toolbar[mode="text"] toolbarbutton.chevron > .toolbarbutton-icon {
+ display: -moz-box; /* display chevron icon in text mode */
+}
+
+/* ::::: Keyboard UI Panel ::::: */
+
+.KUI-panel-closebutton {
+ list-style-image: url(KUI-close.png);
+}
+
+.KUI-panel-closebutton > .toolbarbutton-icon {
+ margin: 0;
+}
+
+/* ::::: Ctrl-Tab and All Tabs Panels ::::: */
+
+.ctrlTab-preview,
+.allTabs-preview {
+ -moz-appearance: toolbarbutton;
+}
+
+.tabPreview-canvas {
+ box-shadow: 0 0 5px ThreeDShadow;
+}
+
+.ctrlTab-preview:focus .tabPreview-canvas,
+.ctrlTab-preview:hover .tabPreview-canvas,
+.allTabs-preview:focus .tabPreview-canvas,
+.allTabs-preview:hover .tabPreview-canvas {
+ box-shadow: none;
+}
+
+.ctrlTab-favicon[src],
+.allTabs-favicon[src] {
+ background-color: white;
+ width: 20px;
+ height: 20px;
+ padding: 2px;
+}
+
+/* Ctrl-Tab */
+
+#ctrlTab-panel {
+ padding: 10px;
+}
+
+.ctrlTab-preview:not(#ctrlTab-showAll) .tabPreview-canvas {
+ margin-bottom: 2px;
+}
+
+#ctrlTab-showAll {
+ -moz-appearance: button;
+ color: ButtonText;
+ padding: 0 3px;
+ margin-top: 10px;
+}
+
+/* All Tabs */
+
+#allTabs-panel {
+ padding-bottom: 10px;
+}
+
+#allTabs-meta {
+ padding: 5px;
+}
+
+#allTabs-filter {
+ -moz-margin-start: 36px;
+ -moz-margin-end: 0;
+}
+
+.allTabs-preview-label {
+ transform: translate(0, 2px);
+}
+
+/* Application button menu */
+
+.splitmenu-menuitem {
+ -moz-margin-end: 1px;
+}
+
+#appmenu-toolbar-button:not(:hover):not([open]):not(:-moz-lwtheme) {
+ color: inherit;
+}
+
+#appmenu-toolbar-button > .toolbarbutton-text,
+#appmenu-toolbar-button > .toolbarbutton-menu-dropmarker {
+ margin-top: -2px !important;
+ margin-bottom: -2px !important;
+}
+#appmenuSecondaryPane {
+ -moz-border-start: 1px solid ThreeDShadow;
+}
+#appmenuSecondaryPane-spacer {
+ min-height: 1em;
+}
+#appmenu-cut,
+#appmenu-editmenu-cut {
+ list-style-image: url("moz-icon://stock/gtk-cut?size=menu");
+}
+#appmenu-copy,
+#appmenu-editmenu-copy {
+ list-style-image: url("moz-icon://stock/gtk-copy?size=menu");
+}
+#appmenu-paste,
+#appmenu-editmenu-paste {
+ list-style-image: url("moz-icon://stock/gtk-paste?size=menu");
+}
+#wrapper-appmenu-toolbar-button,
+.appmenu-edit-button[disabled="true"] {
+ opacity: .3;
+}
+
+/* Add-on bar */
+
+#addon-bar {
+ box-shadow: 0 1px 0 rgba(0,0,0,.15) inset;
+ padding: 0;
+ min-height: 20px;
+}
+
+#status-bar {
+ min-height: 0;
+ -moz-appearance: none;
+ background-color: transparent;
+ border: none;
+}
+
+#addon-bar[customizing] > #status-bar {
+ opacity: .5;
+ background-image: repeating-linear-gradient(135deg,
+ rgba(255,255,255,.3), rgba(255,255,255,.3) 5px,
+ rgba(0,0,0,.3) 5px, rgba(0,0,0,.3) 10px);
+}
+
+#status-bar > statusbarpanel {
+ border-width: 0;
+ -moz-appearance: none;
+}
+
+#addonbar-closebutton > .toolbarbutton-icon {
+ margin-top: -2px;
+ margin-bottom: -2px;
+}
+
+/* Status panel */
+
+.statuspanel-label {
+ margin: 0;
+ padding: 2px 4px;
+ background: linear-gradient(#fff, #ddd);
+ border: 1px none #ccc;
+ border-top-style: solid;
+ color: #333;
+ text-shadow: none;
+}
+
+.statuspanel-label:-moz-locale-dir(ltr):not([mirror]),
+.statuspanel-label:-moz-locale-dir(rtl)[mirror] {
+ border-right-style: solid;
+ border-top-right-radius: .3em;
+ margin-right: 1em;
+}
+
+.statuspanel-label:-moz-locale-dir(rtl):not([mirror]),
+.statuspanel-label:-moz-locale-dir(ltr)[mirror] {
+ border-left-style: solid;
+ border-top-left-radius: .3em;
+ margin-left: 1em;
+}
+
+#full-screen-warning-message {
+ background-color: hsl(0,0%,15%);
+ color: white;
+ border-radius: 8px;
+ margin-top: 30px;
+ padding: 30px 50px;
+ box-shadow: 0 0 2px white;
+}
+
+.full-screen-description {
+ font-size: 150%;
+}
+
+#full-screen-domain-text {
+ font-size: 300%;
+}
+
+%ifdef MOZ_DEVTOOLS
+%include ../../../../devtools/client/themes/responsivedesign.inc.css
+%include ../../../../devtools/client/themes/commandline.inc.css
+%endif
+%include ../shared/plugin-doorhanger.inc.css
+
+%ifdef MOZ_DEVTOOLS
+.gcli-panel {
+ padding: 0;
+}
+
+.gclitoolbar-input-node > .textbox-input-box > html|*.textbox-input::-moz-selection {
+ color: hsl(210,11%,16%);
+}
+
+/* Error counter */
+
+#developer-toolbar-toolbox-button[error-count]:before {
+ color: #FDF3DE;
+ min-width: 16px;
+ text-shadow: none;
+ background-image: linear-gradient(#B4211B, #8A1915);
+ border-radius: 1px;
+ -moz-margin-end: 2px;
+}
+%endif
+
+.toolbarbutton-badge-stack {
+ margin: 5px 3px;
+ position: relative;
+}
+
+toolbar[iconsize="small"] .toolbarbutton-badge-stack {
+ margin: 0;
+}
+
+.toolbarbutton-badge[badge]:not([badge=""]):-moz-locale-dir(rtl)::after {
+ left: 2px;
+ right: auto;
+}
+
+#main-window[privatebrowsingmode=temporary] #TabsToolbar::before {
+ display: -moz-box;
+ content: "";
+ background: url("chrome://browser/skin/privatebrowsing-mask.png") center no-repeat;
+ width: 40px;
+}
diff --git a/themes/linux/click-to-play-warning-stripes.png b/themes/linux/click-to-play-warning-stripes.png
new file mode 100644
index 0000000..29f15f7
--- /dev/null
+++ b/themes/linux/click-to-play-warning-stripes.png
Binary files differ
diff --git a/themes/linux/communicator/communicator.css b/themes/linux/communicator/communicator.css
new file mode 100644
index 0000000..0b57574
--- /dev/null
+++ b/themes/linux/communicator/communicator.css
@@ -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/. */
+
+@import url("chrome://global/skin/");
+
diff --git a/themes/linux/communicator/jar.mn b/themes/linux/communicator/jar.mn
new file mode 100644
index 0000000..612d133
--- /dev/null
+++ b/themes/linux/communicator/jar.mn
@@ -0,0 +1,7 @@
+# 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:
+% skin communicator classic/1.0 %skin/classic/communicator/
+ skin/classic/communicator/communicator.css
diff --git a/themes/linux/communicator/moz.build b/themes/linux/communicator/moz.build
new file mode 100644
index 0000000..c97072b
--- /dev/null
+++ b/themes/linux/communicator/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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/themes/linux/downloads/allDownloadsViewOverlay.css b/themes/linux/downloads/allDownloadsViewOverlay.css
new file mode 100644
index 0000000..3526e01
--- /dev/null
+++ b/themes/linux/downloads/allDownloadsViewOverlay.css
@@ -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/. */
+
+#downloadsRichListBox {
+ /** The default listbox appearance comes with an unwanted margin. **/
+ -moz-appearance: none;
+ margin: 0;
+}
+
+#downloadsRichListBox > richlistitem.download {
+ height: 5em;
+ padding: 5px 8px;
+}
+
+.downloadTypeIcon {
+ -moz-margin-end: 8px;
+ /* Prevent flickering when changing states. */
+ min-height: 32px;
+ min-width: 32px;
+}
+
+.blockedIcon {
+ list-style-image: url("chrome://global/skin/icons/Error.png");
+}
+
+.downloadTarget {
+ margin-bottom: 3px;
+ cursor: inherit;
+}
+
+.downloadDetails {
+ opacity: 0.7;
+ font-size: 95%;
+ cursor: inherit;
+}
+
+.downloadButton {
+ -moz-appearance: none;
+ background: transparent;
+ min-width: 0;
+ min-height: 0;
+ margin: 3px;
+ border: none;
+ padding: 5px;
+ list-style-image: url("chrome://browser/skin/downloads/buttons.png");
+}
+
+/*** Button icons ***/
+
+.downloadButton.downloadCancel {
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+}
+richlistitem.download:hover > .downloadButton.downloadCancel {
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+richlistitem.download:hover > .downloadButton.downloadCancel:hover {
+ -moz-image-region: rect(0px, 48px, 16px, 32px);
+}
+richlistitem.download:hover > .downloadButton.downloadCancel:active {
+ -moz-image-region: rect(0px, 64px, 16px, 48px);
+}
+richlistitem.download[selected] > .downloadButton.downloadCancel {
+ -moz-image-region: rect(0px, 80px, 16px, 64px);
+}
+richlistitem.download:hover[selected] > .downloadButton.downloadCancel {
+ -moz-image-region: rect(0px, 96px, 16px, 80px);
+}
+richlistitem.download:hover[selected] > .downloadButton.downloadCancel:hover {
+ -moz-image-region: rect(0px, 112px, 16px, 96px);
+}
+richlistitem.download:hover[selected] > .downloadButton.downloadCancel:active {
+ -moz-image-region: rect(0px, 128px, 16px, 112px);
+}
+
+.downloadButton.downloadShow {
+ -moz-image-region: rect(16px, 16px, 32px, 0px);
+}
+richlistitem.download:hover > .downloadButton.downloadShow {
+ -moz-image-region: rect(16px, 32px, 32px, 16px);
+}
+richlistitem.download:hover > .downloadButton.downloadShow:hover {
+ -moz-image-region: rect(16px, 48px, 32px, 32px);
+}
+richlistitem.download:hover > .downloadButton.downloadShow:active {
+ -moz-image-region: rect(16px, 64px, 32px, 48px);
+}
+richlistitem.download[selected] > .downloadButton.downloadShow {
+ -moz-image-region: rect(16px, 80px, 32px, 64px);
+}
+richlistitem.download:hover[selected] > .downloadButton.downloadShow {
+ -moz-image-region: rect(16px, 96px, 32px, 80px);
+}
+richlistitem.download:hover[selected] > .downloadButton.downloadShow:hover {
+ -moz-image-region: rect(16px, 112px, 32px, 96px);
+}
+richlistitem.download:hover[selected] > .downloadButton.downloadShow:active {
+ -moz-image-region: rect(16px, 128px, 32px, 112px);
+}
+
+.downloadButton.downloadRetry {
+ -moz-image-region: rect(32px, 16px, 48px, 0px);
+}
+richlistitem.download:hover > .downloadButton.downloadRetry {
+ -moz-image-region: rect(32px, 32px, 48px, 16px);
+}
+richlistitem.download:hover > .downloadButton.downloadRetry:hover {
+ -moz-image-region: rect(32px, 48px, 48px, 32px);
+}
+richlistitem.download:hover > .downloadButton.downloadRetry:active {
+ -moz-image-region: rect(32px, 64px, 48px, 48px);
+}
+richlistitem.download[selected] > .downloadButton.downloadRetry {
+ -moz-image-region: rect(32px, 80px, 48px, 64px);
+}
+richlistitem.download:hover[selected] > .downloadButton.downloadRetry {
+ -moz-image-region: rect(32px, 96px, 48px, 80px);
+}
+richlistitem.download:hover[selected] > .downloadButton.downloadRetry:hover {
+ -moz-image-region: rect(32px, 112px, 48px, 96px);
+}
+richlistitem.download:hover[selected] > .downloadButton.downloadRetry:active {
+ -moz-image-region: rect(32px, 128px, 48px, 112px);
+}
+
diff --git a/themes/linux/downloads/buttons.png b/themes/linux/downloads/buttons.png
new file mode 100644
index 0000000..071f7f7
--- /dev/null
+++ b/themes/linux/downloads/buttons.png
Binary files differ
diff --git a/themes/linux/downloads/contentAreaDownloadsView.css b/themes/linux/downloads/contentAreaDownloadsView.css
new file mode 100644
index 0000000..56917d7
--- /dev/null
+++ b/themes/linux/downloads/contentAreaDownloadsView.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/. */
+
+@import url("chrome://global/skin/inContentUI.css");
+
+#downloadsListEmptyDescription {
+ margin: 1em;
+ text-align: center;
+ color: GrayText;
+}
diff --git a/themes/linux/downloads/download-glow-small.png b/themes/linux/downloads/download-glow-small.png
new file mode 100644
index 0000000..0dbf602
--- /dev/null
+++ b/themes/linux/downloads/download-glow-small.png
Binary files differ
diff --git a/themes/linux/downloads/download-glow.png b/themes/linux/downloads/download-glow.png
new file mode 100644
index 0000000..7514317
--- /dev/null
+++ b/themes/linux/downloads/download-glow.png
Binary files differ
diff --git a/themes/linux/downloads/download-notification-finish.png b/themes/linux/downloads/download-notification-finish.png
new file mode 100644
index 0000000..7bcc7f5
--- /dev/null
+++ b/themes/linux/downloads/download-notification-finish.png
Binary files differ
diff --git a/themes/linux/downloads/download-notification-start.png b/themes/linux/downloads/download-notification-start.png
new file mode 100644
index 0000000..bd548b1
--- /dev/null
+++ b/themes/linux/downloads/download-notification-start.png
Binary files differ
diff --git a/themes/linux/downloads/download-summary.png b/themes/linux/downloads/download-summary.png
new file mode 100644
index 0000000..c5d4754
--- /dev/null
+++ b/themes/linux/downloads/download-summary.png
Binary files differ
diff --git a/themes/linux/downloads/downloads.css b/themes/linux/downloads/downloads.css
new file mode 100644
index 0000000..79bb5ee
--- /dev/null
+++ b/themes/linux/downloads/downloads.css
@@ -0,0 +1,376 @@
+/* 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/. */
+
+/*** Panel and outer controls ***/
+
+#downloadsPanel > .panel-arrowcontainer > .panel-arrowcontent {
+ padding: 0;
+}
+
+#downloadsListBox {
+ background: transparent;
+ padding: 4px;
+ color: inherit;
+}
+
+#downloadsPanel:not([hasdownloads]) > #downloadsListBox {
+ display: none;
+}
+
+#downloadsPanel[hasdownloads] > #emptyDownloads {
+ display: none;
+}
+
+#emptyDownloads {
+ padding: 10px 20px;
+ max-width: 40ch;
+}
+
+#downloadsHistory {
+ background: transparent;
+ color: -moz-nativehyperlinktext;
+ cursor: pointer;
+}
+
+#downloadsFooter {
+ border-top: 1px solid ThreeDShadow;
+ background-image: linear-gradient(hsla(0,0%,0%,.15), hsla(0,0%,0%,.08) 6px);
+}
+
+#downloadsHistory > .button-box {
+ margin: 1em;
+}
+
+#downloadsPanel[keyfocus] > #downloadsFooter > #downloadsHistory:focus > .button-box {
+ outline: 1px -moz-dialogtext dotted;
+}
+
+/*** Downloads Summary and List items ***/
+
+#downloadsSummary,
+richlistitem[type="download"] {
+ height: 6em;
+ -moz-padding-end: 0;
+ color: inherit;
+}
+
+#downloadsSummary {
+ padding: 8px 38px 8px 12px;
+ cursor: pointer;
+ -moz-user-focus: normal;
+}
+
+#downloadsPanel[keyfocus] > #downloadsFooter > #downloadsSummary:focus {
+ outline: 1px -moz-dialogtext dotted;
+ outline-offset: -5px;
+}
+
+#downloadsSummary > .downloadTypeIcon {
+ list-style-image: url("chrome://browser/skin/downloads/download-summary.png");
+}
+
+#downloadsSummaryDescription {
+ color: -moz-nativehyperlinktext;
+}
+
+richlistitem[type="download"] {
+ margin: 0;
+ border-top: 1px solid hsla(0,0%,100%,.2);
+ border-bottom: 1px solid hsla(0,0%,0%,.15);
+ background: transparent;
+ padding: 8px;
+}
+
+richlistitem[type="download"]:first-child {
+ border-top: 1px solid transparent;
+}
+
+richlistitem[type="download"]:last-child {
+ border-bottom: 1px solid transparent;
+}
+
+#downloadsPanel[keyfocus] > #downloadsListBox:focus > richlistitem[type="download"][selected] {
+ outline: 1px -moz-dialogtext dotted;
+ outline-offset: -1px;
+}
+
+.downloadTypeIcon {
+ -moz-margin-end: 8px;
+ /* Prevent flickering when changing states. */
+ min-height: 32px;
+ min-width: 32px;
+}
+
+.blockedIcon {
+ list-style-image: url("chrome://global/skin/icons/Error.png");
+}
+
+/* We hold .downloadDisplayName, .downloadProgress and .downloadDetails
+ inside of a vbox with class .downloadContainer. We set the font-size of
+ the entire container to 90% because:
+
+ 1) This is the size that we want .downloadDetails to be
+ 2) The container's width is set by localizers by &downloadDetails.width;,
+ which is a ch unit. Since this is the value that should control the
+ panel width, we apply it to the outer container to constrain
+ .downloadDisplayName and .downloadProgress.
+
+ Finally, since we want .downloadDisplayName's font-size to be at 100% of
+ the font-size of .downloadContainer's parent, we use calc to go from the
+ smaller font-size back to the original font-size.
+ */
+#downloadsSummaryDetails,
+.downloadContainer {
+ font-size: 90%;
+}
+
+#downloadsSummaryDescription,
+.downloadDisplayName {
+ margin-bottom: 7px;
+ cursor: inherit;
+}
+
+.downloadDisplayName {
+ font-size: calc(100%/0.9);
+}
+
+#downloadsSummaryDetails,
+.downloadDetails {
+ margin-top: 1px;
+ opacity: 0.6;
+ cursor: inherit;
+}
+
+.downloadButton {
+ -moz-appearance: none;
+ min-width: 0;
+ min-height: 0;
+ margin: 3px;
+ border: none;
+ background: transparent;
+ padding: 5px;
+ list-style-image: url("chrome://browser/skin/downloads/buttons.png");
+}
+
+.downloadButton > .button-box {
+ padding: 0;
+}
+
+.downloadButton:focus > .button-box {
+ outline: 1px -moz-dialogtext dotted;
+}
+
+/*** Highlighted list items ***/
+
+#downloadsPanel:not([keyfocus]) > #downloadsListBox > richlistitem[type="download"][state="1"][exists]:hover {
+ border-radius: 3px;
+ border-top: 1px solid hsla(0,0%,100%,.3);
+ border-bottom: 1px solid hsla(0,0%,0%,.2);
+ background-color: Highlight;
+ background-image: linear-gradient(hsla(0,0%,100%,.1), hsla(0,0%,100%,0));
+ color: HighlightText;
+ cursor: pointer;
+}
+
+/*** Button icons ***/
+
+.downloadButton.downloadCancel {
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+}
+richlistitem[type="download"]:hover > stack > .downloadButton.downloadCancel {
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+richlistitem[type="download"]:hover > stack > .downloadButton.downloadCancel:hover {
+ -moz-image-region: rect(0px, 48px, 16px, 32px);
+}
+richlistitem[type="download"]:hover > stack > .downloadButton.downloadCancel:active {
+ -moz-image-region: rect(0px, 64px, 16px, 48px);
+}
+
+.downloadButton.downloadShow {
+ -moz-image-region: rect(16px, 16px, 32px, 0px);
+}
+#downloadsPanel:not([keyfocus]) > #downloadsListBox > richlistitem[type="download"][state="1"]:hover > stack > .downloadButton.downloadShow {
+ -moz-image-region: rect(16px, 96px, 32px, 80px);
+}
+#downloadsPanel:not([keyfocus]) > #downloadsListBox > richlistitem[type="download"][state="1"]:hover > stack > .downloadButton.downloadShow:hover {
+ -moz-image-region: rect(16px, 112px, 32px, 96px);
+}
+#downloadsPanel:not([keyfocus]) > #downloadsListBox > richlistitem[type="download"][state="1"]:hover > stack > .downloadButton.downloadShow:active {
+ -moz-image-region: rect(16px, 128px, 32px, 112px);
+}
+
+.downloadButton.downloadRetry {
+ -moz-image-region: rect(32px, 16px, 48px, 0px);
+}
+richlistitem[type="download"]:hover > stack > .downloadButton.downloadRetry {
+ -moz-image-region: rect(32px, 32px, 48px, 16px);
+}
+richlistitem[type="download"]:hover > stack > .downloadButton.downloadRetry:hover {
+ -moz-image-region: rect(32px, 48px, 48px, 32px);
+}
+richlistitem[type="download"]:hover > stack > .downloadButton.downloadRetry:active {
+ -moz-image-region: rect(32px, 64px, 48px, 48px);
+}
+
+/*** Status and progress indicator ***/
+
+#downloads-indicator-anchor {
+ /* Makes the outermost stack element positioned, so that its contents are
+ rendered over the main browser window in the Z order. This is required by
+ the animated event notification. */
+ position: relative;
+}
+
+toolbar[iconsize="small"] > #downloads-indicator > #downloads-indicator-anchor {
+ min-width: 16px;
+ min-height: 16px;
+}
+
+toolbar[iconsize="large"] > #downloads-indicator > #downloads-indicator-anchor {
+ min-width: 24px;
+ min-height: 24px;
+}
+
+/*** Main indicator icon ***/
+
+toolbar[iconsize="small"] > #downloads-indicator > #downloads-indicator-anchor > #downloads-indicator-icon {
+ background: -moz-image-rect(url("chrome://browser/skin/Toolbar-small.png"),
+ 0, 16, 16, 0) center no-repeat;
+}
+
+toolbar[iconsize="large"] > #downloads-indicator > #downloads-indicator-anchor > #downloads-indicator-icon {
+ background: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"),
+ 0, 24, 24, 0) center no-repeat;
+}
+
+toolbar[iconsize="small"] > #downloads-indicator[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
+ background-image: url("chrome://browser/skin/downloads/download-glow-small.png");
+}
+
+toolbar[iconsize="large"] > #downloads-indicator[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
+ background-image: url("chrome://browser/skin/downloads/download-glow.png");
+}
+
+/* In the next few rules, we use :not([counter]) as a shortcut that is
+ equivalent to -moz-any([progress], [paused]). */
+
+#downloads-indicator:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+ background: -moz-image-rect(url("chrome://browser/skin/Toolbar-small.png"),
+ 0, 16, 16, 0) center no-repeat;
+ background-size: 12px;
+}
+
+#downloads-indicator:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+ background-image: url("chrome://browser/skin/downloads/download-glow.png");
+}
+
+/*** Download notifications ***/
+
+#downloads-indicator-notification {
+ opacity: 0;
+ background-size: 16px;
+ background-position: center;
+ background-repeat: no-repeat;
+}
+
+@keyframes downloadsIndicatorNotificationStartRight {
+ from { opacity: 0; transform: translate(-128px, 128px) scale(8); }
+ 20% { opacity: .85; animation-timing-function: ease-out; }
+ to { opacity: 0; transform: translate(0) scale(1); }
+}
+
+@keyframes downloadsIndicatorNotificationStartLeft {
+ from { opacity: 0; transform: translate(128px, 128px) scale(8); }
+ 20% { opacity: .85; animation-timing-function: ease-out; }
+ to { opacity: 0; transform: translate(0) scale(1); }
+}
+
+#downloads-indicator[notification="start"] > #downloads-indicator-anchor > #downloads-indicator-notification {
+ background-image: url("chrome://browser/skin/downloads/download-notification-start.png");
+ animation-name: downloadsIndicatorNotificationStartRight;
+ animation-duration: 1s;
+}
+
+#downloads-indicator[notification="start"]:-moz-locale-dir(rtl) > #downloads-indicator-anchor > #downloads-indicator-notification {
+ animation-name: downloadsIndicatorNotificationStartLeft;
+}
+
+@keyframes downloadsIndicatorNotificationFinish {
+ from { opacity: 0; transform: scale(1); }
+ 20% { opacity: .65; animation-timing-function: ease-in; }
+ to { opacity: 0; transform: scale(8); }
+}
+
+#downloads-indicator[notification="finish"] > #downloads-indicator-anchor > #downloads-indicator-notification {
+ background-image: url("chrome://browser/skin/downloads/download-notification-finish.png");
+ animation-name: downloadsIndicatorNotificationFinish;
+ animation-duration: 1s;
+}
+
+/*** Progress bar and text ***/
+
+#downloads-indicator-counter {
+ height: 10px;
+ margin: 0;
+ color: hsl(0,0%,30%);
+ text-shadow: 0 1px 0 hsla(0,0%,100%,.5);
+ font-size: 10px;
+ line-height: 10px;
+ text-align: center;
+}
+
+toolbar[brighttext] #downloads-indicator-counter {
+ color: white;
+ text-shadow: 0 0 1px rgba(0,0,0,.7),
+ 0 1px 1.5px rgba(0,0,0,.5);
+}
+
+#downloads-indicator-progress {
+ width: 16px;
+ height: 6px;
+ min-width: 0;
+ min-height: 0;
+ margin-top: 1px;
+ margin-bottom: 2px;
+ border-radius: 2px;
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.4);
+}
+
+#downloads-indicator-progress > .progress-bar {
+ -moz-appearance: none;
+ min-width: 0;
+ min-height: 0;
+ /* The background-clip: border-box; and background-image: none; are there to expand the background-color behind the border */
+ background-clip: padding-box, border-box;
+ background-color: rgb(255, 135, 94);
+ background-image: linear-gradient(transparent 1px, rgba(255, 255, 255, 0.4) 1px, rgba(255, 255, 255, 0.4) 2px, transparent 2px), none;
+ border: 1px solid;
+ border-color: rgba(0,43,86,.6) rgba(0,43,86,.4) rgba(0,43,86,.4);
+ border-radius: 2px 0 0 2px;
+}
+
+#downloads-indicator-progress > .progress-remainder {
+ -moz-appearance: none;
+ min-width: 0;
+ min-height: 0;
+ background-image: linear-gradient(#505050, #575757);
+ border: 1px solid;
+ border-color: hsla(0,0%,0%,.6) hsla(0,0%,0%,.4) hsla(0,0%,0%,.4);
+ -moz-border-start: none;
+ border-radius: 0 2px 2px 0;
+}
+
+#downloads-indicator[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-bar {
+ background-color: rgb(220, 230, 81);
+}
+
+#downloads-indicator[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-remainder {
+ background-image: linear-gradient(#4b5000, #515700);
+}
+
+toolbar[mode="full"] > #downloads-indicator > .toolbarbutton-text {
+ margin: 0;
+ text-align: center;
+}
diff --git a/themes/linux/engineManager.css b/themes/linux/engineManager.css
new file mode 100644
index 0000000..18817cd
--- /dev/null
+++ b/themes/linux/engineManager.css
@@ -0,0 +1,16 @@
+%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
+
+#engineList treechildren::-moz-tree-image(engineName) {
+ -moz-margin-end: 4px;
+ -moz-margin-start: 1px;
+ width: 16px;
+ height: 16px;
+}
+
+#engineList treechildren::-moz-tree-row {
+ height: 20px;
+}
diff --git a/themes/linux/feeds/feedIcon.png b/themes/linux/feeds/feedIcon.png
new file mode 100644
index 0000000..a788fff
--- /dev/null
+++ b/themes/linux/feeds/feedIcon.png
Binary files differ
diff --git a/themes/linux/feeds/feedIcon16.png b/themes/linux/feeds/feedIcon16.png
new file mode 100644
index 0000000..f8536a4
--- /dev/null
+++ b/themes/linux/feeds/feedIcon16.png
Binary files differ
diff --git a/themes/linux/feeds/subscribe-ui.css b/themes/linux/feeds/subscribe-ui.css
new file mode 100644
index 0000000..b3c0b37
--- /dev/null
+++ b/themes/linux/feeds/subscribe-ui.css
@@ -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/. */
+
+.alwaysUse {
+ padding: 5px;
+}
+
+.handlersMenuPopup > menuitem {
+ -moz-padding-start: 23px;
+}
+
+.handlersMenuPopup > menuitem.menuitem-iconic {
+ -moz-padding-start: 2px;
+}
+
+.handlersMenuPopup > .menuitem-iconic > .menu-iconic-left {
+ display: -moz-box;
+ min-width: 16px;
+ -moz-padding-end: 2px;
+}
+
+.chooseApplicationMenuItem {
+ list-style-image: url("moz-icon://dummy.exe?size=16");
+}
+
+#feedHeader[dir="rtl"] .handlersMenuList > menupopup {
+ direction: rtl;
+}
diff --git a/themes/linux/feeds/subscribe.css b/themes/linux/feeds/subscribe.css
new file mode 100644
index 0000000..dd1e24e
--- /dev/null
+++ b/themes/linux/feeds/subscribe.css
@@ -0,0 +1,163 @@
+/* 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 {
+ background: -moz-Dialog;
+ font: 3mm tahoma,arial,helvetica,sans-serif;
+}
+
+#feedBody {
+ border: 1px solid THreeDShadow;
+ padding: 3em;
+ -moz-padding-start: 30px;
+ margin: 2em auto;
+ background: -moz-Field;
+}
+
+#feedHeaderContainer {
+ border: 1px solid ThreeDShadow;
+ border-radius: 10px;
+ margin: -4em auto 0 auto;
+ background-color: InfoBackground;
+}
+
+#feedHeader {
+ margin-top: 4.9em;
+ margin-bottom: 1em;
+ -moz-margin-start: 1.4em;
+ -moz-margin-end: 1em;
+ -moz-padding-start: 2.9em;
+ font-size: 110%;
+ color: InfoText;
+}
+
+.feedBackground {
+ background: url("chrome://browser/skin/feeds/feedIcon.png") 0% 10% no-repeat InfoBackground;
+}
+
+.videoPodcastBackground {
+ background: url("chrome://browser/skin/feeds/videoFeedIcon.png") 0% 10% no-repeat InfoBackground;
+}
+
+.audioPodcastBackground {
+ background: url("chrome://browser/skin/feeds/audioFeedIcon.png") 0% 10% no-repeat InfoBackground;
+}
+
+#feedHeader[dir="rtl"] {
+ background-position: 100% 10%;
+}
+
+#feedIntroText {
+ display: none;
+}
+
+#feedHeader[firstrun="true"] #feedIntroText {
+ padding-top: 0.1em;
+ -moz-padding-start: 0.6em;
+ display: block;
+}
+
+#feedHeader[firstrun="true"] > #feedSubscribeLine {
+ -moz-padding-start: 1.8em;
+}
+
+#feedSubscribeLine {
+ padding-top: 0.2em;
+}
+
+img {
+ max-width: 100%;
+}
+
+/* Don't print subscription UI */
+@media print {
+ #feedHeaderContainer {
+ display: none;
+ }
+}
+
+body {
+ margin: 0;
+ padding: 0 3em;
+ color: -moz-fieldText;
+ font: message-box;
+}
+
+h1 {
+ font-size: 160%;
+ border-bottom: 2px solid ThreeDLightShadow;
+ margin: 0 0 .2em 0;
+}
+
+h2 {
+ color: ThreeDDarkShadow;
+ font-size: 110%;
+ font-weight: normal;
+ margin: 0 0 .6em 0;
+}
+
+#feedTitleLink {
+ float: right;
+ -moz-margin-start: .6em;
+ -moz-margin-end: 0;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+a[href] img {
+ border: none;
+}
+
+#feedTitleContainer {
+ -moz-margin-start: 0;
+ -moz-margin-end: .6em;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+#feedTitleImage {
+ -moz-margin-start: .6em;
+ -moz-margin-end: 0;
+ margin-top: 0;
+ margin-bottom: 0;
+ max-width: 300px;
+ max-height: 150px;
+}
+
+.feedEntryContent {
+ font-size: 110%;
+}
+
+.link {
+ color: #0000FF;
+ text-decoration: underline;
+ cursor: pointer;
+}
+
+.link:hover:active {
+ color: #FF0000;
+}
+
+.lastUpdated {
+ font-size: 85%;
+ font-weight: normal;
+}
+
+.type-icon {
+ vertical-align: bottom;
+ height: 16px;
+ width: 16px;
+}
+
+.enclosures {
+ border: 1px solid THreeDShadow;
+ padding: 1em;
+ margin: 1em auto;
+ background: -moz-Dialog;
+}
+
+.enclosure {
+ vertical-align: middle;
+ margin-left: 2px;
+}
diff --git a/themes/linux/icon.png b/themes/linux/icon.png
new file mode 100644
index 0000000..ff4f21f
--- /dev/null
+++ b/themes/linux/icon.png
Binary files differ
diff --git a/themes/linux/identity-icons-generic.png b/themes/linux/identity-icons-generic.png
new file mode 100644
index 0000000..a39e493
--- /dev/null
+++ b/themes/linux/identity-icons-generic.png
Binary files differ
diff --git a/themes/linux/identity-icons-https-ev.png b/themes/linux/identity-icons-https-ev.png
new file mode 100644
index 0000000..d49be13
--- /dev/null
+++ b/themes/linux/identity-icons-https-ev.png
Binary files differ
diff --git a/themes/linux/identity-icons-https-mixed-active.png b/themes/linux/identity-icons-https-mixed-active.png
new file mode 100644
index 0000000..3c77bc8
--- /dev/null
+++ b/themes/linux/identity-icons-https-mixed-active.png
Binary files differ
diff --git a/themes/linux/identity-icons-https.png b/themes/linux/identity-icons-https.png
new file mode 100644
index 0000000..ffd6694
--- /dev/null
+++ b/themes/linux/identity-icons-https.png
Binary files differ
diff --git a/themes/linux/identity.png b/themes/linux/identity.png
new file mode 100644
index 0000000..f3f790e
--- /dev/null
+++ b/themes/linux/identity.png
Binary files differ
diff --git a/themes/linux/imagedocument.png b/themes/linux/imagedocument.png
new file mode 100644
index 0000000..ff4f21f
--- /dev/null
+++ b/themes/linux/imagedocument.png
Binary files differ
diff --git a/themes/linux/jar.mn b/themes/linux/jar.mn
new file mode 100644
index 0000000..a7c426b
--- /dev/null
+++ b/themes/linux/jar.mn
@@ -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/.
+
+browser.jar:
+% skin browser classic/1.0 %skin/classic/browser/
+% override chrome://global/skin/icons/warning-16.png moz-icon://stock/gtk-dialog-warning?size=menu
+ skin/classic/browser/sanitizeDialog.css
+* skin/classic/browser/aboutPrivateBrowsing.css
+* skin/classic/browser/aboutSessionRestore.css
+ skin/classic/browser/aboutSessionRestore-window-icon.png
+ skin/classic/browser/aboutCertError.css
+ skin/classic/browser/aboutCertError_sectionCollapsed.png
+ skin/classic/browser/aboutCertError_sectionCollapsed-rtl.png
+ skin/classic/browser/aboutCertError_sectionExpanded.png
+#ifdef MOZ_SERVICES_SYNC
+ skin/classic/browser/aboutSyncTabs.css
+#endif
+* skin/classic/browser/autocomplete.css
+ skin/classic/browser/actionicon-tab.png
+* skin/classic/browser/browser.css
+ skin/classic/browser/click-to-play-warning-stripes.png
+* skin/classic/browser/engineManager.css
+ skin/classic/browser/Geolocation-16.png
+ skin/classic/browser/Geolocation-64.png
+ skin/classic/browser/Go-arrow.png
+ skin/classic/browser/identity.png
+ skin/classic/browser/imagedocument.png
+ skin/classic/browser/identity-icons-generic.png
+ skin/classic/browser/identity-icons-https.png
+ skin/classic/browser/identity-icons-https-ev.png
+ skin/classic/browser/identity-icons-https-mixed-active.png
+ skin/classic/browser/Info.png
+ skin/classic/browser/KUI-close.png
+ skin/classic/browser/mixed-content-blocked-16.png
+ skin/classic/browser/mixed-content-blocked-64.png
+ skin/classic/browser/monitor.png
+ skin/classic/browser/monitor_16-10.png
+* skin/classic/browser/pageInfo.css
+ skin/classic/browser/pageInfo.png
+ skin/classic/browser/page-livemarks.png
+ skin/classic/browser/pointerLock-16.png
+ skin/classic/browser/pointerLock-64.png
+ skin/classic/browser/Privacy-16.png
+ skin/classic/browser/Privacy-48.png
+ skin/classic/browser/privatebrowsing-mask.png
+ skin/classic/browser/searchbar.css
+ skin/classic/browser/Secure.png
+ skin/classic/browser/Security-broken.png
+ skin/classic/browser/setDesktopBackground.css
+ skin/classic/browser/slowStartup-16.png
+ skin/classic/browser/Toolbar.png
+ skin/classic/browser/Toolbar-small.png
+ skin/classic/browser/urlbar-arrow.png
+ skin/classic/browser/web-notifications-icon.svg
+ skin/classic/browser/web-notifications-tray.svg
+#ifdef MOZ_WEBRTC
+ skin/classic/browser/webRTC-shareDevice-16.png
+ skin/classic/browser/webRTC-shareDevice-64.png
+ skin/classic/browser/webRTC-sharingDevice-16.png
+#endif
+ skin/classic/browser/downloads/buttons.png (downloads/buttons.png)
+ skin/classic/browser/downloads/download-glow.png (downloads/download-glow.png)
+ skin/classic/browser/downloads/download-glow-small.png (downloads/download-glow-small.png)
+ skin/classic/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png)
+ skin/classic/browser/downloads/download-notification-start.png (downloads/download-notification-start.png)
+ skin/classic/browser/downloads/download-summary.png (downloads/download-summary.png)
+ skin/classic/browser/downloads/downloads.css (downloads/downloads.css)
+ skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css)
+ skin/classic/browser/downloads/contentAreaDownloadsView.css (downloads/contentAreaDownloadsView.css)
+ skin/classic/browser/feeds/feedIcon.png (feeds/feedIcon.png)
+ skin/classic/browser/feeds/feedIcon16.png (feeds/feedIcon16.png)
+ skin/classic/browser/feeds/videoFeedIcon.png (feeds/feedIcon.png)
+ skin/classic/browser/feeds/videoFeedIcon16.png (feeds/feedIcon16.png)
+ skin/classic/browser/feeds/audioFeedIcon.png (feeds/feedIcon.png)
+ skin/classic/browser/feeds/audioFeedIcon16.png (feeds/feedIcon16.png)
+ skin/classic/browser/feeds/subscribe.css (feeds/subscribe.css)
+ skin/classic/browser/feeds/subscribe-ui.css (feeds/subscribe-ui.css)
+* skin/classic/browser/newtab/newTab.css (newtab/newTab.css)
+ skin/classic/browser/newtab/controls.png (../shared/newtab/controls.png)
+ skin/classic/browser/newtab/noise.png (../shared/newtab/noise.png)
+ skin/classic/browser/newtab/pinned.png (../shared/newtab/pinned.png)
+ skin/classic/browser/places/bookmarksMenu.png (places/bookmarksMenu.png)
+ skin/classic/browser/places/bookmarksToolbar.png (places/bookmarksToolbar.png)
+ skin/classic/browser/places/calendar.png (places/calendar.png)
+* skin/classic/browser/places/editBookmarkOverlay.css (places/editBookmarkOverlay.css)
+ skin/classic/browser/places/livemark-item.png (places/livemark-item.png)
+ skin/classic/browser/places/pageStarred.png (places/pageStarred.png)
+ skin/classic/browser/places/star-icons.png (places/star-icons.png)
+ skin/classic/browser/places/starred48.png (places/starred48.png)
+ skin/classic/browser/places/unstarred48.png (places/unstarred48.png)
+ skin/classic/browser/places/places.css (places/places.css)
+ skin/classic/browser/places/organizer.css (places/organizer.css)
+ skin/classic/browser/places/organizer.xml (places/organizer.xml)
+ skin/classic/browser/places/query.png (places/query.png)
+ skin/classic/browser/places/starPage.png (places/starPage.png)
+ skin/classic/browser/places/tag.png (places/tag.png)
+ skin/classic/browser/places/toolbarDropMarker.png (places/toolbarDropMarker.png)
+ skin/classic/browser/places/unsortedBookmarks.png (places/unsortedBookmarks.png)
+ skin/classic/browser/places/downloads.png (places/downloads.png)
+ skin/classic/browser/permissions/aboutPermissions.css (permissions/aboutPermissions.css)
+ skin/classic/browser/preferences/alwaysAsk.png (preferences/alwaysAsk.png)
+ skin/classic/browser/preferences/mail.png (preferences/mail.png)
+ skin/classic/browser/preferences/Options.png (preferences/Options.png)
+#ifdef MOZ_SERVICES_SYNC
+ skin/classic/browser/preferences/Options-sync.png (preferences/Options-sync.png)
+#endif
+* skin/classic/browser/preferences/preferences.css (preferences/preferences.css)
+ skin/classic/browser/preferences/applications.css (preferences/applications.css)
+ skin/classic/browser/statusbar/dynamic.css (../shared/statusbar/dynamic.css)
+* skin/classic/browser/statusbar/overlay.css (statusbar/overlay.css)
+* skin/classic/browser/statusbar/prefs.css (statusbar/prefs.css)
+ skin/classic/browser/statusbar/pulse.png (../shared/statusbar/pulse.png)
+ skin/classic/browser/statusbar/pms16.png (../shared/statusbar/pms16.png)
+ skin/classic/browser/statusbar/pms24.png (../shared/statusbar/pms24.png)
+ skin/classic/browser/statusbar/throbber-idle.png (../shared/statusbar/throbber-idle.png)
+ skin/classic/browser/statusbar/throbberStatic.png (../shared/statusbar/throbberStatic.png)
+ skin/classic/browser/tabbrowser/alltabs.png (tabbrowser/alltabs.png)
+ skin/classic/browser/tabbrowser/connecting.png (tabbrowser/connecting.png)
+ skin/classic/browser/tabbrowser/loading.png (tabbrowser/loading.png)
+ skin/classic/browser/tabbrowser/tab.png (tabbrowser/tab.png)
+ skin/classic/browser/tabbrowser/tab-overflow-border.png (tabbrowser/tab-overflow-border.png)
+ skin/classic/browser/tabbrowser/tabDragIndicator.png (tabbrowser/tabDragIndicator.png)
+ skin/classic/browser/tabbrowser/tab-audio.svg (../shared/tabbrowser/tab-audio.svg)
+ skin/classic/browser/tabbrowser/tab-audio-small.svg (../shared/tabbrowser/tab-audio-small.svg)
+#ifdef MOZ_SERVICES_SYNC
+ skin/classic/browser/sync-16-throbber.png
+ skin/classic/browser/sync-16.png
+ skin/classic/browser/sync-24-throbber.png
+ skin/classic/browser/sync-32.png
+ skin/classic/browser/sync-bg.png
+ skin/classic/browser/sync-128.png
+ skin/classic/browser/sync-desktopIcon.png
+ skin/classic/browser/sync-mobileIcon.png
+ skin/classic/browser/syncSetup.css
+ skin/classic/browser/syncCommon.css
+ skin/classic/browser/syncQuota.css
+ skin/classic/browser/syncProgress.css
+#endif
+ skin/classic/browser/notification-pluginNormal.png (../shared/plugins/notification-pluginNormal.png)
+ skin/classic/browser/notification-pluginAlert.png (../shared/plugins/notification-pluginAlert.png)
+ skin/classic/browser/notification-pluginBlocked.png (../shared/plugins/notification-pluginBlocked.png)
diff --git a/themes/linux/mixed-content-blocked-16.png b/themes/linux/mixed-content-blocked-16.png
new file mode 100644
index 0000000..7cf33ec
--- /dev/null
+++ b/themes/linux/mixed-content-blocked-16.png
Binary files differ
diff --git a/themes/linux/mixed-content-blocked-64.png b/themes/linux/mixed-content-blocked-64.png
new file mode 100644
index 0000000..cac4415
--- /dev/null
+++ b/themes/linux/mixed-content-blocked-64.png
Binary files differ
diff --git a/themes/linux/monitor.png b/themes/linux/monitor.png
new file mode 100644
index 0000000..35e7b20
--- /dev/null
+++ b/themes/linux/monitor.png
Binary files differ
diff --git a/themes/linux/monitor_16-10.png b/themes/linux/monitor_16-10.png
new file mode 100644
index 0000000..4195034
--- /dev/null
+++ b/themes/linux/monitor_16-10.png
Binary files differ
diff --git a/themes/linux/moz.build b/themes/linux/moz.build
new file mode 100644
index 0000000..6a7af20
--- /dev/null
+++ b/themes/linux/moz.build
@@ -0,0 +1,9 @@
+# -*- 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 += ['communicator']
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/themes/linux/newtab/newTab.css b/themes/linux/newtab/newTab.css
new file mode 100644
index 0000000..357b313
--- /dev/null
+++ b/themes/linux/newtab/newTab.css
@@ -0,0 +1,27 @@
+/* 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/. */
+
+%include ../../shared/newtab/newTab.css.inc
+
+.newtab-undo-button {
+ -moz-appearance: none;
+ color: rgb(221,72,20);
+ cursor: pointer;
+ padding: 0;
+ margin: 0 4px;
+ border: 0;
+ background: transparent;
+ text-decoration: none;
+ min-width: 0;
+}
+
+#newtab-undo-close-button {
+ padding: 0;
+ border: none;
+ -moz-user-focus: normal;
+}
+
+#newtab-undo-close-button > .toolbarbutton-icon {
+ margin: -4px;
+}
diff --git a/themes/linux/page-livemarks.png b/themes/linux/page-livemarks.png
new file mode 100644
index 0000000..f8536a4
--- /dev/null
+++ b/themes/linux/page-livemarks.png
Binary files differ
diff --git a/themes/linux/pageInfo.css b/themes/linux/pageInfo.css
new file mode 100644
index 0000000..49ae493
--- /dev/null
+++ b/themes/linux/pageInfo.css
@@ -0,0 +1,276 @@
+%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
+
+@import url("chrome://global/skin/");
+
+/* View buttons */
+#viewGroup > radio {
+ list-style-image: url("chrome://browser/skin/pageInfo.png");
+ -moz-box-orient: vertical;
+ -moz-box-align: center;
+ -moz-appearance: none;
+ min-width: 4.5em;
+ margin: 0;
+ padding: 3px;
+ color: -moz-FieldText;
+}
+
+#viewGroup > radio[selected="true"] {
+ background-color: Highlight;
+ color: HighlightText;
+}
+
+#topBar {
+ -moz-appearance: listbox;
+ margin: 8px 8px 0;
+}
+
+#generalTab {
+ -moz-image-region: rect(0px, 32px, 32px, 0px)
+}
+
+#mediaTab {
+ -moz-image-region: rect(0px, 64px, 32px, 32px)
+}
+
+#feedTab {
+ -moz-image-region: rect(0px, 96px, 32px, 64px)
+}
+
+#permTab {
+ -moz-image-region: rect(0px, 128px, 32px, 96px)
+}
+
+#securityTab {
+ -moz-image-region: rect(0px, 160px, 32px, 128px)
+}
+
+#mainDeck {
+ padding-left: 2px;
+ padding-right: 2px;
+}
+
+deck {
+ padding: 10px 10px 10px 10px;
+}
+
+/* Misc */
+tree {
+ margin: .5em;
+}
+
+.gridSeparator {
+ width: .5em;
+}
+
+textbox {
+ background: transparent !important;
+ border: none;
+ padding: 0px;
+ margin-top: 1px;
+ -moz-appearance: none;
+}
+
+textbox.header {
+ -moz-margin-start: 0;
+}
+
+.iframe {
+ margin: .5em;
+ background: white;
+ overflow: auto;
+}
+
+.fixedsize {
+ height: 8.5em;
+}
+
+textbox[disabled] {
+ font-style: italic;
+}
+
+/* General Tab */
+#generalPanel > #titletext {
+ -moz-margin-start: 5px;
+}
+
+groupbox.collapsable caption .caption-icon {
+ width: 9px;
+ height: 9px;
+ background-repeat: no-repeat;
+ background-position: center;
+ -moz-margin-start: 1px;
+ -moz-margin-end: 3px;
+ background-image: url("chrome://global/skin/tree/twisty-open.png");
+}
+
+groupbox.collapsable[closed="true"] {
+ border: none;
+}
+
+groupbox.collapsable[closed="true"] caption .caption-icon {
+ background-image: url("chrome://global/skin/tree/twisty-clsd.png");
+}
+
+groupbox tree {
+ margin: 0;
+ border: none;
+}
+
+groupbox.treebox .groupbox-body {
+ -moz-margin-start: 5px;
+ -moz-margin-end: 1px;
+ padding-top: 0;
+}
+
+#securityBox description {
+ -moz-margin-start: 10px;
+}
+
+#general-security-identity {
+ white-space: pre-wrap;
+ line-height: 2em;
+}
+
+/* Media Tab */
+#imagetree {
+ min-height: 10em;
+ margin-top: 2px;
+ margin-bottom: 0;
+}
+
+#mediaSplitter {
+ -moz-appearance: none;
+ height: .8em;
+}
+
+#mediaGrid {
+ min-height: 9em;
+}
+
+#mediaLabelColumn {
+ min-width: 10em;
+}
+
+#thepreviewimage {
+ margin: 1em;
+}
+
+treechildren::-moz-tree-cell-text(broken) {
+ font-style: italic;
+ color: graytext;
+}
+
+#mediaPreviewBox .inset {
+ -moz-appearance: listbox;
+ margin-bottom: 0;
+}
+
+/* Feeds Tab */
+#feedPanel {
+ margin-left: 2px;
+ margin-right: 2px;
+}
+
+#feedtree {
+ margin-bottom: 0px;
+}
+
+#feedListbox richlistitem {
+ padding-top: 6px;
+ padding-bottom: 6px;
+ -moz-padding-start: 7px;
+ -moz-padding-end: 7px;
+ min-height: 25px;
+ border-bottom: 1px dotted #C0C0C0;
+ color: -moz-FieldText;
+}
+
+#feedListbox richlistitem[selected="true"] {
+ background-color: -moz-Dialog;
+ color: -moz-DialogText;
+}
+
+#feedListbox {
+ margin-bottom: 0;
+ border: 2px solid;
+ -moz-border-top-colors: ThreeDShadow ThreeDDarkShadow;
+ -moz-border-right-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-bottom-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-left-colors: ThreeDShadow ThreeDDarkShadow;
+}
+
+.feedTitle {
+ font-weight: bold;
+}
+
+/* Permissions Tab */
+#permPanel {
+ margin-left: 6px;
+ margin-right: 6px;
+}
+
+#permList {
+ -moz-appearance: listbox;
+ margin-top: .5em;
+ overflow: auto;
+ border: 2px solid;
+ -moz-border-top-colors: ThreeDShadow ThreeDDarkShadow;
+ -moz-border-right-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-bottom-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-left-colors: ThreeDShadow ThreeDDarkShadow;
+ background-color: -moz-field;
+ color: -moz-FieldText;
+}
+
+.permission {
+ padding-top: 6px;
+ padding-bottom: 6px;
+ -moz-padding-start: 7px;
+ -moz-padding-end: 7px;
+ min-height: 25px;
+ border-bottom: 1px dotted #C0C0C0;
+}
+
+.permissionLabel {
+ font-weight: bold;
+}
+
+.permission:hover {
+ background-color: -moz-dialog;
+ color: -moz-DialogText;
+}
+
+/* Security Tab */
+#securityPanel .caption-icon {
+ display: none;
+}
+
+#securityPanel .header {
+ font-size: 120%;
+}
+
+#securityPanel .fieldLabel {
+ margin: 2px 10px 3px 10px;
+}
+
+#securityPanel .fieldValue {
+ font-weight: bold;
+ margin: 2px 10px 0px 10px;
+}
+
+/* Icons for context menus */
+menuitem:not([type]) {
+ -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem-iconic");
+}
+
+#menu_selectall {
+ list-style-image: url("moz-icon://stock/gtk-select-all?size=menu");
+}
+
+#menu_copy {
+ list-style-image: url("moz-icon://stock/gtk-copy?size=menu");
+}
diff --git a/themes/linux/pageInfo.png b/themes/linux/pageInfo.png
new file mode 100644
index 0000000..2cbb15d
--- /dev/null
+++ b/themes/linux/pageInfo.png
Binary files differ
diff --git a/themes/linux/permissions/aboutPermissions.css b/themes/linux/permissions/aboutPermissions.css
new file mode 100644
index 0000000..386e167
--- /dev/null
+++ b/themes/linux/permissions/aboutPermissions.css
@@ -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/. */
+
+@import url("chrome://global/skin/inContentUI.css");
+
+/* header */
+
+#permissions-pagetitle {
+ font-size: 200%;
+ font-weight: bold;
+ padding-bottom: 0.5em;
+}
+
+/* content box */
+#permissions-content {
+ height: 100%;
+}
+
+/* sites box */
+
+#sites-box {
+ padding: 10px;
+ width: 25em;
+}
+
+#sites-filter {
+ margin: 0;
+}
+
+#sites-list {
+ margin: 5px 0 0 0;
+}
+
+.site {
+ padding: 4px;
+ border-bottom: 1px solid ThreeDLightShadow;
+}
+
+.site-favicon {
+ height: 16px;
+ width: 16px;
+ -moz-margin-end: 4px;
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+
+#all-sites-item > .site-container > .site-favicon {
+ list-style-image: none;
+}
+
+/* permissions box */
+
+#permissions-box {
+ padding-top: 10px;
+ overflow-y: auto;
+}
+
+#site-description {
+ font-size: 125%;
+ -moz-margin-start: 6px; /* to match button margin */
+}
+
+#site-label {
+ font-weight: bold;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+#defaults-description {
+ font-size: 125%;
+ font-weight: bold;
+ -moz-margin-start: 6px;
+}
+
+.pref-item {
+ margin-bottom: 10px;
+}
+
+.pref-icon {
+ width: 36px;
+ height: 36px;
+ -moz-margin-end: 10px;
+}
+
+.pref-icon[type="password"] {
+ list-style-image: url(chrome://mozapps/skin/passwordmgr/key-64.png);
+}
+.pref-icon[type="image"] {
+ list-style-image: url(chrome://global/skin/icons/question-64.png);
+}
+.pref-icon[type="popup"] {
+ list-style-image: url(chrome://global/skin/icons/question-64.png);
+}
+.pref-icon[type="cookie"] {
+ list-style-image: url(chrome://global/skin/icons/question-64.png);
+}
+.pref-icon[type="desktop-notification"] {
+ list-style-image: url(chrome://browser/skin/web-notifications-icon.svg);
+}
+.pref-icon[type="install"] {
+ list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric.png);
+}
+.pref-icon[type="geo"] {
+ list-style-image: url(chrome://browser/skin/Geolocation-64.png);
+}
+.pref-icon[type="plugins"] {
+ list-style-image: url(chrome://mozapps/skin/plugins/pluginGeneric.png);
+}
+
+.pref-title {
+ font-size: 125%;
+ margin-bottom: 0;
+ font-weight: bold;
+ margin-right: 0;
+ padding-right: 0;
+}
+
+.pref-default {
+ margin-left: 0.5em;
+ padding-left: 0;
+}
+
+.pref-set-default {
+ visibility: collapse;
+}
+
+.pref-menulist {
+ margin-left: 6px;
+ margin-right: 6px;
+}
+
+.plugins-label {
+ margin-right: 0;
+ padding-right: 0;
+}
+
+.plugins-vulnerable {
+ margin-left: 0;
+ padding-left: 0;
+ margin-right: 0;
+ padding-right: 0;
+}
+
+.plugins-default {
+ margin-left: 0.5em;
+ padding-left: 0;
+ margin-right: 1em;
+ padding-right: 0;
+}
diff --git a/themes/linux/places/bookmarksMenu.png b/themes/linux/places/bookmarksMenu.png
new file mode 100644
index 0000000..80dd216
--- /dev/null
+++ b/themes/linux/places/bookmarksMenu.png
Binary files differ
diff --git a/themes/linux/places/bookmarksToolbar.png b/themes/linux/places/bookmarksToolbar.png
new file mode 100644
index 0000000..09502fe
--- /dev/null
+++ b/themes/linux/places/bookmarksToolbar.png
Binary files differ
diff --git a/themes/linux/places/calendar.png b/themes/linux/places/calendar.png
new file mode 100644
index 0000000..f712868
--- /dev/null
+++ b/themes/linux/places/calendar.png
Binary files differ
diff --git a/themes/linux/places/downloads.png b/themes/linux/places/downloads.png
new file mode 100644
index 0000000..d641714
--- /dev/null
+++ b/themes/linux/places/downloads.png
Binary files differ
diff --git a/themes/linux/places/editBookmarkOverlay.css b/themes/linux/places/editBookmarkOverlay.css
new file mode 100644
index 0000000..f1f6210
--- /dev/null
+++ b/themes/linux/places/editBookmarkOverlay.css
@@ -0,0 +1,71 @@
+%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
+/**** folder menulist ****/
+.folder-icon > .menulist-label-box > .menulist-icon {
+ width: 16px;
+ height: 16px;
+}
+
+.folder-icon > .menu-iconic-left {
+ display: -moz-box;
+}
+
+.folder-icon {
+ list-style-image: url("moz-icon://stock/gtk-directory?size=menu") !important;
+}
+
+
+/**** expanders ****/
+
+.expander-up,
+.expander-down {
+ min-width: 0;
+ padding: 2px 0;
+ -moz-padding-start: 2px;
+}
+
+.expander-up > .button-box {
+ -moz-appearance: button-arrow-up;
+}
+
+.expander-down > .button-box {
+ -moz-appearance: button-arrow-down;
+}
+
+#editBookmarkPanelContent {
+ min-width: 23em;
+}
+
+#editBMPanel_folderTree {
+ margin-top: 2px;
+ margin-bottom: 2px;
+}
+
+/* Hide the value column of the tag autocomplete popup
+ * leaving only the comment column visible. This is
+ * so that only the tag being edited is shown in the
+ * popup.
+ */
+#editBMPanel_tagsField #treecolAutoCompleteValue {
+ visibility: collapse;
+}
+
+
+/* Bookmark panel dropdown menu items */
+#editBMPanel_folderMenuList[selectedIndex="0"],
+#editBMPanel_toolbarFolderItem {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png") !important;
+}
+
+#editBMPanel_folderMenuList[selectedIndex="1"],
+#editBMPanel_bmRootItem {
+ list-style-image: url("chrome://browser/skin/places/bookmarksMenu.png") !important;
+}
+
+#editBMPanel_folderMenuList[selectedIndex="2"],
+#editBMPanel_unfiledRootItem {
+ list-style-image: url("chrome://browser/skin/places/unsortedBookmarks.png") !important;
+}
diff --git a/themes/linux/places/livemark-item.png b/themes/linux/places/livemark-item.png
new file mode 100644
index 0000000..9184b95
--- /dev/null
+++ b/themes/linux/places/livemark-item.png
Binary files differ
diff --git a/themes/linux/places/organizer.css b/themes/linux/places/organizer.css
new file mode 100644
index 0000000..cabeebb
--- /dev/null
+++ b/themes/linux/places/organizer.css
@@ -0,0 +1,107 @@
+/* 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/. */
+
+
+/* Toolbar */
+#placesToolbar {
+ border: none;
+}
+
+/* back button */
+
+#back-button {
+ list-style-image: url("moz-icon://stock/gtk-go-back-ltr?size=toolbar");
+}
+#back-button[disabled="true"] {
+ list-style-image: url("moz-icon://stock/gtk-go-back-ltr?size=toolbar&state=disabled");
+}
+
+#back-button:-moz-locale-dir(rtl) {
+ list-style-image: url("moz-icon://stock/gtk-go-back-rtl?size=toolbar");
+}
+#back-button[disabled="true"]:-moz-locale-dir(rtl) {
+ list-style-image: url("moz-icon://stock/gtk-go-back-rtl?size=toolbar&state=disabled");
+}
+
+/* forward button */
+
+#forward-button {
+ list-style-image: url("moz-icon://stock/gtk-go-forward-ltr?size=toolbar");
+}
+#forward-button[disabled="true"] {
+ list-style-image: url("moz-icon://stock/gtk-go-forward-ltr?size=toolbar&state=disabled");
+}
+
+#forward-button:-moz-locale-dir(rtl) {
+ list-style-image: url("moz-icon://stock/gtk-go-forward-rtl?size=toolbar");
+}
+#forward-button[disabled="true"]:-moz-locale-dir(rtl) {
+ list-style-image: url("moz-icon://stock/gtk-go-forward-rtl?size=toolbar&state=disabled");
+}
+
+/* Menu */
+#placesMenu {
+ -moz-appearance: none;
+ border: none;
+}
+
+#placesMenu > menu {
+ -moz-padding-start: 4px;
+ -moz-binding: url("chrome://browser/skin/places/organizer.xml#toolbarbutton-dropdown");
+ -moz-appearance: toolbarbutton;
+ color: -moz-DialogText;
+}
+
+#placesMenu > menu:hover:not(:active):not([open="true"]) {
+ color: -moz-buttonhovertext;
+}
+
+#placesMenu > menu > .menubar-right {
+ -moz-appearance: toolbarbutton-dropdown;
+ width: 12px;
+ height: 12px;
+}
+
+/* Root View */
+#placesView {
+ background-color: Window;
+}
+
+/* Info box */
+#detailsDeck {
+ padding: 5px;
+}
+
+#infoBoxExpanderLabel {
+ -moz-padding-start: 2px;
+}
+
+#searchModifiers {
+ padding-right: 3px;
+}
+
+#saveSearch {
+ list-style-image: url("moz-icon://stock/gtk-save?size=menu");
+}
+
+/**** menuitem stock icons ****/
+#orgClose {
+ list-style-image: url("moz-icon://stock/gtk-close?size=menu");
+}
+
+#fileImport {
+ list-style-image: url("moz-icon://stock/gtk-revert-to-saved?size=menu");
+}
+
+#fileExport {
+ list-style-image: url("moz-icon://stock/gtk-save-as?size=menu");
+}
+
+/**
+ * Downloads pane
+ */
+
+#clearDownloadsButton > .toolbarbutton-icon {
+ display: none;
+}
diff --git a/themes/linux/places/organizer.xml b/themes/linux/places/organizer.xml
new file mode 100644
index 0000000..8331ebb
--- /dev/null
+++ b/themes/linux/places/organizer.xml
@@ -0,0 +1,21 @@
+<?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="organizerBindings"
+ 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="toolbarbutton-dropdown"
+ extends="chrome://global/content/bindings/menu.xml#menu-base">
+ <content>
+ <xul:image class="menubar-left" xbl:inherits="src=image"/>
+ <xul:label class="menubar-text" xbl:inherits="value=label,accesskey,crop" crop="right"/>
+ <xul:hbox class="menubar-right"/>
+ <children includes="menupopup"/>
+ </content>
+ </binding>
+</bindings>
diff --git a/themes/linux/places/pageStarred.png b/themes/linux/places/pageStarred.png
new file mode 100644
index 0000000..61a9f90
--- /dev/null
+++ b/themes/linux/places/pageStarred.png
Binary files differ
diff --git a/themes/linux/places/places.css b/themes/linux/places/places.css
new file mode 100644
index 0000000..d2b806b
--- /dev/null
+++ b/themes/linux/places/places.css
@@ -0,0 +1,221 @@
+/* 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/. */
+
+/* Sidebars */
+.sidebar-placesTree {
+ margin: 0;
+}
+
+.sidebar-placesTreechildren::-moz-tree-cell(leaf) ,
+.sidebar-placesTreechildren::-moz-tree-image(leaf) {
+ cursor: pointer;
+}
+
+.sidebar-placesTreechildren::-moz-tree-cell-text(leaf, hover) {
+ cursor: pointer;
+ text-decoration: underline;
+}
+
+.sidebar-placesTreechildren::-moz-tree-cell(separator) {
+ cursor: default;
+}
+
+/* Trees */
+treechildren::-moz-tree-image(title) {
+ padding-right: 2px;
+ margin: 0px 2px;
+ width: 16px;
+ height: 16px;
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+
+treechildren::-moz-tree-image(title, livemarkItem) {
+ list-style-image: url("chrome://browser/skin/places/livemark-item.png");
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+}
+
+treechildren::-moz-tree-image(title, livemarkItem, visited) {
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+treechildren::-moz-tree-image(title, separator) {
+ list-style-image: none;
+ width: 0;
+ height: 0;
+}
+
+treechildren::-moz-tree-image(title, container) {
+ list-style-image: url("moz-icon://stock/gtk-directory?size=menu");
+}
+
+treechildren::-moz-tree-image(title, container, livemark) {
+ list-style-image: url("chrome://browser/skin/feeds/feedIcon16.png");
+ -moz-image-region: auto;
+}
+
+treechildren::-moz-tree-image(container, OrganizerQuery_AllBookmarks) {
+ list-style-image: url("chrome://browser/skin/Toolbar-small.png");
+ -moz-image-region: rect(0px 48px 16px 32px);
+}
+
+treechildren::-moz-tree-image(container, OrganizerQuery_BookmarksToolbar) {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png");
+ -moz-image-region: auto;
+}
+
+treechildren::-moz-tree-image(container, OrganizerQuery_BookmarksMenu) {
+ list-style-image: url("chrome://browser/skin/places/bookmarksMenu.png");
+ -moz-image-region: auto;
+}
+
+treechildren::-moz-tree-image(container, OrganizerQuery_UnfiledBookmarks) {
+ list-style-image: url("chrome://browser/skin/places/unsortedBookmarks.png");
+ -moz-image-region: auto;
+}
+
+/* query-nodes should be styled even if they're not expandable */
+treechildren::-moz-tree-image(title, query) {
+ list-style-image: url("chrome://browser/skin/places/query.png");
+}
+
+treechildren::-moz-tree-image(query, OrganizerQuery_Downloads) {
+ list-style-image: url("chrome://browser/skin/places/downloads.png");
+ -moz-image-region: auto;
+}
+
+treechildren::-moz-tree-image(title, query, tagContainer),
+treechildren::-moz-tree-image(query, OrganizerQuery_Tags) {
+ list-style-image: url("chrome://browser/skin/places/tag.png");
+}
+
+/* calendar icon for folders grouping items by date */
+treechildren::-moz-tree-image(title, query, dayContainer) {
+ list-style-image: url("chrome://browser/skin/places/calendar.png");
+}
+
+treechildren::-moz-tree-image(title, query, hostContainer) {
+ list-style-image: url("moz-icon://stock/gtk-directory?size=menu");
+}
+
+treechildren::-moz-tree-image(title, query, hostContainer, open) {
+ list-style-image: url("moz-icon://stock/gtk-directory?size=menu");
+}
+
+treechildren::-moz-tree-image(title, query, OrganizerQuery_History) {
+ list-style-image: url("chrome://browser/skin/Toolbar-small.png");
+ -moz-image-region: rect(0px 32px 16px 16px);
+}
+
+/* We want some queries to look like ordinary folders. This must come
+ after the (title, query) selector, or it would get overridden. */
+treechildren::-moz-tree-image(title, query, folder) {
+ list-style-image: url("moz-icon://stock/gtk-directory?size=menu");
+}
+
+treechildren::-moz-tree-image(cutting) {
+ opacity: 0.5;
+}
+
+treechildren::-moz-tree-cell-text(cutting) {
+ opacity: 0.7;
+}
+
+/**** menuitem stock icons ****/
+menuitem:not([type]) {
+ -moz-binding: url("chrome://global/content/bindings/menu.xml#menuitem-iconic");
+}
+
+menuitem[command="cmd_cut"],
+menuitem[cmd="cmd_cut"] {
+ list-style-image: url("moz-icon://stock/gtk-cut?size=menu");
+}
+
+menuitem[command="cmd_cut"][disabled],
+menuitem[cmd="cmd_cut"][disabled] {
+ list-style-image: url("moz-icon://stock/gtk-cut?size=menu&state=disabled");
+}
+
+menuitem[command="cmd_copy"],
+menuitem[cmd="cmd_copy"] {
+ list-style-image: url("moz-icon://stock/gtk-copy?size=menu");
+}
+
+menuitem[command="cmd_copy"][disabled],
+menuitem[cmd="cmd_copy"][disabled] {
+ list-style-image: url("moz-icon://stock/gtk-copy?size=menu&state=disabled");
+}
+
+menuitem[command="cmd_paste"],
+menuitem[cmd="cmd_paste"] {
+ list-style-image: url("moz-icon://stock/gtk-paste?size=menu");
+}
+
+menuitem[command="cmd_paste"][disabled],
+menuitem[cmd="cmd_paste"][disabled] {
+ list-style-image: url("moz-icon://stock/gtk-paste?size=menu&state=disabled");
+}
+
+menuitem[command="cmd_delete"],
+menuitem[cmd="cmd_delete"] {
+ list-style-image: url("moz-icon://stock/gtk-delete?size=menu");
+}
+
+menuitem[command="cmd_delete"][disabled],
+menuitem[cmd="cmd_delete"][disabled] {
+ list-style-image: url("moz-icon://stock/gtk-delete?size=menu&state=disabled");
+}
+
+menuitem[command="cmd_undo"],
+menuitem[cmd="cmd_undo"] {
+ list-style-image: url("moz-icon://stock/gtk-undo?size=menu");
+}
+
+menuitem[command="cmd_undo"][disabled],
+menuitem[cmd="cmd_undo"][disabled] {
+ list-style-image: url("moz-icon://stock/gtk-undo?size=menu&state=disabled");
+}
+
+menuitem[command="cmd_redo"] {
+ list-style-image: url("moz-icon://stock/gtk-redo?size=menu");
+}
+
+menuitem[command="cmd_redo"][disabled] {
+ list-style-image: url("moz-icon://stock/gtk-redo?size=menu&state=disabled");
+}
+
+menuitem[command="cmd_selectAll"],
+menuitem[cmd="cmd_selectAll"] {
+ list-style-image: url("moz-icon://stock/gtk-select-all?size=menu");
+}
+
+menuitem[command="cmd_selectAll"][disabled],
+menuitem[cmd="cmd_selectAll"][disabled] {
+ list-style-image: url("moz-icon://stock/gtk-select-all?size=menu&state=disabled");
+}
+
+#placesContext_open\:newwindow,
+menuitem[command="placesCmd_open:window"] {
+ list-style-image: url("chrome://browser/skin/Toolbar-small.png");
+ -moz-image-region: rect(0px 80px 16px 64px);
+}
+
+#placesContext_open\:newprivatewindow,
+menuitem[command="placesCmd_open:privatewindow"] {
+ list-style-image: url("chrome://browser/skin/Privacy-16.png");
+}
+
+#placesContext_open\:newtab,
+menuitem[command="placesCmd_open:tab"] {
+ list-style-image: url("chrome://browser/skin/Toolbar-small.png");
+ -moz-image-region: rect(0px 64px 16px 48px);
+}
+
+#placesContext_show\:info,
+menuitem[command="placesCmd_show:info"] {
+ list-style-image: url("moz-icon://stock/gtk-properties?size=menu");
+}
+
+#placesContext_reload {
+ list-style-image: url("moz-icon://stock/gtk-refresh?size=menu");
+}
diff --git a/themes/linux/places/query.png b/themes/linux/places/query.png
new file mode 100644
index 0000000..2420dee
--- /dev/null
+++ b/themes/linux/places/query.png
Binary files differ
diff --git a/themes/linux/places/star-icons.png b/themes/linux/places/star-icons.png
new file mode 100644
index 0000000..2f50c6a
--- /dev/null
+++ b/themes/linux/places/star-icons.png
Binary files differ
diff --git a/themes/linux/places/starPage.png b/themes/linux/places/starPage.png
new file mode 100644
index 0000000..3193a35
--- /dev/null
+++ b/themes/linux/places/starPage.png
Binary files differ
diff --git a/themes/linux/places/starred48.png b/themes/linux/places/starred48.png
new file mode 100644
index 0000000..deefaec
--- /dev/null
+++ b/themes/linux/places/starred48.png
Binary files differ
diff --git a/themes/linux/places/tag.png b/themes/linux/places/tag.png
new file mode 100644
index 0000000..27176cc
--- /dev/null
+++ b/themes/linux/places/tag.png
Binary files differ
diff --git a/themes/linux/places/toolbarDropMarker.png b/themes/linux/places/toolbarDropMarker.png
new file mode 100644
index 0000000..ed3200f
--- /dev/null
+++ b/themes/linux/places/toolbarDropMarker.png
Binary files differ
diff --git a/themes/linux/places/unsortedBookmarks.png b/themes/linux/places/unsortedBookmarks.png
new file mode 100644
index 0000000..4dcf761
--- /dev/null
+++ b/themes/linux/places/unsortedBookmarks.png
Binary files differ
diff --git a/themes/linux/places/unstarred48.png b/themes/linux/places/unstarred48.png
new file mode 100644
index 0000000..1544863
--- /dev/null
+++ b/themes/linux/places/unstarred48.png
Binary files differ
diff --git a/themes/linux/pointerLock-16.png b/themes/linux/pointerLock-16.png
new file mode 100644
index 0000000..862cd11
--- /dev/null
+++ b/themes/linux/pointerLock-16.png
Binary files differ
diff --git a/themes/linux/pointerLock-64.png b/themes/linux/pointerLock-64.png
new file mode 100644
index 0000000..a35ce04
--- /dev/null
+++ b/themes/linux/pointerLock-64.png
Binary files differ
diff --git a/themes/linux/preferences/Options-sync.png b/themes/linux/preferences/Options-sync.png
new file mode 100644
index 0000000..89901fb
--- /dev/null
+++ b/themes/linux/preferences/Options-sync.png
Binary files differ
diff --git a/themes/linux/preferences/Options.png b/themes/linux/preferences/Options.png
new file mode 100644
index 0000000..82bebd2
--- /dev/null
+++ b/themes/linux/preferences/Options.png
Binary files differ
diff --git a/themes/linux/preferences/alwaysAsk.png b/themes/linux/preferences/alwaysAsk.png
new file mode 100644
index 0000000..45256d4
--- /dev/null
+++ b/themes/linux/preferences/alwaysAsk.png
Binary files differ
diff --git a/themes/linux/preferences/applications.css b/themes/linux/preferences/applications.css
new file mode 100644
index 0000000..c9d1b8c
--- /dev/null
+++ b/themes/linux/preferences/applications.css
@@ -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/. */
+
+/**
+ * Line up the actions menu with action labels above and below it.
+ * Equalize the distance from the left side of the action box to the left side
+ * of the icon for both the menu and the non-menu versions of the action box.
+ * Also make sure the labels are the same distance away from the icons.
+ */
+.actionsMenu {
+ margin-top: -1px;
+ margin-bottom: -1px;
+ -moz-margin-start: -1px;
+ -moz-margin-end: 0;
+}
+
+.typeIcon,
+.actionIcon {
+ -moz-margin-start: 3px;
+ -moz-margin-end: 3px;
+}
+
+richlistitem label {
+ -moz-margin-start: 1px;
+ margin-top: 2px;
+}
+
+richlistitem {
+ min-height: 25px;
+}
+
+richlistitem[appHandlerIcon="ask"],
+menuitem[appHandlerIcon="ask"] {
+ list-style-image: url("chrome://browser/skin/preferences/alwaysAsk.png");
+}
+
+richlistitem[appHandlerIcon="save"],
+menuitem[appHandlerIcon="save"] {
+ list-style-image: url("moz-icon://stock/gtk-save?size=menu");
+}
+
+richlistitem[appHandlerIcon="feed"],
+menuitem[appHandlerIcon="feed"] {
+ list-style-image: url("chrome://browser/skin/page-livemarks.png");
+}
+
+richlistitem[appHandlerIcon="plugin"],
+menuitem[appHandlerIcon="plugin"] {
+ list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric-16.png");
+}
+
+.actionsMenu .menulist-icon {
+ -moz-margin-end: 1px;
+ height: 16px;
+ width: 16px;
+}
+
+.actionsMenu > menupopup > menuitem > .menu-iconic-left {
+ -moz-padding-start: 0;
+ -moz-padding-end: 4px !important;
+}
+
+.actionsMenu > menupopup > menuitem {
+ -moz-padding-start: 3px;
+}
diff --git a/themes/linux/preferences/mail.png b/themes/linux/preferences/mail.png
new file mode 100644
index 0000000..66d2bc9
--- /dev/null
+++ b/themes/linux/preferences/mail.png
Binary files differ
diff --git a/themes/linux/preferences/preferences.css b/themes/linux/preferences/preferences.css
new file mode 100644
index 0000000..52f8658
--- /dev/null
+++ b/themes/linux/preferences/preferences.css
@@ -0,0 +1,156 @@
+%if 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/.
+*/
+%endif
+
+/* Global Styles */
+#BrowserPreferences radio[pane] {
+ list-style-image: url("chrome://browser/skin/preferences/Options.png");
+}
+
+radio[pane=paneMain] {
+ -moz-image-region: rect(0px, 32px, 32px, 0px)
+}
+
+radio[pane=paneTabs] {
+ -moz-image-region: rect(0px, 64px, 32px, 32px)
+}
+
+radio[pane=paneContent] {
+ -moz-image-region: rect(0px, 96px, 32px, 64px)
+}
+
+radio[pane=paneApplications] {
+ -moz-image-region: rect(0px, 128px, 32px, 96px)
+}
+
+radio[pane=panePrivacy] {
+ -moz-image-region: rect(0px, 160px, 32px, 128px)
+}
+
+radio[pane=paneSecurity] {
+ -moz-image-region: rect(0px, 192px, 32px, 160px)
+}
+
+radio[pane=paneAdvanced] {
+ -moz-image-region: rect(0px, 224px, 32px, 192px)
+}
+
+%ifdef MOZ_SERVICES_SYNC
+radio[pane=paneSync] {
+ list-style-image: url("chrome://browser/skin/preferences/Options-sync.png") !important;
+}
+%endif
+
+/* Applications Pane */
+#BrowserPreferences[animated="true"] #handlersView {
+ height: 25em;
+}
+
+#BrowserPreferences[animated="false"] #handlersView {
+ -moz-box-flex: 1;
+}
+
+/* Privacy Pane */
+
+/* styles for the link elements copied from .text-link in global.css */
+.inline-link {
+ color: -moz-nativehyperlinktext;
+ text-decoration: none;
+}
+
+.inline-link:hover {
+ text-decoration: underline;
+}
+
+/* Modeless Window Dialogs */
+.windowDialog,
+.windowDialog prefpane {
+ padding: 0px;
+}
+
+#browserHomePage:-moz-locale-dir(rtl) input {
+ unicode-bidi: plaintext;
+ direction: rtl;
+}
+
+.contentPane {
+ margin: 9px 8px 5px 8px;
+}
+
+.actionButtons {
+ margin: 0px 3px 6px 3px !important;
+}
+
+/* Cookies Manager */
+#cookiesChildren::-moz-tree-image(domainCol) {
+ width: 16px;
+ height: 16px;
+ margin: 0px 2px;
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+
+#paneApplications {
+ margin-left: 4px;
+ margin-right: 4px;
+ padding-left: 0;
+ padding-right: 0;
+}
+
+#linksOpenInBox {
+ margin-top: 5px;
+}
+
+#paneAdvanced {
+ padding-bottom: 10px;
+}
+#advancedPrefs {
+ margin-left: 0;
+ margin-right: 0;
+}
+
+#cookiesChildren::-moz-tree-image(domainCol, container) {
+ list-style-image: url("moz-icon://stock/gtk-directory?size=menu");
+}
+
+#cookieInfoBox {
+ border: 1px solid ThreeDShadow;
+ border-radius: 0px;
+ margin: 4px;
+ padding: 0px;
+}
+
+/* bottom-most box containing a groupbox in a prefpane. Prevents the bottom
+ of the groupbox from being cutoff */
+.bottomBox {
+ padding-bottom: 4px;
+}
+
+/**
+ * Clear Private Data
+ */
+#SanitizeDialogPane > groupbox {
+ margin-top: 0;
+}
+
+%ifdef MOZ_SERVICES_SYNC
+/* Sync Pane */
+
+#syncDesc {
+ padding: 0 8em;
+}
+
+#accountCaptionImage {
+ list-style-image: url("chrome://mozapps/skin/profile/profileicon.png");
+}
+
+#syncAddDeviceLabel {
+ margin-top: 1em;
+ margin-bottom: 1em;
+}
+
+%endif
diff --git a/themes/linux/privatebrowsing-mask.png b/themes/linux/privatebrowsing-mask.png
new file mode 100644
index 0000000..9eaf3ae
--- /dev/null
+++ b/themes/linux/privatebrowsing-mask.png
Binary files differ
diff --git a/themes/linux/sanitizeDialog.css b/themes/linux/sanitizeDialog.css
new file mode 100644
index 0000000..96cf3bc
--- /dev/null
+++ b/themes/linux/sanitizeDialog.css
@@ -0,0 +1,107 @@
+/* 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/. */
+
+#sanitizeDurationChoice {
+ -moz-margin-end: 0;
+}
+
+/* Align the duration label with the warning box and item list */
+#sanitizeDurationLabel {
+ -moz-margin-start: 3px;
+}
+
+
+/* Hide the duration dropdown suffix label if it's empty. Otherwise it
+ takes up a little space, causing the end of the dropdown to not be aligned
+ with the warning box. */
+#sanitizeDurationSuffixLabel[value=""] {
+ display: none;
+}
+
+
+/* Places tree */
+#placesTreechildren::-moz-tree-row(selected),
+#placesTreechildren::-moz-tree-row(grippyRow) {
+ background: #999;
+}
+
+#placesTreechildren::-moz-tree-cell-text(selected) {
+ color: #111;
+}
+
+
+/* Sanitize everything warning box */
+#sanitizeEverythingWarningBox {
+ background-color: Window;
+ border: 1px solid ThreeDDarkShadow;
+ border-radius: 5px;
+ padding: 16px;
+}
+
+#sanitizeEverythingWarningIcon {
+ list-style-image: url("moz-icon://stock/gtk-dialog-warning?size=dialog");
+ padding: 0;
+ margin: 0;
+}
+
+#sanitizeEverythingWarningDescBox {
+ padding: 0 16px;
+ margin: 0;
+}
+
+
+/* Progressive disclosure button */
+#detailsExpanderWrapper {
+ padding: 0;
+ margin-top: 6px;
+ margin-bottom: 6px;
+ -moz-margin-start: -6px;
+ -moz-margin-end: 0;
+}
+
+.expander-up,
+.expander-down {
+ min-width: 0;
+ padding: 2px 0;
+ -moz-padding-start: 2px;
+}
+
+.expander-up {
+ list-style-image: url("chrome://global/skin/arrow/arrow-up.gif");
+}
+
+.expander-down {
+ list-style-image: url("chrome://global/skin/arrow/arrow-dn.gif");
+}
+
+.expander-down:hover:active {
+ list-style-image: url("chrome://global/skin/arrow/arrow-dn-hov.gif");
+}
+
+.expander-up:hover:active {
+ list-style-image: url("chrome://global/skin/arrow/arrow-up-hov.gif");
+}
+
+
+/* Make the item list the same width as the warning box */
+#itemList {
+ -moz-margin-start: 0;
+ -moz-margin-end: 0;
+}
+
+/* Without this a useless scrollbar appears in the listbox when its rows
+ attribute is set to the total number of listitems, as it is currently. See
+ bug 489958 comment 14 and bug 491788. */
+#itemList > listitem {
+ padding: 1px 0;
+}
+
+
+/* Align the last dialog button with the end of the warning box */
+.prefWindow-dlgbuttons {
+ -moz-margin-end: 0;
+}
+.dialog-button[dlgtype="accept"] {
+ -moz-margin-end: 0;
+}
diff --git a/themes/linux/searchbar.css b/themes/linux/searchbar.css
new file mode 100644
index 0000000..cb6ecff
--- /dev/null
+++ b/themes/linux/searchbar.css
@@ -0,0 +1,72 @@
+/* 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/. */
+
+.searchbar-textbox {
+ min-height: 22px;
+ width: 6em;
+ min-width: 6em;
+ background-color: -moz-field;
+}
+
+.autocomplete-textbox-container {
+ -moz-box-align: stretch;
+}
+
+.textbox-input-box {
+ margin: 0;
+}
+
+.searchbar-engine-menuitem[selected="true"] > .menu-iconic-text {
+ font-weight: bold;
+}
+
+/* Engine button */
+.searchbar-engine-image {
+ height: 16px;
+ width: 16px;
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+ -moz-margin-start: 2px;
+}
+
+.searchbar-engine-button {
+ -moz-appearance: none;
+ min-width: 0;
+ margin: 0;
+ border: 0;
+ -moz-box-align: center;
+ background-color: transparent;
+}
+
+.searchbar-engine-button > .button-box {
+ -moz-appearance: none;
+ padding: 2px 0;
+ -moz-padding-end: 2px;
+ border: 0;
+}
+
+.searchbar-dropmarker-image {
+ -moz-appearance: toolbarbutton-dropdown !important;
+ width: 12px;
+ height: 12px;
+}
+
+/* Search go button */
+.search-go-container {
+ -moz-box-align: center;
+}
+
+.search-go-button {
+ padding: 1px;
+ list-style-image: url(moz-icon://stock/gtk-find?size=menu);
+ cursor: pointer;
+}
+
+menuitem[cmd="cmd_clearhistory"] {
+ list-style-image: url("moz-icon://stock/gtk-clear?size=menu");
+}
+
+menuitem[cmd="cmd_clearhistory"][disabled] {
+ list-style-image: url("moz-icon://stock/gtk-clear?size=menu&state=disabled");
+}
+
diff --git a/themes/linux/setDesktopBackground.css b/themes/linux/setDesktopBackground.css
new file mode 100644
index 0000000..585284c
--- /dev/null
+++ b/themes/linux/setDesktopBackground.css
@@ -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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
+
+html|canvas#screen {
+ margin: 12px 11px 32px;
+}
+
+#monitor {
+ list-style-image: url("chrome://browser/skin/monitor.png");
+}
+
+#monitor[aspectratio="16:10"] {
+ list-style-image: url("chrome://browser/skin/monitor_16-10.png");
+}
diff --git a/themes/linux/slowStartup-16.png b/themes/linux/slowStartup-16.png
new file mode 100644
index 0000000..834dc0f
--- /dev/null
+++ b/themes/linux/slowStartup-16.png
Binary files differ
diff --git a/themes/linux/statusbar/overlay.css b/themes/linux/statusbar/overlay.css
new file mode 100644
index 0000000..2351aac
--- /dev/null
+++ b/themes/linux/statusbar/overlay.css
@@ -0,0 +1,114 @@
+/* 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");
+
+%include ../../shared/statusbar/overlay.css
+
+/*
+ * General
+ */
+
+#status4evar-status-text,
+#status4evar-progress-bar
+{
+ margin: 0px 4px;
+}
+
+/*
+ * Download status
+ */
+
+#status4evar-download-progress-bar
+{
+ height: 6px;
+}
+
+toolbar[iconsize="small"] #status4evar-download-progress-bar
+{
+ height: 4px;
+}
+
+#status4evar-download-button[attention] #status4evar-download-icon
+{
+ background-image: url("chrome://browser/skin/downloads/download-glow.png");
+}
+
+#status4evar-download-button #status4evar-download-icon
+{
+ background: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 0, 24, 24, 0) center no-repeat;
+ min-width: 24px;
+ min-height: 24px;
+}
+
+toolbar[iconsize="small"] #status4evar-download-button #status4evar-download-icon
+{
+ background: -moz-image-rect(url("chrome://browser/skin/Toolbar-small.png"), 0, 16, 16, 0) center no-repeat;
+ min-width: 16px;
+ min-height: 16px;
+}
+
+toolbar[iconsize="small"] > #status4evar-download-button[attention] #status4evar-download-icon {
+ background-image: url("chrome://browser/skin/downloads/download-glow-small.png");
+}
+
+toolbar[mode="icons"] #status4evar-download-button[forcelabel="true"] > label
+{
+ margin: 0px 2px !important;
+ margin-top: -1px !important;
+ -moz-margin-start: 4px !important;
+}
+
+/*
+ * Splitter
+ */
+
+splitter.status4evar-status-splitter
+{
+ width: 8px;
+ margin: 0px -4px;
+}
+
+/*
+ * Location bar
+ */
+
+#urlbar-progress-alt
+{
+ margin: -1px;
+}
+
+#notification-popup-box
+{
+ -moz-margin-start: -1px;
+}
+
+#urlbar .urlbar-over-link-box
+{
+ margin-top: -1px;
+ margin-bottom: -1px;
+}
+
+/*
+ * Add-on bar
+ */
+
+#browser-bottombox[s4eboarder="true"] :-moz-any(#status4evar-status-bar, #addon-bar)
+{
+ -moz-appearance: none;
+}
+
+#browser-bottombox[s4eboarder="true"] > *:not([hidden="true"]):not([collapsed="true"])
+{
+ box-shadow: none !important;
+ border: none !important;
+ border-top: 2px solid !important;
+ -moz-border-top-colors: ThreeDShadow ThreeDHighlight !important;
+}
+
+#browser-bottombox[s4eboarder="true"] > *:not([hidden="true"]):not([collapsed="true"]) ~ *
+{
+ border: none !important;
+}
+
diff --git a/themes/linux/statusbar/prefs.css b/themes/linux/statusbar/prefs.css
new file mode 100644
index 0000000..db6f24e
--- /dev/null
+++ b/themes/linux/statusbar/prefs.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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+%include ../../shared/statusbar/prefs.css
+
diff --git a/themes/linux/sync-128.png b/themes/linux/sync-128.png
new file mode 100644
index 0000000..1ea3481
--- /dev/null
+++ b/themes/linux/sync-128.png
Binary files differ
diff --git a/themes/linux/sync-16-throbber.png b/themes/linux/sync-16-throbber.png
new file mode 100644
index 0000000..d6f801a
--- /dev/null
+++ b/themes/linux/sync-16-throbber.png
Binary files differ
diff --git a/themes/linux/sync-16.png b/themes/linux/sync-16.png
new file mode 100644
index 0000000..0afb1c7
--- /dev/null
+++ b/themes/linux/sync-16.png
Binary files differ
diff --git a/themes/linux/sync-24-throbber.png b/themes/linux/sync-24-throbber.png
new file mode 100644
index 0000000..5587174
--- /dev/null
+++ b/themes/linux/sync-24-throbber.png
Binary files differ
diff --git a/themes/linux/sync-32.png b/themes/linux/sync-32.png
new file mode 100644
index 0000000..7a762cb
--- /dev/null
+++ b/themes/linux/sync-32.png
Binary files differ
diff --git a/themes/linux/sync-bg.png b/themes/linux/sync-bg.png
new file mode 100644
index 0000000..893a27d
--- /dev/null
+++ b/themes/linux/sync-bg.png
Binary files differ
diff --git a/themes/linux/sync-desktopIcon.png b/themes/linux/sync-desktopIcon.png
new file mode 100644
index 0000000..d3d1e27
--- /dev/null
+++ b/themes/linux/sync-desktopIcon.png
Binary files differ
diff --git a/themes/linux/sync-mobileIcon.png b/themes/linux/sync-mobileIcon.png
new file mode 100644
index 0000000..a3bda57
--- /dev/null
+++ b/themes/linux/sync-mobileIcon.png
Binary files differ
diff --git a/themes/linux/syncCommon.css b/themes/linux/syncCommon.css
new file mode 100644
index 0000000..9a84ceb
--- /dev/null
+++ b/themes/linux/syncCommon.css
@@ -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/. */
+
+/* The following are used by both sync/setup.xul and sync/genericChange.xul */
+.status {
+ color: -moz-dialogtext;
+}
+
+.statusIcon {
+ -moz-margin-start: 4px;
+ max-height: 16px;
+ max-width: 16px;
+}
+
+.statusIcon[status="active"] {
+ list-style-image: url("chrome://global/skin/icons/loading_16.png");
+}
+
+.statusIcon[status="error"] {
+ list-style-image: url("moz-icon://stock/gtk-dialog-error?size=menu");
+}
+
+.statusIcon[status="success"] {
+ list-style-image: url("moz-icon://stock/gtk-dialog-info?size=menu");
+}
+
+/* .data is only used by sync/genericChange.xul, but it seems unnecessary to have
+ a separate stylesheet for it. */
+.data {
+ font-size: 90%;
+ font-weight: bold;
+}
+
+dialog#change-dialog {
+ width: 40em;
+}
+
+image#syncIcon {
+ list-style-image: url("chrome://browser/skin/sync-32.png");
+}
+
+#introText {
+ margin-top: 2px;
+}
+
+#feedback {
+ height: 2em;
+}
diff --git a/themes/linux/syncProgress.css b/themes/linux/syncProgress.css
new file mode 100644
index 0000000..d7aa599
--- /dev/null
+++ b/themes/linux/syncProgress.css
@@ -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/. */
+@import url(chrome://global/skin/inContentUI.css);
+
+:root {
+ height: 100%;
+ width: 100%;
+ padding: 0;
+}
+
+body {
+ margin: 0;
+ padding: 0 2em;
+}
+
+#floatingBox {
+ margin: 4em auto;
+ max-width: 40em;
+ min-width: 23em;
+ padding: 1em 1.5em;
+ position: relative;
+ text-align: center;
+}
+
+#successLogo {
+ margin: 1em 2em;
+}
+
+#loadingText {
+ margin: 2em 6em;
+}
+
+#progressBar {
+ margin: 2em 10em;
+}
+
+#uploadProgressBar{
+ width: 100%;
+}
+
+#bottomRow {
+ margin-top: 2em;
+ padding: 0;
+ text-align: end;
+}
diff --git a/themes/linux/syncQuota.css b/themes/linux/syncQuota.css
new file mode 100644
index 0000000..1577de8
--- /dev/null
+++ b/themes/linux/syncQuota.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/. */
+
+#quotaDialog {
+ width: 33em;
+ height: 25em;
+}
+
+treechildren::-moz-tree-checkbox {
+ list-style-image: none;
+}
+treechildren::-moz-tree-checkbox(checked) {
+ list-style-image: url("chrome://global/skin/checkbox/cbox-check.gif");
+}
+treechildren::-moz-tree-checkbox(disabled) {
+ list-style-image: url("chrome://global/skin/checkbox/cbox-check-dis.gif");
+}
+
+#treeCaption {
+ height: 4em;
+}
+
+.captionWarning {
+ font-weight: bold;
+}
diff --git a/themes/linux/syncSetup.css b/themes/linux/syncSetup.css
new file mode 100644
index 0000000..4c6518a
--- /dev/null
+++ b/themes/linux/syncSetup.css
@@ -0,0 +1,127 @@
+/* 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/. */
+
+wizard {
+ -moz-appearance: none;
+ width: 55em;
+ height: 45em;
+ padding: 0;
+ background-color: Window;
+}
+
+.wizard-page-box {
+ -moz-appearance: none;
+ padding-left: 0;
+ padding-right: 0;
+ margin: 0;
+}
+
+wizardpage {
+ -moz-box-pack: center;
+ -moz-box-align: center;
+ margin: 0;
+ padding: 0 6em;
+ background-color: Window;
+}
+
+.wizard-header {
+ -moz-appearance: none;
+ border: none;
+ padding: 2em 0 1em 0;
+ text-align: center;
+}
+.wizard-header-label {
+ font-size: 24pt;
+ font-weight: normal;
+}
+
+.wizard-buttons {
+ background-color: rgba(0,0,0,0.1);
+ padding: 1em;
+}
+
+.wizard-buttons-separator {
+ visibility: collapse;
+}
+
+.wizard-header-icon {
+ visibility: collapse;
+}
+
+.accountChoiceButton {
+ font: menu;
+}
+
+.confirm {
+ border: 1px solid black;
+ padding: 1em;
+ border-radius: 5px;
+}
+
+/* Override the text-link style from global.css */
+description > .text-link,
+description > .text-link:focus {
+ margin: 0px;
+ padding: 0px;
+ border: 0px;
+}
+
+
+.success,
+.error {
+ padding: 2px;
+ border-radius: 2px;
+}
+
+.error {
+ background-color: #FF0000 !important;
+ color: #FFFFFF !important;
+}
+
+.success {
+ background-color: #00FF00 !important;
+}
+
+.warning {
+ font-weight: bold;
+ font-size: 100%;
+ color: red;
+}
+
+.mainDesc {
+ font-weight: bold;
+ font-size: 100%;
+}
+
+.normal {
+ font-size: 100%;
+}
+
+.inputColumn {
+ -moz-margin-end: 2px
+}
+
+.pin {
+ font-size: 18pt;
+ width: 4em;
+ text-align: center;
+}
+
+#passphraseHelpSpacer {
+ width: 0.5em;
+}
+
+#pairDeviceThrobber > image,
+#login-throbber > image {
+ list-style-image: url("chrome://global/skin/icons/loading_16.png");
+}
+
+#captchaFeedback {
+ visibility: hidden;
+}
+
+#successPageIcon {
+ /* TODO replace this with a 128px version (bug 591122) */
+ list-style-image: url("chrome://browser/skin/sync-32.png");
+}
diff --git a/themes/linux/tabbrowser/alltabs.png b/themes/linux/tabbrowser/alltabs.png
new file mode 100644
index 0000000..2f19025
--- /dev/null
+++ b/themes/linux/tabbrowser/alltabs.png
Binary files differ
diff --git a/themes/linux/tabbrowser/connecting.png b/themes/linux/tabbrowser/connecting.png
new file mode 100644
index 0000000..e564fb5
--- /dev/null
+++ b/themes/linux/tabbrowser/connecting.png
Binary files differ
diff --git a/themes/linux/tabbrowser/loading.png b/themes/linux/tabbrowser/loading.png
new file mode 100644
index 0000000..55f25e5
--- /dev/null
+++ b/themes/linux/tabbrowser/loading.png
Binary files differ
diff --git a/themes/linux/tabbrowser/tab-overflow-border.png b/themes/linux/tabbrowser/tab-overflow-border.png
new file mode 100644
index 0000000..77f2462
--- /dev/null
+++ b/themes/linux/tabbrowser/tab-overflow-border.png
Binary files differ
diff --git a/themes/linux/tabbrowser/tab.png b/themes/linux/tabbrowser/tab.png
new file mode 100644
index 0000000..deeeb0a
--- /dev/null
+++ b/themes/linux/tabbrowser/tab.png
Binary files differ
diff --git a/themes/linux/tabbrowser/tabDragIndicator.png b/themes/linux/tabbrowser/tabDragIndicator.png
new file mode 100644
index 0000000..df7d914
--- /dev/null
+++ b/themes/linux/tabbrowser/tabDragIndicator.png
Binary files differ
diff --git a/themes/linux/urlbar-arrow.png b/themes/linux/urlbar-arrow.png
new file mode 100644
index 0000000..fcab253
--- /dev/null
+++ b/themes/linux/urlbar-arrow.png
Binary files differ
diff --git a/themes/linux/web-notifications-icon.svg b/themes/linux/web-notifications-icon.svg
new file mode 100644
index 0000000..f7186c7
--- /dev/null
+++ b/themes/linux/web-notifications-icon.svg
@@ -0,0 +1,15 @@
+<?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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid" width="64" height="64" viewBox="0 0 64 64">
+ <defs>
+ <style>
+ .icon {
+ fill: #a6a6a6;
+ fill-rule: evenodd;
+ }
+ </style>
+ </defs>
+ <path d="M57,48 L46,48 L46,60.016 L32.482,48 L7,48 C5.343,48 4,46.657 4,45 L4,11.031 C4,9.374 5.343,8.031 7,8.031 L57,8.031 C58.657,8.031 60,9.374 60,11.031 L60,45 C60,46.657 58.657,48 57,48 ZM36,16.031 C36,14.927 35.105,14.031 34,14.031 L30,14.031 C28.895,14.031 28,14.927 28,16.031 L28,30.031 C28,31.136 28.895,32.031 30,32.031 L34,32.031 C35.105,32.031 36,31.136 36,30.031 L36,16.031 ZM36,37.5 C36,36.672 35.328,36 34.5,36 L29.5,36 C28.672,36 28,36.672 28,37.5 L28,40.5 C28,41.328 28.672,42 29.5,42 L34.5,42 C35.328,42 36,41.328 36,40.5 L36,37.5 Z" class="icon"/>
+</svg>
diff --git a/themes/linux/web-notifications-tray.svg b/themes/linux/web-notifications-tray.svg
new file mode 100644
index 0000000..314026a
--- /dev/null
+++ b/themes/linux/web-notifications-tray.svg
@@ -0,0 +1,23 @@
+<?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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="16" viewBox="0 0 96 32">
+ <defs>
+ <style>
+ .style-icon-notification {
+ fill: #666666;
+ }
+ .style-icon-notification.hover {
+ fill: #808080;
+ }
+ .style-icon-notification.active {
+ fill: #4d4d4d;
+ }
+ </style>
+ <path id="shape-notifcations-push" d="M27,23.969 L24,23.969 L24,29.977 L17.241,23.969 L5,23.969 C3.343,23.969 2,22.626 2,20.969 L2,6.969 C2,5.312 3.343,3.969 5,3.969 L27,3.969 C28.657,3.969 30,5.312 30,6.969 L30,20.969 C30,22.626 28.657,23.969 27,23.969 ZM18,8.969 C18,7.864 17.105,6.969 16,6.969 C14.895,6.969 14,7.864 14,8.969 L14,13.969 C14,15.073 14.895,15.969 16,15.969 C17.105,15.969 18,15.073 18,13.969 L18,8.969 ZM16.5,17.969 L15.5,17.969 C14.672,17.969 14,18.640 14,19.469 C14,20.297 14.672,20.969 15.5,20.969 L16.5,20.969 C17.328,20.969 18,20.297 18,19.469 C18,18.640 17.328,17.969 16.5,17.969 Z"/>
+ </defs>
+ <use xlink:href="#shape-notifcations-push" class="style-icon-notification"/>
+ <use xlink:href="#shape-notifcations-push" transform="translate(32)" class="style-icon-notification hover"/>
+ <use xlink:href="#shape-notifcations-push" transform="translate(64)" class="style-icon-notification active"/>
+</svg>
diff --git a/themes/linux/webRTC-shareDevice-16.png b/themes/linux/webRTC-shareDevice-16.png
new file mode 100644
index 0000000..8bc5b3a
--- /dev/null
+++ b/themes/linux/webRTC-shareDevice-16.png
Binary files differ
diff --git a/themes/linux/webRTC-shareDevice-64.png b/themes/linux/webRTC-shareDevice-64.png
new file mode 100644
index 0000000..d125789
--- /dev/null
+++ b/themes/linux/webRTC-shareDevice-64.png
Binary files differ
diff --git a/themes/linux/webRTC-sharingDevice-16.png b/themes/linux/webRTC-sharingDevice-16.png
new file mode 100644
index 0000000..a670676
--- /dev/null
+++ b/themes/linux/webRTC-sharingDevice-16.png
Binary files differ
diff --git a/themes/moz.build b/themes/moz.build
new file mode 100644
index 0000000..5040c10
--- /dev/null
+++ b/themes/moz.build
@@ -0,0 +1,14 @@
+# -*- 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/.
+
+toolkit = CONFIG['MOZ_WIDGET_TOOLKIT']
+
+if toolkit == 'cocoa':
+ DIRS += ['osx']
+elif toolkit in ('gtk2', 'gtk3', 'qt'):
+ DIRS += ['linux']
+else:
+ DIRS += ['windows']
diff --git a/themes/osx/Geolocation-16.png b/themes/osx/Geolocation-16.png
new file mode 100644
index 0000000..d710e73
--- /dev/null
+++ b/themes/osx/Geolocation-16.png
Binary files differ
diff --git a/themes/osx/Geolocation-64.png b/themes/osx/Geolocation-64.png
new file mode 100644
index 0000000..1bd46ba
--- /dev/null
+++ b/themes/osx/Geolocation-64.png
Binary files differ
diff --git a/themes/osx/Info.png b/themes/osx/Info.png
new file mode 100644
index 0000000..d09e82b
--- /dev/null
+++ b/themes/osx/Info.png
Binary files differ
diff --git a/themes/osx/KUI-background.png b/themes/osx/KUI-background.png
new file mode 100644
index 0000000..104a49f
--- /dev/null
+++ b/themes/osx/KUI-background.png
Binary files differ
diff --git a/themes/osx/KUI-close.png b/themes/osx/KUI-close.png
new file mode 100644
index 0000000..08eeb81
--- /dev/null
+++ b/themes/osx/KUI-close.png
Binary files differ
diff --git a/themes/osx/Makefile.in b/themes/osx/Makefile.in
new file mode 100644
index 0000000..173ca68
--- /dev/null
+++ b/themes/osx/Makefile.in
@@ -0,0 +1,7 @@
+# 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/.
+
+ICON_FILES := icon.png
+ICON_DEST = $(FINAL_TARGET)/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}
+INSTALL_TARGETS += ICON
diff --git a/themes/osx/Privacy-16.png b/themes/osx/Privacy-16.png
new file mode 100644
index 0000000..a4be3e9
--- /dev/null
+++ b/themes/osx/Privacy-16.png
Binary files differ
diff --git a/themes/osx/Privacy-32.png b/themes/osx/Privacy-32.png
new file mode 100644
index 0000000..dd07c1a
--- /dev/null
+++ b/themes/osx/Privacy-32.png
Binary files differ
diff --git a/themes/osx/Privacy-48.png b/themes/osx/Privacy-48.png
new file mode 100644
index 0000000..2396f99
--- /dev/null
+++ b/themes/osx/Privacy-48.png
Binary files differ
diff --git a/themes/osx/Privacy-64.png b/themes/osx/Privacy-64.png
new file mode 100644
index 0000000..d360501
--- /dev/null
+++ b/themes/osx/Privacy-64.png
Binary files differ
diff --git a/themes/osx/Search-glass.png b/themes/osx/Search-glass.png
new file mode 100644
index 0000000..9eb0e25
--- /dev/null
+++ b/themes/osx/Search-glass.png
Binary files differ
diff --git a/themes/osx/Secure24.png b/themes/osx/Secure24.png
new file mode 100644
index 0000000..896343a
--- /dev/null
+++ b/themes/osx/Secure24.png
Binary files differ
diff --git a/themes/osx/Toolbar-glass.png b/themes/osx/Toolbar-glass.png
new file mode 100644
index 0000000..23cc4bf
--- /dev/null
+++ b/themes/osx/Toolbar-glass.png
Binary files differ
diff --git a/themes/osx/Toolbar-inverted.png b/themes/osx/Toolbar-inverted.png
new file mode 100644
index 0000000..2c3253f
--- /dev/null
+++ b/themes/osx/Toolbar-inverted.png
Binary files differ
diff --git a/themes/osx/Toolbar.png b/themes/osx/Toolbar.png
new file mode 100644
index 0000000..3d1b80e
--- /dev/null
+++ b/themes/osx/Toolbar.png
Binary files differ
diff --git a/themes/osx/aboutCertError.css b/themes/osx/aboutCertError.css
new file mode 100644
index 0000000..dbb3530
--- /dev/null
+++ b/themes/osx/aboutCertError.css
@@ -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/. */
+
+
+html {
+ background: #833;
+}
+
+body {
+ margin: 0;
+ padding: 0 1em;
+ color: -moz-FieldText;
+ font: message-box;
+}
+
+h1 {
+ margin: 0 0 .6em 0;
+ border-bottom: 1px solid ThreeDLightShadow;
+ font-size: 160%;
+}
+
+h2 {
+ font-size: 130%;
+}
+
+#errorPageContainer {
+ position: relative;
+ min-width: 13em;
+ max-width: 52em;
+ margin: 4em auto;
+ border: 2px solid #DD0D09;
+ border-radius: 10px;
+ box-shadow: 0px 0px 8px red;
+ padding: 3em;
+ -moz-padding-start: 30px;
+ background: url("chrome://global/skin/icons/sslWarning.png") left 0 no-repeat -moz-Field;
+ background-origin: content-box;
+}
+
+#errorPageContainer:-moz-dir(rtl) {
+ background-position: right 0;
+}
+
+#errorTitle {
+ -moz-margin-start: 80px;
+}
+
+#errorLongContent {
+ -moz-margin-start: 80px;
+}
+
+.expander > button {
+ -moz-padding-start: 20px;
+ -moz-margin-start: -20px;
+ background: url("chrome://browser/skin/aboutCertError_sectionExpanded.png") left center no-repeat;
+ border: none;
+ font: inherit;
+ color: inherit;
+ cursor: pointer;
+}
+
+.expander > button:-moz-dir(rtl) {
+ background-position: right center;
+}
+
+.expander[collapsed] > button {
+ background-image: url("chrome://browser/skin/aboutCertError_sectionCollapsed.png");
+}
+
+.expander[collapsed] > button:-moz-dir(rtl) {
+ background-image: url("chrome://browser/skin/aboutCertError_sectionCollapsed-rtl.png");
+}
diff --git a/themes/osx/aboutCertError_sectionCollapsed-rtl.png b/themes/osx/aboutCertError_sectionCollapsed-rtl.png
new file mode 100644
index 0000000..84ba18c
--- /dev/null
+++ b/themes/osx/aboutCertError_sectionCollapsed-rtl.png
Binary files differ
diff --git a/themes/osx/aboutCertError_sectionCollapsed.png b/themes/osx/aboutCertError_sectionCollapsed.png
new file mode 100644
index 0000000..c9805f6
--- /dev/null
+++ b/themes/osx/aboutCertError_sectionCollapsed.png
Binary files differ
diff --git a/themes/osx/aboutCertError_sectionExpanded.png b/themes/osx/aboutCertError_sectionExpanded.png
new file mode 100644
index 0000000..128cef9
--- /dev/null
+++ b/themes/osx/aboutCertError_sectionExpanded.png
Binary files differ
diff --git a/themes/osx/aboutPrivateBrowsing.css b/themes/osx/aboutPrivateBrowsing.css
new file mode 100644
index 0000000..cd6026b
--- /dev/null
+++ b/themes/osx/aboutPrivateBrowsing.css
@@ -0,0 +1,47 @@
+%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
+
+body.private > #errorPageContainer {
+ background-image: url("chrome://browser/skin/Privacy-48.png");
+}
+
+body.normal > #errorPageContainer {
+ background-image: url("chrome://global/skin/icons/question-48.png");
+}
+
+#clearRecentHistoryDesc {
+ margin-top: 2em;
+}
+
+#clearRecentHistoryDesc > p {
+ font-size: 110%; /* to match the value set in chrome://global/skin/netError.css */
+}
+
+#startPrivateBrowsingDesc > button {
+ -moz-margin-start: 0;
+}
+
+#footerDesc > p {
+ font-size: 110%; /* to match the value set in chrome://global/skin/netError.css */
+}
+
+#moreInfo {
+ font-size: 110%; /* to match the value set in chrome://global/skin/netError.css */
+ -moz-padding-start: 25px;
+ background: url("chrome://global/skin/icons/information-16.png") no-repeat top left;
+}
+
+#moreInfo:-moz-dir(rtl) {
+ background-position: top right;
+}
+
+#moreInfoText {
+ margin-bottom: 0;
+}
+
+#moreInfoLinkContainer {
+ margin-top: 0.5em;
+}
diff --git a/themes/osx/aboutSessionRestore.css b/themes/osx/aboutSessionRestore.css
new file mode 100644
index 0000000..4fa4907
--- /dev/null
+++ b/themes/osx/aboutSessionRestore.css
@@ -0,0 +1,73 @@
+%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 {
+ background: #f8ffd0;
+ height: 100%;
+}
+
+body {
+ height: 100%;
+ text-align: center;
+}
+
+#errorPageContainer {
+ background-image: url("chrome://global/skin/icons/warning-large.png");
+ display: -moz-box;
+ width: -moz-available;
+ max-width: 85%;
+ height: 75%;
+ max-height: 85%;
+ -moz-box-orient: vertical;
+ text-align: start;
+ border: 2px solid #efc;
+ box-shadow: 0px 0px 8px #aaa;
+}
+
+#errorShortDesc > p {
+ margin-top: 0.4em;
+ margin-bottom: 0;
+}
+
+#errorLongContent, #errorTrailerDesc {
+ display: -moz-box;
+ -moz-box-flex: 1;
+ -moz-box-orient: vertical;
+}
+
+#tabList {
+ margin-top: 2.5em;
+ width: 100%;
+ min-height: 12em;
+}
+
+treechildren::-moz-tree-image(icon),
+treechildren::-moz-tree-image(noicon) {
+ padding-right: 2px;
+ margin: 0px 2px;
+ width: 16px;
+ height: 16px;
+}
+
+treechildren::-moz-tree-image(noicon) {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+treechildren::-moz-tree-image(container, noicon) {
+ list-style-image: url("chrome://browser/skin/aboutSessionRestore-window-icon.png");
+}
+treechildren::-moz-tree-image(checked) {
+ list-style-image: url("chrome://global/skin/checkbox/cbox-check.gif");
+}
+treechildren::-moz-tree-image(partial) {
+ list-style-image: url("chrome://global/skin/checkbox/cbox-check-dis.gif");
+}
+
+#buttons {
+ width: 100%;
+}
+#buttons > button {
+ margin-top: 2em;
+}
diff --git a/themes/osx/aboutSyncTabs.css b/themes/osx/aboutSyncTabs.css
new file mode 100644
index 0000000..4f21a9d
--- /dev/null
+++ b/themes/osx/aboutSyncTabs.css
@@ -0,0 +1,101 @@
+/* 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/. */
+
+#tabs-display,
+#tabsList {
+ background-color: transparent;
+ -moz-appearance: none;
+ margin: 0;
+}
+
+#tabsList {
+ width: 100%;
+}
+
+#tabs-display {
+ background: #fff url(chrome://browser/skin/sync-bg.png) repeat-x center -80px;
+}
+
+#headers {
+ background: url(chrome://browser/skin/sync-32.png) no-repeat;
+ margin-top: 4px;
+ width: 45em;
+ height: 32px;
+ -moz-margin-start: 2em;
+ -moz-margin-end: 2em;
+}
+
+#tabsListHeading {
+ font-size: 140%;
+ font-weight: bold;
+ -moz-margin-start: 40px;
+}
+
+richlistitem {
+ -moz-margin-end: 2em;
+}
+
+richlistitem[selected="true"],
+richlistitem:focus {
+ outline-style: none;
+}
+
+richlistitem[type="tab"] {
+ min-height: 3em;
+ border: #999999 1px solid !important;
+ padding: 2px 5px;
+ margin-bottom: 4px;
+ -moz-margin-start: 4em;
+ border-radius: 6px;
+ background-color: menu;
+ width: 44em;
+ opacity: 0.9;
+ box-shadow:
+ inset rgba(255, 255, 255, 0.5) 0 1px 0px,
+ inset rgba(0, 0, 0, 0.1) 0 -2px 0px,
+ rgba(0, 0, 0, 0.1) 0px 1px 0px;
+}
+
+richlistitem[type="tab"][selected="true"] {
+ background-color: -moz-MenuHover;
+}
+
+richlistitem[type="client"] {
+ min-height: 2em;
+ color: #000000;
+ -moz-margin-start: 2em;
+ margin-top: 2px;
+ margin-bottom: 3px;
+ width: 42em;
+ border-radius: 6px;
+ background-color: transparent;
+ -moz-user-focus: ignore !important;
+}
+richlistitem.mobile[type="client"] {
+ list-style-image: url("chrome://browser/skin/sync-mobileIcon.png");
+}
+richlistitem.desktop[type="client"] {
+ list-style-image: url("chrome://browser/skin/sync-desktopIcon.png");
+}
+
+.title,
+.clientName {
+ color: #000000;
+ font-size: 1.1em;
+}
+
+.title[selected="true"],
+.url[selected="true"] {
+ color: inherit;
+}
+
+.url {
+ color: -moz-nativehyperlinktext;
+ font-size: 0.95em;
+}
+
+.tabIcon {
+ -moz-padding-start: 2px;
+ padding-top: 2px;
+}
diff --git a/themes/osx/actionicon-tab.png b/themes/osx/actionicon-tab.png
new file mode 100644
index 0000000..ced958e
--- /dev/null
+++ b/themes/osx/actionicon-tab.png
Binary files differ
diff --git a/themes/osx/appmenu-dropmarker.png b/themes/osx/appmenu-dropmarker.png
new file mode 100644
index 0000000..27deaff
--- /dev/null
+++ b/themes/osx/appmenu-dropmarker.png
Binary files differ
diff --git a/themes/osx/appmenu-icons.png b/themes/osx/appmenu-icons.png
new file mode 100644
index 0000000..78f3658
--- /dev/null
+++ b/themes/osx/appmenu-icons.png
Binary files differ
diff --git a/themes/osx/autocomplete.css b/themes/osx/autocomplete.css
new file mode 100644
index 0000000..a50dbd8
--- /dev/null
+++ b/themes/osx/autocomplete.css
@@ -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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
+
+/* .padded is used by autocomplete widgets that don't have an icon. Gross. -dwh */
+textbox:not(.padded) {
+ cursor: default;
+ padding: 0;
+}
+
+textbox[nomatch="true"][highlightnonmatches="true"] {
+ color: red;
+}
+
+textbox:not(.padded) .textbox-input-box {
+ margin: 0 3px;
+}
+
+.textbox-input-box {
+ -moz-box-align: center;
+}
+
+/* ::::: history button ::::: */
+
+.private-autocomplete-history-dropmarker {
+ -moz-appearance: none !important;
+ border: none !important;
+ background-color: transparent !important;
+ padding: 0px;
+ list-style-image: url("chrome://global/skin/icons/autocomplete-dropmarker.png");
+ margin: 0px;
+}
+
+/* ::::: autocomplete popups ::::: */
+
+panel[type="private-autocomplete"],
+panel[type="private-autocomplete-richlistbox"],
+.private-autocomplete-history-popup {
+ padding: 0px !important;
+ color: -moz-FieldText;
+ background-color: -moz-Field;
+ font: icon;
+ -moz-appearance: none;
+}
+
+.private-autocomplete-history-popup {
+ max-height: 180px;
+}
+
+/* ::::: tree ::::: */
+
+.private-autocomplete-tree {
+ -moz-appearance: none !important;
+ border: none !important;
+ background-color: transparent !important;
+}
+
+.private-autocomplete-treecol {
+ -moz-appearance: none !important;
+ margin: 0 !important;
+ border: none !important;
+ padding: 0 !important;
+}
+
+.private-autocomplete-treebody::-moz-tree-cell-text {
+ padding-left: 2px;
+}
+
+.private-autocomplete-treebody::-moz-tree-row {
+ border-top: none;
+}
+
+treechildren.private-autocomplete-treebody::-moz-tree-row(selected) {
+ background-color: Highlight;
+}
+
+treechildren.private-autocomplete-treebody::-moz-tree-cell-text(selected) {
+ color: HighlightText !important;
+}
+
+.private-autocomplete-treebody::-moz-tree-image(treecolAutoCompleteValue) {
+ max-width: 16px;
+ height: 16px;
+}
+
+/* ::::: richlistbox autocomplete ::::: */
+
+.private-autocomplete-richlistbox {
+ -moz-appearance: none;
+ margin: 0;
+}
+
+.private-autocomplete-richlistitem[selected="true"] {
+ background-color: Highlight;
+ color: HighlightText;
+ background-image: linear-gradient(rgba(255,255,255,0.3), transparent);
+}
+
+.private-autocomplete-richlistitem {
+ padding: 5px 2px;
+}
+
+.ac-url-box {
+ /* When setting a vertical margin here, half of that needs to be added
+ .ac-title-box's translateY for when .ac-url-box is hidden (see below). */
+ margin-top: 1px;
+}
+
+.private-autocomplete-richlistitem[actiontype="keyword"] .ac-url-box,
+.private-autocomplete-richlistitem[actiontype="searchengine"] .ac-url-box,
+.private-autocomplete-richlistitem[actiontype="visiturl"] .ac-url-box,
+.private-autocomplete-richlistitem[type~="autofill"] .ac-url-box {
+ visibility: hidden;
+}
+
+.private-autocomplete-richlistitem[actiontype="keyword"] .ac-title-box,
+.private-autocomplete-richlistitem[actiontype="searchengine"] .ac-title-box,
+.private-autocomplete-richlistitem[actiontype="visiturl"] .ac-title-box,
+.private-autocomplete-richlistitem[type~="autofill"] .ac-title-box {
+ /* Center the title by moving it down by half of .ac-url-box's height,
+ including vertical margins (if any). */
+ transform: translateY(.5em);
+}
+
+.ac-site-icon {
+ width: 16px;
+ height: 16px;
+ margin-bottom: -1px;
+ -moz-margin-start: 7px;
+ -moz-margin-end: 5px;
+}
+
+.ac-type-icon {
+ width: 16px;
+ height: 16px;
+ -moz-margin-start: 6px;
+ -moz-margin-end: 4px;
+}
+
+.ac-url-box > .ac-site-icon,
+.ac-url-box > .ac-type-icon {
+ /* Otherwise the spacer is big enough to stretch its container */
+ height: auto;
+}
+
+.ac-extra > .ac-result-type-tag {
+ margin: 0 4px;
+}
+
+.ac-extra > .ac-comment {
+ padding-right: 4px;
+}
+
+.ac-ellipsis-after {
+ margin: 0 !important;
+ padding: 0;
+ min-width: 1.1em;
+}
+
+.ac-normal-text {
+ margin: 0 !important;
+ padding: 0;
+}
+
+.ac-normal-text > html|span {
+ margin: 0 !important;
+ padding: 0;
+}
+
+html|span.ac-emphasize-text {
+ box-shadow: inset 0 0 1px 1px rgba(208,208,208,0.4);
+ background-color: rgba(208,208,208,0.2);
+ border-radius: 2px;
+ text-shadow: 0 0 currentColor;
+}
+
+.ac-url-text > html|span.ac-emphasize-text,
+.ac-action-text > html|span.ac-emphasize-text {
+ box-shadow: inset 0 0 1px 1px rgba(183,210,226,0.4);
+ background-color: rgba(183,210,226,0.3);
+}
+
+.ac-title, .ac-url {
+ overflow: hidden;
+}
+
+/* ::::: textboxes inside toolbarpaletteitems ::::: */
+
+toolbarpaletteitem > toolbaritem > textbox > hbox > hbox > html|*.textbox-input {
+ visibility: hidden;
+}
+
+toolbarpaletteitem > toolbaritem > * > textbox > hbox > hbox > html|*.textbox-input {
+ visibility: hidden;
+}
diff --git a/themes/osx/browser.css b/themes/osx/browser.css
new file mode 100644
index 0000000..20e453d
--- /dev/null
+++ b/themes/osx/browser.css
@@ -0,0 +1,2802 @@
+/* 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/. */
+
+@import url("chrome://global/skin/");
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
+
+%include shared.inc
+%filter substitution
+%define toolbarHighlight rgba(255,255,255,.5)
+%define selectedTabHighlight rgba(255,255,255,.7)
+%define toolbarShadowColor rgba(10%,10%,10%,.4)
+%define toolbarShadowOnTab linear-gradient(to top, rgba(10%,10%,10%,.4) 1px, transparent 1px)
+%define bgTabTexture linear-gradient(transparent, hsla(0,0%,45%,.1) 1px, hsla(0,0%,32%,.2) 80%, hsla(0,0%,0%,.2))
+%define bgTabTextureHover linear-gradient(hsla(0,0%,100%,.3) 1px, hsla(0,0%,75%,.2) 80%, hsla(0,0%,60%,.2))
+%define navbarTextboxCustomBorder border-color: rgba(0,0,0,.32);
+%define navbarLargeIcons #navigator-toolbox[iconsize=large][mode=icons] > #nav-bar
+%define forwardTransitionLength 150ms
+%define conditionalForwardWithUrlbar window:not([chromehidden~=toolbar]) #navigator-toolbox[iconsize=large][mode=icons] > :-moz-any(#nav-bar[currentset*="unified-back-forward-button"],#nav-bar:not([currentset])) > #unified-back-forward-button
+%define conditionalForwardWithUrlbarWidth 27
+
+#navigator-toolbox {
+ -moz-appearance: none;
+ background-color: transparent;
+ border-top: none;
+}
+
+#main-window {
+ -moz-appearance: none;
+ background-color: #eeeeee;
+}
+
+#navigator-toolbox::after {
+ content: "";
+ display: -moz-box;
+ -moz-box-ordinal-group: 101; /* tabs toolbar is 100 */
+ height: 1px;
+ background-color: ThreeDShadow;
+}
+#navigator-toolbox[tabsontop=false]::after,
+#main-window[disablechrome] #navigator-toolbox::after {
+ visibility: collapse;
+}
+
+#navigator-toolbox > toolbar:not(:-moz-lwtheme) {
+ -moz-appearance: none;
+ border-style: none;
+ background-color: -moz-Dialog;
+}
+
+#nav-bar[tabsontop=true]:not(:-moz-lwtheme),
+#nav-bar[tabsontop=true][collapsed=true]:not([customizing]):not(:-moz-lwtheme) + toolbar,
+#nav-bar[tabsontop=true][collapsed=true]:not([customizing]):not(:-moz-lwtheme) + #customToolbars + #PersonalToolbar {
+ background-image: linear-gradient(@toolbarHighlight@, rgba(255,255,255,0));
+}
+
+#nav-bar[tabsontop=true]:-moz-lwtheme,
+#nav-bar[tabsontop=true][collapsed=true]:not([customizing]):-moz-lwtheme + toolbar,
+#nav-bar[tabsontop=true][collapsed=true]:not([customizing]):-moz-lwtheme + #customToolbars + #PersonalToolbar {
+ background-image: linear-gradient(rgba(255,255,255,.8), rgba(255,255,255,0));
+}
+
+#nav-bar[tabsontop=true]:-moz-lwtheme-brighttext,
+#nav-bar[tabsontop=true][collapsed=true]:not([customizing]):-moz-lwtheme-brighttext + toolbar,
+#nav-bar[tabsontop=true][collapsed=true]:not([customizing]):-moz-lwtheme-brighttext + #customToolbars + #PersonalToolbar {
+ background-image: linear-gradient(rgba(32,32,32,.8), rgba(32,32,32,0));
+}
+
+#personal-bookmarks {
+ min-height: 24px;
+}
+
+#print-preview-toolbar:not(:-moz-lwtheme) {
+ -moz-appearance: toolbox;
+}
+
+#browser-bottombox:not(:-moz-lwtheme) {
+ background-color: -moz-dialog;
+}
+
+
+
+/* ::::: titlebar ::::: */
+
+#main-window[sizemode="normal"]:not([privatebrowsingmode=temporary]) > #titlebar {
+ -moz-appearance: -moz-window-titlebar;
+}
+
+#main-window[sizemode="maximized"] > #titlebar {
+ -moz-appearance: -moz-window-titlebar-maximized;
+}
+
+#titlebar-buttonbox {
+ -moz-appearance: -moz-window-button-box;
+}
+
+#main-window[sizemode="maximized"] #titlebar-buttonbox {
+ -moz-appearance: -moz-window-button-box-maximized;
+}
+
+.titlebar-placeholder[type="appmenu-button"] {
+ margin-left: 4px;
+}
+
+.titlebar-placeholder[type="caption-buttons"] {
+ margin-left: 10px;
+}
+
+/* titlebar command buttons */
+
+#titlebar-min {
+ -moz-appearance: -moz-window-button-minimize;
+}
+
+#titlebar-max {
+ -moz-appearance: -moz-window-button-maximize;
+}
+
+#main-window[sizemode="maximized"] #titlebar-max {
+ -moz-appearance: -moz-window-button-restore;
+}
+
+#titlebar-close {
+ -moz-appearance: -moz-window-button-close;
+}
+
+/* ensure titlebar on privacy windows is of correct size */
+#titlebar {
+ height: 22px;
+}
+
+/* ensure extra titlebar doesn't appear on normal (e.g. non-privacy) windows */
+#main-window:not([privatebrowsingmode=temporary]):not(:-moz-lwtheme) > #titlebar > #titlebar-content > #titlebar-buttonbox-container,
+#main-window:not([drawintitlebar=true]):not(:-moz-lwtheme) > #titlebar {
+ display: none;
+}
+
+#titlebar-buttonbox-container {
+ margin-left: 7px;
+ margin-top: 3px;
+}
+
+/* ::::: bookmark toolbar ::::: */
+
+#personal-bookmarks {
+ min-height: 17px; /* 16px button height + 1px margin-bottom */
+}
+
+toolbarbutton.chevron {
+ list-style-image: url("chrome://global/skin/icons/chevron.png");
+ margin: 1px 0 0;
+ padding: 0;
+}
+
+toolbarbutton.chevron > .toolbarbutton-text {
+ display: none;
+}
+
+toolbar[mode="text"] toolbarbutton.chevron > .toolbarbutton-icon {
+ display: -moz-box; /* display chevron icon in text mode */
+}
+
+toolbarbutton.chevron:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+ transform: scaleX(-1);
+}
+
+@media (min-resolution: 2dppx) {
+ toolbarbutton.chevron {
+ list-style-image: url("chrome://global/skin/icons/chevron@2x.png");
+ }
+
+ toolbarbutton.chevron > .toolbarbutton-icon {
+ width: 13px;
+ }
+}
+
+/* ::::: bookmark buttons ::::: */
+
+toolbarbutton.bookmark-item {
+ color: #222;
+ border: 0;
+ border-radius: 10000px;
+ padding: 1px 8px;
+ margin: 0 0 1px;
+}
+
+.bookmark-item > .toolbarbutton-menu-dropmarker {
+ list-style-image: url("chrome://browser/skin/places/folderDropArrow.png");
+ -moz-image-region: rect(0, 7px, 5px, 0);
+ margin-top: 1px;
+ -moz-margin-start: 3px;
+ -moz-margin-end: -2px;
+}
+
+@media (min-resolution: 2dppx) {
+ .bookmark-item > .toolbarbutton-menu-dropmarker {
+ list-style-image: url("chrome://browser/skin/places/folderDropArrow@2x.png");
+ -moz-image-region: rect(0, 14px, 10px, 0);
+ }
+
+ .bookmark-item > .toolbarbutton-menu-dropmarker > .dropmarker-icon {
+ width: 7px;
+ }
+}
+
+.bookmark-item > .toolbarbutton-text {
+ display: -moz-box !important; /* prevent [mode="icons"] from hiding the label */
+ margin: 0 !important;
+}
+
+toolbarbutton.bookmark-item:hover,
+toolbarbutton.bookmark-item[open="true"] {
+ background-color: rgba(0, 0, 0, .205);
+}
+
+toolbarbutton.bookmark-item:hover,
+toolbarbutton.bookmark-item[open="true"] {
+ color: #FFF !important;
+ text-shadow: 0 1px rgba(0, 0, 0, .4) !important;
+}
+
+.bookmark-item:hover > .toolbarbutton-menu-dropmarker,
+.bookmark-item[open="true"] > .toolbarbutton-menu-dropmarker {
+ -moz-image-region: rect(5px, 7px, 10px, 0);
+}
+
+@media (min-resolution: 2dppx) {
+ .bookmark-item:hover > .toolbarbutton-menu-dropmarker,
+ .bookmark-item[open="true"] > .toolbarbutton-menu-dropmarker {
+ -moz-image-region: rect(10px, 14px, 20px, 0);
+ }
+}
+
+toolbarbutton.bookmark-item:active:hover,
+toolbarbutton.bookmark-item[open="true"] {
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.4), 0 1px rgba(255, 255, 255, 0.4);
+ background-color: rgba(0, 0, 0, .5);
+}
+
+toolbarbutton.bookmark-item > menupopup {
+ margin-top: 2px;
+ -moz-margin-start: 3px;
+}
+
+.bookmark-item > .toolbarbutton-icon {
+ width: 16px;
+ min-height: 16px;
+ max-height: 16px;
+}
+
+.bookmark-item > .toolbarbutton-icon[label]:not([label=""]),
+.bookmark-item > .toolbarbutton-icon[type="menu"] {
+ -moz-margin-end: 5px;
+}
+
+.bookmark-item[container] {
+ list-style-image: url("chrome://global/skin/tree/folder.png");
+}
+
+.bookmark-item[container][livemark] {
+ list-style-image: url("chrome://browser/skin/page-livemarks.png");
+}
+
+.bookmark-item[container][livemark] .bookmark-item {
+ list-style-image: url("chrome://browser/skin/places/livemark-item.png");
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+}
+
+.bookmark-item[container][livemark] .bookmark-item[visited] {
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+.bookmark-item[container][query] {
+ list-style-image: url("chrome://browser/skin/places/query.png");
+}
+
+.bookmark-item[query][tagContainer] {
+ list-style-image: url("chrome://browser/skin/places/tag.png");
+}
+
+.bookmark-item[query][dayContainer] {
+ list-style-image: url("chrome://browser/skin/places/history.png");
+}
+
+.bookmark-item[query][hostContainer] {
+ list-style-image: url("chrome://global/skin/tree/folder.png");
+}
+
+.bookmark-item[query][hostContainer][open] {
+ list-style-image: url("chrome://global/skin/tree/folder.png");
+}
+
+@media (min-resolution: 2dppx) {
+ .bookmark-item[container] {
+ list-style-image: url("chrome://global/skin/tree/folder@2x.png");
+ }
+
+ .bookmark-item[container][livemark] {
+ list-style-image: url("chrome://browser/skin/page-livemarks@2x.png");
+ }
+
+ .bookmark-item[container][livemark] .bookmark-item {
+ list-style-image: url("chrome://browser/skin/places/livemark-item.png");
+ }
+
+ .bookmark-item[container][query] {
+ list-style-image: url("chrome://browser/skin/places/query@2x.png");
+ }
+
+ .bookmark-item[query][tagContainer] {
+ list-style-image: url("chrome://browser/skin/places/tag@2x.png");
+ }
+
+ .bookmark-item[query][dayContainer] {
+ list-style-image: url("chrome://browser/skin/places/history@2x.png");
+ }
+
+ .bookmark-item[query][hostContainer] {
+ list-style-image: url("chrome://global/skin/tree/folder@2x.png");
+ }
+
+ .bookmark-item[query][hostContainer][open] {
+ list-style-image: url("chrome://global/skin/tree/folder@2x.png");
+ }
+}
+
+/* Workaround for native menubar inheritance */
+.openintabs-menuitem,
+.openlivemarksite-menuitem,
+.livemarkstatus-menuitem {
+ list-style-image: none;
+}
+
+.bookmark-item[cutting] > .toolbarbutton-icon,
+.bookmark-item[cutting] > .menu-iconic-left > .menu-iconic-icon {
+ opacity: 0.5;
+}
+
+.bookmark-item[cutting] > .toolbarbutton-text,
+.bookmark-item[cutting] > .menu-iconic-left > .menu-iconic-text {
+ opacity: 0.7;
+}
+
+@media (min-resolution: 2dppx) {
+ .bookmark-item > .toolbarbutton-icon,
+ .bookmark-item > .menu-iconic-left > .menu-iconic-icon {
+ image-rendering: -moz-crisp-edges;
+ }
+}
+
+#wrapper-personal-bookmarks[place="palette"] > .toolbarpaletteitem-box {
+ background: url("chrome://browser/skin/places/bookmarksToolbar.png") no-repeat center;
+}
+
+.bookmarks-toolbar-customize {
+ max-width: 15em !important;
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png") !important;
+}
+
+@media (min-resolution: 2dppx) {
+ #wrapper-personal-bookmarks[place="palette"] > .toolbarpaletteitem-box {
+ background-image: url("chrome://browser/skin/places/bookmarksToolbar@2x.png");
+ background-size: 16px;
+ }
+
+ .bookmarks-toolbar-customize {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar@2x.png") !important;
+ }
+
+ .bookmarks-toolbar-customize > .toolbarbutton-icon {
+ width: 16px;
+ }
+}
+
+/* ::::: bookmark menus ::::: */
+
+.bookmark-item > .menu-iconic-left > .menu-iconic-icon {
+ width: 16px;
+ height: 16px;
+}
+
+/* ::::: primary toolbar buttons ::::: */
+
+.toolbarbutton-1 {
+ list-style-image: url("chrome://browser/skin/Toolbar.png");
+}
+
+toolbar[brighttext] .toolbarbutton-1 {
+ list-style-image: url("chrome://browser/skin/Toolbar-inverted.png");
+}
+
+.toolbarbutton-1[disabled=true] > .toolbarbutton-icon,
+.toolbarbutton-1[disabled=true] > .toolbarbutton-menu-dropmarker,
+.toolbarbutton-1[disabled=true] > .toolbarbutton-menubutton-dropmarker,
+.toolbarbutton-1[disabled=true] > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
+.toolbarbutton-1 > .toolbarbutton-menubutton-button[disabled] > .toolbarbutton-icon {
+ opacity: .4;
+}
+
+.toolbarbutton-1 > .toolbarbutton-menu-dropmarker,
+.toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+ list-style-image: url("chrome://browser/skin/toolbarbutton-dropdown-arrow.png");
+}
+
+toolbar[brighttext] .toolbarbutton-1 > .toolbarbutton-menu-dropmarker,
+toolbar[brighttext] .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+ list-style-image: url("chrome://browser/skin/toolbarbutton-dropdown-arrow-inverted.png");
+}
+
+.toolbarbutton-1 > .toolbarbutton-icon,
+.toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
+ -moz-margin-end: 0;
+}
+
+toolbar[mode=full] .toolbarbutton-1:not([type=menu-button]) {
+ -moz-box-orient: vertical;
+}
+
+toolbar[mode=full] .toolbarbutton-1,
+toolbar[mode=full] .toolbarbutton-1 > .toolbarbutton-menubutton-button {
+ min-width: 57px;
+}
+
+#nav-bar {
+ /* force iconsize="small" on this toolbar */
+ counter-reset: smallicons;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1,
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-button {
+ -moz-appearance: none;
+ border: none;
+ padding: 0;
+ background: none;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1:not([type=menu-button]),
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-button,
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+ padding: 5px 2px;
+ -moz-box-pack: center;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1[type=menu]:not(#back-button):not(#forward-button):not(#feed-button) {
+ padding-left: 5px;
+ padding-right: 5px;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1 > menupopup {
+ margin-top: -3px;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-button {
+ -moz-padding-end: 0;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+ -moz-padding-start: 0;
+ -moz-box-align: center;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-icon,
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-badge-stack,
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ padding: 2px 6px;
+ background: hsla(210,32%,93%,.3) padding-box;
+ background-image: linear-gradient(hsla(0,0%,100%,.4), hsla(0,0%,100%,.1));
+ background-clip: padding-box;
+ border-radius: 2.5px;
+ border: 1px solid;
+ border-color: hsla(210,54%,20%,.2) hsla(210,54%,20%,.2) hsla(210,54%,20%,.2);
+ box-shadow: 0 1px hsla(0,0%,100%,.05) inset,
+ 0 1px hsla(210,54%,20%,.05),
+ 0 0 2px hsla(210,54%,20%,.05);
+ transition-property: background-image, background-color, border-color, box-shadow;
+ transition-duration: 150ms;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@)) > .toolbarbutton-icon,
+@navbarLargeIcons@ .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@)) > .toolbarbutton-badge-stack,
+@navbarLargeIcons@ .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@)) > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
+ padding: 3px 7px;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1[type=menu]:not(#back-button):not(#forward-button):not(#feed-button) > .toolbarbutton-icon,
+@navbarLargeIcons@ .toolbarbutton-1[type=menu] > .toolbarbutton-text /* hack for add-ons that forcefully display the label */ {
+ -moz-padding-end: 17px;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menu-dropmarker {
+ -moz-margin-start: -15px;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
+ -moz-border-end: none;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ padding: 8px 5px 7px;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1:not(:hover):not(:active):not([open]) > .toolbarbutton-menubutton-dropmarker::before {
+ content: "";
+ display: -moz-box;
+ width: 1px;
+ height: 18px;
+ -moz-margin-end: -1px;
+ background-image: linear-gradient(hsla(210,54%,20%,.2) 0, hsla(210,54%,20%,.2) 18px);
+ background-clip: padding-box;
+ background-position: center;
+ background-repeat: no-repeat;
+ background-size: 1px 18px;
+ box-shadow: 0 0 0 1px hsla(0,0%,100%,.2);
+}
+
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon:-moz-locale-dir(ltr),
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon:-moz-locale-dir(rtl) {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon:-moz-locale-dir(rtl),
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon:-moz-locale-dir(ltr) {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1:not([disabled]):-moz-any(:hover,[open]) > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
+@navbarLargeIcons@ .toolbarbutton-1:not([disabled]):hover > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
+@navbarLargeIcons@ .toolbarbutton-1:not([disabled]):not([checked]):not([open]):not(:active):hover > .toolbarbutton-icon,
+@navbarLargeIcons@ .toolbarbutton-1:not([disabled]):not([checked]):not([open]):not(:active):hover > .toolbarbutton-badge-stack,
+@conditionalForwardWithUrlbar@ > .toolbarbutton-1:-moz-any([disabled],:not([open]):not([disabled]):not(:active)) > .toolbarbutton-icon {
+ background-image: linear-gradient(hsla(0,0%,100%,.8), hsla(0,0%,100%,.5));
+ border-color: hsla(210,54%,20%,.25) hsla(210,54%,20%,.3) hsla(210,54%,20%,.35);
+ box-shadow: 0 1px hsla(0,0%,100%,.3) inset,
+ 0 1px hsla(210,54%,20%,.03),
+ 0 0 2px hsla(210,54%,20%,.1);
+ transition-property: background-image, background-color, border-color, box-shadow;
+ transition-duration: 150ms;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-button:not([disabled]):not([open]):not(:active):hover > .toolbarbutton-icon,
+@navbarLargeIcons@ .toolbarbutton-1:not([buttonover]):not([open]):not(:active):hover > .toolbarbutton-menubutton-dropmarker:not([disabled]) > .dropmarker-icon,
+@conditionalForwardWithUrlbar@ > #forward-button:not([open]):not(:active):not([disabled]):hover > .toolbarbutton-icon {
+ border-color: hsla(210,54%,20%,.3) hsla(210,54%,20%,.35) hsla(210,54%,20%,.4);
+ background-color: hsla(210,48%,96%,.75);
+ box-shadow: 0 0 1px hsla(210,54%,20%,.03),
+ 0 0 2px hsla(210,54%,20%,.1);
+}
+
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-button:not([disabled]):hover:active > .toolbarbutton-icon,
+@navbarLargeIcons@ .toolbarbutton-1[open] > .toolbarbutton-menubutton-dropmarker:not([disabled]) > .dropmarker-icon,
+@navbarLargeIcons@ .toolbarbutton-1:not([disabled]):-moz-any([open],[checked],:hover:active) > .toolbarbutton-icon,
+@navbarLargeIcons@ .toolbarbutton-1:not([disabled]):-moz-any([open],[checked],:hover:active) > .toolbarbutton-badge-stack {
+ background-image: linear-gradient(hsla(0,0%,100%,.6), hsla(0,0%,100%,.1));
+ background-color: hsla(210,54%,20%,.15);
+ border-color: hsla(210,54%,20%,.3) hsla(210,54%,20%,.35) hsla(210,54%,20%,.4);
+ box-shadow: 0 1px 1px hsla(210,54%,20%,.1) inset,
+ 0 0 1px hsla(210,54%,20%,.2) inset,
+ /* allows windows-keyhole-forward-clip-path to be used for non-hover as well as hover: */
+ 0 1px 0 hsla(210,54%,20%,0),
+ 0 0 2px hsla(210,54%,20%,0);
+ text-shadow: none;
+ transition: none;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1:-moz-any(:hover,[open]) > .toolbarbutton-menubutton-dropmarker:not([disabled]) > .dropmarker-icon {
+ -moz-border-start-color: hsla(210,54%,20%,.35);
+}
+
+@navbarLargeIcons@ .toolbarbutton-1[checked]:not(:active):hover > .toolbarbutton-icon {
+ background-color: rgba(90%,90%,90%,.4);
+ transition: background-color .4s;
+}
+
+:-moz-any(#TabsToolbar, #addon-bar) .toolbarbutton-1,
+:-moz-any(#TabsToolbar, #addon-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-button,
+.tabbrowser-arrowscrollbox > .scrollbutton-up,
+.tabbrowser-arrowscrollbox > .scrollbutton-down {
+ -moz-appearance: none;
+ border-style: none;
+ padding: 0 3px;
+}
+
+#TabsToolbar .toolbarbutton-1:not([disabled]):hover,
+#TabsToolbar .toolbarbutton-1[open],
+#TabsToolbar .toolbarbutton-1 > .toolbarbutton-menubutton-button:not([disabled]):hover,
+.tabbrowser-arrowscrollbox > .scrollbutton-up:not([disabled]):hover,
+.tabbrowser-arrowscrollbox > .scrollbutton-down:not([disabled]):hover {
+ background-image: linear-gradient(rgba(255,255,255,0), rgba(255,255,255,.5)),
+ linear-gradient(transparent, rgba(0,0,0,.25) 30%),
+ linear-gradient(transparent, rgba(0,0,0,.25) 30%);
+ background-position: 1px -1px, 0 -1px, 100% -1px;
+ background-size: calc(100% - 2px) 100%, 1px 100%, 1px 100%;
+ background-repeat: no-repeat;
+}
+
+#addon-bar .toolbarbutton-1:not([disabled]):hover,
+#addon-bar .toolbarbutton-1[open],
+#addon-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button:not([disabled]):hover {
+ background-image: linear-gradient(to top, transparent, rgba(0,0,0,.15)),
+ linear-gradient(to top, transparent, rgba(0,0,0,.15) 30%),
+ linear-gradient(to top, transparent, rgba(0,0,0,.15) 30%);
+ background-position: left, left, right;
+ background-size: auto, 1px 100%, 1px 100%;
+ background-repeat: no-repeat;
+}
+
+/* unified back/forward button */
+
+#back-button {
+ -moz-image-region: rect(0, 18px, 18px, 0);
+}
+
+#forward-button {
+ -moz-image-region: rect(0, 36px, 18px, 18px);
+}
+
+#back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
+#forward-button:-moz-locale-dir(rtl),
+#forward-button:-moz-locale-dir(rtl) > .toolbarbutton-text {
+ transform: scaleX(-1);
+}
+
+@conditionalForwardWithUrlbar@ {
+ -moz-box-align: center;
+}
+
+@conditionalForwardWithUrlbar@ > #forward-button {
+ padding: 0;
+}
+
+@conditionalForwardWithUrlbar@ > #forward-button > menupopup {
+ margin-top: 1px;
+}
+
+@conditionalForwardWithUrlbar@ > #forward-button > .toolbarbutton-icon {
+ /*mask: url(keyhole-forward-mask.svg#mask); XXX: this regresses twinopen */
+ clip-path: url(chrome://browser/content/browser.xul#windows-keyhole-forward-clip-path);
+ -moz-margin-start: -6px !important;
+ border-left-style: none;
+ border-radius: 0;
+ padding-left: 7px;
+ padding-right: 3px;
+}
+
+@conditionalForwardWithUrlbar@:not([switchingtabs]) > #forward-button {
+ transition: opacity @forwardTransitionLength@ ease-out;
+}
+
+@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] {
+ /* opacity: 0; */
+}
+
+@conditionalForwardWithUrlbar@ > #back-button {
+ -moz-image-region: rect(18px, 20px, 38px, 0);
+ padding-top: 3px;
+ padding-bottom: 3px;
+ -moz-padding-start: 5px;
+ -moz-padding-end: 0;
+ position: relative;
+ z-index: 1;
+ border-radius: 0 10000px 10000px 0;
+}
+
+@conditionalForwardWithUrlbar@ > #back-button:-moz-locale-dir(rtl) {
+ border-radius: 10000px 0 0 10000px;
+}
+
+@conditionalForwardWithUrlbar@ > #back-button > menupopup {
+ margin-top: -1px;
+}
+
+@conditionalForwardWithUrlbar@ > #back-button > .toolbarbutton-icon {
+ border-radius: 10000px;
+ padding: 5px;
+ border: none;
+ background-image: linear-gradient(hsla(0,0%,100%,.6), hsla(0,0%,100%,.1));
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset,
+ 0 0 0 1px hsla(0,0%,100%,.3) inset,
+ 0 0 0 1px hsla(210,54%,20%,.25),
+ 0 1px 0 hsla(210,54%,20%,.35);
+ transition-property: background-color, box-shadow;
+ transition-duration: 250ms;
+}
+
+@conditionalForwardWithUrlbar@ > #back-button:not([disabled="true"]):not([open="true"]):not(:active):hover > .toolbarbutton-icon {
+ background-color: hsla(210,48%,96%,.75);
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset,
+ 0 0 0 1px hsla(0,0%,100%,.3) inset,
+ 0 0 0 1px hsla(210,54%,20%,.3),
+ 0 1px 0 hsla(210,54%,20%,.4),
+ 0 0 4px hsla(210,54%,20%,.2);
+}
+
+@conditionalForwardWithUrlbar@ > #back-button:not([disabled="true"]):hover:active > .toolbarbutton-icon,
+@conditionalForwardWithUrlbar@ > #back-button[open="true"] > .toolbarbutton-icon {
+ background-color: hsla(210,54%,20%,.15);
+ box-shadow: 0 1px 1px hsla(210,54%,20%,.1) inset,
+ 0 0 1px hsla(210,54%,20%,.2) inset,
+ 0 0 0 1px hsla(210,54%,20%,.4),
+ 0 1px 0 hsla(210,54%,20%,.2);
+ transition: none;
+}
+
+@conditionalForwardWithUrlbar@ > #back-button[disabled] > .toolbarbutton-icon {
+ box-shadow: 0 0 0 1px hsla(210,54%,20%,.55),
+ 0 1px 0 hsla(210,54%,20%,.65);
+ transition: none;
+}
+
+.unified-nav-back[_moz-menuactive]:-moz-locale-dir(ltr),
+.unified-nav-forward[_moz-menuactive]:-moz-locale-dir(rtl) {
+ list-style-image: url("chrome://browser/skin/menu-back.png") !important;
+}
+
+.unified-nav-forward[_moz-menuactive]:-moz-locale-dir(ltr),
+.unified-nav-back[_moz-menuactive]:-moz-locale-dir(rtl) {
+ list-style-image: url("chrome://browser/skin/menu-forward.png") !important;
+}
+
+#stop-button {
+ -moz-image-region: rect(0, 54px, 18px, 36px);
+}
+
+#reload-button {
+ -moz-image-region: rect(0, 72px, 18px, 54px);
+}
+
+#home-button.bookmark-item {
+ list-style-image: url("chrome://browser/skin/Toolbar.png");
+}
+toolbar[brighttext] #home-button.bookmark-item {
+ list-style-image: url("chrome://browser/skin/Toolbar-inverted.png");
+}
+
+#home-button {
+ -moz-image-region: rect(0, 90px, 18px, 72px);
+}
+
+#downloads-button {
+ -moz-image-region: rect(0, 108px, 18px, 90px);
+}
+
+#history-button,
+#history-menu-button {
+ -moz-image-region: rect(0, 126px, 18px, 108px);
+}
+
+#bookmarks-button,
+#bookmarks-menu-button {
+ -moz-image-region: rect(0, 144px, 18px, 126px);
+}
+
+#bookmarks-menu-button.bookmark-item {
+ list-style-image: url("chrome://browser/skin/Toolbar.png");
+}
+
+toolbar[brighttext] #bookmarks-menu-button.bookmark-item {
+ list-style-image: url("chrome://browser/skin/Toolbar-inverted.png");
+}
+
+#print-button {
+ -moz-image-region: rect(0, 162px, 18px, 144px);
+}
+
+#new-tab-button {
+ -moz-image-region: rect(0, 180px, 18px, 162px);
+}
+
+#new-window-button {
+ -moz-image-region: rect(0, 198px, 18px, 180px);
+}
+
+#cut-button {
+ -moz-image-region: rect(0, 216px, 18px, 198px);
+}
+
+#copy-button {
+ -moz-image-region: rect(0, 234px, 18px, 216px);
+}
+
+#paste-button {
+ -moz-image-region: rect(0, 252px, 18px, 234px);
+}
+
+#fullscreen-button {
+ -moz-image-region: rect(0, 270px, 18px, 252px);
+}
+
+#zoom-out-button {
+ -moz-image-region: rect(0, 288px, 18px, 270px);
+}
+
+#zoom-in-button {
+ -moz-image-region: rect(0, 306px, 18px, 288px);
+}
+
+#sync-button {
+ -moz-image-region: rect(0, 324px, 18px, 306px);
+}
+#sync-button[status="active"] {
+ list-style-image: url("chrome://browser/skin/sync-throbber.png");
+ -moz-image-region: rect(0, 18px, 18px, 0);
+}
+
+#feed-button {
+ -moz-image-region: rect(0, 342px, 18px, 324px);
+}
+
+%ifdef MOZ_WEBRTC
+#webrtc-status-button {
+ -moz-image-region: rect(0, 360px, 18px, 342px);
+}
+%endif
+
+/* ::::: Location Bar ::::: */
+
+#urlbar,
+.searchbar-textbox {
+ -moz-appearance: none;
+ margin: 1px 3px;
+ padding: 0;
+ background-clip: padding-box;
+ border: 1px solid ThreeDShadow;
+ border-radius: 2px;
+}
+
+#urlbar {
+ width: 7em;
+ -moz-padding-end: 2px;
+}
+
+@media (-moz-mac-lion-theme) {
+ #urlbar,
+ .searchbar-textbox {
+ @navbarTextboxCustomBorder@
+ }
+}
+
+#urlbar:-moz-lwtheme,
+.searchbar-textbox:-moz-lwtheme {
+ background-color: rgba(255,255,255,.8);
+ @navbarTextboxCustomBorder@
+ color: black;
+}
+
+@conditionalForwardWithUrlbar@ + #urlbar-container {
+ padding-left: @conditionalForwardWithUrlbarWidth@px;
+ -moz-margin-start: -@conditionalForwardWithUrlbarWidth@px;
+ position: relative;
+ pointer-events: none;
+}
+
+@conditionalForwardWithUrlbar@ + #urlbar-container > #urlbar {
+ -moz-border-start: none;
+ margin-left: 0;
+ pointer-events: all;
+}
+
+@conditionalForwardWithUrlbar@:not([switchingtabs]) + #urlbar-container > #urlbar {
+ transition: margin-left @forwardTransitionLength@ ease-out;
+}
+
+@conditionalForwardWithUrlbar@ + #urlbar-container > #urlbar:-moz-locale-dir(ltr) {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+}
+
+@conditionalForwardWithUrlbar@ + #urlbar-container > #urlbar:-moz-locale-dir(rtl) {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+@conditionalForwardWithUrlbar@[forwarddisabled] + #urlbar-container {
+ clip-path: url("chrome://browser/content/browser.xul#windows-urlbar-back-button-clip-path");
+}
+
+@conditionalForwardWithUrlbar@ + #urlbar-container:-moz-locale-dir(rtl),
+@conditionalForwardWithUrlbar@ + #urlbar-container > #urlbar:-moz-locale-dir(rtl) {
+ /* let windows-urlbar-back-button-mask clip the urlbar's right side for RTL */
+ transform: scaleX(-1);
+}
+
+html|*.urlbar-input:-moz-lwtheme::-moz-placeholder,
+.searchbar-textbox:-moz-lwtheme > .autocomplete-textbox-container > .textbox-input-box > html|*.textbox-input::-moz-placeholder {
+ opacity: 1.0;
+ color: #777;
+}
+
+#urlbar:-moz-lwtheme[focused="true"],
+.searchbar-textbox:-moz-lwtheme[focused="true"] {
+ background-color: white;
+}
+
+#urlbar-container {
+ -moz-box-orient: horizontal;
+ -moz-box-align: stretch;
+}
+
+.urlbar-textbox-container {
+ -moz-box-align: stretch;
+}
+
+.urlbar-input-box {
+ -moz-margin-start: 0;
+ min-width: 4em;
+}
+
+#urlbar-icons {
+ -moz-box-align: center;
+}
+
+.urlbar-icon {
+ padding: 0 3px;
+}
+
+.searchbar-engine-button,
+.search-go-container {
+ padding: 2px 2px;
+}
+
+.urlbar-icon:hover {
+ background-image: radial-gradient(circle closest-side, hsla(200,100%,70%,.3), hsla(200,100%,70%,0));
+}
+
+.urlbar-icon[open="true"],
+.urlbar-icon:hover:active {
+ background-image: radial-gradient(circle closest-side, hsla(200,100%,70%,.1), hsla(200,100%,70%,0));
+}
+
+#urlbar-search-splitter {
+ min-width: 6px;
+ -moz-margin-start: -3px;
+ border: none;
+ background: transparent;
+}
+
+#urlbar-search-splitter + #urlbar-container > #urlbar ,
+#urlbar-search-splitter + #search-container > #searchbar > .searchbar-textbox {
+ -moz-margin-start: 0;
+}
+
+#urlbar-display-box {
+ -moz-border-end: 1px solid #AAA;
+ -moz-margin-end: 3px;
+}
+
+#urlbar-display {
+ margin-top: 0;
+ margin-bottom: 0;
+ -moz-margin-start: 0;
+ color: GrayText;
+}
+
+/* identity box */
+
+#identity-box {
+ padding: 2px;
+ font-size: .9em;
+}
+
+#identity-box:-moz-locale-dir(ltr) {
+ border-top-left-radius: 1.5px;
+ border-bottom-left-radius: 1.5px;
+}
+
+#identity-box:-moz-locale-dir(rtl) {
+ border-top-right-radius: 1.5px;
+ border-bottom-right-radius: 1.5px;
+}
+
+#notification-popup-box:not([hidden]) + #identity-box {
+ -moz-padding-start: 10px;
+ border-radius: 0;
+}
+
+@conditionalForwardWithUrlbar@ + #urlbar-container > #urlbar > #identity-box {
+ border-radius: 0;
+}
+
+#urlbar[pageproxystate="valid"] > #identity-box.verifiedIdentity {
+ color: hsl(92,100%,30%);
+ -moz-margin-end: 4px;
+ background-image: -moz-linear-gradient(hsla(92,81%,16%,0),
+ hsla(92,81%,16%,.08) 25%,
+ hsla(92,81%,16%,.08) 75%,
+ hsla(92,81%,16%,0));
+ background-position: right;
+ background-repeat: no-repeat;
+}
+
+#urlbar[pageproxystate="valid"] > #identity-box.verifiedDomain {
+ color: rgb(0,79,168);
+ -moz-margin-end: 4px;
+ background-image: -moz-linear-gradient(rgba(0,79,168,0),
+ rgba(0,79,168,.08) 25%,
+ rgba(0,79,168,.08) 75%,
+ rgba(0,79,168,0));
+ background-position: right;
+ background-repeat: no-repeat;
+}
+
+#identity-box.verifiedIdentity:-moz-locale-dir(rtl) {
+ background-position: left;
+}
+
+#identity-box.verifiedIdentity:not(:-moz-lwtheme) {
+ background-color: #fff;
+ box-shadow: inset 0 0 2px rgb(0,168,0);
+}
+
+#identity-box.verifiedDomain:not(:-moz-lwtheme) {
+ background-color: rgb(224,234,247);
+ box-shadow: inset 0 0 2px rgb(0,79,168);
+}
+
+#identity-box:-moz-focusring {
+ outline: 1px dotted #000;
+ outline-offset: -3px;
+}
+
+#identity-icon-labels {
+ -moz-padding-start: 2px;
+ -moz-padding-end: 5px;
+}
+
+/* Address bar shading for SSL */
+
+#urlbar[https_color="all"][security_level="broken"],
+#urlbar[https_color="all"][security_level="low"] {
+ box-shadow: inset 0 0 4px rgb(168,0,0);
+}
+
+#urlbar[https_color="all"][security_level="mixed"],
+#urlbar[https_color="secure-mixed"][security_level="mixed"] {
+ box-shadow: inset 0 0 4px rgb(168,79,0);
+}
+
+#urlbar[https_color="all"][security_level="high"],
+#urlbar[https_color="secure-mixed"][security_level="high"],
+#urlbar[https_color="secure-only"][security_level="high"] {
+ box-shadow: inset 0 0 4px rgb(0,79,168);
+}
+
+#urlbar[https_color="all"][security_level="ev"],
+#urlbar[https_color="secure-mixed"][security_level="ev"],
+#urlbar[https_color="secure-only"][security_level="ev"] {
+ box-shadow: inset 0 0 4px rgb(0,168,0);
+}
+
+#urlbar[https_color="all"]:-moz-lwtheme-darktext,
+#urlbar[https_color="secure-mixed"]:-moz-lwtheme-darktext,
+#urlbar[https_color="secure-only"]:-moz-lwtheme-darktext {
+ box-shadow: inset 0 0 2px;
+}
+
+
+/* Location bar dropmarker */
+
+.urlbar-history-dropmarker {
+ -moz-appearance: none;
+ padding: 0 3px;
+ background-color: transparent;
+ border: none;
+ width: auto;
+ list-style-image: url("chrome://browser/skin/urlbar-history-dropmarker.png");
+ -moz-image-region: rect(0px, 11px, 14px, 0px);
+}
+
+.urlbar-history-dropmarker:hover {
+ background-image: radial-gradient(circle closest-side, hsla(205,100%,70%,.3), hsla(205,100%,70%,0));
+ -moz-image-region: rect(0px, 22px, 14px, 11px);
+}
+
+.urlbar-history-dropmarker:hover:active,
+.urlbar-history-dropmarker[open="true"] {
+ background-image: radial-gradient(circle closest-side, hsla(205,100%,70%,.1), hsla(205,100%,70%,0));
+ -moz-image-region: rect(0px, 33px, 14px, 22px);
+}
+
+/* page proxy icon */
+
+#page-proxy-favicon {
+ width: 16px;
+ height: 16px;
+ margin-top: 1px;
+ margin-bottom: 1px;
+ -moz-margin-start: 3px;
+ -moz-margin-end: 2px;
+ list-style-image: url(chrome://browser/skin/identity-icons-generic.png);
+ -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+@conditionalForwardWithUrlbar@ + #urlbar-container > #urlbar > #identity-box > #page-proxy-favicon {
+ -moz-margin-end: 1px;
+}
+
+/* Since we already have a padlock, always use the generic icon until the favicon loads
+.verifiedDomain > #page-proxy-favicon[pageproxystate="valid"] {
+ list-style-image: url(chrome://browser/skin/identity-icons-https.png);
+}
+
+.verifiedIdentity > #page-proxy-favicon[pageproxystate="valid"] {
+ list-style-image: url(chrome://browser/skin/identity-icons-https-ev.png);
+}
+
+.mixedActiveContent > #page-proxy-favicon[pageproxystate="valid"] {
+ list-style-image: url(chrome://browser/skin/identity-icons-https-mixed-active.png);
+}
+*/
+
+#identity-box:hover > #page-proxy-favicon {
+ -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
+#identity-box:hover:active > #page-proxy-favicon,
+#identity-box[open=true] > #page-proxy-favicon {
+ -moz-image-region: rect(0, 48px, 16px, 32px);
+}
+
+#page-proxy-favicon[pageproxystate="invalid"] {
+ opacity: 0.3;
+}
+
+/* autocomplete */
+
+#treecolAutoCompleteImage {
+ max-width: 36px;
+}
+
+.ac-result-type-bookmark,
+.autocomplete-treebody::-moz-tree-image(bookmark, treecolAutoCompleteImage) {
+ list-style-image: url("chrome://browser/skin/places/bookmark.png");
+ -moz-image-region: rect(0px 48px 16px 32px);
+ width: 16px;
+ height: 16px;
+}
+
+.ac-result-type-keyword,
+.autocomplete-treebody::-moz-tree-image(keyword, treecolAutoCompleteImage) {
+ list-style-image: url(chrome://global/skin/icons/search-textbox.png);
+ margin: 2px;
+ width: 12px;
+ height: 12px;
+}
+
+.ac-result-type-tag,
+.autocomplete-treebody::-moz-tree-image(tag, treecolAutoCompleteImage) {
+ list-style-image: url("chrome://browser/skin/places/tag.png");
+ width: 16px;
+ height: 16px;
+}
+
+.ac-comment {
+ font-size: 1.06em;
+}
+
+.ac-extra > .ac-comment {
+ font-size: 1em;
+}
+
+.ac-url-text,
+.ac-action-text {
+ font-size: 1em;
+ color: -moz-nativehyperlinktext;
+}
+
+@media (-moz-mac-lion-theme) {
+ .ac-url-text:not([selected="true"]),
+ .ac-action-text:not([selected="true"]) {
+ color: #008800;
+ }
+}
+
+richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-icon {
+ list-style-image: url("chrome://browser/skin/actionicon-tab.png");
+ -moz-image-region: rect(0, 16px, 11px, 0);
+ padding: 0 3px;
+}
+
+richlistitem[type~="action"][actiontype="switchtab"][selected="true"] > .ac-url-box > .ac-action-icon {
+ -moz-image-region: rect(11px, 16px, 22px, 0);
+}
+
+.ac-comment[selected="true"],
+.ac-url-text[selected="true"],
+.ac-action-text[selected="true"] {
+ color: inherit !important;
+}
+
+.autocomplete-treebody::-moz-tree-cell-text(treecolAutoCompleteComment) {
+ color: GrayText;
+}
+
+.autocomplete-treebody::-moz-tree-cell-text(suggesthint, treecolAutoCompleteComment),
+.autocomplete-treebody::-moz-tree-cell-text(suggestfirst, treecolAutoCompleteComment)
+{
+ color: GrayText;
+ font-size: smaller;
+}
+
+.autocomplete-treebody::-moz-tree-cell(suggesthint) {
+ border-top: 1px solid GrayText;
+}
+
+/* combined go/reload/stop button in location bar */
+
+#go-button,
+#urlbar > toolbarbutton {
+ -moz-appearance: none;
+ padding: 0 2px;
+ background-origin: border-box;
+ border: none;
+ list-style-image: url("chrome://browser/skin/reload-stop-go.png");
+}
+
+#go-button {
+ padding: 0 3px;
+}
+
+#urlbar-reload-button {
+ -moz-image-region: rect(0, 14px, 14px, 0);
+}
+
+#urlbar-reload-button:not([disabled]):hover {
+ background-image: radial-gradient(circle closest-side, hsla(200,100%,70%,.2), hsla(200,100%,70%,0));
+ -moz-image-region: rect(14px, 14px, 28px, 0);
+}
+
+#urlbar-reload-button:not([disabled]):hover:active {
+ background-image: radial-gradient(circle closest-side, hsla(200,100%,60%,.1), hsla(200,100%,60%,0));
+ -moz-image-region: rect(28px, 14px, 42px, 0);
+}
+
+#urlbar-reload-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+ transform: scaleX(-1);
+}
+
+#go-button,
+#urlbar-go-button {
+ -moz-image-region: rect(0, 42px, 14px, 28px);
+}
+
+#go-button:hover,
+#urlbar-go-button:hover {
+ background-image: radial-gradient(circle closest-side, hsla(110,70%,50%,.2), hsla(110,70%,50%,0));
+ -moz-image-region: rect(14px, 42px, 28px, 28px);
+}
+
+#go-button:hover:active,
+#urlbar-go-button:hover:active {
+ background-image: radial-gradient(circle closest-side, hsla(110,70%,50%,.1), hsla(110,70%,50%,0));
+ -moz-image-region: rect(28px, 42px, 42px, 28px);
+}
+
+#go-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
+#urlbar-go-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+ transform: scaleX(-1);
+}
+
+#urlbar-stop-button {
+ -moz-image-region: rect(0, 28px, 14px, 14px);
+}
+
+#urlbar-stop-button:not([disabled]):hover {
+ background-image: radial-gradient(circle closest-side, hsla(5,100%,75%,.3), hsla(5,100%,75%,0));
+ -moz-image-region: rect(14px, 28px, 28px, 14px);
+}
+
+#urlbar-stop-button:hover:active {
+ background-image: radial-gradient(circle closest-side, hsla(5,100%,75%,.1), hsla(5,100%,75%,0));
+ -moz-image-region: rect(28px, 28px, 42px, 14px);
+}
+
+/* popup blocker button */
+
+#page-report-button {
+ list-style-image: url("chrome://browser/skin/urlbar-popup-blocked.png");
+ -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+#page-report-button:hover {
+ -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
+#page-report-button:hover:active,
+#page-report-button[open="true"] {
+ -moz-image-region: rect(0, 48px, 16px, 32px);
+}
+
+
+/* star button */
+
+#star-button {
+ list-style-image: url("chrome://browser/skin/places/bookmark.png");
+ -moz-image-region: rect(0px 16px 16px 0px);
+}
+
+#star-button:hover {
+ background-image: radial-gradient(circle closest-side, hsla(45,100%,73%,.3), hsla(45,100%,73%,0));
+ -moz-image-region: rect(0px 32px 16px 16px);
+}
+
+#star-button:hover:active {
+ background-image: radial-gradient(circle closest-side, hsla(45,100%,73%,.1), hsla(45,100%,73%,0));
+ -moz-image-region: rect(0px 48px 16px 32px);
+}
+
+#star-button[starred] {
+ list-style-image: url("chrome://browser/skin/places/editBookmark.png");
+}
+
+/* bookmarking panel */
+
+#editBookmarkPanelStarIcon {
+ list-style-image: url("chrome://browser/skin/places/starred48.png");
+ width: 48px;
+ height: 48px;
+}
+
+#editBookmarkPanelStarIcon[unstarred] {
+ list-style-image: url("chrome://browser/skin/places/unstarred48.png");
+}
+
+#editBookmarkPanelTitle {
+ font-size: 130%;
+}
+
+#editBookmarkPanelHeader,
+#editBookmarkPanelContent {
+ margin-bottom: .5em;
+}
+
+/* Implements editBookmarkPanel resizing on folderTree un-collapse. */
+#editBMPanel_folderTree {
+ min-width: 27em;
+}
+
+/* BOOKMARKING PANEL */
+#editBookmarkPanelStarIcon {
+ list-style-image: url("chrome://browser/skin/places/starred48.png");
+ width: 48px;
+ height: 48px;
+}
+
+#editBookmarkPanelStarIcon[unstarred] {
+ list-style-image: url("chrome://browser/skin/places/unstarred48.png");
+}
+
+#editBookmarkPanelTitle {
+ font-size: 130%;
+ font-weight: bold;
+}
+
+#editBMPanel_rows > row {
+ margin-bottom: 8px;
+}
+
+#editBMPanel_rows > row:last-of-type {
+ margin-bottom: 0;
+}
+
+/**** Input elements ****/
+
+#editBMPanel_rows > row > textbox,
+#editBMPanel_rows > row > hbox > textbox {
+ -moz-appearance: none;
+ background: linear-gradient(#fafafa, #fff);
+ background-clip: padding-box;
+ border-radius: 3px;
+ border: 1px solid rgba(0,0,0,.3) !important;
+ box-shadow: inset 0 1px 1px 1px rgba(0,0,0,.05),
+ 0 1px rgba(255,255,255,.3);
+ margin: 0;
+ padding: 3px 6px;
+}
+
+#editBMPanel_rows > row > textbox[focused="true"],
+#editBMPanel_rows > row > hbox > textbox[focused="true"] {
+ border-color: -moz-mac-focusring !important;
+ box-shadow: @focusRingShadow@;
+}
+
+/**** HUD style buttons ****/
+
+.editBookmarkPanelHeaderButton,
+.editBookmarkPanelBottomButton {
+ @hudButton@
+ margin: 0;
+ min-width: 82px;
+ min-height: 22px;
+}
+
+.editBookmarkPanelHeaderButton:hover:active,
+.editBookmarkPanelBottomButton:hover:active {
+ @hudButtonPressed@
+}
+
+.editBookmarkPanelHeaderButton:-moz-focusring,
+.editBookmarkPanelBottomButton:-moz-focusring {
+ @hudButtonFocused@
+}
+
+.editBookmarkPanelBottomButton[default="true"] {
+ background-color: #666;
+}
+
+#editBookmarkPanelHeader {
+ margin-bottom: 6px;
+}
+
+.editBookmarkPanelBottomButton:last-child {
+ -moz-margin-start: 8px;
+}
+
+/* The following elements come from editBookmarkOverlay.xul. Styling that's
+ specific to the editBookmarkPanel should be in browser.css. Styling that
+ should be shared by all editBookmarkOverlay.xul consumers should be in
+ editBookmarkOverlay.css. */
+
+#editBMPanel_newFolderBox {
+ background: linear-gradient(#fff, #f2f2f2);
+ background-origin: padding-box;
+ background-clip: padding-box;
+ border-radius: 0 0 3px 3px;
+ border: 1px solid #a5a5a5;
+ box-shadow: inset 0 1px rgba(255,255,255,.8),
+ inset 0 0 1px rgba(255,255, 255,.25),
+ 0 1px rgba(255,255,255,.3);
+ margin: 0;
+ padding: 0;
+ height: 20px;
+}
+
+#editBMPanel_newFolderButton {
+ -moz-appearance: none;
+ border: 0 solid #a5a5a5;
+ -moz-border-end-width: 1px;
+ padding: 0 9px;
+ margin: 0;
+ min-width: 21px;
+ min-height: 20px;
+ height: 20px;
+ color: #fff;
+ list-style-image: url("chrome://browser/skin/panel-plus-sign.png");
+ position: relative;
+}
+
+#editBMPanel_newFolderButton:hover:active {
+ background: linear-gradient(rgba(40,40,40,.9), rgba(70,70,70,.9));
+ box-shadow: inset 0 0 3px rgba(0,0,0,.2), inset 0 1px 7px rgba(0,0,0,.4);
+}
+
+#editBMPanel_newFolderButton:-moz-focusring {
+ @hudButtonFocused@
+}
+
+#editBMPanel_newFolderButton .button-text {
+ display: none;
+}
+
+#editBMPanel_folderMenuList {
+ @hudButton@
+ background-clip: padding-box;
+ margin: 0;
+ min-height: 22px;
+ padding-top: 2px;
+ padding-bottom: 1px;
+ -moz-padding-start: 8px;
+ -moz-padding-end: 4px;
+}
+
+#editBMPanel_folderMenuList:-moz-focusring {
+ @hudButtonFocused@
+}
+
+#editBMPanel_folderMenuList[open="true"],
+#editBMPanel_folderMenuList:hover:active {
+ @hudButtonPressed@
+}
+
+#editBMPanel_folderMenuList > .menulist-dropmarker {
+ -moz-appearance: none;
+ display: -moz-box;
+ background-color: transparent;
+ border: 0;
+ margin: 0;
+ padding: 0;
+ -moz-padding-end: 4px;
+ width: 7px;
+}
+
+#editBMPanel_folderMenuList > .menulist-dropmarker > .dropmarker-icon {
+ list-style-image: url("chrome://global/skin/icons/panel-dropmarker.png");
+}
+
+/**** folder tree and tag selector ****/
+
+#editBMPanel_folderTree,
+#editBMPanel_tagsSelector {
+ -moz-appearance: none;
+ background: linear-gradient(#fafafa, #fff);
+ background-clip: padding-box;
+ border-radius: 3px;
+ border: 1px solid rgba(0,0,0,.3);
+ box-shadow: inset 0 1px 1px 1px rgba(0,0,0,.05),
+ 0 1px rgba(255,255,255,.3);
+ margin: 0;
+}
+
+#editBMPanel_folderTree:-moz-focusring,
+#editBMPanel_tagsSelector:-moz-focusring {
+ border-color: -moz-mac-focusring;
+ box-shadow: @focusRingShadow@;
+}
+
+#editBMPanel_folderTree {
+ border-bottom: none;
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+ /* Implements editBookmarkPanel resizing on folderTree un-collapse. */
+ margin: 0 !important;
+ min-width: 27em;
+ position: relative;
+}
+
+/**** expanders ****/
+
+#editBookmarkPanel .expander-up,
+#editBookmarkPanel .expander-down {
+ @hudButton@
+ margin: 0;
+ -moz-margin-start: 4px;
+ min-width: 27px;
+ min-height: 22px;
+}
+
+#editBookmarkPanel .expander-up:-moz-focusring,
+#editBookmarkPanel .expander-down:-moz-focusring {
+ @hudButtonFocused@
+}
+
+#editBookmarkPanel .expander-up:hover:active,
+#editBookmarkPanel .expander-down:hover:active {
+ @hudButtonPressed@
+}
+
+#editBookmarkPanel .expander-up {
+ list-style-image: url("chrome://browser/skin/panel-expander-open.png");
+}
+
+#editBookmarkPanel .expander-down {
+ list-style-image: url("chrome://browser/skin/panel-expander-closed.png");
+}
+
+#editBookmarkPanel .expander-up > .button-box > .button-icon,
+#editBookmarkPanel .expander-down > .button-box > .button-icon {
+ margin: 1px 0 0;
+}
+
+#editBookmarkPanel .expander-up > .button-box > .button-text,
+#editBookmarkPanel .expander-down > .button-box > .button-text {
+ display: none;
+}
+
+@media (min-resolution: 2dppx) {
+ #editBookmarkPanel .expander-up {
+ list-style-image: url("chrome://browser/skin/panel-expander-open@2x.png");
+ }
+
+ #editBookmarkPanel .expander-down {
+ list-style-image: url("chrome://browser/skin/panel-expander-closed@2x.png");
+ }
+
+ #editBookmarkPanel .expander-up > .button-box > .button-icon,
+ #editBookmarkPanel .expander-down > .button-box > .button-icon {
+ width: 9px;
+ }
+}
+
+#editBMPanel_tagsField > .autocomplete-textbox-container > .textbox-input-box > html|*.textbox-input::-moz-placeholder {
+ opacity: 1.0;
+ color: #bbb;
+}
+
+.editBMPanel_rowLabel {
+ text-align: end;
+}
+
+/* ::::: content area ::::: */
+
+#sidebar {
+ background-color: Window;
+}
+
+#sidebar-title {
+ -moz-padding-start: 0px;
+}
+
+/* ::::: throbber ::::: */
+
+#navigator-throbber {
+ width: 16px;
+ min-height: 16px;
+ margin: 0 3px;
+}
+
+#navigator-throbber[busy="true"] {
+ list-style-image: url("chrome://global/skin/icons/loading_16.png");
+}
+
+#navigator-throbber,
+#wrapper-navigator-throbber > #navigator-throbber {
+ list-style-image: url("chrome://global/skin/icons/notloading_16.png");
+}
+
+/* Tabstrip */
+
+#TabsToolbar {
+ min-height: 0;
+ padding: 0;
+}
+
+#TabsToolbar:not(:-moz-lwtheme),
+#TabsToolbar[tabsontop=false] {
+ background-image: linear-gradient(to top, @toolbarShadowColor@ 1px, rgba(0,0,0,.05) 1px, transparent 50%);
+}
+
+/* When the tab bar is collapsed, show a 1px border in its place. */
+#TabsToolbar[tabsontop="false"][collapsed="true"]:not([customizing="true"]) {
+ visibility: visible;
+ height: 1px;
+ border-bottom-width: 1px;
+ /* !important here to override border-style: none on the toolbar */
+ border-bottom-style: solid !important;
+ border-bottom-color: ThreeDShadow;
+ overflow: hidden;
+}
+
+@media (-moz-mac-lion-theme) {
+ #main-window[sizemode=normal] #TabsToolbar {
+ padding-left: 2px;
+ padding-right: 2px;
+ }
+}
+
+/* remove 5 pixel border on left and right of browser screen */
+.tabbrowser-tabbox {
+ margin: 0;
+}
+
+.tabbrowser-tab,
+.tabs-newtab-button {
+ -moz-appearance: none;
+ background: @toolbarShadowOnTab@, @bgTabTexture@,
+ linear-gradient(-moz-dialog, -moz-dialog);
+ background-clip: padding-box;
+ padding: 3px 1px 4px;
+ /* Setting a transparent outer border allows us to have a 1px gap
+ between the tabs and the top edge of the screen, even when the
+ tabs have a top margin of 0, which is important for Fitts' law
+ compliance */
+ border: 2px solid;
+ border-bottom: none;
+ border-radius: 6px 6px 0px 0px;
+ -moz-border-top-colors: transparent #929292;
+ -moz-border-left-colors: transparent #929292;
+ -moz-border-right-colors: transparent #929292;
+ /* Hide the transparent top border by default */
+ margin-top: -1px;
+ /* Reduce the gap between the tabs */
+ -moz-margin-start: -1px;
+ box-shadow: inset 0.5px 1px 1px rgba(255,255,255,.7);
+}
+
+.tabbrowser-tab {
+ -moz-padding-end: 3px;
+}
+
+/* Override the default (globally-set) tab width values; increase
+ by 2px to compensate for the transparent outer border of the tabs */
+.tabbrowser-tab:not([pinned]) {
+ max-width: 252px;
+ min-width: 102px;
+}
+
+/* When the tabs are on top and the window is maximized or in full-
+ screen mode, unhide the transparent top border of the tabs so we
+ have a 1px gap between the tabs and the top edge of the screen */
+#main-window[sizemode="maximized"][tabsontop=true] .tabbrowser-tab,
+#main-window[sizemode="maximized"][tabsontop=true] .tabs-newtab-button,
+#main-window[sizemode="fullscreen"][tabsontop=true] .tabbrowser-tab,
+#main-window[sizemode="fullscreen"][tabsontop=true] .tabs-newtab-button {
+ margin-top: 0px;
+}
+
+/* make the tab text colors match those of the Windows client */
+@media (-moz-mac-lion-theme) {
+ tab {
+ text-shadow: none;
+ color: black !important;
+ }
+}
+
+.tabbrowser-tab:hover,
+.tabs-newtab-button:hover {
+ background-image: @toolbarShadowOnTab@, @bgTabTextureHover@,
+ linear-gradient(-moz-dialog, -moz-dialog);
+}
+
+.tabbrowser-tab[selected="true"] {
+ background-image: linear-gradient(@selectedTabHighlight@, @toolbarHighlight@ 50%),
+ linear-gradient(-moz-dialog, -moz-dialog);
+}
+
+#main-window[tabsontop=false]:not([disablechrome]) .tabbrowser-tab[selected=true]:not(:-moz-lwtheme) {
+ background-image: @toolbarShadowOnTab@,
+ linear-gradient(@selectedTabHighlight@, @toolbarHighlight@ 50%),
+ linear-gradient(-moz-dialog, -moz-dialog);
+}
+
+.tabbrowser-tab[visuallyselected=true]:not(:-moz-lwtheme) {
+ /* overriding tabbox.css */
+ color: inherit;
+}
+
+.tabbrowser-tab[visuallyselected=true] {
+ /* overriding tabbox.css */
+ text-shadow: inherit;
+}
+
+/* Remove highlight fuzz on dark themes */
+.tabbrowser-tab:-moz-lwtheme-brighttext,
+.tabs-newtab-button:-moz-lwtheme-brighttext {
+ box-shadow:none;
+ -moz-border-top-colors: transparent #707070;
+ -moz-border-left-colors: transparent #707070;
+ -moz-border-right-colors: transparent #707070;
+}
+
+.tabbrowser-tab[selected="true"]:-moz-lwtheme {
+ background-image: linear-gradient(rgba(255,255,255,.6), rgba(255,255,255,.8) 50%);
+}
+
+.tabbrowser-tab[selected="true"]:-moz-lwtheme-brighttext {
+ background-image: linear-gradient(rgba(128,128,128,.9), rgba(32,32,32,.9) 50%, rgba(32,32,32,.9) 80%, rgba(32,32,32,.8) 100%);
+ -moz-border-top-colors: transparent #D0D0D0;
+ -moz-border-left-colors: transparent #D0D0D0;
+ -moz-border-right-colors: transparent #D0D0D0;
+}
+
+.tabbrowser-tab:-moz-lwtheme-brighttext:not([selected="true"]),
+.tabs-newtab-button:-moz-lwtheme-brighttext {
+ background-image: linear-gradient(hsla(0,0%,25%,.4), hsla(0,0%,15%,.6) 80%);
+}
+
+.tabbrowser-tab:-moz-lwtheme-brighttext:not([selected="true"]):hover,
+.tabs-newtab-button:-moz-lwtheme-brighttext:hover {
+ background-image: linear-gradient(hsla(0,0%,60%,.4), hsla(0,0%,10%,.8) 80%);
+}
+
+.tabbrowser-tab:-moz-lwtheme-darktext:not([selected="true"]),
+.tabs-newtab-button:-moz-lwtheme-darktext {
+ background-image: linear-gradient(hsla(0,0%,75%,.4), hsla(0,0%,85%,.6) 80%);
+}
+
+.tabbrowser-tab:-moz-lwtheme-darktext:not([selected="true"]):hover,
+.tabs-newtab-button:-moz-lwtheme-darktext:hover {
+ background-image: linear-gradient(hsla(0,0%,60%,.4), hsla(0,0%,90%,.8) 80%);
+}
+
+.tabbrowser-tab[pinned][titlechanged]:not([selected="true"]) {
+ background-image: radial-gradient(circle farthest-corner at 50% 3px, rgba(255,255,255,1) 3%, rgba(186,221,251,.75) 40%, rgba(127,179,255,.5) 80%, rgba(127,179,255,.25));
+}
+.tabbrowser-tab[pinned][titlechanged]:not([selected="true"]):hover {
+ background-image: linear-gradient(hsla(0,0%,100%,.4), hsla(0,0%,75%,.4) 80%),
+ radial-gradient(circle farthest-corner at 50% 3px, rgba(255,255,255,1) 3%, rgba(186,221,251,.75) 40%, rgba(127,179,255,.5) 80%, rgba(127,179,255,.25));
+}
+
+.tab-throbber,
+.tab-icon-image {
+ width: 16px;
+ height: 16px;
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+ -moz-margin-start: 2px;
+ -moz-margin-end: 3px;
+}
+
+.tab-throbber {
+ list-style-image: url("chrome://browser/skin/tabbrowser/connecting.png");
+}
+
+.tab-throbber[progress] {
+ list-style-image: url("chrome://browser/skin/tabbrowser/loading.png");
+}
+
+.tab-throbber[pinned],
+.tab-icon-image[pinned] {
+ -moz-margin-start: 5px;
+ -moz-margin-end: 5px;
+}
+
+/* tabbrowser-tab focus ring */
+.tabbrowser-tab:focus > .tab-stack {
+ outline: 1px dotted;
+}
+
+/* Tab DnD indicator */
+.tab-drop-indicator {
+ list-style-image: url(chrome://browser/skin/tabbrowser/tabDragIndicator.png);
+ margin-bottom: -11px;
+}
+
+/* Tab close button */
+.tab-close-button {
+ -moz-appearance: none;
+ border: none;
+ padding: 0px;
+}
+
+.tab-close-button:-moz-lwtheme-brighttext {
+ list-style-image: url("chrome://global/skin/icons/close-inverted.png");
+}
+
+@media (min-resolution: 2dppx) {
+ .tab-close-button:-moz-lwtheme-brighttext {
+ list-style-image: url("chrome://global/skin/icons/close-inverted@2x.png");
+ }
+}
+
+/* Tab sound indicator */
+.tab-icon-sound {
+ -moz-margin-start: 4px;
+ width: 16px;
+ height: 16px;
+ padding: 0;
+}
+
+.allTabs-endimage[soundplaying],
+.tab-icon-sound[soundplaying] {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio");
+}
+
+.allTabs-endimage[muted],
+.tab-icon-sound[muted] {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-muted");
+}
+
+.allTabs-endimage[blocked],
+.tab-icon-sound[blocked] {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-blocked");
+}
+
+#TabsToolbar:-moz-lwtheme-brighttext .tab-icon-sound[soundplaying],
+#TabsToolbar:-moz-lwtheme-brighttext .tab-icon-sound[blocked],
+#TabsToolbar:-moz-lwtheme-brighttext .tab-icon-sound[muted] {
+ filter: invert(1);
+}
+
+.tab-icon-sound[soundplaying-scheduledremoval]:not([muted]):not(:hover),
+.tab-icon-overlay[soundplaying-scheduledremoval]:not([muted]):not(:hover) {
+ transition: opacity .3s linear var(--soundplaying-removal-delay);
+ opacity: 0;
+}
+
+/* Tab icon overlay */
+.tab-icon-overlay {
+ width: 16px;
+ height: 16px;
+ margin-top: -8px;
+ margin-inline-start: -15px;
+ margin-inline-end: -1px;
+ position: relative;
+}
+
+.tab-icon-overlay[soundplaying],
+.tab-icon-overlay[muted]:not([crashed]),
+.tab-icon-overlay[blocked]:not([crashed]) {
+ border-radius: 10px;
+}
+
+.tab-icon-overlay[soundplaying]:hover,
+.tab-icon-overlay[muted]:not([crashed]):hover,
+.tab-icon-overlay[blocked]:not([crashed]):hover {
+ background-color: white;
+}
+
+.tab-icon-overlay[soundplaying] {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio");
+}
+
+.tab-icon-overlay[muted] {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-muted");
+}
+
+.tab-icon-overlay[blocked] {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-blocked");
+}
+
+#TabsToolbar:-moz-lwtheme-brighttext .tab-icon-overlay[soundplaying]:not([selected]):not(:hover),
+.tab-icon-overlay[soundplaying][selected]:-moz-lwtheme-brighttext:not(:hover) {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-white");
+}
+
+#TabsToolbar:-moz-lwtheme-brighttext .tab-icon-overlay[muted]:not([crashed]):not([selected]):not(:hover),
+.tab-icon-overlay[muted][selected]:-moz-lwtheme-brighttext:not(:hover) {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-white-muted");
+}
+
+#TabsToolbar:-moz-lwtheme-brighttext .tab-icon-overlay[blocked]:not([crashed]):not([selected]):not(:hover),
+.tab-icon-overlay[blocked][selected]:-moz-lwtheme-brighttext:not(:hover) {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-white-blocked");
+}
+
+/* Tab scrollbox arrow, tabstrip new tab and all-tabs buttons */
+
+.tabbrowser-arrowscrollbox > .scrollbutton-up,
+.tabbrowser-arrowscrollbox > .scrollbutton-down {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-arrow-left.png");
+ margin: 0;
+ padding-right: 2px;
+ border-right: 2px solid transparent;
+ background-origin: border-box;
+}
+
+toolbar[brighttext] .tabbrowser-arrowscrollbox > .scrollbutton-up,
+toolbar[brighttext] .tabbrowser-arrowscrollbox > .scrollbutton-down {
+ list-style-image: url(chrome://browser/skin/tabbrowser/tab-arrow-left-inverted.png);
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-up[disabled],
+.tabbrowser-arrowscrollbox > .scrollbutton-down[disabled] {
+ opacity: .4;
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-up:-moz-locale-dir(rtl),
+.tabbrowser-arrowscrollbox > .scrollbutton-down:-moz-locale-dir(ltr) {
+ transform: scaleX(-1);
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-down {
+ transition: 1s background-color ease-out;
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-down[notifybgtab] {
+ background-color: Highlight;
+ transition: none;
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-up:not([disabled]),
+.tabbrowser-arrowscrollbox > .scrollbutton-down:not([disabled]) {
+ border-width: 0 2px 0 0;
+ border-style: solid;
+ border-image: url("chrome://browser/skin/tabbrowser/tab-overflow-border.png") 0 2 0 2 fill;
+}
+
+.tabs-newtab-button > .toolbarbutton-icon {
+ margin-top: -1px;
+ margin-bottom: -1px;
+}
+
+.tabs-newtab-button,
+#TabsToolbar > #new-tab-button,
+#TabsToolbar > toolbarpaletteitem > #new-tab-button {
+ list-style-image: url(chrome://browser/skin/tabbrowser/newtab.png);
+ -moz-image-region: auto;
+}
+
+.tabs-newtab-button:-moz-lwtheme-brighttext,
+toolbar[brighttext] #TabsToolbar > #new-tab-button,
+toolbar[brighttext] #TabsToolbar > toolbarpaletteitem > #new-tab-button {
+ list-style-image: url(chrome://browser/skin/tabbrowser/newtab-inverted.png);
+}
+
+.tabs-newtab-button {
+ width: 28px;
+}
+
+#TabsToolbar > #new-tab-button {
+ width: 26px;
+}
+
+#alltabs-button {
+ list-style-image: url("chrome://browser/skin/tabbrowser/alltabs.png");
+ -moz-image-region: rect(0, 14px, 16px, 0);
+}
+
+#alltabs-button[type="menu"] {
+ list-style-image: url("chrome://browser/skin/toolbarbutton-dropdown-arrow.png");
+ -moz-image-region: auto;
+}
+
+toolbar[brighttext] #alltabs-button {
+ list-style-image: url("chrome://browser/skin/tabbrowser/alltabs-inverted.png");
+}
+
+toolbar[brighttext] #alltabs-button[type="menu"] {
+ list-style-image: url("chrome://browser/skin/toolbarbutton-dropdown-arrow-inverted.png");
+}
+
+#alltabs-button[type="menu"] > .toolbarbutton-icon {
+ margin: 0 2px;
+}
+
+#alltabs-button[type="menu"] > .toolbarbutton-menu-dropmarker {
+ display: none;
+}
+
+/* All tabs menupopup */
+.alltabs-item > .menu-iconic-left > .menu-iconic-icon {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+
+.alltabs-item[selected="true"] {
+ font-weight: bold;
+}
+
+.alltabs-item[busy] > .menu-iconic-left > .menu-iconic-icon {
+ list-style-image: url("chrome://global/skin/icons/loading_16.png");
+}
+
+.alltabs-item[tabIsVisible] {
+ /* box-shadow instead of background-color to work around native styling */
+ box-shadow: inset -5px 0 ThreeDShadow;
+}
+
+/* Tabstrip close button */
+.tabs-closebutton {
+ -moz-appearance: none;
+ padding: 4px 2px;
+ margin: 0px;
+ border: none;
+}
+
+toolbar[brighttext] .tabs-closebutton {
+ list-style-image: url("chrome://global/skin/icons/close-inverted.png");
+}
+
+@media (min-resolution: 2dppx) {
+ toolbar[brighttext] .tabs-closebutton {
+ list-style-image: url("chrome://global/skin/icons/close-inverted@2x.png");
+ }
+}
+
+.tabs-closebutton > .toolbarbutton-icon {
+ -moz-margin-end: 0px !important;
+ -moz-padding-end: 2px !important;
+ -moz-padding-start: 2px !important;
+}
+
+#sidebar-throbber[loading="true"] {
+ list-style-image: url("chrome://global/skin/icons/loading_16.png");
+ -moz-margin-end: 4px;
+}
+
+/* Pale Moon: Feed icon */
+#ub-feed-button,
+#ub-feed-button > .button-box,
+#ub-feed-button:hover:active > .button-box {
+ padding: 0px;
+ margin: 0px;
+ border: 0px;
+ background-color: transparent !important;
+}
+
+#ub-feed-button {
+ -moz-appearance: none;
+ min-width: 0px;
+ list-style-image: url("chrome://browser/skin/feeds/feed-icons-16.png");
+ -moz-image-region: rect(0px 16px 16px 0px);
+}
+
+#ub-feed-button:hover {
+ -moz-image-region: rect(0px 32px 16px 16px);
+}
+
+#ub-feed-button[open="true"],
+#ub-feed-button:hover:active {
+ -moz-image-region: rect(0px 48px 16px 32px);
+}
+
+
+/* Bookmarks toolbar */
+#PlacesToolbarDropIndicator {
+ list-style-image: url(chrome://browser/skin/places/toolbarDropMarker.png);
+}
+
+toolbarbutton.bookmark-item[dragover="true"][open="true"] {
+ -moz-appearance: none;
+ background: Highlight !important;
+ color: HighlightText !important;
+}
+
+/* rules for menupopup drop indicators */
+.menupopup-drop-indicator-bar {
+ position: relative;
+ /* these two margins must together compensate the indicator's height */
+ margin-top: -1px;
+ margin-bottom: -1px;
+}
+
+.menupopup-drop-indicator {
+ list-style-image: none;
+ height: 2px;
+ -moz-margin-end: -4em;
+ background-color: Highlight;
+}
+
+/* ::::: Identity Indicator Styling ::::: */
+
+/* Popup Icons */
+#identity-popup-icon {
+ height: 64px;
+ width: 64px;
+ padding: 0;
+ list-style-image: url("chrome://browser/skin/identity.png");
+ -moz-image-region: rect(0px, 64px, 64px, 0px);
+}
+
+#identity-popup.verifiedDomain > #identity-popup-container > #identity-popup-icon {
+ -moz-image-region: rect(64px, 64px, 128px, 0px);
+}
+
+#identity-popup.verifiedIdentity > #identity-popup-container > #identity-popup-icon {
+ -moz-image-region: rect(128px, 64px, 192px, 0px);
+}
+
+/* Popup Body Text */
+.identity-popup-description {
+ white-space: pre-wrap;
+ -moz-padding-start: 15px;
+ margin: 2px 0 4px;
+}
+
+.identity-popup-label {
+ white-space: pre-wrap;
+ -moz-padding-start: 15px;
+ margin: 0;
+}
+
+#identity-popup-content-host,
+#identity-popup-content-box.verifiedIdentity > #identity-popup-content-owner {
+ font-size: 1.2em;
+}
+
+#identity-popup-content-host {
+ margin-top: 3px;
+ margin-bottom: 5px;
+ font-weight: bold;
+ max-width: 300px;
+}
+
+#identity-popup-content-owner {
+ margin-top: 4px;
+ margin-bottom: 0 !important;
+ font-weight: bold;
+ max-width: 300px;
+}
+
+.verifiedDomain > #identity-popup-content-owner {
+ font-weight: normal;
+}
+
+#identity-popup-content-verifier {
+ margin: 4px 0 2px;
+}
+
+#identity-popup-content-box.verifiedIdentity > #identity-popup-encryption ,
+#identity-popup-content-box.verifiedDomain > #identity-popup-encryption {
+ margin-top: 10px;
+ -moz-margin-start: -24px;
+}
+
+#identity-popup-content-box.verifiedIdentity > #identity-popup-encryption > vbox > #identity-popup-encryption-icon ,
+#identity-popup-content-box.verifiedDomain > #identity-popup-encryption > vbox > #identity-popup-encryption-icon {
+ list-style-image: url("chrome://browser/skin/Secure24.png");
+}
+
+#identity-popup-more-info-button {
+ margin-top: 6px;
+ margin-bottom: 0;
+ -moz-margin-end: 0;
+}
+
+.popup-notification-icon {
+ width: 64px;
+ height: 64px;
+ -moz-margin-end: 10px;
+}
+
+.popup-notification-icon[popupid="geolocation"] {
+ list-style-image: url(chrome://browser/skin/Geolocation-64.png);
+}
+
+.popup-notification-icon[popupid="xpinstall-disabled"],
+.popup-notification-icon[popupid="addon-progress"],
+.popup-notification-icon[popupid="addon-install-cancelled"],
+.popup-notification-icon[popupid="addon-install-blocked"],
+.popup-notification-icon[popupid="addon-install-origin-blocked"],
+.popup-notification-icon[popupid="addon-install-failed"],
+.popup-notification-icon[popupid="addon-install-complete"] {
+ list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric.png);
+ width: 32px;
+ height: 32px;
+}
+
+.popup-notification-icon[popupid="click-to-play-plugins"] {
+ list-style-image: url(chrome://mozapps/skin/plugins/pluginBlocked-64.png);
+}
+
+.popup-notification-icon[popupid="web-notifications"] {
+ list-style-image: url(chrome://browser/skin/notification-64.png);
+}
+
+.addon-progress-description {
+ width: 350px;
+ max-width: 350px;
+}
+
+.popup-progress-label,
+.popup-progress-meter {
+ -moz-margin-start: 0;
+ -moz-margin-end: 0;
+}
+
+.popup-progress-cancel {
+ -moz-appearance: none;
+ background: transparent;
+ border: none;
+ padding: 0;
+ min-height: 16px;
+ min-width: 16px;
+ max-height: 16px;
+ max-width: 16px;
+ margin: 0 1px 0 1px;
+ list-style-image: url(chrome://mozapps/skin/downloads/buttons.png);
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+}
+
+.popup-progress-cancel:hover {
+ -moz-image-region: rect(16px, 32px, 32px, 16px);
+}
+
+.popup-progress-cancel:active {
+ -moz-image-region: rect(32px, 32px, 48px, 16px);
+}
+
+.popup-notification-icon[popupid="indexedDB-permissions-prompt"],
+.popup-notification-icon[popupid="indexedDB-quota-prompt"],
+.popup-notification-icon[popupid*="offline-app-requested"],
+.popup-notification-icon[popupid="offline-app-usage"] {
+ list-style-image: url(chrome://global/skin/icons/question-64.png);
+}
+
+.popup-notification-icon[popupid="password"] {
+ list-style-image: url(chrome://mozapps/skin/passwordmgr/key-64.png);
+}
+
+.popup-notification-icon[popupid="mixed-content-blocked"] {
+ list-style-image: url(chrome://browser/skin/mixed-content-blocked-64.png);
+}
+
+%ifdef MOZ_WEBRTC
+.popup-notification-icon[popupid="webRTC-sharingDevices"],
+.popup-notification-icon[popupid="webRTC-shareDevices"] {
+ list-style-image: url(chrome://browser/skin/webRTC-shareDevice-64.png);
+}
+
+%endif
+.popup-notification-icon[popupid="pointerLock"] {
+ list-style-image: url(chrome://browser/skin/pointerLock-64.png);
+}
+
+/* Notification icon box */
+#notification-popup-box {
+ position: relative;
+ background-color: #fff;
+ background-clip: padding-box;
+ padding-left: 3px;
+ border-radius: 2.5px 0 0 2.5px;
+ border-width: 0 8px 0 0;
+ border-style: solid;
+ border-image: url("chrome://browser/skin/urlbar-arrow.png") 0 8 0 0 fill;
+ -moz-margin-end: -8px;
+}
+
+@conditionalForwardWithUrlbar@[forwarddisabled] + #urlbar-container > #urlbar > #notification-popup-box {
+ padding-left: 5px;
+}
+
+#notification-popup-box:-moz-locale-dir(rtl),
+.notification-anchor-icon:-moz-locale-dir(rtl) {
+ transform: scaleX(-1);
+}
+
+.notification-anchor-icon {
+ width: 16px;
+ height: 16px;
+ margin: 0 2px;
+}
+
+.notification-anchor-icon:-moz-focusring {
+ outline: 1px dotted -moz-DialogText;
+ outline-offset: -3px;
+}
+
+.default-notification-icon,
+#default-notification-icon {
+ list-style-image: url(chrome://global/skin/icons/information-16.png);
+}
+
+.geo-notification-icon,
+#geo-notification-icon {
+ list-style-image: url(chrome://browser/skin/Geolocation-16.png);
+}
+
+#addons-notification-icon {
+ list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric-16.png);
+}
+
+.indexedDB-notification-icon,
+#indexedDB-notification-icon {
+ list-style-image: url(chrome://global/skin/icons/question-16.png);
+}
+
+#password-notification-icon {
+ list-style-image: url(chrome://mozapps/skin/passwordmgr/key-16.png);
+}
+
+#plugins-notification-icon {
+ list-style-image: url(chrome://browser/skin/notification-pluginNormal.png);
+}
+
+#alert-plugins-notification-icon {
+ list-style-image: url(chrome://browser/skin/notification-pluginAlert.png);
+}
+
+#blocked-plugins-notification-icon {
+ list-style-image: url(chrome://browser/skin/notification-pluginBlocked.png);
+}
+
+#plugins-notification-icon,
+#alert-plugins-notification-icon,
+#blocked-plugins-notification-icon {
+ -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+#plugins-notification-icon:hover,
+#alert-plugins-notification-icon:hover,
+#blocked-plugins-notification-icon:hover {
+ -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
+#plugins-notification-icon:active,
+#alert-plugins-notification-icon:active,
+#blocked-plugins-notification-icon:active {
+ -moz-image-region: rect(0, 48px, 16px, 32px);
+}
+
+#notification-popup-box[hidden] {
+ /* Override display:none to make the pluginBlockedNotification animation work
+ when showing the notification repeatedly. */
+ display: -moz-box;
+ visibility: collapse;
+}
+
+#blocked-plugins-notification-icon[showing] {
+ animation: pluginBlockedNotification 500ms ease 0s 5 alternate both;
+}
+
+@keyframes pluginBlockedNotification {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
+.mixed-content-blocked-notification-icon,
+#mixed-content-blocked-notification-icon {
+ list-style-image: url(chrome://browser/skin/mixed-content-blocked-16.png);
+}
+
+%ifdef MOZ_WEBRTC
+.webRTC-shareDevices-notification-icon,
+#webRTC-shareDevices-notification-icon {
+ list-style-image: url(chrome://browser/skin/webRTC-shareDevice-16.png);
+}
+
+.webRTC-sharingDevices-notification-icon,
+#webRTC-sharingDevices-notification-icon {
+ list-style-image: url(chrome://browser/skin/webRTC-sharingDevice-16.png);
+}
+%endif
+
+.web-notifications-notification-icon,
+#web-notifications-notification-icon {
+ list-style-image: url(chrome://browser/skin/web-notifications-tray.svg);
+ -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+.web-notifications-notification-icon:hover,
+#web-notifications-notification-icon:hover {
+ -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
+.web-notifications-notification-icon:hover:active,
+#web-notifications-notification-icon:hover:active {
+ -moz-image-region: rect(0, 48px, 16px, 32px);
+}
+
+#pointerLock-notification-icon {
+ list-style-image: url(chrome://browser/skin/pointerLock-16.png);
+}
+#pointerLock-cancel {
+ margin: 0px;
+}
+
+#identity-popup-container {
+ min-width: 280px;
+}
+
+/* Bookmarks roots menu-items */
+#appmenu_subscribeToPage:not([disabled]),
+#appmenu_subscribeToPageMenu,
+#subscribeToPageMenuitem:not([disabled]),
+#subscribeToPageMenupopup,
+#BMB_subscribeToPageMenuitem:not([disabled]),
+#BMB_subscribeToPageMenupopup {
+ list-style-image: url("chrome://browser/skin/feeds/feedIcon16.png");
+}
+
+#appmenu_bookmarksToolbar,
+#bookmarksToolbarFolderMenu,
+#BMB_bookmarksToolbar {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png");
+ -moz-image-region: auto;
+}
+
+@media (min-resolution: 2dppx) {
+ #bookmarksToolbarFolderMenu,
+ #BMB_bookmarksToolbar {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar@2x.png");
+ -moz-image-region: auto;
+ }
+}
+
+#appmenu_unsortedBookmarks,
+#BMB_unsortedBookmarks {
+ list-style-image: url("chrome://browser/skin/places/unsortedBookmarks.png");
+ -moz-image-region: auto;
+}
+
+/* ::::: Keyboard UI Panel ::::: */
+
+.KUI-panel {
+ -moz-appearance: none;
+ background: rgba(27%,27%,27%,.9) url(KUI-background.png) repeat-x;
+ color: white;
+ border-style: none;
+ border-radius: 20px;
+}
+
+.KUI-panel[level="top"] {
+ background-color: rgba(27%,27%,27%,.65);
+}
+
+.KUI-panel-closebutton {
+ list-style-image: url(KUI-close.png);
+ -moz-appearance: none;
+ border: none;
+ padding: 0;
+ width: 24px;
+ height: 24px;
+}
+
+.KUI-panel-closebutton:not(:hover) {
+ opacity: .6;
+}
+
+.KUI-panel-closebutton > .toolbarbutton-icon {
+ margin: 0;
+}
+
+/* ::::: Ctrl-Tab and All Tabs Panels ::::: */
+
+/* Ctrl-Tab */
+
+#ctrlTab-panel {
+ padding: 20px 10px 10px;
+ font-weight: bold;
+ text-shadow: 0 0 1px rgb(27%,27%,27%), 0 0 2px rgb(27%,27%,27%);
+}
+
+.ctrlTab-favicon[src] {
+ background-color: white;
+ width: 20px;
+ height: 20px;
+ padding: 2px;
+}
+
+.ctrlTab-preview-inner > .tabPreview-canvas {
+ box-shadow: 1px 1px 2px rgb(12%,12%,12%);
+}
+
+.ctrlTab-preview:not(#ctrlTab-showAll) > * > .ctrlTab-preview-inner > .tabPreview-canvas {
+ margin-bottom: 2px;
+}
+
+.ctrlTab-preview-inner {
+ padding-bottom: 10px;
+}
+
+#ctrlTab-showAll:not(:focus) > * > .ctrlTab-preview-inner {
+ padding: 10px;
+ background-color: rgba(255,255,255,.2);
+ border-radius: .5em;
+}
+
+.ctrlTab-preview:focus > * > .ctrlTab-preview-inner {
+ color: white;
+ background-color: rgba(0,0,0,.6);
+ text-shadow: none;
+ padding: 8px;
+ border: 2px solid white;
+ border-radius: .5em;
+}
+
+.ctrlTab-preview:not(#ctrlTab-showAll):focus > * > .ctrlTab-preview-inner {
+ margin: -10px -10px 0;
+}
+
+#ctrlTab-showAll {
+ margin-top: .5em;
+}
+
+/* All Tabs */
+
+#allTabs-panel {
+ padding-bottom: 10px;
+ -moz-appearance: none;
+ border: none;
+ background: -moz-dialog;
+ color: -moz-dialogText;
+}
+
+#allTabs-meta {
+ margin: 10px;
+}
+
+#allTabs-filter {
+ -moz-margin-start: 24px;
+ -moz-margin-end: 0;
+}
+
+#allTabs-tab-close-button > .toolbarbutton-icon {
+ margin: 0;
+}
+
+/* Make sure the allTab previews always have regular close buttons */
+#allTabs-tab-close-button:-moz-lwtheme-brighttext {
+ list-style-image: url("chrome://global/skin/icons/close.png");
+}
+
+.allTabs-favicon[src] {
+ background-color: -moz-dialog;
+ width: 22px;
+ height: 22px;
+ padding-top: 1px;
+ padding-bottom: 5px;
+ -moz-padding-start: 1px;
+ -moz-padding-end: 5px;
+ margin-top: -2px;
+ -moz-margin-start: -2px;
+ border-bottom-right-radius: 4px;
+}
+
+.allTabs-favicon[src]:-moz-locale-dir(rtl) {
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 4px;
+}
+
+.allTabs-preview-inner > .tabPreview-canvas {
+ background-color: rgb(60%,60%,60%);
+ box-shadow: 0 0 1.5px ThreeDShadow;
+}
+
+.allTabs-preview:not(:hover):not([closebuttonhover]) > html|canvas {
+ opacity: .8;
+}
+
+.allTabs-preview:focus > * > .allTabs-preview-inner {
+ outline: 1px dotted -moz-dialogText;
+}
+
+/* Add-on bar */
+
+#addon-bar {
+ -moz-appearance: none;
+ min-height: 20px;
+ border-top-style: none;
+ border-bottom-style: none;
+ padding-top: 1px;
+ background-image: linear-gradient(rgba(0,0,0,.15) 1px, rgba(255,255,255,.15) 1px);
+ background-size: 100% 2px;
+ background-repeat: no-repeat;
+}
+
+#status-bar {
+ -moz-appearance: none;
+ background-color: transparent;
+ border: none;
+ min-height: 0;
+}
+
+#addon-bar[customizing] > #status-bar {
+ opacity: .5;
+ background-image: repeating-linear-gradient(135deg,
+ rgba(255,255,255,.3), rgba(255,255,255,.3) 5px,
+ rgba(0,0,0,.3) 5px, rgba(0,0,0,.3) 10px);
+}
+
+#status-bar > statusbarpanel {
+ border-width: 0;
+ -moz-appearance: none;
+}
+
+#addonbar-closebutton {
+ border: none;
+ padding: 0 5px;
+ -moz-appearance: none;
+}
+
+toolbar[brighttext] #addonbar-closebutton {
+ list-style-image: url("chrome://global/skin/icons/close-inverted.png");
+}
+
+@media (min-resolution: 2dppx) {
+ toolbar[brighttext] #addonbar-closebutton {
+ list-style-image: url("chrome://global/skin/icons/close-inverted@2x.png");
+ }
+}
+
+/* Status panel */
+
+.statuspanel-label {
+ margin: 0;
+ padding: 2px 4px;
+ background: linear-gradient(#fff, #ddd);
+ border: 1px none #ccc;
+ border-top-style: solid;
+ color: #333;
+ text-shadow: none;
+}
+
+.statuspanel-label:-moz-locale-dir(ltr):not([mirror]),
+.statuspanel-label:-moz-locale-dir(rtl)[mirror] {
+ border-right-style: solid;
+ /* disabled for triggering grayscale AA (bug 659213)
+ border-top-right-radius: .3em;
+ */
+ margin-right: 1em;
+}
+
+.statuspanel-label:-moz-locale-dir(rtl):not([mirror]),
+.statuspanel-label:-moz-locale-dir(ltr)[mirror] {
+ border-left-style: solid;
+ /* disabled for triggering grayscale AA (bug 659213)
+ border-top-left-radius: .3em;
+ */
+ margin-left: 1em;
+}
+
+#full-screen-warning-message {
+ background-color: hsl(0,0%,15%);
+ color: white;
+ border-radius: 8px;
+ margin-top: 30px;
+ padding: 30px 50px;
+ box-shadow: 0 0 2px white;
+}
+
+.full-screen-description {
+ font-size: 150%;
+}
+
+#full-screen-domain-text {
+ font-size: 300%;
+}
+
+%ifdef MOZ_DEVTOOLS
+%include ../../../../devtools/client/themes/responsivedesign.inc.css
+%include ../../../../devtools/client/themes/commandline.inc.css
+%endif
+%include ../shared/plugin-doorhanger.inc.css
+
+%ifdef MOZ_DEVTOOLS
+/* Error counter */
+
+#developer-toolbar-toolbox-button[error-count]:before {
+ color: #FDF3DE;
+ min-width: 16px;
+ text-shadow: none;
+ background-image: linear-gradient(#B4211B, #8A1915);
+ border-radius: 1px;
+ -moz-margin-end: 5px;
+}
+%endif
+
+.toolbarbutton-badge-stack {
+ margin: 0;
+ padding: 0;
+ position: relative;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-badge-stack {
+ padding: 2px 5px;
+}
+
+.toolbarbutton-1 > .toolbarbutton-badge-stack > .toolbar-icon {
+ position: absolute;
+ top: 2px;
+ right: 2px;
+}
+
+.toolbarbutton-badge-stack > .toolbarbutton-icon[label]:not([label=""]) {
+ -moz-margin-end: 0;
+}
+
+@navbarLargeIcons@ *|* > .toolbarbutton-badge[badge]:not([badge=""])::after {
+ top: 1px;
+ right: 1px;
+}
+
+.toolbarbutton-badge[badge]:not([badge=""]):-moz-locale-dir(rtl)::after {
+ left: 0;
+ right: auto;
+}
+
+@navbarLargeIcons@ *|* > .toolbarbutton-badge[badge]:not([badge=""]):-moz-locale-dir(rtl)::after {
+ left: 1px;
+ right: auto;
+}
+
+#main-window[privatebrowsingmode=temporary] {
+ background-image: url("chrome://browser/skin/privatebrowsing-mask.png");
+ background-position: top right;
+ background-repeat: no-repeat;
+ background-color: moz-mac-chrome-active;
+}
+
+@media (min-resolution: 2dppx) {
+ #main-window[privatebrowsingmode=temporary] {
+ background-image: url("chrome://browser/skin/privatebrowsing-mask@2x.png");
+ background-size: 38px;
+ }
+}
+
+#main-window[privatebrowsingmode=temporary] {
+ background-position: top right 40px;
+}
+
+#main-window[privatebrowsingmode=temporary][inFullscreen] {
+ background-position: top right 10px;
+}
+
+#main-window[privatebrowsingmode=temporary]:-moz-locale-dir(rtl) {
+ background-position: top left 70px;
+}
+
+#main-window[privatebrowsingmode=temporary][inFullscreen]:-moz-locale-dir(rtl) {
+ background-position: top left 10px;
+}
+
+#main-window[privatebrowsingmode=temporary]:-moz-window-inactive {
+ background-color: -moz-mac-chrome-inactive;
+}
+
+#main-window[privatebrowsingmode=temporary][inFullscreen] #nav-bar[tabsontop=false] {
+ -moz-padding-end: 50px !important;
+}
+
+@media (-moz-mac-lion-theme) {
+ #main-window[privatebrowsingmode=temporary][inFullscreen] #TabsToolbar[tabsontop=true] {
+ -moz-padding-end: 50px;
+ }
+}
+
+@media not all and (-moz-mac-lion-theme) {
+ #main-window[privatebrowsingmode=temporary] {
+ background-position: top right 10px;
+ }
+
+ #main-window[privatebrowsingmode=temporary][inFullscreen][tabsontop=true] #window-controls {
+ -moz-padding-end: 50px;
+ }
+}
diff --git a/themes/osx/click-to-play-warning-stripes.png b/themes/osx/click-to-play-warning-stripes.png
new file mode 100644
index 0000000..29f15f7
--- /dev/null
+++ b/themes/osx/click-to-play-warning-stripes.png
Binary files differ
diff --git a/themes/osx/communicator/communicator.css b/themes/osx/communicator/communicator.css
new file mode 100644
index 0000000..0b57574
--- /dev/null
+++ b/themes/osx/communicator/communicator.css
@@ -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/. */
+
+@import url("chrome://global/skin/");
+
diff --git a/themes/osx/communicator/jar.mn b/themes/osx/communicator/jar.mn
new file mode 100644
index 0000000..612d133
--- /dev/null
+++ b/themes/osx/communicator/jar.mn
@@ -0,0 +1,7 @@
+# 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:
+% skin communicator classic/1.0 %skin/classic/communicator/
+ skin/classic/communicator/communicator.css
diff --git a/themes/osx/communicator/moz.build b/themes/osx/communicator/moz.build
new file mode 100644
index 0000000..c97072b
--- /dev/null
+++ b/themes/osx/communicator/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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/themes/osx/downloads/allDownloadsViewOverlay.css b/themes/osx/downloads/allDownloadsViewOverlay.css
new file mode 100644
index 0000000..eb99f4c
--- /dev/null
+++ b/themes/osx/downloads/allDownloadsViewOverlay.css
@@ -0,0 +1,146 @@
+/* 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/. */
+
+#downloadsRichListBox {
+ /** The default listbox appearance comes with an unwanted margin. **/
+ -moz-appearance: none;
+ margin: 0;
+}
+
+#downloadsRichListBox > richlistitem.download {
+ height: 6em;
+ padding: 5px 8px;
+}
+
+.downloadTypeIcon {
+ -moz-margin-end: 8px;
+ /* explicitly size the icon, so size doesn't vary on hidpi systems */
+ height: 32px;
+ width: 32px;
+}
+
+.blockedIcon {
+ list-style-image: url("chrome://global/skin/icons/Error.png");
+}
+
+.downloadTarget {
+ margin-bottom: 3px;
+ cursor: inherit;
+}
+
+.downloadDetails {
+ opacity: 0.7;
+ font-size: 95%;
+ cursor: inherit;
+}
+
+.downloadButton {
+ -moz-appearance: none;
+ background: transparent;
+ min-width: 0;
+ min-height: 0;
+ margin: 3px;
+ border: none;
+ padding: 5px;
+ list-style-image: url("chrome://browser/skin/downloads/buttons.png");
+}
+
+/*** Button icons ***/
+
+.downloadButton.downloadCancel {
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+}
+
+richlistitem.download:hover > .downloadButton.downloadCancel {
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+richlistitem.download:hover > .downloadButton.downloadCancel:hover {
+ -moz-image-region: rect(0px, 48px, 16px, 32px);
+}
+
+richlistitem.download:hover > .downloadButton.downloadCancel:active {
+ -moz-image-region: rect(0px, 64px, 16px, 48px);
+}
+
+.downloadButton.downloadShow {
+ -moz-image-region: rect(16px, 16px, 32px, 0px);
+}
+
+richlistitem.download:hover > .downloadButton.downloadShow {
+ -moz-image-region: rect(16px, 32px, 32px, 16px);
+}
+
+richlistitem.download:hover > .downloadButton.downloadShow:hover {
+ -moz-image-region: rect(16px, 48px, 32px, 32px);
+}
+
+richlistitem.download:hover > .downloadButton.downloadShow:active {
+ -moz-image-region: rect(16px, 64px, 32px, 48px);
+}
+
+.downloadButton.downloadRetry {
+ -moz-image-region: rect(32px, 16px, 48px, 0px);
+}
+
+richlistitem.download:hover > .downloadButton.downloadRetry {
+ -moz-image-region: rect(32px, 32px, 48px, 16px);
+}
+
+richlistitem.download:hover > .downloadButton.downloadRetry:hover {
+ -moz-image-region: rect(32px, 48px, 48px, 32px);
+}
+
+richlistitem.download:hover > .downloadButton.downloadRetry:active {
+ -moz-image-region: rect(32px, 64px, 48px, 48px);
+}
+
+richlistitem.download[selected] > .downloadButton.downloadCancel {
+ -moz-image-region: rect(0px, 80px, 16px, 64px);
+}
+
+richlistitem.download[selected]:hover > .downloadButton.downloadCancel {
+ -moz-image-region: rect(0px, 96px, 16px, 80px);
+}
+
+richlistitem.download[selected]:hover > .downloadButton.downloadCancel:hover {
+ -moz-image-region: rect(0px, 112px, 16px, 96px);
+}
+
+richlistitem.download[selected]:hover > .downloadButton.downloadCancel:active {
+ -moz-image-region: rect(0px, 128px, 16px, 112px);
+}
+
+richlistitem.download[selected] > .downloadButton.downloadShow {
+ -moz-image-region: rect(16px, 80px, 32px, 64px);
+}
+
+richlistitem.download[selected]:hover > .downloadButton.downloadShow {
+ -moz-image-region: rect(16px, 96px, 32px, 80px);
+}
+
+richlistitem.download[selected]:hover > .downloadButton.downloadShow:hover {
+ -moz-image-region: rect(16px, 112px, 32px, 96px);
+}
+
+richlistitem.download[selected]:hover > .downloadButton.downloadShow:active {
+ -moz-image-region: rect(16px, 128px, 32px, 112px);
+}
+
+richlistitem.download[selected] > .downloadButton.downloadRetry {
+ -moz-image-region: rect(32px, 80px, 48px, 64px);
+}
+
+richlistitem.download[selected]:hover > .downloadButton.downloadRetry {
+ -moz-image-region: rect(32px, 96px, 48px, 80px);
+}
+
+richlistitem.download[selected]:hover > .downloadButton.downloadRetry:hover {
+ -moz-image-region: rect(32px, 112px, 48px, 96px);
+}
+
+richlistitem.download[selected]:hover > .downloadButton.downloadRetry:active {
+ -moz-image-region: rect(32px, 128px, 48px, 112px);
+}
+
diff --git a/themes/osx/downloads/buttons.png b/themes/osx/downloads/buttons.png
new file mode 100644
index 0000000..ca87b40
--- /dev/null
+++ b/themes/osx/downloads/buttons.png
Binary files differ
diff --git a/themes/osx/downloads/contentAreaDownloadsView.css b/themes/osx/downloads/contentAreaDownloadsView.css
new file mode 100644
index 0000000..ece99ea
--- /dev/null
+++ b/themes/osx/downloads/contentAreaDownloadsView.css
@@ -0,0 +1,22 @@
+/* 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/. */
+
+@import url("chrome://global/skin/inContentUI.css");
+
+.downloadButton {
+ box-shadow: none;
+}
+
+.downloadButton:not([disabled="true"]):hover:active,
+.downloadButton:not([disabled]):hover:active {
+ background: transparent;
+ border: none;
+ box-shadow: none;
+}
+
+#downloadsListEmptyDescription {
+ margin: 1em;
+ text-align: center;
+ color: GrayText;
+}
diff --git a/themes/osx/downloads/download-glow.png b/themes/osx/downloads/download-glow.png
new file mode 100644
index 0000000..53182d7
--- /dev/null
+++ b/themes/osx/downloads/download-glow.png
Binary files differ
diff --git a/themes/osx/downloads/download-notification-finish.png b/themes/osx/downloads/download-notification-finish.png
new file mode 100644
index 0000000..5194f5d
--- /dev/null
+++ b/themes/osx/downloads/download-notification-finish.png
Binary files differ
diff --git a/themes/osx/downloads/download-notification-start.png b/themes/osx/downloads/download-notification-start.png
new file mode 100644
index 0000000..bd548b1
--- /dev/null
+++ b/themes/osx/downloads/download-notification-start.png
Binary files differ
diff --git a/themes/osx/downloads/download-summary.png b/themes/osx/downloads/download-summary.png
new file mode 100644
index 0000000..67003c7
--- /dev/null
+++ b/themes/osx/downloads/download-summary.png
Binary files differ
diff --git a/themes/osx/downloads/downloads.css b/themes/osx/downloads/downloads.css
new file mode 100644
index 0000000..267574e
--- /dev/null
+++ b/themes/osx/downloads/downloads.css
@@ -0,0 +1,394 @@
+/* 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/. */
+
+/*** Panel and outer controls ***/
+
+#downloadsPanel > .panel-arrowcontainer > .panel-arrowcontent {
+ padding: 0;
+}
+
+#downloadsListBox {
+ background-color: transparent;
+ padding: 4px;
+ color: inherit;
+}
+
+#downloadsPanel:not([hasdownloads]) > #downloadsListBox {
+ display: none;
+}
+
+#downloadsPanel[hasdownloads] > #emptyDownloads {
+ display: none;
+}
+
+#emptyDownloads {
+ padding: 10px 20px;
+ max-width: 40ch;
+}
+
+#downloadsHistory {
+ background: transparent;
+ color: -moz-nativehyperlinktext;
+ cursor: pointer;
+}
+
+#downloadsPanel[keyfocus] > #downloadsFooter > #downloadsHistory:focus {
+ outline: 1px -moz-dialogtext dotted;
+ outline-offset: -1px;
+}
+
+#downloadsHistory > .button-box {
+ border: none;
+ margin: 1em;
+}
+
+@media (-moz-mac-lion-theme) {
+ #downloadsFooter {
+ background-color: hsla(216,45%,88%,.98);
+ box-shadow: 0px 1px 2px rgb(204,214,234) inset;
+ border-bottom-left-radius: 3px;
+ border-bottom-right-radius: 3px;
+ }
+}
+
+/*** Downloads Summary and List items ***/
+
+#downloadsSummary,
+richlistitem[type="download"] {
+ height: 7em;
+ -moz-padding-end: 0;
+ color: inherit;
+}
+
+#downloadsSummary {
+ padding: 8px 38px 8px 12px;
+ cursor: pointer;
+ -moz-user-focus: normal;
+}
+
+#downloadsPanel[keyfocus] > #downloadsFooter > #downloadsSummary:focus {
+ outline: 1px -moz-dialogtext dotted;
+ outline-offset: -5px;
+}
+
+#downloadsSummary > .downloadTypeIcon {
+ list-style-image: url("chrome://browser/skin/downloads/download-summary.png");
+}
+
+#downloadsSummaryDescription {
+ color: -moz-nativehyperlinktext;
+}
+
+richlistitem[type="download"] {
+ margin: 0;
+ border-top: 1px solid hsla(0,0%,100%,.3);
+ border-bottom: 1px solid hsla(220,18%,51%,.25);
+ background: transparent;
+ padding: 8px;
+}
+
+richlistitem[type="download"]:first-child {
+ border-top: 1px solid transparent;
+}
+
+@media (-moz-mac-lion-theme) {
+ richlistitem[type="download"]:last-child {
+ border-bottom: 1px solid transparent;
+ }
+}
+
+#downloadsPanel[keyfocus] > #downloadsListBox:focus > richlistitem[type="download"][selected] {
+ outline: 1px -moz-dialogtext dotted;
+ outline-offset: -1px;
+}
+
+.downloadTypeIcon {
+ -moz-margin-end: 8px;
+ /* Prevent flickering when changing states. */
+ height: 32px;
+ width: 32px;
+}
+
+.blockedIcon {
+ list-style-image: url("chrome://global/skin/icons/Error.png");
+}
+
+/* We hold .downloadDisplayName, .downloadProgress and .downloadDetails
+ inside of a vbox with class .downloadContainer. We set the font-size of
+ the entire container to 90% because:
+
+ 1) This is the size that we want .downloadDetails to be
+ 2) The container's width is set by localizers by &downloadDetails.width;,
+ which is a ch unit. Since this is the value that should control the
+ panel width, we apply it to the outer container to constrain
+ .downloadDisplayName and .downloadProgress.
+
+ Finally, since we want .downloadDisplayName's font-size to be at 100% of
+ the font-size of .downloadContainer's parent, we use calc to go from the
+ smaller font-size back to the original font-size.
+ */
+#downloadsSummaryDetails,
+.downloadContainer {
+ font-size: 90%;
+}
+
+#downloadsSummaryDescription,
+.downloadDisplayName {
+ margin-bottom: 6px;
+ cursor: inherit;
+}
+
+.downloadDisplayName {
+ font-size: calc(100%/0.9);
+}
+
+#downloadsSummaryDetails,
+.downloadDetails {
+ opacity: 0.6;
+ cursor: inherit;
+}
+
+.downloadButton {
+ -moz-appearance: none;
+ min-width: 0;
+ min-height: 0;
+ margin: 3px;
+ border: none;
+ background: transparent;
+ padding: 5px;
+ list-style-image: url("chrome://browser/skin/downloads/buttons.png");
+}
+
+.downloadButton > .button-box {
+ border: 1px solid transparent;
+ padding: 0;
+}
+
+#downloadsPanel[keyfocus] .downloadButton:focus > .button-box {
+ border: 1px dotted ThreeDDarkShadow;
+}
+
+/*** Highlighted list items ***/
+
+#downloadsPanel:not([keyfocus]) > #downloadsListBox > richlistitem[type="download"][state="1"][exists]:hover {
+ border-radius: 3px;
+ border-top: 1px solid hsla(0,0%,100%,.2);
+ border-bottom: 1px solid hsla(0,0%,0%,.2);
+ background-color: Highlight;
+ color: HighlightText;
+ cursor: pointer;
+}
+
+/*** Button icons ***/
+
+.downloadButton.downloadCancel {
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+}
+richlistitem[type="download"]:hover > stack > .downloadButton.downloadCancel {
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+richlistitem[type="download"]:hover > stack > .downloadButton.downloadCancel:hover {
+ -moz-image-region: rect(0px, 48px, 16px, 32px);
+}
+richlistitem[type="download"]:hover > stack > .downloadButton.downloadCancel:active {
+ -moz-image-region: rect(0px, 64px, 16px, 48px);
+}
+
+.downloadButton.downloadShow {
+ -moz-image-region: rect(16px, 16px, 32px, 0px);
+}
+richlistitem[type="download"]:hover > stack > .downloadButton.downloadShow {
+ -moz-image-region: rect(16px, 32px, 32px, 16px);
+}
+richlistitem[type="download"]:hover > stack > .downloadButton.downloadShow:hover {
+ -moz-image-region: rect(16px, 48px, 32px, 32px);
+}
+richlistitem[type="download"]:hover > stack > .downloadButton.downloadShow:active {
+ -moz-image-region: rect(16px, 64px, 32px, 48px);
+}
+#downloadsPanel[keyfocus] > #downloadsListBox > richlistitem[type="download"][state="1"]:hover > stack > .downloadButton.downloadShow {
+ -moz-image-region: rect(16px, 32px, 32px, 16px);
+}
+#downloadsPanel[keyfocus] > #downloadsListBox > richlistitem[type="download"][state="1"]:hover > stack > .downloadButton.downloadShow:hover {
+ -moz-image-region: rect(16px, 48px, 32px, 32px);
+}
+#downloadsPanel[keyfocus] > #downloadsListBox > richlistitem[type="download"][state="1"]:hover > stack > .downloadButton.downloadShow:active {
+ -moz-image-region: rect(16px, 64px, 32px, 48px);
+}
+#downloadsPanel:not([keyfocus]) > #downloadsListBox > richlistitem[type="download"][state="1"]:hover > stack > .downloadButton.downloadShow {
+ -moz-image-region: rect(16px, 96px, 32px, 80px);
+}
+#downloadsPanel:not([keyfocus]) > #downloadsListBox > richlistitem[type="download"][state="1"]:hover > stack > .downloadButton.downloadShow:hover {
+ -moz-image-region: rect(16px, 112px, 32px, 96px);
+}
+#downloadsPanel:not([keyfocus]) > #downloadsListBox > richlistitem[type="download"][state="1"]:hover > stack > .downloadButton.downloadShow:active {
+ -moz-image-region: rect(16px, 128px, 32px, 112px);
+}
+
+.downloadButton.downloadRetry {
+ -moz-image-region: rect(32px, 16px, 48px, 0px);
+}
+richlistitem[type="download"]:hover > stack > .downloadButton.downloadRetry {
+ -moz-image-region: rect(32px, 32px, 48px, 16px);
+}
+richlistitem[type="download"]:hover > stack > .downloadButton.downloadRetry:hover {
+ -moz-image-region: rect(32px, 48px, 48px, 32px);
+}
+richlistitem[type="download"]:hover > stack > .downloadButton.downloadRetry:active {
+ -moz-image-region: rect(32px, 64px, 48px, 48px);
+}
+
+/*** Status and progress indicator ***/
+
+#downloads-indicator-anchor {
+ /* Makes the outermost stack element positioned, so that its contents are
+ rendered over the main browser window in the Z order. This is required by
+ the animated event notification. */
+ position: relative;
+}
+
+/*** Main indicator icon ***/
+
+#downloads-indicator-icon {
+ background: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"),
+ 0, 108, 18, 90) center no-repeat;
+ min-width: 18px;
+ min-height: 18px;
+}
+
+toolbar[brighttext] #downloads-indicator-icon {
+ background: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"),
+ 0, 108, 18, 90) center no-repeat;
+}
+
+#downloads-indicator[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
+ background-image: url("chrome://browser/skin/downloads/download-glow.png");
+}
+
+/* In the next few rules, we use :not([counter]) as a shortcut that is
+ equivalent to -moz-any([progress], [paused]). */
+
+#downloads-indicator:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+ background: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"),
+ 0, 108, 18, 90) center no-repeat;
+ background-size: 12px;
+}
+
+toolbar[brighttext] #downloads-indicator:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+ background: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"),
+ 0, 108, 18, 90) center no-repeat;
+}
+
+#downloads-indicator:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+ background-image: url("chrome://browser/skin/downloads/download-glow.png");
+}
+
+/*** Download notifications ***/
+
+#downloads-indicator-notification {
+ opacity: 0;
+ background-size: 16px;
+ background-position: center;
+ background-repeat: no-repeat;
+}
+
+@keyframes downloadsIndicatorNotificationStartRight {
+ from { opacity: 0; transform: translate(-128px, 128px) scale(8); }
+ 20% { opacity: .85; animation-timing-function: ease-out; }
+ to { opacity: 0; transform: translate(0) scale(1); }
+}
+
+@keyframes downloadsIndicatorNotificationStartLeft {
+ from { opacity: 0; transform: translate(128px, 128px) scale(8); }
+ 20% { opacity: .85; animation-timing-function: ease-out; }
+ to { opacity: 0; transform: translate(0) scale(1); }
+}
+
+#downloads-indicator[notification="start"] > #downloads-indicator-anchor > #downloads-indicator-notification {
+ background-image: url("chrome://browser/skin/downloads/download-notification-start.png");
+ animation-name: downloadsIndicatorNotificationStartRight;
+ animation-duration: 1s;
+}
+
+#downloads-indicator[notification="start"]:-moz-locale-dir(rtl) > #downloads-indicator-anchor > #downloads-indicator-notification {
+ animation-name: downloadsIndicatorNotificationStartLeft;
+}
+
+@keyframes downloadsIndicatorNotificationFinish {
+ from { opacity: 0; transform: scale(1); }
+ 20% { opacity: .65; animation-timing-function: ease-in; }
+ to { opacity: 0; transform: scale(8); }
+}
+
+#downloads-indicator[notification="finish"] > #downloads-indicator-anchor > #downloads-indicator-notification {
+ background-image: url("chrome://browser/skin/downloads/download-notification-finish.png");
+ animation-name: downloadsIndicatorNotificationFinish;
+ animation-duration: 1s;
+}
+
+/*** Progress bar and text ***/
+
+#downloads-indicator-counter {
+ height: 9px;
+ margin: -3px 0px 0px 0px;
+ color: hsl(0,0%,30%);
+ text-shadow: hsla(0,0%,100%,.5) 0 1px;
+ font-size: 9px;
+ line-height: 9px;
+ text-align: center;
+}
+
+toolbar[brighttext] #downloads-indicator-counter {
+ color: white;
+ text-shadow: 0 0 1px rgba(0,0,0,.7),
+ 0 1px 1.5px rgba(0,0,0,.5);
+}
+
+#downloads-indicator-progress {
+ width: 16px;
+ height: 5px;
+ min-width: 0;
+ min-height: 0;
+ margin-top: 1px;
+ margin-bottom: 2px;
+ border-radius: 2px;
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.4);
+}
+
+#downloads-indicator-progress > .progress-bar {
+ -moz-appearance: none;
+ min-width: 0;
+ min-height: 0;
+ /* The background-clip: border-box; and background-image: none; are there to expand the background-color behind the border */
+ background-clip: padding-box, border-box;
+ background-color: rgb(90, 201, 66);
+ background-image: linear-gradient(transparent 1px, rgba(255, 255, 255, 0.4) 1px, rgba(255, 255, 255, 0.4) 2px, transparent 2px), none;
+ border: 1px solid;
+ border-color: rgba(0,43,86,.6) rgba(0,43,86,.4) rgba(0,43,86,.4);
+ border-radius: 2px 0 0 2px;
+}
+
+#downloads-indicator-progress > .progress-remainder {
+ -moz-appearance: none;
+ min-width: 0;
+ min-height: 0;
+ background-image: linear-gradient(#505050, #575757);
+ border: 1px solid;
+ border-color: hsla(0,0%,0%,.6) hsla(0,0%,0%,.4) hsla(0,0%,0%,.4);
+ -moz-border-start: none;
+ border-radius: 0 2px 2px 0;
+}
+
+#downloads-indicator[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-bar {
+ background-color: rgb(220, 230, 81);
+}
+
+#downloads-indicator[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-remainder {
+ background-image: linear-gradient(#4b5000, #515700);
+}
+
+toolbar[mode="full"] > #downloads-indicator > .toolbarbutton-text {
+ margin: 0;
+ text-align: center;
+}
diff --git a/themes/osx/engineManager.css b/themes/osx/engineManager.css
new file mode 100644
index 0000000..18817cd
--- /dev/null
+++ b/themes/osx/engineManager.css
@@ -0,0 +1,16 @@
+%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
+
+#engineList treechildren::-moz-tree-image(engineName) {
+ -moz-margin-end: 4px;
+ -moz-margin-start: 1px;
+ width: 16px;
+ height: 16px;
+}
+
+#engineList treechildren::-moz-tree-row {
+ height: 20px;
+}
diff --git a/themes/osx/feeds/feed-icons-16.png b/themes/osx/feeds/feed-icons-16.png
new file mode 100644
index 0000000..bcca086
--- /dev/null
+++ b/themes/osx/feeds/feed-icons-16.png
Binary files differ
diff --git a/themes/osx/feeds/feedIcon.png b/themes/osx/feeds/feedIcon.png
new file mode 100644
index 0000000..e69bc44
--- /dev/null
+++ b/themes/osx/feeds/feedIcon.png
Binary files differ
diff --git a/themes/osx/feeds/feedIcon16.png b/themes/osx/feeds/feedIcon16.png
new file mode 100644
index 0000000..d778807
--- /dev/null
+++ b/themes/osx/feeds/feedIcon16.png
Binary files differ
diff --git a/themes/osx/feeds/subscribe-ui.css b/themes/osx/feeds/subscribe-ui.css
new file mode 100644
index 0000000..8ca5328
--- /dev/null
+++ b/themes/osx/feeds/subscribe-ui.css
@@ -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/. */
+
+.alwaysUse {
+ padding: 5px;
+}
+
+.handlersMenuPopup > menuitem {
+ -moz-padding-start: 23px;
+}
+
+.handlersMenuPopup > menuitem.menuitem-iconic {
+ -moz-padding-start: 2px;
+}
+
+.handlersMenuPopup > .menuitem-iconic > .menu-iconic-left {
+ display: -moz-box;
+ min-width: 16px;
+ -moz-padding-end: 2px;
+}
+
+.chooseApplicationMenuItem {
+ list-style-image: url("chrome://browser/skin/preferences/application.png");
+}
+
+#feedHeader[dir="rtl"] .handlersMenuList > menupopup {
+ direction: rtl;
+}
diff --git a/themes/osx/feeds/subscribe.css b/themes/osx/feeds/subscribe.css
new file mode 100644
index 0000000..780a7f8
--- /dev/null
+++ b/themes/osx/feeds/subscribe.css
@@ -0,0 +1,159 @@
+/* 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 {
+ background: -moz-Dialog;
+ font: 3mm tahoma,arial,helvetica,sans-serif;
+}
+
+#feedBody {
+ border: 1px solid THreeDShadow;
+ padding: 3em;
+ -moz-padding-start: 30px;
+ margin: 2em auto;
+ background: -moz-Field;
+}
+
+#feedHeaderContainer {
+ border: 1px solid ThreeDShadow;
+ border-radius: 10px;
+ margin: -4em auto 0 auto;
+ background-color: InfoBackground;
+}
+
+#feedHeader {
+ margin-top: 4.9em;
+ margin-bottom: 1em;
+ -moz-margin-start: 1.4em;
+ -moz-margin-end: 1em;
+ -moz-padding-start: 2.9em;
+ font-size: 110%;
+ color: InfoText;
+}
+
+#feedIntroText {
+ display: none;
+}
+
+.feedBackground {
+ background: url("chrome://browser/skin/feeds/feedIcon.png") 0% 10% no-repeat InfoBackground;
+}
+
+.videoPodcastBackground {
+ background: url("chrome://browser/skin/feeds/videoFeedIcon.png") 0% 10% no-repeat InfoBackground;
+}
+
+.audioPodcastBackground {
+ background: url("chrome://browser/skin/feeds/audioFeedIcon.png") 0% 10% no-repeat InfoBackground;
+}
+
+#feedHeader[dir="rtl"] {
+ background-position: 100% 10%;
+}
+
+#feedHeader[firstrun="true"] #feedIntroText {
+ padding-top: 0.1em;
+ -moz-padding-start: 0.6em;
+ display: block;
+}
+
+#feedHeader[firstrun="true"] > #feedSubscribeLine {
+ -moz-padding-start: 1.8em;
+}
+
+#feedSubscribeLine {
+ padding-top: 0.2em;
+}
+
+/* Don't print subscription UI */
+@media print {
+ #feedHeaderContainer {
+ display: none;
+ }
+}
+
+body {
+ margin: 0;
+ padding: 0 3em;
+ color: -moz-fieldText;
+ font: message-box;
+}
+
+h1 {
+ font-size: 160%;
+ border-bottom: 2px solid ThreeDLightShadow;
+ margin: 0 0 .2em 0;
+}
+
+h2 {
+ color: ThreeDDarkShadow;
+ font-size: 110%;
+ font-weight: normal;
+ margin: 0 0 .6em 0;
+}
+
+#feedTitleLink {
+ float: right;
+ -moz-margin-start: .6em;
+ -moz-margin-end: 0;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+a[href] img {
+ border: none;
+}
+
+#feedTitleContainer {
+ -moz-margin-start: 0;
+ -moz-margin-end: .6em;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+#feedTitleImage {
+ -moz-margin-start: .6em;
+ -moz-margin-end: 0;
+ margin-top: 0;
+ margin-bottom: 0;
+ max-width: 300px;
+ max-height: 150px;
+}
+
+.feedEntryContent {
+ font-size: 110%;
+}
+
+.link {
+ color: #0000FF;
+ text-decoration: underline;
+ cursor: pointer;
+}
+
+.link:hover:active {
+ color: #FF0000;
+}
+
+.lastUpdated {
+ font-size: 85%;
+ font-weight: normal;
+}
+
+.type-icon {
+ vertical-align: bottom;
+ height: 16px;
+ width: 16px;
+}
+
+.enclosures {
+ border: 1px solid THreeDShadow;
+ padding: 1em;
+ margin: 1em auto;
+ background: -moz-Dialog;
+}
+
+.enclosure {
+ vertical-align: middle;
+ margin-left: 2px;
+}
diff --git a/themes/osx/icon.png b/themes/osx/icon.png
new file mode 100644
index 0000000..ff4f21f
--- /dev/null
+++ b/themes/osx/icon.png
Binary files differ
diff --git a/themes/osx/identity-icons-generic.png b/themes/osx/identity-icons-generic.png
new file mode 100644
index 0000000..a39e493
--- /dev/null
+++ b/themes/osx/identity-icons-generic.png
Binary files differ
diff --git a/themes/osx/identity-icons-https-ev.png b/themes/osx/identity-icons-https-ev.png
new file mode 100644
index 0000000..d49be13
--- /dev/null
+++ b/themes/osx/identity-icons-https-ev.png
Binary files differ
diff --git a/themes/osx/identity-icons-https-mixed-active.png b/themes/osx/identity-icons-https-mixed-active.png
new file mode 100644
index 0000000..3c77bc8
--- /dev/null
+++ b/themes/osx/identity-icons-https-mixed-active.png
Binary files differ
diff --git a/themes/osx/identity-icons-https.png b/themes/osx/identity-icons-https.png
new file mode 100644
index 0000000..ffd6694
--- /dev/null
+++ b/themes/osx/identity-icons-https.png
Binary files differ
diff --git a/themes/osx/identity.png b/themes/osx/identity.png
new file mode 100644
index 0000000..60d5261
--- /dev/null
+++ b/themes/osx/identity.png
Binary files differ
diff --git a/themes/osx/imagedocument.png b/themes/osx/imagedocument.png
new file mode 100644
index 0000000..ff4f21f
--- /dev/null
+++ b/themes/osx/imagedocument.png
Binary files differ
diff --git a/themes/osx/jar.mn b/themes/osx/jar.mn
new file mode 100644
index 0000000..67339c7
--- /dev/null
+++ b/themes/osx/jar.mn
@@ -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/.
+
+browser.jar:
+% skin browser classic/1.0 %skin/classic/browser/
+ skin/classic/browser/sanitizeDialog.css
+* skin/classic/browser/aboutPrivateBrowsing.css
+* skin/classic/browser/aboutSessionRestore.css
+ skin/classic/browser/aboutSessionRestore-window-icon.png (preferences/application.png)
+ skin/classic/browser/aboutCertError.css
+ skin/classic/browser/aboutCertError_sectionCollapsed.png
+ skin/classic/browser/aboutCertError_sectionCollapsed-rtl.png
+ skin/classic/browser/aboutCertError_sectionExpanded.png
+#ifdef MOZ_SERVICES_SYNC
+ skin/classic/browser/aboutSyncTabs.css
+#endif
+* skin/classic/browser/autocomplete.css
+ skin/classic/browser/actionicon-tab.png
+ skin/classic/browser/appmenu-icons.png
+ skin/classic/browser/appmenu-dropmarker.png
+* skin/classic/browser/browser.css
+ skin/classic/browser/click-to-play-warning-stripes.png
+* skin/classic/browser/engineManager.css
+ skin/classic/browser/Geolocation-16.png
+ skin/classic/browser/Geolocation-64.png
+ skin/classic/browser/Info.png
+ skin/classic/browser/identity.png
+ skin/classic/browser/imagedocument.png
+ skin/classic/browser/identity-icons-generic.png
+ skin/classic/browser/identity-icons-https.png
+ skin/classic/browser/identity-icons-https-ev.png
+ skin/classic/browser/identity-icons-https-mixed-active.png
+ skin/classic/browser/keyhole-forward-mask.svg
+ skin/classic/browser/KUI-background.png
+ skin/classic/browser/KUI-close.png
+ skin/classic/browser/livemark-folder.png
+ skin/classic/browser/menu-back.png
+ skin/classic/browser/menu-forward.png
+ skin/classic/browser/mixed-content-blocked-16.png
+ skin/classic/browser/mixed-content-blocked-64.png
+ skin/classic/browser/monitor.png
+ skin/classic/browser/monitor_16-10.png
+ skin/classic/browser/panel-expander-closed.png
+ skin/classic/browser/panel-expander-closed@2x.png
+ skin/classic/browser/panel-expander-open.png
+ skin/classic/browser/panel-expander-open@2x.png
+ skin/classic/browser/panel-plus-sign.png
+ skin/classic/browser/pageInfo.css
+ skin/classic/browser/pageInfo.png
+ skin/classic/browser/page-livemarks.png
+ skin/classic/browser/page-livemarks@2x.png
+ skin/classic/browser/pointerLock-16.png
+ skin/classic/browser/pointerLock-64.png
+ skin/classic/browser/Privacy-16.png
+ skin/classic/browser/Privacy-32.png
+ skin/classic/browser/Privacy-48.png
+ skin/classic/browser/privatebrowsing-mask.png
+ skin/classic/browser/privatebrowsing-mask@2x.png
+ skin/classic/browser/privatebrowsing-light.png
+ skin/classic/browser/privatebrowsing-dark.png
+ skin/classic/browser/reload-stop-go.png
+ skin/classic/browser/Search-glass.png
+ skin/classic/browser/searchbar.css
+ skin/classic/browser/searchbar-dropdown-arrow.png
+ skin/classic/browser/Secure24.png
+ skin/classic/browser/setDesktopBackground.css
+ skin/classic/browser/slowStartup-16.png
+ skin/classic/browser/Toolbar.png
+ skin/classic/browser/Toolbar-inverted.png
+ skin/classic/browser/toolbarbutton-dropdown-arrow.png
+ skin/classic/browser/toolbarbutton-dropdown-arrow-inverted.png
+ skin/classic/browser/urlbar-arrow.png
+ skin/classic/browser/urlbar-popup-blocked.png
+ skin/classic/browser/urlbar-history-dropmarker.png
+ skin/classic/browser/web-notifications-icon.svg
+ skin/classic/browser/web-notifications-tray.svg
+ skin/classic/browser/notification-pluginNormal.png (../shared/plugins/notification-pluginNormal.png)
+ skin/classic/browser/notification-pluginAlert.png (../shared/plugins/notification-pluginAlert.png)
+ skin/classic/browser/notification-pluginBlocked.png (../shared/plugins/notification-pluginBlocked.png)
+#ifdef MOZ_WEBRTC
+ skin/classic/browser/webRTC-shareDevice-16.png
+ skin/classic/browser/webRTC-shareDevice-64.png
+ skin/classic/browser/webRTC-sharingDevice-16.png
+#endif
+ skin/classic/browser/downloads/buttons.png (downloads/buttons.png)
+ skin/classic/browser/downloads/download-glow.png (downloads/download-glow.png)
+ skin/classic/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png)
+ skin/classic/browser/downloads/download-notification-start.png (downloads/download-notification-start.png)
+ skin/classic/browser/downloads/download-summary.png (downloads/download-summary.png)
+* skin/classic/browser/downloads/downloads.css (downloads/downloads.css)
+* skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css)
+ skin/classic/browser/downloads/contentAreaDownloadsView.css (downloads/contentAreaDownloadsView.css)
+ skin/classic/browser/feeds/feedIcon.png (feeds/feedIcon.png)
+ skin/classic/browser/feeds/feedIcon16.png (feeds/feedIcon16.png)
+ skin/classic/browser/feeds/feed-icons-16.png (feeds/feed-icons-16.png)
+ skin/classic/browser/feeds/audioFeedIcon.png (feeds/feedIcon.png)
+ skin/classic/browser/feeds/audioFeedIcon16.png (feeds/feedIcon16.png)
+ skin/classic/browser/feeds/videoFeedIcon.png (feeds/feedIcon.png)
+ skin/classic/browser/feeds/videoFeedIcon16.png (feeds/feedIcon16.png)
+ skin/classic/browser/feeds/subscribe.css (feeds/subscribe.css)
+ skin/classic/browser/feeds/subscribe-ui.css (feeds/subscribe-ui.css)
+* skin/classic/browser/newtab/newTab.css (newtab/newTab.css)
+ skin/classic/browser/newtab/controls.png (../shared/newtab/controls.png)
+ skin/classic/browser/newtab/noise.png (../shared/newtab/noise.png)
+ skin/classic/browser/newtab/pinned.png (../shared/newtab/pinned.png)
+ skin/classic/browser/places/places.css (places/places.css)
+* skin/classic/browser/places/organizer.css (places/organizer.css)
+ skin/classic/browser/places/editBookmark.png (places/editBookmark.png)
+ skin/classic/browser/places/bookmark.png (places/bookmark.png)
+ skin/classic/browser/places/query.png (places/query.png)
+ skin/classic/browser/places/query@2x.png (places/query@2x.png)
+ skin/classic/browser/places/bookmarksMenu.png (places/bookmarksMenu.png)
+ skin/classic/browser/places/bookmarksToolbar.png (places/bookmarksToolbar.png)
+ skin/classic/browser/places/bookmarksToolbar@2x.png (places/bookmarksToolbar@2x.png)
+ skin/classic/browser/places/calendar.png (places/calendar.png)
+ skin/classic/browser/places/folderDropArrow.png (places/folderDropArrow.png)
+ skin/classic/browser/places/folderDropArrow@2x.png (places/folderDropArrow@2x.png)
+ skin/classic/browser/places/toolbarDropMarker.png (places/toolbarDropMarker.png)
+ skin/classic/browser/places/editBookmarkOverlay.css (places/editBookmarkOverlay.css)
+ skin/classic/browser/places/libraryToolbar.png (places/libraryToolbar.png)
+ skin/classic/browser/places/starred48.png (places/starred48.png)
+ skin/classic/browser/places/unstarred48.png (places/unstarred48.png)
+ skin/classic/browser/places/unfiledBookmarks.png (places/unfiledBookmarks.png)
+ skin/classic/browser/places/unfiledBookmarks@2x.png (places/unfiledBookmarks@2x.png)
+ skin/classic/browser/places/tag.png (places/tag.png)
+ skin/classic/browser/places/tag@2x.png (places/tag@2x.png)
+ skin/classic/browser/places/history.png (places/history.png)
+ skin/classic/browser/places/history@2x.png (places/history@2x.png)
+ skin/classic/browser/places/allBookmarks.png (places/allBookmarks.png)
+ skin/classic/browser/places/unsortedBookmarks.png (places/unsortedBookmarks.png)
+ skin/classic/browser/places/downloads.png (places/downloads.png)
+ skin/classic/browser/places/expander-closed-active.png (places/expander-closed-active.png)
+ skin/classic/browser/places/expander-open-active.png (places/expander-open-active.png)
+ skin/classic/browser/places/livemark-item.png (places/livemark-item.png)
+ skin/classic/browser/places/expander-closed.png (places/expander-closed.png)
+ skin/classic/browser/places/expander-open.png (places/expander-open.png)
+ skin/classic/browser/permissions/aboutPermissions.css (permissions/aboutPermissions.css)
+ skin/classic/browser/preferences/alwaysAsk.png (preferences/alwaysAsk.png)
+ skin/classic/browser/preferences/application.png (preferences/application.png)
+ skin/classic/browser/preferences/mail.png (preferences/mail.png)
+ skin/classic/browser/preferences/Options.png (preferences/Options.png)
+#ifdef MOZ_SERVICES_SYNC
+ skin/classic/browser/preferences/Options-sync.png (preferences/Options-sync.png)
+#endif
+ skin/classic/browser/preferences/saveFile.png (preferences/saveFile.png)
+* skin/classic/browser/preferences/preferences.css (preferences/preferences.css)
+ skin/classic/browser/preferences/applications.css (preferences/applications.css)
+ skin/classic/browser/statusbar/dynamic.css (../shared/statusbar/dynamic.css)
+* skin/classic/browser/statusbar/overlay.css (statusbar/overlay.css)
+* skin/classic/browser/statusbar/prefs.css (statusbar/prefs.css)
+ skin/classic/browser/statusbar/pulse.png (../shared/statusbar/pulse.png)
+ skin/classic/browser/statusbar/pms16.png (../shared/statusbar/pms16.png)
+ skin/classic/browser/statusbar/pms24.png (../shared/statusbar/pms24.png)
+ skin/classic/browser/statusbar/throbber-idle.png (../shared/statusbar/throbber-idle.png)
+ skin/classic/browser/statusbar/throbberStatic.png (../shared/statusbar/throbberStatic.png)
+ skin/classic/browser/tabbrowser/alltabs.png (tabbrowser/alltabs.png)
+ skin/classic/browser/tabbrowser/alltabs-inverted.png (tabbrowser/alltabs-inverted.png)
+ skin/classic/browser/tabbrowser/newtab.png (tabbrowser/newtab.png)
+ skin/classic/browser/tabbrowser/newtab-inverted.png (tabbrowser/newtab-inverted.png)
+ skin/classic/browser/tabbrowser/connecting.png (tabbrowser/connecting.png)
+ skin/classic/browser/tabbrowser/loading.png (tabbrowser/loading.png)
+ skin/classic/browser/tabbrowser/tab-arrow-left.png (tabbrowser/tab-arrow-left.png)
+ skin/classic/browser/tabbrowser/tab-arrow-left-inverted.png (tabbrowser/tab-arrow-left-inverted.png)
+ skin/classic/browser/tabbrowser/tab-overflow-border.png (tabbrowser/tab-overflow-border.png)
+ skin/classic/browser/tabbrowser/tabDragIndicator.png (tabbrowser/tabDragIndicator.png)
+ skin/classic/browser/tabbrowser/tab-audio.svg (../shared/tabbrowser/tab-audio.svg)
+ skin/classic/browser/tabbrowser/tab-audio-small.svg (../shared/tabbrowser/tab-audio-small.svg)
+#ifdef MOZ_SERVICES_SYNC
+ skin/classic/browser/sync-throbber.png
+ skin/classic/browser/sync-16.png
+ skin/classic/browser/sync-32.png
+ skin/classic/browser/sync-128.png
+ skin/classic/browser/sync-bg.png
+ skin/classic/browser/sync-desktopIcon.png
+ skin/classic/browser/sync-mobileIcon.png
+ skin/classic/browser/syncSetup.css
+ skin/classic/browser/syncCommon.css
+ skin/classic/browser/syncQuota.css
+ skin/classic/browser/syncProgress.css
+#endif
diff --git a/themes/osx/keyhole-forward-mask.svg b/themes/osx/keyhole-forward-mask.svg
new file mode 100644
index 0000000..8355447
--- /dev/null
+++ b/themes/osx/keyhole-forward-mask.svg
@@ -0,0 +1,15 @@
+<?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/. -->
+
+<svg xmlns="http://www.w3.org/2000/svg">
+ <mask id="mask" maskContentUnits="objectBoundingBox">
+ <rect x="0" y="0" width="1" height="1" fill="white"/>
+ <circle cx="-0.46" cy="0.5" r="0.63"/>
+ </mask>
+ <mask id="mask-hover" maskContentUnits="objectBoundingBox">
+ <rect x="0" y="0" width="1" height="1" fill="white"/>
+ <circle cx="-0.35" cy="0.5" r="0.58"/>
+ </mask>
+</svg>
diff --git a/themes/osx/livemark-folder.png b/themes/osx/livemark-folder.png
new file mode 100644
index 0000000..38a4f10
--- /dev/null
+++ b/themes/osx/livemark-folder.png
Binary files differ
diff --git a/themes/osx/menu-back.png b/themes/osx/menu-back.png
new file mode 100644
index 0000000..ecb8ccd
--- /dev/null
+++ b/themes/osx/menu-back.png
Binary files differ
diff --git a/themes/osx/menu-forward.png b/themes/osx/menu-forward.png
new file mode 100644
index 0000000..2083492
--- /dev/null
+++ b/themes/osx/menu-forward.png
Binary files differ
diff --git a/themes/osx/mixed-content-blocked-16.png b/themes/osx/mixed-content-blocked-16.png
new file mode 100644
index 0000000..7cf33ec
--- /dev/null
+++ b/themes/osx/mixed-content-blocked-16.png
Binary files differ
diff --git a/themes/osx/mixed-content-blocked-64.png b/themes/osx/mixed-content-blocked-64.png
new file mode 100644
index 0000000..cac4415
--- /dev/null
+++ b/themes/osx/mixed-content-blocked-64.png
Binary files differ
diff --git a/themes/osx/monitor.png b/themes/osx/monitor.png
new file mode 100644
index 0000000..35e7b20
--- /dev/null
+++ b/themes/osx/monitor.png
Binary files differ
diff --git a/themes/osx/monitor_16-10.png b/themes/osx/monitor_16-10.png
new file mode 100644
index 0000000..4195034
--- /dev/null
+++ b/themes/osx/monitor_16-10.png
Binary files differ
diff --git a/themes/osx/moz.build b/themes/osx/moz.build
new file mode 100644
index 0000000..6a7af20
--- /dev/null
+++ b/themes/osx/moz.build
@@ -0,0 +1,9 @@
+# -*- 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 += ['communicator']
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/themes/osx/newtab/newTab.css b/themes/osx/newtab/newTab.css
new file mode 100644
index 0000000..b8b0fd6
--- /dev/null
+++ b/themes/osx/newtab/newTab.css
@@ -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/. */
+
+%include ../../shared/newtab/newTab.css.inc
+
+.newtab-undo-button {
+ -moz-appearance: none;
+ color: -moz-nativehyperlinktext;
+ color: rgb(0,102,204);
+ cursor: pointer;
+ padding: 0;
+ margin: 0 4px;
+ border: 0;
+ background: transparent;
+ text-decoration: none;
+ min-width: 0;
+}
+
+.newtab-undo-button > .button-box {
+ padding: 0;
+}
+
+#newtab-undo-close-button {
+ -moz-appearance: none;
+ padding: 0;
+ border: none;
+ -moz-user-focus: normal;
+}
diff --git a/themes/osx/page-livemarks.png b/themes/osx/page-livemarks.png
new file mode 100644
index 0000000..e526458
--- /dev/null
+++ b/themes/osx/page-livemarks.png
Binary files differ
diff --git a/themes/osx/page-livemarks@2x.png b/themes/osx/page-livemarks@2x.png
new file mode 100644
index 0000000..7b17089
--- /dev/null
+++ b/themes/osx/page-livemarks@2x.png
Binary files differ
diff --git a/themes/osx/pageInfo.css b/themes/osx/pageInfo.css
new file mode 100644
index 0000000..f205b57
--- /dev/null
+++ b/themes/osx/pageInfo.css
@@ -0,0 +1,258 @@
+/* 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/. */
+
+@import "chrome://global/skin/";
+
+/* View buttons */
+#viewGroup {
+ -moz-padding-start: 10px;
+}
+
+#viewGroup > radio {
+ list-style-image: url("chrome://browser/skin/pageInfo.png");
+ -moz-box-orient: vertical;
+ -moz-box-align: center;
+ -moz-appearance: none;
+ padding: 5px 3px 1px 3px;
+ margin: 0 1px;
+ min-width: 4.5em;
+}
+
+#viewGroup > radio:hover {
+ background-color: #E0E8F6;
+ color: black;
+}
+
+#viewGroup > radio[selected="true"] {
+ background-color: #C1D2EE;
+ color: black;
+}
+
+#topBar {
+ border-bottom: 2px groove ThreeDFace;
+ -moz-padding-start: 10px;
+ background-color: -moz-Field;
+ color: -moz-FieldText;
+}
+
+#generalTab {
+ -moz-image-region: rect(0px, 32px, 32px, 0px)
+}
+
+#generalTab:hover, #generalTab[selected="true"] {
+ -moz-image-region: rect(32px, 32px, 64px, 0px)
+}
+
+#mediaTab {
+ -moz-image-region: rect(0px, 64px, 32px, 32px)
+}
+
+#mediaTab:hover, #mediaTab[selected="true"] {
+ -moz-image-region: rect(32px, 64px, 64px, 32px)
+}
+
+#feedTab {
+ -moz-image-region: rect(0px, 96px, 32px, 64px)
+}
+
+#feedTab:hover, #feedTab[selected="true"] {
+ -moz-image-region: rect(32px, 96px, 64px, 64px)
+}
+
+#permTab {
+ -moz-image-region: rect(0px, 128px, 32px, 96px)
+}
+
+#permTab:hover, #permTab[selected="true"] {
+ -moz-image-region: rect(32px, 128px, 64px, 96px)
+}
+
+#securityTab {
+ -moz-image-region: rect(0px, 160px, 32px, 128px)
+}
+
+#securityTab:hover, #securityTab[selected="true"] {
+ -moz-image-region: rect(32px, 160px, 64px, 128px)
+}
+
+deck {
+ padding: 10px 10px 10px 10px;
+}
+
+/* Misc */
+tree {
+ margin: .5em;
+}
+
+.gridSeparator {
+ width: .5em;
+}
+
+textbox {
+ background: transparent !important;
+ border: none;
+ padding: 0px;
+ margin-top: 1px;
+ -moz-appearance: none;
+}
+
+textbox.header {
+ -moz-margin-start: 0;
+}
+
+.iframe {
+ margin: .5em;
+ background: white;
+ overflow: auto;
+}
+
+.fixedsize {
+ height: 8.5em;
+}
+
+textbox[disabled] {
+ font-style: italic;
+}
+
+/* General Tab */
+groupbox.collapsable caption .caption-icon {
+ width: 9px;
+ height: 9px;
+ background-repeat: no-repeat;
+ background-position: center;
+ -moz-margin-start: 2px;
+ -moz-margin-end: 2px;
+ background-image: url("chrome://global/skin/tree/twisty-open.png");
+}
+
+groupbox.collapsable[closed="true"] {
+ border: none;
+ margin-bottom: 9px;
+ -moz-appearance: none;
+}
+
+groupbox.collapsable[closed="true"] caption .caption-icon {
+ background-image: url("chrome://global/skin/tree/twisty-clsd.png");
+}
+
+groupbox tree {
+ margin: 0 3px;
+ border: none;
+}
+
+#securityBox description {
+ -moz-margin-start: 10px;
+}
+
+#general-security-identity {
+ white-space: pre-wrap;
+ line-height: 2em;
+}
+
+/* Media Tab */
+#imagetree {
+ min-height: 10em;
+ margin-bottom: 0;
+}
+
+#mediaSplitter {
+ border-style: none;
+ background: none;
+ height: .8em;
+}
+
+#mediaGrid {
+ min-height: 9em;
+}
+
+#mediaLabelColumn {
+ min-width: 10em;
+}
+
+#thepreviewimage {
+ margin: 1em;
+}
+
+treechildren::-moz-tree-cell-text(broken) {
+ font-style: italic;
+ color: graytext;
+}
+
+/* Feeds Tab */
+#feedtree {
+ margin-bottom: 0px;
+}
+
+#feedListbox richlistitem {
+ padding-top: 6px;
+ padding-bottom: 6px;
+ -moz-padding-start: 7px;
+ -moz-padding-end: 7px;
+ min-height: 25px;
+ border-bottom: 1px dotted #C0C0C0;
+}
+
+#feedListbox richlistitem[selected="true"] {
+ background-color: -moz-Dialog;
+ color: -moz-DialogText;
+}
+
+#feedListbox {
+ border: 2px solid;
+ -moz-border-top-colors: ThreeDShadow ThreeDDarkShadow;
+ -moz-border-right-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-bottom-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-left-colors: ThreeDShadow ThreeDDarkShadow;
+}
+
+.feedTitle {
+ font-weight: bold;
+}
+
+/* Permissions Tab */
+#permList {
+ margin-top: .5em;
+ overflow: auto;
+ border: 2px solid;
+ -moz-border-top-colors: ThreeDShadow ThreeDDarkShadow;
+ -moz-border-right-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-bottom-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-left-colors: ThreeDShadow ThreeDDarkShadow;
+ background-color: -moz-field;
+}
+
+.permission {
+ padding-top: 6px;
+ padding-bottom: 6px;
+ -moz-padding-start: 7px;
+ -moz-padding-end: 7px;
+ min-height: 25px;
+ border-bottom: 1px dotted #C0C0C0;
+}
+
+.permissionLabel {
+ font-weight: bold;
+}
+
+.permission:hover {
+ background-color: -moz-dialog;
+}
+
+/* Security Tab */
+#securityPanel .caption-icon {
+ display: none;
+}
+
+#securityPanel .header {
+ font-size: 120%;
+}
+
+#securityPanel .fieldLabel {
+ margin: 2px 10px 3px 10px;
+}
+
+#securityPanel .fieldValue {
+ font-weight: bold;
+ margin: 2px 10px 0px 10px;
+}
diff --git a/themes/osx/pageInfo.png b/themes/osx/pageInfo.png
new file mode 100644
index 0000000..237381b
--- /dev/null
+++ b/themes/osx/pageInfo.png
Binary files differ
diff --git a/themes/osx/panel-expander-closed.png b/themes/osx/panel-expander-closed.png
new file mode 100644
index 0000000..f0e97b2
--- /dev/null
+++ b/themes/osx/panel-expander-closed.png
Binary files differ
diff --git a/themes/osx/panel-expander-closed@2x.png b/themes/osx/panel-expander-closed@2x.png
new file mode 100644
index 0000000..0e7ded5
--- /dev/null
+++ b/themes/osx/panel-expander-closed@2x.png
Binary files differ
diff --git a/themes/osx/panel-expander-open.png b/themes/osx/panel-expander-open.png
new file mode 100644
index 0000000..e3febf4
--- /dev/null
+++ b/themes/osx/panel-expander-open.png
Binary files differ
diff --git a/themes/osx/panel-expander-open@2x.png b/themes/osx/panel-expander-open@2x.png
new file mode 100644
index 0000000..3913370
--- /dev/null
+++ b/themes/osx/panel-expander-open@2x.png
Binary files differ
diff --git a/themes/osx/panel-plus-sign.png b/themes/osx/panel-plus-sign.png
new file mode 100644
index 0000000..375601e
--- /dev/null
+++ b/themes/osx/panel-plus-sign.png
Binary files differ
diff --git a/themes/osx/permissions/aboutPermissions.css b/themes/osx/permissions/aboutPermissions.css
new file mode 100644
index 0000000..f9a4a88
--- /dev/null
+++ b/themes/osx/permissions/aboutPermissions.css
@@ -0,0 +1,153 @@
+/* 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/. */
+
+@import url("chrome://global/skin/inContentUI.css");
+
+/* header */
+
+#permissions-pagetitle {
+ font-size: 200%;
+ font-weight: bold;
+ padding-bottom: 0.5em;
+}
+
+/* content box */
+#permissions-content {
+ height: 100%;
+}
+
+/* sites box */
+
+#sites-box {
+ padding: 10px;
+ width: 25em;
+}
+
+#sites-filter {
+ margin: 0;
+}
+
+#sites-list {
+ -moz-appearance: none;
+ border: 1px solid rgba(0, 0, 0, 0.32);
+ background-color: rgba(255, 255, 255, 0.4);
+ margin: 5px 0 0 0;
+}
+
+.site {
+ padding: 4px;
+ border-bottom: 1px solid ThreeDLightShadow;
+}
+
+.site-favicon {
+ height: 16px;
+ width: 16px;
+ -moz-margin-end: 4px;
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+
+#all-sites-item > .site-container > .site-favicon {
+ list-style-image: none;
+}
+
+/* permissions box */
+
+#permissions-box {
+ padding-top: 10px;
+ overflow-y: auto;
+}
+
+#site-description {
+ font-size: 125%;
+ -moz-margin-start: 6px; /* to match button margin */
+}
+
+#site-label {
+ font-weight: bold;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+#defaults-description {
+ font-size: 125%;
+ font-weight: bold;
+ -moz-margin-start: 6px;
+}
+
+.pref-item {
+ margin-bottom: 10px;
+}
+
+.pref-icon {
+ width: 36px;
+ height: 36px;
+ -moz-margin-end: 10px;
+}
+
+.pref-icon[type="password"] {
+ list-style-image: url(chrome://mozapps/skin/passwordmgr/key-64.png);
+}
+.pref-icon[type="image"] {
+ list-style-image: url(chrome://global/skin/icons/question-64.png);
+}
+.pref-icon[type="popup"] {
+ list-style-image: url(chrome://global/skin/icons/question-64.png);
+}
+.pref-icon[type="cookie"] {
+ list-style-image: url(chrome://global/skin/icons/question-64.png);
+}
+.pref-icon[type="desktop-notification"] {
+ list-style-image: url(chrome://browser/skin/web-notifications-icon.svg);
+}
+.pref-icon[type="install"] {
+ list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric.png);
+}
+.pref-icon[type="geo"] {
+ list-style-image: url(chrome://browser/skin/Geolocation-64.png);
+}
+.pref-icon[type="plugins"] {
+ list-style-image: url(chrome://mozapps/skin/plugins/pluginGeneric.png);
+}
+
+.pref-title {
+ font-size: 125%;
+ margin-bottom: 0;
+ font-weight: bold;
+ margin-right: 0;
+ padding-right: 0;
+}
+
+.pref-default {
+ margin-left: 0.5em;
+ padding-left: 0;
+}
+
+.pref-set-default {
+ visibility: collapse;
+}
+
+.pref-menulist {
+ margin-left: 6px;
+ margin-right: 6px;
+ min-width: 10em; /* native menulists ellipsize their longest entries by default on many themes */
+}
+
+.plugins-label {
+ margin-right: 0;
+ padding-right: 0;
+}
+
+.plugins-vulnerable {
+ margin-left: 0;
+ padding-left: 0;
+ margin-right: 0;
+ padding-right: 0;
+}
+
+.plugins-default {
+ margin-left: 0.5em;
+ padding-left: 0;
+ margin-right: 1em;
+ padding-right: 0;
+}
diff --git a/themes/osx/places/allBookmarks.png b/themes/osx/places/allBookmarks.png
new file mode 100644
index 0000000..d1abe81
--- /dev/null
+++ b/themes/osx/places/allBookmarks.png
Binary files differ
diff --git a/themes/osx/places/bookmark.png b/themes/osx/places/bookmark.png
new file mode 100644
index 0000000..2e9a206
--- /dev/null
+++ b/themes/osx/places/bookmark.png
Binary files differ
diff --git a/themes/osx/places/bookmarksMenu.png b/themes/osx/places/bookmarksMenu.png
new file mode 100644
index 0000000..c27bd6a
--- /dev/null
+++ b/themes/osx/places/bookmarksMenu.png
Binary files differ
diff --git a/themes/osx/places/bookmarksToolbar.png b/themes/osx/places/bookmarksToolbar.png
new file mode 100644
index 0000000..2047bff
--- /dev/null
+++ b/themes/osx/places/bookmarksToolbar.png
Binary files differ
diff --git a/themes/osx/places/bookmarksToolbar@2x.png b/themes/osx/places/bookmarksToolbar@2x.png
new file mode 100644
index 0000000..dd45898
--- /dev/null
+++ b/themes/osx/places/bookmarksToolbar@2x.png
Binary files differ
diff --git a/themes/osx/places/calendar.png b/themes/osx/places/calendar.png
new file mode 100644
index 0000000..1855c9f
--- /dev/null
+++ b/themes/osx/places/calendar.png
Binary files differ
diff --git a/themes/osx/places/downloads.png b/themes/osx/places/downloads.png
new file mode 100644
index 0000000..0756cb6
--- /dev/null
+++ b/themes/osx/places/downloads.png
Binary files differ
diff --git a/themes/osx/places/editBookmark.png b/themes/osx/places/editBookmark.png
new file mode 100644
index 0000000..fbca052
--- /dev/null
+++ b/themes/osx/places/editBookmark.png
Binary files differ
diff --git a/themes/osx/places/editBookmarkOverlay.css b/themes/osx/places/editBookmarkOverlay.css
new file mode 100644
index 0000000..cc749c1
--- /dev/null
+++ b/themes/osx/places/editBookmarkOverlay.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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
+
+/**** folder menulist ****/
+.folder-icon > .menulist-label-box > .menulist-icon,
+.folder-icon > .menu-iconic-left > .menu-iconic-icon {
+ width: 16px;
+ height: 16px;
+}
+
+.folder-icon > .menu-iconic-left {
+ display: -moz-box;
+}
+
+.folder-icon {
+ list-style-image: url("chrome://global/skin/tree/folder.png") !important;
+}
+
+@media (min-resolution: 2dppx) {
+ .folder-icon {
+ list-style-image: url("chrome://global/skin/tree/folder@2x.png") !important;
+ }
+}
+
+.menulist-icon {
+ margin: 0 !important;
+}
+
+/**** expanders ****/
+
+.expander-up,
+.expander-down {
+ -moz-appearance: none;
+ margin: 0;
+ margin-left: 8px;
+ padding: 0;
+ min-width: 0;
+}
+
+.expander-up {
+ list-style-image: url("chrome://browser/skin/places/expander-open.png");
+}
+
+.expander-down {
+ list-style-image: url("chrome://browser/skin/places/expander-closed.png");
+}
+
+.expander-down:hover:active {
+ list-style-image: url("chrome://browser/skin/places/expander-closed-active.png");
+}
+
+.expander-up:hover:active {
+ list-style-image: url("chrome://browser/skin/places/expander-open-active.png");
+}
+
+#editBookmarkPanelContent {
+ min-width: 23em;
+}
+
+#editBMPanel_folderTree {
+ margin: 6px 4px 0 4px;
+}
+
+/* Hide the value column of the tag autocomplete popup
+ * leaving only the comment column visible. This is
+ * so that only the tag being edited is shown in the
+ * popup.
+ */
+#editBMPanel_tagsField #treecolAutoCompleteValue {
+ visibility: collapse;
+}
+
+
+/* ----- BOOKMARK PANEL DROPDOWN MENU ITEMS ----- */
+
+#editBMPanel_folderMenuList[selectedIndex="0"],
+#editBMPanel_toolbarFolderItem {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png") !important;
+}
+
+#editBMPanel_folderMenuList[selectedIndex="1"],
+#editBMPanel_bmRootItem {
+ list-style-image: url("chrome://browser/skin/places/bookmarksMenu.png") !important;
+}
+
+#editBMPanel_folderMenuList[selectedIndex="2"],
+#editBMPanel_unfiledRootItem {
+ list-style-image: url("chrome://browser/skin/places/unfiledBookmarks.png") !important;
+}
+
+@media (min-resolution: 2dppx) {
+ #editBMPanel_folderMenuList[selectedIndex="0"],
+ #editBMPanel_toolbarFolderItem {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar@2x.png") !important;
+ }
+
+ #editBMPanel_folderMenuList[selectedIndex="2"],
+ #editBMPanel_unfiledRootItem {
+ list-style-image: url("chrome://browser/skin/places/unfiledBookmarks@2x.png") !important;
+ }
+}
diff --git a/themes/osx/places/expander-closed-active.png b/themes/osx/places/expander-closed-active.png
new file mode 100644
index 0000000..7ef5f04
--- /dev/null
+++ b/themes/osx/places/expander-closed-active.png
Binary files differ
diff --git a/themes/osx/places/expander-closed.png b/themes/osx/places/expander-closed.png
new file mode 100644
index 0000000..7850c9e
--- /dev/null
+++ b/themes/osx/places/expander-closed.png
Binary files differ
diff --git a/themes/osx/places/expander-open-active.png b/themes/osx/places/expander-open-active.png
new file mode 100644
index 0000000..673ead5
--- /dev/null
+++ b/themes/osx/places/expander-open-active.png
Binary files differ
diff --git a/themes/osx/places/expander-open.png b/themes/osx/places/expander-open.png
new file mode 100644
index 0000000..7655357
--- /dev/null
+++ b/themes/osx/places/expander-open.png
Binary files differ
diff --git a/themes/osx/places/folderDropArrow.png b/themes/osx/places/folderDropArrow.png
new file mode 100644
index 0000000..8d722cc
--- /dev/null
+++ b/themes/osx/places/folderDropArrow.png
Binary files differ
diff --git a/themes/osx/places/folderDropArrow@2x.png b/themes/osx/places/folderDropArrow@2x.png
new file mode 100644
index 0000000..9efb6d9
--- /dev/null
+++ b/themes/osx/places/folderDropArrow@2x.png
Binary files differ
diff --git a/themes/osx/places/history.png b/themes/osx/places/history.png
new file mode 100644
index 0000000..e5a00b5
--- /dev/null
+++ b/themes/osx/places/history.png
Binary files differ
diff --git a/themes/osx/places/history@2x.png b/themes/osx/places/history@2x.png
new file mode 100644
index 0000000..684b374
--- /dev/null
+++ b/themes/osx/places/history@2x.png
Binary files differ
diff --git a/themes/osx/places/libraryToolbar.png b/themes/osx/places/libraryToolbar.png
new file mode 100644
index 0000000..2ed627a
--- /dev/null
+++ b/themes/osx/places/libraryToolbar.png
Binary files differ
diff --git a/themes/osx/places/livemark-item.png b/themes/osx/places/livemark-item.png
new file mode 100644
index 0000000..9184b95
--- /dev/null
+++ b/themes/osx/places/livemark-item.png
Binary files differ
diff --git a/themes/osx/places/organizer.css b/themes/osx/places/organizer.css
new file mode 100644
index 0000000..611c0c0
--- /dev/null
+++ b/themes/osx/places/organizer.css
@@ -0,0 +1,134 @@
+/* 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/. */
+
+/* Toolbar */
+#placesToolbar {
+ padding: 3px;
+ -moz-padding-end: 6px;
+}
+
+#placesToolbar > toolbarbutton[disabled] > .toolbarbutton-icon {
+ opacity: .4;
+}
+
+#back-button,
+#forward-button {
+ list-style-image: url("chrome://browser/skin/Toolbar.png");
+}
+
+#back-button {
+ -moz-image-region: rect(0, 18px, 18px, 0);
+}
+
+#forward-button {
+ -moz-image-region: rect(0, 36px, 18px, 18px);
+}
+
+#back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
+#forward-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+ transform: scaleX(-1);
+}
+
+/* organize, view and maintenance buttons icons */
+#organizeButton,
+#viewMenu,
+#maintenanceButton {
+ list-style-image: url("chrome://browser/skin/places/libraryToolbar.png");
+}
+
+/* organize button */
+#organizeButton {
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+}
+#organizeButton:hover,
+#organizeButton[open="true"] {
+ -moz-image-region: rect(16px, 16px, 32px, 0px);
+}
+
+/* view button */
+#viewMenu {
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+#viewMenu:hover,
+#viewMenu[open="true"] {
+ -moz-image-region: rect(16px, 32px, 32px, 16px);
+}
+
+/* maintenance button */
+#maintenanceButton {
+ -moz-image-region: rect(0px, 48px, 16px, 32px);
+}
+#maintenanceButton:hover,
+#maintenanceButton[open="true"] {
+ -moz-image-region: rect(16px, 48px, 32px, 32px);
+}
+
+/* Root View */
+#placesView {
+ border-top: 1px solid ThreeDDarkShadow;
+}
+
+/* Info box */
+#detailsDeck {
+ border-top: 1px solid ThreeDShadow;
+ padding: 5px;
+}
+
+#infoBoxExpanderLabel {
+ -moz-padding-start: 2px;
+}
+
+#searchFilter {
+ margin: 0;
+}
+
+/**
+ * Downloads pane
+ */
+
+#clearDownloadsButton > .toolbarbutton-icon {
+ display: none;
+}
+
+#clearDownloadsButton {
+ -moz-padding-start: 9px;
+ -moz-padding-end: 9px;
+}
+
+/* hover-over/open button highlighting */
+
+#organizeButton,
+#viewMenu,
+#maintenanceButton,
+#organizeButton,
+#clearDownloadsButton {
+ color: #222;
+ border: 0;
+ border-radius: 10000px;
+ padding: 1px 8px;
+ margin: 0 0 1px;
+}
+
+#back-button,
+#forward-button {
+ color: #222;
+ border: 0;
+ border-radius: 10000px;
+ padding: 1px 1px;
+ margin: 0 0 1px;
+}
+
+#organizeButton:hover,
+#organizeButton[open="true"],
+#viewMenu:hover,
+#viewMenu[open="true"],
+#maintenanceButton:hover,
+#maintenanceButton[open="true"],
+#organizeButton:hover,
+#organizeButton[open="true"],
+#clearDownloadsButton:hover,
+#back-button:not([disabled]):hover,
+#forward-button:not([disabled]):hover {
+ background-color: rgba(0, 0, 0, .205);
+}
diff --git a/themes/osx/places/places.css b/themes/osx/places/places.css
new file mode 100644
index 0000000..4243779
--- /dev/null
+++ b/themes/osx/places/places.css
@@ -0,0 +1,146 @@
+/* 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/. */
+
+/* Sidebars */
+.sidebar-placesTree {
+ -moz-appearance: none;
+ border: 0;
+ margin: 0;
+ border-top: 1px solid ThreeDShadow;
+}
+
+.sidebar-placesTreechildren::-moz-tree-cell(leaf) ,
+.sidebar-placesTreechildren::-moz-tree-image(leaf) {
+ cursor: pointer;
+}
+
+.sidebar-placesTreechildren::-moz-tree-cell-text(leaf, hover) {
+ cursor: pointer;
+ text-decoration: underline;
+}
+
+.sidebar-placesTreechildren::-moz-tree-cell(separator) {
+ cursor: default;
+}
+
+/* Trees */
+treechildren::-moz-tree-image(title) {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+ padding-right: 2px;
+ margin: 0px 2px;
+ width: 16px;
+ height: 16px;
+}
+
+treechildren::-moz-tree-image(title, livemarkItem) {
+ list-style-image: url("chrome://browser/skin/places/livemark-item.png");
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+}
+
+treechildren::-moz-tree-image(title, livemarkItem, visited) {
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+treechildren::-moz-tree-image(title, separator) {
+ list-style-image: none;
+ width: 0;
+ height: 0;
+}
+
+treechildren::-moz-tree-image(title, container),
+treechildren::-moz-tree-image(title, open) {
+ list-style-image: url("chrome://global/skin/tree/folder.png");
+ -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+treechildren::-moz-tree-image(title, container, livemark) {
+ list-style-image: url("chrome://browser/skin/livemark-folder.png");
+ -moz-image-region: auto;
+}
+
+treechildren::-moz-tree-image(container, OrganizerQuery_AllBookmarks) {
+ list-style-image: url("chrome://browser/skin/places/allBookmarks.png");
+ -moz-image-region: auto;
+}
+
+treechildren::-moz-tree-image(container, OrganizerQuery_BookmarksToolbar) {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png");
+ -moz-image-region: auto;
+}
+
+treechildren::-moz-tree-image(container, OrganizerQuery_BookmarksMenu) {
+ list-style-image: url("chrome://browser/skin/places/bookmarksMenu.png");
+ -moz-image-region: auto;
+}
+
+treechildren::-moz-tree-image(container, OrganizerQuery_UnfiledBookmarks) {
+ list-style-image: url("chrome://browser/skin/places/unsortedBookmarks.png");
+ -moz-image-region: auto;
+}
+
+/* query-nodes should be styled even if they're not expandable */
+treechildren::-moz-tree-image(title, query) {
+ list-style-image: url("chrome://browser/skin/places/query.png");
+ -moz-image-region: auto;
+}
+
+treechildren::-moz-tree-image(title, query, tagContainer),
+treechildren::-moz-tree-image(query, OrganizerQuery_Tags) {
+ list-style-image: url("chrome://browser/skin/places/tag.png");
+ -moz-image-region: auto;
+}
+
+treechildren::-moz-tree-image(query, OrganizerQuery_Downloads) {
+ list-style-image: url("chrome://browser/skin/places/downloads.png");
+ -moz-image-region: auto;
+}
+
+/* calendar icon for folders grouping items by date */
+treechildren::-moz-tree-image(title, query, dayContainer) {
+ list-style-image: url("chrome://browser/skin/places/calendar.png");
+ -moz-image-region: auto;
+}
+
+treechildren::-moz-tree-image(title, query, hostContainer) {
+ list-style-image: url("chrome://global/skin/tree/folder.png");
+}
+
+treechildren::-moz-tree-image(title, query, hostContainer, open) {
+ list-style-image: url("chrome://global/skin/tree/folder.png");
+}
+
+treechildren::-moz-tree-image(title, query, OrganizerQuery_History) {
+ list-style-image: url("chrome://browser/skin/places/history.png");
+}
+
+/* We want some queries to look like ordinary folders. This must come
+ after the (title, query) selector, or it would get overridden. */
+
+treechildren::-moz-tree-image(title, query, folder),
+treechildren::-moz-tree-image(title, query, folder, open) {
+ list-style-image: url("chrome://global/skin/tree/folder.png");
+ -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+treechildren::-moz-tree-image(cutting) {
+ opacity: 0.5;
+}
+
+treechildren::-moz-tree-cell-text(cutting) {
+ opacity: 0.7;
+}
+
+/* Browser Sidebars */
+
+/* Default button vert. margins are 1px/2px, and this can cause misalignment */
+#viewButton {
+ margin-top: 2px;
+ margin-bottom: 2px;
+}
+
+#viewButton > .button-box > .button-menu-dropmarker {
+ height: auto;
+ width: auto;
+ -moz-margin-end: -3px;
+}
diff --git a/themes/osx/places/query.png b/themes/osx/places/query.png
new file mode 100644
index 0000000..0ccb847
--- /dev/null
+++ b/themes/osx/places/query.png
Binary files differ
diff --git a/themes/osx/places/query@2x.png b/themes/osx/places/query@2x.png
new file mode 100644
index 0000000..20b458a
--- /dev/null
+++ b/themes/osx/places/query@2x.png
Binary files differ
diff --git a/themes/osx/places/starred48.png b/themes/osx/places/starred48.png
new file mode 100644
index 0000000..bfa1459
--- /dev/null
+++ b/themes/osx/places/starred48.png
Binary files differ
diff --git a/themes/osx/places/tag.png b/themes/osx/places/tag.png
new file mode 100644
index 0000000..a4038bb
--- /dev/null
+++ b/themes/osx/places/tag.png
Binary files differ
diff --git a/themes/osx/places/tag@2x.png b/themes/osx/places/tag@2x.png
new file mode 100644
index 0000000..673814b
--- /dev/null
+++ b/themes/osx/places/tag@2x.png
Binary files differ
diff --git a/themes/osx/places/toolbarDropMarker.png b/themes/osx/places/toolbarDropMarker.png
new file mode 100644
index 0000000..a217b0e
--- /dev/null
+++ b/themes/osx/places/toolbarDropMarker.png
Binary files differ
diff --git a/themes/osx/places/unfiledBookmarks.png b/themes/osx/places/unfiledBookmarks.png
new file mode 100644
index 0000000..69495da
--- /dev/null
+++ b/themes/osx/places/unfiledBookmarks.png
Binary files differ
diff --git a/themes/osx/places/unfiledBookmarks@2x.png b/themes/osx/places/unfiledBookmarks@2x.png
new file mode 100644
index 0000000..44efd6a
--- /dev/null
+++ b/themes/osx/places/unfiledBookmarks@2x.png
Binary files differ
diff --git a/themes/osx/places/unsortedBookmarks.png b/themes/osx/places/unsortedBookmarks.png
new file mode 100644
index 0000000..893e75a
--- /dev/null
+++ b/themes/osx/places/unsortedBookmarks.png
Binary files differ
diff --git a/themes/osx/places/unstarred48.png b/themes/osx/places/unstarred48.png
new file mode 100644
index 0000000..8b82aab
--- /dev/null
+++ b/themes/osx/places/unstarred48.png
Binary files differ
diff --git a/themes/osx/pointerLock-16.png b/themes/osx/pointerLock-16.png
new file mode 100644
index 0000000..862cd11
--- /dev/null
+++ b/themes/osx/pointerLock-16.png
Binary files differ
diff --git a/themes/osx/pointerLock-64.png b/themes/osx/pointerLock-64.png
new file mode 100644
index 0000000..a35ce04
--- /dev/null
+++ b/themes/osx/pointerLock-64.png
Binary files differ
diff --git a/themes/osx/preferences/Options-sync.png b/themes/osx/preferences/Options-sync.png
new file mode 100644
index 0000000..89901fb
--- /dev/null
+++ b/themes/osx/preferences/Options-sync.png
Binary files differ
diff --git a/themes/osx/preferences/Options.png b/themes/osx/preferences/Options.png
new file mode 100644
index 0000000..448eca6
--- /dev/null
+++ b/themes/osx/preferences/Options.png
Binary files differ
diff --git a/themes/osx/preferences/alwaysAsk.png b/themes/osx/preferences/alwaysAsk.png
new file mode 100644
index 0000000..ddd4cb2
--- /dev/null
+++ b/themes/osx/preferences/alwaysAsk.png
Binary files differ
diff --git a/themes/osx/preferences/application.png b/themes/osx/preferences/application.png
new file mode 100644
index 0000000..ff2ecc2
--- /dev/null
+++ b/themes/osx/preferences/application.png
Binary files differ
diff --git a/themes/osx/preferences/applications.css b/themes/osx/preferences/applications.css
new file mode 100644
index 0000000..80699b4
--- /dev/null
+++ b/themes/osx/preferences/applications.css
@@ -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/. */
+
+/**
+ * Line up the actions menu with action labels above and below it.
+ * Equalize the distance from the left side of the action box to the left side
+ * of the icon for both the menu and the non-menu versions of the action box.
+ * Also make sure the labels are the same distance away from the icons.
+ */
+.actionsMenu {
+ margin-top: 0;
+ margin-bottom: 0;
+ -moz-margin-start: -2px;
+ -moz-margin-end: 0;
+}
+
+.typeIcon,
+.actionIcon {
+ -moz-margin-start: 3px;
+ -moz-margin-end: 3px;
+}
+
+richlistitem label {
+ -moz-margin-start: 1px;
+ margin-top: 2px;
+}
+
+richlistitem {
+ min-height: 22px;
+}
+
+richlistitem[appHandlerIcon="ask"],
+menuitem[appHandlerIcon="ask"] {
+ list-style-image: url("chrome://browser/skin/preferences/alwaysAsk.png");
+}
+
+richlistitem[appHandlerIcon="save"],
+menuitem[appHandlerIcon="save"] {
+ list-style-image: url("chrome://browser/skin/preferences/application.png");
+}
+
+richlistitem[appHandlerIcon="feed"],
+menuitem[appHandlerIcon="feed"] {
+ list-style-image: url("chrome://browser/skin/page-livemarks.png");
+}
+
+richlistitem[appHandlerIcon="plugin"],
+menuitem[appHandlerIcon="plugin"] {
+ list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric-16.png");
+}
+
+.actionsMenu .menulist-icon {
+ -moz-margin-end: 3px;
+}
+
+.actionsMenu > menupopup > menuitem > .menu-iconic-left {
+ -moz-padding-start: 0px;
+ -moz-padding-end: 2px;
+}
+
+.actionsMenu > menupopup > menuitem {
+ -moz-padding-start: 4px;
+}
diff --git a/themes/osx/preferences/mail.png b/themes/osx/preferences/mail.png
new file mode 100644
index 0000000..c40d159
--- /dev/null
+++ b/themes/osx/preferences/mail.png
Binary files differ
diff --git a/themes/osx/preferences/preferences.css b/themes/osx/preferences/preferences.css
new file mode 100644
index 0000000..4075b1e
--- /dev/null
+++ b/themes/osx/preferences/preferences.css
@@ -0,0 +1,142 @@
+/*
+# -*- 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/.
+*/
+
+/* Global Styles */
+#BrowserPreferences radio[pane] {
+ list-style-image: url("chrome://browser/skin/preferences/Options.png");
+ padding: 5px 3px 1px;
+}
+
+radio[pane=paneMain] {
+ -moz-image-region: rect(0, 32px, 32px, 0);
+}
+
+radio[pane=paneTabs] {
+ -moz-image-region: rect(0, 64px, 32px, 32px);
+}
+
+radio[pane=paneContent] {
+ -moz-image-region: rect(0, 96px, 32px, 64px);
+}
+
+radio[pane=paneApplications] {
+ -moz-image-region: rect(0, 128px, 32px, 96px);
+}
+
+radio[pane=panePrivacy] {
+ -moz-image-region: rect(0, 160px, 32px, 128px);
+}
+
+radio[pane=paneSecurity] {
+ -moz-image-region: rect(0, 192px, 32px, 160px);
+}
+
+radio[pane=paneAdvanced] {
+ -moz-image-region: rect(0, 224px, 32px, 192px);
+}
+
+%ifdef MOZ_SERVICES_SYNC
+radio[pane=paneSync] {
+ list-style-image: url("chrome://browser/skin/preferences/Options-sync.png") !important;
+}
+%endif
+
+/* Applications Pane */
+#BrowserPreferences[animated="true"] #handlersView {
+ height: 25em;
+}
+
+#BrowserPreferences[animated="false"] #handlersView {
+ -moz-box-flex: 1;
+}
+
+/* Privacy Pane */
+
+/* styles for the link elements copied from .text-link in global.css */
+.inline-link {
+ color: -moz-nativehyperlinktext;
+ text-decoration: none;
+}
+
+.inline-link:hover {
+ text-decoration: underline;
+}
+
+/* Modeless Window Dialogs */
+.windowDialog,
+.windowDialog prefpane {
+ padding: 0;
+}
+
+#browserHomePage:-moz-locale-dir(rtl) input {
+ unicode-bidi: plaintext;
+ direction: rtl;
+}
+
+.contentPane {
+ margin: 9px 8px 5px;
+}
+
+.actionButtons {
+ margin: 0 3px 6px !important;
+}
+
+/* Cookies Manager */
+#cookiesChildren::-moz-tree-image(domainCol) {
+ width: 16px;
+ height: 16px;
+ margin: 0 2px;
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png") !important;
+}
+
+#cookiesChildren::-moz-tree-image(domainCol, container),
+#cookiesChildren::-moz-tree-image(domainCol, container, open) {
+ list-style-image: url("chrome://global/skin/tree/folder.png") !important;
+}
+
+#cookieInfoBox {
+ border: 1px solid ThreeDShadow;
+ border-radius: 0;
+ margin: 4px;
+ padding: 0;
+}
+
+/* Advanced Pane */
+
+/* Adding padding-bottom prevents the bottom of the tabpanel from being cutoff
+ when browser.preferences.animateFadeIn = true */
+#advancedPrefs {
+ padding-bottom: 8px;
+}
+
+/* bottom-most box containing a groupbox in a prefpane. Prevents the bottom
+ of the groupbox from being cutoff */
+.bottomBox {
+ padding-bottom: 4px;
+}
+
+%ifdef MOZ_SERVICES_SYNC
+/* Sync Pane */
+
+#syncDesc {
+ padding: 0 8em;
+}
+
+.syncGroupBox {
+ padding: 10px;
+}
+
+#accountCaptionImage {
+ list-style-image: url("chrome://mozapps/skin/profile/profileicon.png");
+}
+
+#syncAddDeviceLabel {
+ margin-top: 1em;
+ margin-bottom: 1em;
+}
+
+%endif
diff --git a/themes/osx/preferences/saveFile.png b/themes/osx/preferences/saveFile.png
new file mode 100644
index 0000000..c210e84
--- /dev/null
+++ b/themes/osx/preferences/saveFile.png
Binary files differ
diff --git a/themes/osx/privatebrowsing-dark.png b/themes/osx/privatebrowsing-dark.png
new file mode 100644
index 0000000..9eaf3ae
--- /dev/null
+++ b/themes/osx/privatebrowsing-dark.png
Binary files differ
diff --git a/themes/osx/privatebrowsing-light.png b/themes/osx/privatebrowsing-light.png
new file mode 100644
index 0000000..c12f507
--- /dev/null
+++ b/themes/osx/privatebrowsing-light.png
Binary files differ
diff --git a/themes/osx/privatebrowsing-mask.png b/themes/osx/privatebrowsing-mask.png
new file mode 100644
index 0000000..92f60e2
--- /dev/null
+++ b/themes/osx/privatebrowsing-mask.png
Binary files differ
diff --git a/themes/osx/privatebrowsing-mask@2x.png b/themes/osx/privatebrowsing-mask@2x.png
new file mode 100644
index 0000000..ec1cf74
--- /dev/null
+++ b/themes/osx/privatebrowsing-mask@2x.png
Binary files differ
diff --git a/themes/osx/reload-stop-go.png b/themes/osx/reload-stop-go.png
new file mode 100644
index 0000000..1017be9
--- /dev/null
+++ b/themes/osx/reload-stop-go.png
Binary files differ
diff --git a/themes/osx/sanitizeDialog.css b/themes/osx/sanitizeDialog.css
new file mode 100644
index 0000000..40dc4b2
--- /dev/null
+++ b/themes/osx/sanitizeDialog.css
@@ -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/. */
+
+#sanitizeDurationChoice {
+ -moz-margin-end: 0;
+}
+
+/* Align the duration label with the warning box and item list */
+#sanitizeDurationLabel {
+ -moz-margin-start: 3px;
+}
+
+
+/* Hide the duration dropdown suffix label if it's empty. Otherwise it
+ takes up a little space, causing the end of the dropdown to not be aligned
+ with the warning box. */
+#sanitizeDurationSuffixLabel[value=""] {
+ display: none;
+}
+
+
+/* Places tree */
+#placesTreechildren::-moz-tree-row(selected),
+#placesTreechildren::-moz-tree-row(grippyRow) {
+ background: #999;
+}
+
+#placesTreechildren::-moz-tree-cell-text(selected) {
+ color: #111;
+}
+
+
+/* Sanitize everything warning box */
+#sanitizeEverythingWarningBox {
+ background-color: Window;
+ border: 1px solid ThreeDDarkShadow;
+ border-radius: 5px;
+ padding: 16px;
+}
+
+#sanitizeEverythingWarningIcon {
+ list-style-image: url("chrome://global/skin/icons/warning-large.png");
+ padding: 0;
+ margin: 0;
+}
+
+#sanitizeEverythingWarningDescBox {
+ padding: 0 16px;
+ margin: 0;
+}
+
+
+/* Progressive disclosure button */
+#detailsExpanderWrapper {
+ padding: 0;
+ margin: 6px 0;
+}
+
+.expander-up,
+.expander-down {
+ min-width: 0;
+ margin: 0;
+}
+
+.expander-up > .button-box,
+.expander-down > .button-box {
+ padding: 0;
+}
+
+.expander-up {
+ list-style-image: url("chrome://browser/skin/places/expander-open.png");
+}
+
+.expander-down {
+ list-style-image: url("chrome://browser/skin/places/expander-closed.png");
+}
+
+
+/* Make the item list the same width as the warning box */
+#itemList {
+ -moz-margin-start: 0;
+ -moz-margin-end: 0;
+}
+
+
+/* Align the last dialog button with the end of the warning box */
+.prefWindow-dlgbuttons {
+ -moz-margin-end: 0;
+}
+.dialog-button[dlgtype="cancel"] {
+ -moz-margin-end: 0;
+}
diff --git a/themes/osx/searchbar-dropdown-arrow.png b/themes/osx/searchbar-dropdown-arrow.png
new file mode 100644
index 0000000..7f5f55e
--- /dev/null
+++ b/themes/osx/searchbar-dropdown-arrow.png
Binary files differ
diff --git a/themes/osx/searchbar.css b/themes/osx/searchbar.css
new file mode 100644
index 0000000..55fdfc4
--- /dev/null
+++ b/themes/osx/searchbar.css
@@ -0,0 +1,79 @@
+/* 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/. */
+
+.searchbar-textbox {
+ width: 6em;
+ min-width: 6em;
+}
+
+.autocomplete-textbox-container {
+ -moz-box-align: stretch;
+}
+
+.textbox-input-box {
+ margin: 0;
+}
+
+/* ::::: searchbar-engine-button ::::: */
+
+.searchbar-engine-image {
+ height: 16px;
+ width: 16px;
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+
+.searchbar-engine-button {
+ -moz-appearance: none;
+ min-width: 0;
+ margin: 0;
+ padding: 0;
+ -moz-padding-end: 2px;
+ -moz-box-align: center;
+ background: none;
+ border: none;
+}
+
+.searchbar-engine-button > .button-box {
+ -moz-appearance: none;
+ padding: 0;
+ border: 0;
+}
+
+.searchbar-dropmarker-image {
+ list-style-image: url("chrome://browser/skin/searchbar-dropdown-arrow.png");
+ -moz-image-region: rect(0, 13px, 11px, 0);
+}
+
+.searchbar-engine-button[open="true"] > .searchbar-dropmarker-image {
+ -moz-image-region: rect(0, 26px, 11px, 13px);
+}
+
+
+/* ::::: search-go-button ::::: */
+
+.search-go-container {
+ -moz-box-align: center;
+}
+
+.search-go-button {
+ padding: 1px;
+ list-style-image: url("chrome://browser/skin/Search-glass.png");
+ -moz-image-region: rect(0px 16px 16px 0px);
+}
+
+.search-go-button:-moz-locale-dir(rtl) {
+ transform: scaleX(-1);
+}
+
+.search-go-button:hover {
+ -moz-image-region: rect(0px 32px 16px 16px);
+}
+
+.search-go-button:hover:active {
+ -moz-image-region: rect(0px, 48px, 16px, 32px);
+}
+
+.searchbar-engine-menuitem[selected="true"] > .menu-iconic-text {
+ font-weight: bold;
+}
diff --git a/themes/osx/setDesktopBackground.css b/themes/osx/setDesktopBackground.css
new file mode 100644
index 0000000..585284c
--- /dev/null
+++ b/themes/osx/setDesktopBackground.css
@@ -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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
+
+html|canvas#screen {
+ margin: 12px 11px 32px;
+}
+
+#monitor {
+ list-style-image: url("chrome://browser/skin/monitor.png");
+}
+
+#monitor[aspectratio="16:10"] {
+ list-style-image: url("chrome://browser/skin/monitor_16-10.png");
+}
diff --git a/themes/osx/shared.inc b/themes/osx/shared.inc
new file mode 100644
index 0000000..ef27746
--- /dev/null
+++ b/themes/osx/shared.inc
@@ -0,0 +1,6 @@
+%include ../../../../toolkit/themes/osx/global/shared.inc
+%include ../shared/browser.inc
+
+%define hudButton -moz-appearance: none; color: #434343; border-radius: 4px; border: 1px solid #b5b5b5; background: linear-gradient(#fff, #f2f2f2); box-shadow: inset 0 1px rgba(255,255,255,.8), inset 0 0 1px rgba(255,255, 255,.25), 0 1px rgba(255,255,255,.3); background-clip: padding-box; background-origin: padding-box; padding: 2px 6px;
+%define hudButtonPressed box-shadow: inset 0 1px 4px -3px #000, 0 1px rgba(255,255,255,.3);
+%define hudButtonFocused box-shadow: 0 0 1px -moz-mac-focusring inset, 0 0 4px 1px -moz-mac-focusring, 0 0 2px 1px -moz-mac-focusring;
diff --git a/themes/osx/slowStartup-16.png b/themes/osx/slowStartup-16.png
new file mode 100644
index 0000000..5551ef0
--- /dev/null
+++ b/themes/osx/slowStartup-16.png
Binary files differ
diff --git a/themes/osx/statusbar/overlay.css b/themes/osx/statusbar/overlay.css
new file mode 100644
index 0000000..ab1cc8f
--- /dev/null
+++ b/themes/osx/statusbar/overlay.css
@@ -0,0 +1,108 @@
+/* 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");
+
+%include ../../shared/statusbar/overlay.css
+
+/*
+ * General
+ */
+
+#status4evar-status-text,
+#status4evar-progress-bar
+{
+ margin: 0px 4px;
+}
+
+/*
+ * Download status
+ */
+
+#status4evar-download-progress-bar
+{
+ height: 5px;
+}
+
+#status4evar-download-button[attention] #status4evar-download-icon
+{
+ background-image: url("chrome://browser/skin/downloads/download-glow.png");
+}
+
+#status-bar
+{
+ padding-right: 0px;
+}
+
+#browser-bottombox > #addon-bar:last-child
+{
+ padding-right: 16px;
+}
+
+#status4evar-download-button #status4evar-download-icon
+{
+ background: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 0, 108, 18, 90) center no-repeat;
+ min-width: 18px;
+ min-height: 18px;
+}
+
+#status4evar-download-button:-moz-lwtheme-brighttext #status4evar-download-icon
+{
+ background: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 0, 108, 18, 90) center no-repeat;
+}
+
+#status4evar-download-button[attention] #status4evar-download-icon
+{
+ background-image: url("chrome://browser/skin/downloads/download-glow.png");
+}
+
+toolbar[mode="icons"] #status4evar-download-button[forcelabel="true"] > label
+{
+ margin: 0px 2px !important;
+ -moz-margin-start: 3px !important;
+}
+
+/*
+ * Splitter
+ */
+
+splitter.status4evar-status-splitter
+{
+ width: 8px;
+ margin: 0px -4px;
+}
+
+/*
+ * Location bar
+ */
+
+#urlbar-progress-alt
+{
+ margin-right: -1px;
+}
+
+/*
+ * Toolbar progress
+ */
+
+#status4evar-progress-bar[s4estyle="true"]
+{
+ -moz-appearance: none;
+ border: 1px solid gray;
+}
+
+#status4evar-progress-bar[s4estyle="true"] > .progress-remainder
+{
+ background-color: white;
+}
+
+/*
+ * Gripper
+ */
+
+#status4evar-window-gripper
+{
+ display: none;
+}
+
diff --git a/themes/osx/statusbar/prefs.css b/themes/osx/statusbar/prefs.css
new file mode 100644
index 0000000..798a62d
--- /dev/null
+++ b/themes/osx/statusbar/prefs.css
@@ -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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+%include ../../shared/statusbar/prefs.css
+
+#status4evar-addonbar-windowGripper-check
+{
+ display: none;
+}
+
diff --git a/themes/osx/sync-128.png b/themes/osx/sync-128.png
new file mode 100644
index 0000000..1ea3481
--- /dev/null
+++ b/themes/osx/sync-128.png
Binary files differ
diff --git a/themes/osx/sync-16.png b/themes/osx/sync-16.png
new file mode 100644
index 0000000..0afb1c7
--- /dev/null
+++ b/themes/osx/sync-16.png
Binary files differ
diff --git a/themes/osx/sync-32.png b/themes/osx/sync-32.png
new file mode 100644
index 0000000..7a762cb
--- /dev/null
+++ b/themes/osx/sync-32.png
Binary files differ
diff --git a/themes/osx/sync-bg.png b/themes/osx/sync-bg.png
new file mode 100644
index 0000000..893a27d
--- /dev/null
+++ b/themes/osx/sync-bg.png
Binary files differ
diff --git a/themes/osx/sync-desktopIcon.png b/themes/osx/sync-desktopIcon.png
new file mode 100644
index 0000000..d3d1e27
--- /dev/null
+++ b/themes/osx/sync-desktopIcon.png
Binary files differ
diff --git a/themes/osx/sync-mobileIcon.png b/themes/osx/sync-mobileIcon.png
new file mode 100644
index 0000000..a3bda57
--- /dev/null
+++ b/themes/osx/sync-mobileIcon.png
Binary files differ
diff --git a/themes/osx/sync-throbber.png b/themes/osx/sync-throbber.png
new file mode 100644
index 0000000..d25490b
--- /dev/null
+++ b/themes/osx/sync-throbber.png
Binary files differ
diff --git a/themes/osx/syncCommon.css b/themes/osx/syncCommon.css
new file mode 100644
index 0000000..f0beae0
--- /dev/null
+++ b/themes/osx/syncCommon.css
@@ -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/. */
+
+/* The following are used by both sync/setup.xul and sync/genericChange.xul */
+.status {
+ color: -moz-dialogtext;
+}
+
+.statusIcon {
+ -moz-margin-start: 4px;
+ max-height: 16px;
+ max-width: 16px;
+}
+
+.statusIcon[status="active"] {
+ list-style-image: url("chrome://global/skin/icons/loading_16.png");
+}
+
+.statusIcon[status="error"] {
+ list-style-image: url("chrome://global/skin/icons/error-16.png");
+}
+
+.statusIcon[status="success"] {
+ list-style-image: url("chrome://global/skin/icons/information-16.png");
+}
+
+/* .data is only used by sync/genericChange.xul, but it seems unnecessary to have
+ a separate stylesheet for it. */
+.data {
+ font-size: 90%;
+ font-weight: bold;
+}
+
+dialog#change-dialog {
+ width: 40em;
+}
+
+image#syncIcon {
+ list-style-image: url("chrome://browser/skin/sync-32.png");
+}
+
+#introText {
+ margin-top: 2px;
+}
+
+#feedback {
+ height: 2em;
+}
diff --git a/themes/osx/syncProgress.css b/themes/osx/syncProgress.css
new file mode 100644
index 0000000..d7aa599
--- /dev/null
+++ b/themes/osx/syncProgress.css
@@ -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/. */
+@import url(chrome://global/skin/inContentUI.css);
+
+:root {
+ height: 100%;
+ width: 100%;
+ padding: 0;
+}
+
+body {
+ margin: 0;
+ padding: 0 2em;
+}
+
+#floatingBox {
+ margin: 4em auto;
+ max-width: 40em;
+ min-width: 23em;
+ padding: 1em 1.5em;
+ position: relative;
+ text-align: center;
+}
+
+#successLogo {
+ margin: 1em 2em;
+}
+
+#loadingText {
+ margin: 2em 6em;
+}
+
+#progressBar {
+ margin: 2em 10em;
+}
+
+#uploadProgressBar{
+ width: 100%;
+}
+
+#bottomRow {
+ margin-top: 2em;
+ padding: 0;
+ text-align: end;
+}
diff --git a/themes/osx/syncQuota.css b/themes/osx/syncQuota.css
new file mode 100644
index 0000000..1577de8
--- /dev/null
+++ b/themes/osx/syncQuota.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/. */
+
+#quotaDialog {
+ width: 33em;
+ height: 25em;
+}
+
+treechildren::-moz-tree-checkbox {
+ list-style-image: none;
+}
+treechildren::-moz-tree-checkbox(checked) {
+ list-style-image: url("chrome://global/skin/checkbox/cbox-check.gif");
+}
+treechildren::-moz-tree-checkbox(disabled) {
+ list-style-image: url("chrome://global/skin/checkbox/cbox-check-dis.gif");
+}
+
+#treeCaption {
+ height: 4em;
+}
+
+.captionWarning {
+ font-weight: bold;
+}
diff --git a/themes/osx/syncSetup.css b/themes/osx/syncSetup.css
new file mode 100644
index 0000000..fff65e9
--- /dev/null
+++ b/themes/osx/syncSetup.css
@@ -0,0 +1,132 @@
+/* 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/. */
+
+wizard {
+ -moz-appearance: none;
+ width: 55em;
+ height: 45em;
+ padding: 0;
+ background-color: Window;
+}
+
+.wizard-page-box {
+ -moz-appearance: none;
+ padding-left: 0;
+ padding-right: 0;
+ margin: 0;
+}
+
+wizardpage {
+ -moz-box-pack: center;
+ -moz-box-align: center;
+ margin: 0;
+ padding: 0 6em;
+ background-color: Window;
+}
+
+.wizard-header {
+ -moz-appearance: none;
+ border: none;
+ padding: 2em 0 1em 0;
+ text-align: center;
+}
+.wizard-header-label {
+ font-size: 24pt;
+ font-weight: normal;
+}
+
+.wizard-buttons {
+ background-color: rgba(0,0,0,0.1);
+ padding: 1em;
+}
+
+.wizard-buttons-separator {
+ visibility: collapse;
+}
+
+.wizard-header-icon {
+ visibility: collapse;
+}
+
+.accountChoiceButton {
+ font: menu;
+}
+
+.confirm {
+ border: 1px solid black;
+ padding: 1em;
+ border-radius: 5px;
+}
+
+/* Override the text-link style from global.css */
+description > .text-link,
+description > .text-link:focus {
+ margin: 0px;
+ padding: 0px;
+ border: 0px;
+}
+
+
+.success,
+.error {
+ padding: 2px;
+ border-radius: 2px;
+}
+
+.error {
+ background-color: #FF0000 !important;
+ color: #FFFFFF !important;
+}
+
+.success {
+ background-color: #00FF00 !important;
+}
+
+.warning {
+ font-weight: bold;
+ font-size: 100%;
+ color: red;
+}
+
+.mainDesc {
+ font-weight: bold;
+ font-size: 100%;
+}
+
+.normal {
+ font-size: 100%;
+}
+
+.inputColumn {
+ -moz-margin-end: 2px
+}
+
+.pin {
+ font-size: 18pt;
+ width: 4em;
+ text-align: center;
+}
+
+#passphraseHelpSpacer {
+ width: 0.5em;
+}
+
+#pairDeviceThrobber > image,
+#login-throbber > image {
+ list-style-image: url("chrome://global/skin/icons/loading_16.png");
+}
+
+#captchaFeedback {
+ visibility: hidden;
+}
+
+#successPageIcon {
+ /* TODO replace this with a 128px version (bug 591122) */
+ list-style-image: url("chrome://browser/skin/sync-32.png");
+}
+
+#tosDesc {
+ margin-left: -7px;
+ margin-bottom: 3px;
+} \ No newline at end of file
diff --git a/themes/osx/tabbrowser/alltabs-inverted.png b/themes/osx/tabbrowser/alltabs-inverted.png
new file mode 100644
index 0000000..002bdd4
--- /dev/null
+++ b/themes/osx/tabbrowser/alltabs-inverted.png
Binary files differ
diff --git a/themes/osx/tabbrowser/alltabs.png b/themes/osx/tabbrowser/alltabs.png
new file mode 100644
index 0000000..172d425
--- /dev/null
+++ b/themes/osx/tabbrowser/alltabs.png
Binary files differ
diff --git a/themes/osx/tabbrowser/connecting.png b/themes/osx/tabbrowser/connecting.png
new file mode 100644
index 0000000..e564fb5
--- /dev/null
+++ b/themes/osx/tabbrowser/connecting.png
Binary files differ
diff --git a/themes/osx/tabbrowser/loading.png b/themes/osx/tabbrowser/loading.png
new file mode 100644
index 0000000..ba54836
--- /dev/null
+++ b/themes/osx/tabbrowser/loading.png
Binary files differ
diff --git a/themes/osx/tabbrowser/newtab-glass.png b/themes/osx/tabbrowser/newtab-glass.png
new file mode 100644
index 0000000..15185be
--- /dev/null
+++ b/themes/osx/tabbrowser/newtab-glass.png
Binary files differ
diff --git a/themes/osx/tabbrowser/newtab-inverted.png b/themes/osx/tabbrowser/newtab-inverted.png
new file mode 100644
index 0000000..4ac1eba
--- /dev/null
+++ b/themes/osx/tabbrowser/newtab-inverted.png
Binary files differ
diff --git a/themes/osx/tabbrowser/newtab.png b/themes/osx/tabbrowser/newtab.png
new file mode 100644
index 0000000..7cea7bd
--- /dev/null
+++ b/themes/osx/tabbrowser/newtab.png
Binary files differ
diff --git a/themes/osx/tabbrowser/tab-arrow-left-glass.png b/themes/osx/tabbrowser/tab-arrow-left-glass.png
new file mode 100644
index 0000000..aac93a7
--- /dev/null
+++ b/themes/osx/tabbrowser/tab-arrow-left-glass.png
Binary files differ
diff --git a/themes/osx/tabbrowser/tab-arrow-left-inverted.png b/themes/osx/tabbrowser/tab-arrow-left-inverted.png
new file mode 100644
index 0000000..16cd7a2
--- /dev/null
+++ b/themes/osx/tabbrowser/tab-arrow-left-inverted.png
Binary files differ
diff --git a/themes/osx/tabbrowser/tab-arrow-left.png b/themes/osx/tabbrowser/tab-arrow-left.png
new file mode 100644
index 0000000..e0fb348
--- /dev/null
+++ b/themes/osx/tabbrowser/tab-arrow-left.png
Binary files differ
diff --git a/themes/osx/tabbrowser/tab-overflow-border.png b/themes/osx/tabbrowser/tab-overflow-border.png
new file mode 100644
index 0000000..77f2462
--- /dev/null
+++ b/themes/osx/tabbrowser/tab-overflow-border.png
Binary files differ
diff --git a/themes/osx/tabbrowser/tabDragIndicator.png b/themes/osx/tabbrowser/tabDragIndicator.png
new file mode 100644
index 0000000..c67c233
--- /dev/null
+++ b/themes/osx/tabbrowser/tabDragIndicator.png
Binary files differ
diff --git a/themes/osx/toolbarbutton-dropdown-arrow-inverted.png b/themes/osx/toolbarbutton-dropdown-arrow-inverted.png
new file mode 100644
index 0000000..f3261f1
--- /dev/null
+++ b/themes/osx/toolbarbutton-dropdown-arrow-inverted.png
Binary files differ
diff --git a/themes/osx/toolbarbutton-dropdown-arrow.png b/themes/osx/toolbarbutton-dropdown-arrow.png
new file mode 100644
index 0000000..a7abe73
--- /dev/null
+++ b/themes/osx/toolbarbutton-dropdown-arrow.png
Binary files differ
diff --git a/themes/osx/urlbar-arrow.png b/themes/osx/urlbar-arrow.png
new file mode 100644
index 0000000..fcab253
--- /dev/null
+++ b/themes/osx/urlbar-arrow.png
Binary files differ
diff --git a/themes/osx/urlbar-history-dropmarker.png b/themes/osx/urlbar-history-dropmarker.png
new file mode 100644
index 0000000..fc8b0be
--- /dev/null
+++ b/themes/osx/urlbar-history-dropmarker.png
Binary files differ
diff --git a/themes/osx/urlbar-popup-blocked.png b/themes/osx/urlbar-popup-blocked.png
new file mode 100644
index 0000000..e6fd29f
--- /dev/null
+++ b/themes/osx/urlbar-popup-blocked.png
Binary files differ
diff --git a/themes/osx/web-notifications-icon.svg b/themes/osx/web-notifications-icon.svg
new file mode 100644
index 0000000..f7186c7
--- /dev/null
+++ b/themes/osx/web-notifications-icon.svg
@@ -0,0 +1,15 @@
+<?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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid" width="64" height="64" viewBox="0 0 64 64">
+ <defs>
+ <style>
+ .icon {
+ fill: #a6a6a6;
+ fill-rule: evenodd;
+ }
+ </style>
+ </defs>
+ <path d="M57,48 L46,48 L46,60.016 L32.482,48 L7,48 C5.343,48 4,46.657 4,45 L4,11.031 C4,9.374 5.343,8.031 7,8.031 L57,8.031 C58.657,8.031 60,9.374 60,11.031 L60,45 C60,46.657 58.657,48 57,48 ZM36,16.031 C36,14.927 35.105,14.031 34,14.031 L30,14.031 C28.895,14.031 28,14.927 28,16.031 L28,30.031 C28,31.136 28.895,32.031 30,32.031 L34,32.031 C35.105,32.031 36,31.136 36,30.031 L36,16.031 ZM36,37.5 C36,36.672 35.328,36 34.5,36 L29.5,36 C28.672,36 28,36.672 28,37.5 L28,40.5 C28,41.328 28.672,42 29.5,42 L34.5,42 C35.328,42 36,41.328 36,40.5 L36,37.5 Z" class="icon"/>
+</svg>
diff --git a/themes/osx/web-notifications-tray.svg b/themes/osx/web-notifications-tray.svg
new file mode 100644
index 0000000..314026a
--- /dev/null
+++ b/themes/osx/web-notifications-tray.svg
@@ -0,0 +1,23 @@
+<?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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="16" viewBox="0 0 96 32">
+ <defs>
+ <style>
+ .style-icon-notification {
+ fill: #666666;
+ }
+ .style-icon-notification.hover {
+ fill: #808080;
+ }
+ .style-icon-notification.active {
+ fill: #4d4d4d;
+ }
+ </style>
+ <path id="shape-notifcations-push" d="M27,23.969 L24,23.969 L24,29.977 L17.241,23.969 L5,23.969 C3.343,23.969 2,22.626 2,20.969 L2,6.969 C2,5.312 3.343,3.969 5,3.969 L27,3.969 C28.657,3.969 30,5.312 30,6.969 L30,20.969 C30,22.626 28.657,23.969 27,23.969 ZM18,8.969 C18,7.864 17.105,6.969 16,6.969 C14.895,6.969 14,7.864 14,8.969 L14,13.969 C14,15.073 14.895,15.969 16,15.969 C17.105,15.969 18,15.073 18,13.969 L18,8.969 ZM16.5,17.969 L15.5,17.969 C14.672,17.969 14,18.640 14,19.469 C14,20.297 14.672,20.969 15.5,20.969 L16.5,20.969 C17.328,20.969 18,20.297 18,19.469 C18,18.640 17.328,17.969 16.5,17.969 Z"/>
+ </defs>
+ <use xlink:href="#shape-notifcations-push" class="style-icon-notification"/>
+ <use xlink:href="#shape-notifcations-push" transform="translate(32)" class="style-icon-notification hover"/>
+ <use xlink:href="#shape-notifcations-push" transform="translate(64)" class="style-icon-notification active"/>
+</svg>
diff --git a/themes/osx/webRTC-shareDevice-16.png b/themes/osx/webRTC-shareDevice-16.png
new file mode 100644
index 0000000..df01b33
--- /dev/null
+++ b/themes/osx/webRTC-shareDevice-16.png
Binary files differ
diff --git a/themes/osx/webRTC-shareDevice-64.png b/themes/osx/webRTC-shareDevice-64.png
new file mode 100644
index 0000000..d125789
--- /dev/null
+++ b/themes/osx/webRTC-shareDevice-64.png
Binary files differ
diff --git a/themes/osx/webRTC-sharingDevice-16.png b/themes/osx/webRTC-sharingDevice-16.png
new file mode 100644
index 0000000..a670676
--- /dev/null
+++ b/themes/osx/webRTC-sharingDevice-16.png
Binary files differ
diff --git a/themes/shared/browser.inc b/themes/shared/browser.inc
new file mode 100644
index 0000000..18e69ad
--- /dev/null
+++ b/themes/shared/browser.inc
@@ -0,0 +1,8 @@
+%filter substitution
+
+%ifndef MOZ_WEBRTC
+%define primaryToolbarButtons #back-button, #forward-button, #reload-button, #stop-button, #home-button, #print-button, #downloads-button, #downloads-indicator, #history-button, #history-menu-button, #bookmarks-button, #bookmarks-menu-button, #new-tab-button, #new-window-button, #cut-button, #copy-button, #paste-button, #fullscreen-button, #zoom-out-button, #zoom-in-button, #sync-button, #feed-button, #alltabs-button
+%else
+%define primaryToolbarButtons #back-button, #forward-button, #reload-button, #stop-button, #home-button, #print-button, #downloads-button, #downloads-indicator, #history-button, #history-menu-button, #bookmarks-button, #bookmarks-menu-button, #new-tab-button, #new-window-button, #cut-button, #copy-button, #paste-button, #fullscreen-button, #zoom-out-button, #zoom-in-button, #sync-button, #feed-button, #alltabs-button, #webrtc-status-button
+%endif
+
diff --git a/themes/shared/newtab/controls.png b/themes/shared/newtab/controls.png
new file mode 100644
index 0000000..14f382f
--- /dev/null
+++ b/themes/shared/newtab/controls.png
Binary files differ
diff --git a/themes/shared/newtab/newTab.css.inc b/themes/shared/newtab/newTab.css.inc
new file mode 100644
index 0000000..3341ba7
--- /dev/null
+++ b/themes/shared/newtab/newTab.css.inc
@@ -0,0 +1,203 @@
+/* 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/. */
+
+:root {
+ -moz-appearance: none;
+ font-size: 75%;
+ background-color: transparent;
+}
+
+body {
+ background: linear-gradient(to top,#DFF3FF,#F9F9F9) fixed;
+}
+
+/* SCROLLBOX */
+#newtab-scrollbox:not([page-disabled]) {
+ background-color: rgb(229,229,229);
+ background-image: url(chrome://browser/skin/newtab/noise.png),
+ linear-gradient(rgba(255,255,255,.5), rgba(255,255,255,.2));
+ background-attachment: fixed;
+}
+
+/* UNDO */
+#newtab-undo-container {
+ padding: 4px 3px;
+ border: 1px solid;
+ border-color: rgba(8,22,37,.12) rgba(8,22,37,.14) rgba(8,22,37,.16);
+ background-color: rgba(255,255,255,.4);
+ color: #525e69;
+}
+
+#newtab-undo-label {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+
+.newtab-undo-button:hover {
+ text-decoration: underline;
+}
+
+.newtab-undo-button:-moz-focusring {
+ outline: 1px dotted;
+}
+
+
+#newtab-undo-close-button > .toolbarbutton-text {
+ display: none;
+}
+
+#newtab-undo-close-button:-moz-focusring {
+ outline: 1px dotted;
+}
+
+/* TOGGLE */
+#newtab-toggle {
+ width: 16px;
+ height: 16px;
+ padding: 0;
+ border: none;
+ background: -216px 0 transparent url(chrome://browser/skin/newtab/controls.png);
+}
+
+#newtab-toggle[page-disabled] {
+ background-position: -232px 0;
+}
+
+/* ROWS */
+.newtab-row {
+ margin-bottom: 20px;
+}
+
+.newtab-row:last-child {
+ margin-bottom: 0;
+}
+
+/* CELLS */
+.newtab-cell {
+ -moz-margin-end: 20px;
+ background-color: rgba(255,255,255,.2);
+ border: 1px solid;
+ border-color: rgba(8,22,37,.12) rgba(8,22,37,.14) rgba(8,22,37,.16);
+ border-radius: 1px;
+ transition: border-color 100ms ease-out;
+}
+
+.newtab-cell:empty {
+ border: 1px dashed;
+ border-color: rgba(8,22,37,.15) rgba(8,22,37,.17) rgba(8,22,37,.19);
+}
+
+.newtab-cell:last-child {
+ -moz-margin-end: 0;
+}
+
+.newtab-cell:hover:not(:empty):not([dragged]) {
+ border-color: rgba(8,22,37,.25) rgba(8,22,37,.27) rgba(8,22,37,.3);
+}
+
+/* SITES */
+.newtab-site {
+ text-decoration: none;
+ transition-property: top, left, opacity, box-shadow, background-color;
+}
+
+.newtab-site:hover,
+.newtab-site[dragged] {
+ box-shadow: 0 3px 6px 1px rgba(8,20,37,.6);
+}
+
+.newtab-site[dragged] {
+ transition-property: box-shadow, background-color;
+ background-color: rgb(242,242,242);
+}
+
+/* THUMBNAILS */
+.newtab-thumbnail {
+ background-origin: padding-box;
+ background-clip: padding-box;
+ background-repeat: no-repeat;
+ background-size: cover;
+}
+
+/* TITLES */
+.newtab-title {
+ padding: 0 8px 1px;
+ background-color: rgba(248,249,251,.95);
+ color: #1f364c;
+ line-height: 24px;
+}
+
+.newtab-site[pinned] .newtab-title {
+ padding-inline-start: 16px;
+}
+
+.newtab-site[pinned] .newtab-title::before {
+ background-image: url(chrome://browser/skin/newtab/pinned.png);
+ content: "";
+ left: 2px;
+ top: 2px;
+ position: absolute;
+ width: 12px;
+ height: 20px;
+}
+
+.newtab-site[pinned] .newtab-title:dir(rtl)::before {
+ left: auto;
+ right: 2px;
+}
+
+/* CONTROLS */
+.newtab-control {
+ background-color: transparent;
+ background-size: 24px;
+ border: none;
+ height: 24px;
+ width: 24px;
+ top: 4px;
+ background: transparent url(chrome://browser/skin/newtab/controls.png);
+}
+
+.newtab-control-pin:dir(ltr),
+.newtab-control-block:dir(rtl) {
+ left: 4px;
+}
+
+.newtab-control-block:dir(ltr),
+.newtab-control-pin:dir(rtl) {
+ right: 4px;
+}
+
+.newtab-control-pin:hover {
+ background-position: -24px 0;
+}
+
+.newtab-control-pin:active {
+ background-position: -48px 0;
+}
+
+.newtab-site[pinned] .newtab-control-pin {
+ background-position: -72px 0;
+}
+
+.newtab-site[pinned] .newtab-control-pin:hover {
+ background-position: -96px 0;
+}
+
+.newtab-site[pinned] .newtab-control-pin:active {
+ background-position: -120px 0;
+}
+
+.newtab-control-block {
+ background-position: -144px 0;
+}
+
+.newtab-control-block:hover {
+ background-position: -168px 0;
+}
+
+.newtab-control-block:active {
+ background-position: -192px 0;
+}
+
diff --git a/themes/shared/newtab/noise.png b/themes/shared/newtab/noise.png
new file mode 100644
index 0000000..01d340a
--- /dev/null
+++ b/themes/shared/newtab/noise.png
Binary files differ
diff --git a/themes/shared/newtab/pinned.png b/themes/shared/newtab/pinned.png
new file mode 100644
index 0000000..ddd731b
--- /dev/null
+++ b/themes/shared/newtab/pinned.png
Binary files differ
diff --git a/themes/shared/plugin-doorhanger.inc.css b/themes/shared/plugin-doorhanger.inc.css
new file mode 100644
index 0000000..bda08e4
--- /dev/null
+++ b/themes/shared/plugin-doorhanger.inc.css
@@ -0,0 +1,53 @@
+#notification-popup[popupid="click-to-play-plugins"] > .panel-arrowcontainer > .panel-arrowcontent {
+ padding: 0px;
+}
+
+.click-to-play-plugins-notification-content {
+ width: 28em;
+}
+
+.click-to-play-plugins-notification-center-box {
+ border: 1px solid ThreeDShadow;
+ margin: 10px;
+}
+
+.plugin-popupnotification-centeritem:nth-child(odd) {
+ background-color: rgba(0,0,0,0.1);
+}
+
+.center-item-label {
+ -moz-margin-start: 6px;
+ margin-bottom: 0;
+ text-overflow: ellipsis;
+}
+
+.center-item-warning-icon {
+ background-image: url("chrome://mozapps/skin/extensions/alerticon-info-negative.png");
+ background-repeat: no-repeat;
+ width: 16px;
+ height: 15px;
+ -moz-margin-start: 6px;
+}
+
+.click-to-play-plugins-notification-button-container {
+ background: linear-gradient(rgba(0,0,0,0.04) 60%, transparent);
+ padding: 10px;
+ margin-top: 5px;
+}
+
+.click-to-play-popup-button {
+ width: 50%;
+}
+
+.click-to-play-plugins-notification-description-box {
+ padding: 10px;
+}
+
+.click-to-play-plugins-outer-description {
+ margin-top: 8px;
+}
+
+.click-to-play-plugins-notification-link,
+.center-item-link {
+ margin: 0;
+}
diff --git a/themes/shared/plugins/notification-pluginAlert.png b/themes/shared/plugins/notification-pluginAlert.png
new file mode 100644
index 0000000..7492fdd
--- /dev/null
+++ b/themes/shared/plugins/notification-pluginAlert.png
Binary files differ
diff --git a/themes/shared/plugins/notification-pluginAlert@2x.png b/themes/shared/plugins/notification-pluginAlert@2x.png
new file mode 100644
index 0000000..f335996
--- /dev/null
+++ b/themes/shared/plugins/notification-pluginAlert@2x.png
Binary files differ
diff --git a/themes/shared/plugins/notification-pluginBlocked.png b/themes/shared/plugins/notification-pluginBlocked.png
new file mode 100644
index 0000000..e2e9489
--- /dev/null
+++ b/themes/shared/plugins/notification-pluginBlocked.png
Binary files differ
diff --git a/themes/shared/plugins/notification-pluginBlocked@2x.png b/themes/shared/plugins/notification-pluginBlocked@2x.png
new file mode 100644
index 0000000..5126be0
--- /dev/null
+++ b/themes/shared/plugins/notification-pluginBlocked@2x.png
Binary files differ
diff --git a/themes/shared/plugins/notification-pluginNormal.png b/themes/shared/plugins/notification-pluginNormal.png
new file mode 100644
index 0000000..979e92b
--- /dev/null
+++ b/themes/shared/plugins/notification-pluginNormal.png
Binary files differ
diff --git a/themes/shared/plugins/notification-pluginNormal@2x.png b/themes/shared/plugins/notification-pluginNormal@2x.png
new file mode 100644
index 0000000..c081bbb
--- /dev/null
+++ b/themes/shared/plugins/notification-pluginNormal@2x.png
Binary files differ
diff --git a/themes/shared/statusbar/dynamic.css b/themes/shared/statusbar/dynamic.css
new file mode 100644
index 0000000..2c53cb2
--- /dev/null
+++ b/themes/shared/statusbar/dynamic.css
@@ -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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+/* Progress bar/line styles */
+
+#status4evar-progress-bar[s4estyle="true"] > .progress-bar
+{
+ -moz-appearance: none;
+ background: #333399;
+ border-radius: 3px;
+}
+
+#status4evar-download-progress-bar[pmType^="active"] > .progress-bar
+{
+ background-color: #333399;
+}
+
+#status4evar-download-progress-bar[pmType^="paused"] > .progress-bar
+{
+ background-color: gray;
+}
+
diff --git a/themes/shared/statusbar/overlay.css b/themes/shared/statusbar/overlay.css
new file mode 100644
index 0000000..89b3dac
--- /dev/null
+++ b/themes/shared/statusbar/overlay.css
@@ -0,0 +1,169 @@
+/* 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");
+
+/*
+ * General styles
+ */
+
+#status4evar-status-widget,
+#status4evar-progress-widget
+{
+ -moz-box-align: center;
+}
+
+[forcevisible="true"]
+{
+ visibility: visible !important;
+}
+
+#wrapper-status4evar-progress-widget progressmeter,
+#wrapper-status4evar-download-button toolbarbutton
+{
+ visibility: visible;
+}
+
+#wrapper-status4evar-status-widget toolbaritem label
+{
+ background-color: rgba(0,0,0,0.2);
+ padding: 2px 4px;
+}
+
+/*
+ * Options button
+ */
+
+#status4evar-options-button
+{
+ list-style-image: url("chrome://browser/skin/statusbar/pms24.png");
+}
+
+toolbar[iconsize="small"] #status4evar-options-button
+{
+ list-style-image: url("chrome://browser/skin/statusbar/pms16.png");
+}
+
+/*
+ * Download status
+ */
+
+toolbar[mode="icons"] #status4evar-download-button[forcelabel="true"],
+toolbar[mode="text"] #status4evar-download-button
+{
+ -moz-box-orient: horizontal !important;
+}
+
+toolbar[mode="icons"] #status4evar-download-button[forcelabel="true"] > label
+{
+ display: -moz-box !important;
+}
+
+#status4evar-download-progress-bar
+{
+ border: 1px solid gray;
+ -moz-appearance: none;
+ margin: 0px;
+ min-height: 0px;
+ min-width: 0px;
+}
+
+#status4evar-download-progress-bar > *
+{
+ -moz-appearance: none;
+ background: #FFFFFF;
+}
+
+#status4evar-download-progress-bar[pmType$="unknown"] > .progress-bar
+{
+ background-image: url("chrome://browser/skin/statusbar/pulse.png");
+}
+
+#status4evar-download-notification-container
+{
+ min-height: 1px;
+ min-width: 1px;
+ height: 1px;
+ margin-bottom: -1px;
+ position: relative;
+ z-index: 5;
+}
+
+#status4evar-download-notification-icon
+{
+ opacity: 0;
+ background-size: 16px;
+ background-position: center;
+ background-repeat: no-repeat;
+ width: 16px;
+ height: 16px;
+}
+
+@keyframes status4evar-download-notification-finish
+{
+ from { opacity: 0; transform: scale(1); }
+ 20% { opacity: .65; animation-timing-function: ease-in; }
+ to { opacity: 0; transform: scale(8); }
+}
+
+#status4evar-download-notification-anchor[notification="finish"][forcevisible="true"] #status4evar-download-notification-icon
+{
+ background-image: url("chrome://browser/skin/downloads/download-notification-finish.png");
+ animation-name: status4evar-download-notification-finish;
+ animation-duration: 1s;
+}
+
+/*
+ * Splitter
+ */
+
+splitter.status4evar-status-splitter
+{
+ -moz-appearance: splitter;
+ border: none;
+ background: transparent;
+ position: relative;
+}
+
+splitter.status4evar-status-splitter:not(:hover)
+{
+ -moz-appearance: none;
+}
+
+/*
+ * General progress
+ */
+
+#status4evar-progress-bar[s4estyle="true"] > *
+{
+ border: none;
+}
+
+#status4evar-progress-bar > .progress-remainder
+{
+ background-image: none;
+ background-color: transparent;
+}
+
+#status4evar-progress-bar[s4estyle="true"] > .progress-bar
+{
+ border-right: 1px solid rgba(0,0,0,.2);
+}
+
+#status4evar-progress-bar[s4estyle="true"][value="0"] > .progress-bar,
+#status4evar-progress-bar[s4estyle="true"][value="100"] > .progress-bar
+{
+ border-right: none;
+}
+
+/*
+ * Status bar
+ */
+
+#status-bar > .statusbar-resizerpanel
+{
+ display: none !important;
+}
+
diff --git a/themes/shared/statusbar/pms16.png b/themes/shared/statusbar/pms16.png
new file mode 100644
index 0000000..830c586
--- /dev/null
+++ b/themes/shared/statusbar/pms16.png
Binary files differ
diff --git a/themes/shared/statusbar/pms24.png b/themes/shared/statusbar/pms24.png
new file mode 100644
index 0000000..cdc69b9
--- /dev/null
+++ b/themes/shared/statusbar/pms24.png
Binary files differ
diff --git a/themes/shared/statusbar/prefs.css b/themes/shared/statusbar/prefs.css
new file mode 100644
index 0000000..45b138f
--- /dev/null
+++ b/themes/shared/statusbar/prefs.css
@@ -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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+#status4evar-prefs > prefpane > vbox.content-box
+{
+ min-height: 18em !important;
+}
+
+radio[pane="status4evar-pane-status"]
+{
+ list-style-image: url("chrome://global/skin/icons/information-32.png");
+}
+
+radio[pane="status4evar-pane-progress"]
+{
+ list-style-image: url("chrome://browser/skin/statusbar/throbberStatic.png");
+}
+
+radio[pane="status4evar-pane-download"]
+{
+ list-style-image: url("chrome://mozapps/skin/downloads/downloadIcon.png");
+}
+
+radio[pane="status4evar-pane-addonbar"]
+{
+ list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric.png");
+}
+
+radio[pane="status4evar-pane-advanced"]
+{
+ list-style-image: url("chrome://browser/skin/preferences/Options.png");
+ -moz-image-region: rect(0px, 224px, 32px, 192px);
+}
+
+
diff --git a/themes/shared/statusbar/pulse.png b/themes/shared/statusbar/pulse.png
new file mode 100644
index 0000000..374369c
--- /dev/null
+++ b/themes/shared/statusbar/pulse.png
Binary files differ
diff --git a/themes/shared/statusbar/throbber-idle.png b/themes/shared/statusbar/throbber-idle.png
new file mode 100644
index 0000000..bcdd65b
--- /dev/null
+++ b/themes/shared/statusbar/throbber-idle.png
Binary files differ
diff --git a/themes/shared/statusbar/throbberStatic.png b/themes/shared/statusbar/throbberStatic.png
new file mode 100644
index 0000000..e2bf274
--- /dev/null
+++ b/themes/shared/statusbar/throbberStatic.png
Binary files differ
diff --git a/themes/shared/tabbrowser/tab-audio-small.svg b/themes/shared/tabbrowser/tab-audio-small.svg
new file mode 100644
index 0000000..abfe712
--- /dev/null
+++ b/themes/shared/tabbrowser/tab-audio-small.svg
@@ -0,0 +1,58 @@
+<?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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
+ <style>
+ .icon:not(:target) {
+ display: none;
+ }
+
+ .icon {
+ fill: #262626;
+ }
+ .icon > .outline {
+ fill: #fff;
+ }
+
+ .icon.white {
+ fill: #fff;
+ }
+ .icon.white > .outline {
+ fill: #000;
+ fill-opacity: .5;
+ }
+ </style>
+
+ <g id="tab-audio" class="icon">
+ <path class="outline" d="M12.4,3.6l-1-0.6l-0.9,2.5H10V1.8c0-0.4-0.5-0.7-0.9-0.4L5.6,5H4C2.9,5,2,5.9,2,7v2c0,1.1,0.9,2,2,2h1.6l3.6,3.6 c0.3,0.3,0.9,0.1,0.9-0.4v-3.7h0.5l0.9,2.5l1-0.6C14,11.5,15,9.8,15,8S14,4.5,12.4,3.6z M9,13l-3-3H4c-0.6,0-1-0.4-1-1V7 c0-0.6,0.4-1,1-1h2l3-3V13z M10,9.5v-3c0.8,0,1.5,0.7,1.5,1.5S10.8,9.5,10,9.5z M11.9,11.5l-0.4-0.9C12.4,10,13,9.1,13,8 s-0.6-2-1.4-2.5l0.3-1C13.2,5.2,14,6.5,14,8S13.2,10.8,11.9,11.5z"/>
+ <path d="M4,6C3.4,6,3,6.4,3,7v2c0,0.6,0.4,1,1,1h2l3,3V3L6,6H4z M10,6.5v3c0.8,0,1.5-0.7,1.5-1.5S10.8,6.5,10,6.5z M11.9,4.5 l-0.4,0.9C12.4,6,13,6.9,13,8s-0.6,2-1.4,2.5l0.4,0.9c1.2-0.7,2.1-2,2.1-3.5S13.2,5.2,11.9,4.5z"/>
+ </g>
+ <g id="tab-audio-muted" class="icon">
+ <path class="outline" d="M5.6,5H4C2.9,5,2,5.9,2,7v2c0,0.7,0.3,1.3,0.9,1.7l-1.8,1.8l2.5,2.5l3-3l2.6,2.6c0.3,0.3,0.9,0.1,0.9-0.4V8.5l3.9-3.9 l-2.5-2.5L10,3.5V1.8c0-0.4-0.5-0.7-0.9-0.4L5.6,5z"/>
+ <path d="M11.5,3.5L9,5.9V3L6,6H4C3.4,6,3,6.4,3,7v2c0,0.6,0.4,1,1,1h0.9l-2.5,2.5l1.1,1.1l9-9L11.5,3.5z M9,13V9.7l-1.7,1.7L9,13z"/>
+ </g>
+
+ <g id="tab-audio-white" class="icon white">
+ <path class="outline" d="M12.4,3.6l-1-0.6l-0.9,2.5H10V1.8c0-0.4-0.5-0.7-0.9-0.4L5.6,5H4C2.9,5,2,5.9,2,7v2c0,1.1,0.9,2,2,2h1.6l3.6,3.6 c0.3,0.3,0.9,0.1,0.9-0.4v-3.7h0.5l0.9,2.5l1-0.6C14,11.5,15,9.8,15,8S14,4.5,12.4,3.6z M9,13l-3-3H4c-0.6,0-1-0.4-1-1V7 c0-0.6,0.4-1,1-1h2l3-3V13z M10,9.5v-3c0.8,0,1.5,0.7,1.5,1.5S10.8,9.5,10,9.5z M11.9,11.5l-0.4-0.9C12.4,10,13,9.1,13,8 s-0.6-2-1.4-2.5l0.3-1C13.2,5.2,14,6.5,14,8S13.2,10.8,11.9,11.5z"/>
+ <path d="M4,6C3.4,6,3,6.4,3,7v2c0,0.6,0.4,1,1,1h2l3,3V3L6,6H4z M10,6.5v3c0.8,0,1.5-0.7,1.5-1.5S10.8,6.5,10,6.5z M11.9,4.5 l-0.4,0.9C12.4,6,13,6.9,13,8s-0.6,2-1.4,2.5l0.4,0.9c1.2-0.7,2.1-2,2.1-3.5S13.2,5.2,11.9,4.5z"/>
+ </g>
+ <g id="tab-audio-white-muted" class="icon white">
+ <path class="outline" d="M5.6,5H4C2.9,5,2,5.9,2,7v2c0,0.7,0.3,1.3,0.9,1.7l-1.8,1.8l2.5,2.5l3-3l2.6,2.6c0.3,0.3,0.9,0.1,0.9-0.4V8.5l3.9-3.9 l-2.5-2.5L10,3.5V1.8c0-0.4-0.5-0.7-0.9-0.4L5.6,5z"/>
+ <path d="M11.5,3.5L9,5.9V3L6,6H4C3.4,6,3,6.4,3,7v2c0,0.6,0.4,1,1,1h0.9l-2.5,2.5l1.1,1.1l9-9L11.5,3.5z M9,13V9.7l-1.7,1.7L9,13z"/>
+ </g>
+
+ <g id="tab-audio-blocked" class="icon">
+ <path class="outline" d="M8,1.2C4.3,1.2,1.2,4.3,1.2,8s3.1,6.8,6.8,6.8s6.8-3.1,6.8-6.8S11.7,1.2,8,1.2z M8,11.9
+ c-2.1,0-3.9-1.7-3.9-3.9c0-2.1,1.7-3.9,3.9-3.9s3.9,1.7,3.9,3.9C11.9,10.1,10.1,11.9,8,11.9z M11.1,7.3L6.6,4.6L5.4,3.9v1.4v5.3V12
+ l1.2-0.7L11,8.6L12.2,8L11.1,7.3z"/>
+ <path d="M8,2C4.7,2,2,4.7,2,8s2.7,6,6,6s6-2.7,6-6S11.3,2,8,2z M8,12.7c-2.6,0-4.7-2.1-4.7-4.7
+ S5.4,3.3,8,3.3s4.7,2.1,4.7,4.7S10.6,12.7,8,12.7z M10.7,8L6.2,5.3v5.4L10.7,8z"/>
+ </g>
+ <g id="tab-audio-white-blocked" class="icon">
+ <path class="outline" d="M8,0c3.3,0,6.4,2.2,7.5,5.3c1.1,3.1,0.1,6.7-2.5,8.9c-2.6,2.1-6.3,2.4-9.2,0.7
+ C1,13.1-0.5,9.8,0.1,6.5C0.9,2.8,4.2,0,8,0z"/>
+ <path d="M8,2C4.7,2,2,4.7,2,8s2.7,6,6,6s6-2.7,6-6S11.3,2,8,2z M8,12.7c-2.6,0-4.7-2.1-4.7-4.7
+ S5.4,3.3,8,3.3s4.7,2.1,4.7,4.7S10.6,12.7,8,12.7z M10.7,8L6.2,5.3v5.4L10.7,8z"/>
+ </g>
+</svg>
diff --git a/themes/shared/tabbrowser/tab-audio.svg b/themes/shared/tabbrowser/tab-audio.svg
new file mode 100644
index 0000000..274e10c
--- /dev/null
+++ b/themes/shared/tabbrowser/tab-audio.svg
@@ -0,0 +1,18 @@
+<?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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
+ <style>
+ path:not(:target) {
+ display: none;
+ }
+ </style>
+
+ <path id="tab-audio" d="M4,5C2.9,5,2,5.9,2,7v2c0,1.1,0.9,2,2,2h1.2L9,14V2L5.2,5H4z M11,8c0-0.6-0.4-1-1-1v2C10.6,9,11,8.6,11,8z M13,8 c0-1.4-1-2.6-2.3-2.9L10.4,6C11.3,6.2,12,7,12,8s-0.7,1.8-1.6,2l0.4,0.9C12,10.6,13,9.4,13,8z M11.4,3.2l-0.4,0.9 C12.8,4.6,14,6.2,14,8s-1.2,3.4-2.9,3.8l0.4,0.9C13.5,12.2,15,10.3,15,8S13.5,3.8,11.4,3.2z"/>
+
+ <path id="tab-audio-muted" d="M12.5,3.4L9,6.3V2L5.2,5H4C2.9,5,2,5.9,2,7v2c0,0.9,0.6,1.6,1.4,1.9l-1.9,1.5l1,1.2l11-9L12.5,3.4z M9,14v-4l-2.5,2L9,14z"/>
+
+ <path id="tab-audio-blocked" d="M8,0C3.6,0,0,3.6,0,8s3.6,8,8,8s8-3.6,8-8S12.4,0,8,0z M5.6,11.6l6-3.6l-6-3.6V11.6z M8,14.2
+ c-3.4,0-6.2-2.8-6.2-6.2S4.6,1.8,8,1.8s6.2,2.8,6.2,6.2S11.4,14.2,8,14.2z"/>
+</svg>
diff --git a/themes/windows/Geolocation-16.png b/themes/windows/Geolocation-16.png
new file mode 100644
index 0000000..d710e73
--- /dev/null
+++ b/themes/windows/Geolocation-16.png
Binary files differ
diff --git a/themes/windows/Geolocation-64.png b/themes/windows/Geolocation-64.png
new file mode 100644
index 0000000..1bd46ba
--- /dev/null
+++ b/themes/windows/Geolocation-64.png
Binary files differ
diff --git a/themes/windows/Info.png b/themes/windows/Info.png
new file mode 100644
index 0000000..f9c6ef2
--- /dev/null
+++ b/themes/windows/Info.png
Binary files differ
diff --git a/themes/windows/KUI-background.png b/themes/windows/KUI-background.png
new file mode 100644
index 0000000..104a49f
--- /dev/null
+++ b/themes/windows/KUI-background.png
Binary files differ
diff --git a/themes/windows/KUI-close.png b/themes/windows/KUI-close.png
new file mode 100644
index 0000000..08eeb81
--- /dev/null
+++ b/themes/windows/KUI-close.png
Binary files differ
diff --git a/themes/windows/Makefile.in b/themes/windows/Makefile.in
new file mode 100644
index 0000000..173ca68
--- /dev/null
+++ b/themes/windows/Makefile.in
@@ -0,0 +1,7 @@
+# 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/.
+
+ICON_FILES := icon.png
+ICON_DEST = $(FINAL_TARGET)/extensions/{972ce4c6-7e08-4474-a285-3208198ce6fd}
+INSTALL_TARGETS += ICON
diff --git a/themes/windows/Privacy-16.png b/themes/windows/Privacy-16.png
new file mode 100644
index 0000000..f801bfe
--- /dev/null
+++ b/themes/windows/Privacy-16.png
Binary files differ
diff --git a/themes/windows/Privacy-32.png b/themes/windows/Privacy-32.png
new file mode 100644
index 0000000..41ecd5d
--- /dev/null
+++ b/themes/windows/Privacy-32.png
Binary files differ
diff --git a/themes/windows/Privacy-48.png b/themes/windows/Privacy-48.png
new file mode 100644
index 0000000..372b823
--- /dev/null
+++ b/themes/windows/Privacy-48.png
Binary files differ
diff --git a/themes/windows/Privacy-64.png b/themes/windows/Privacy-64.png
new file mode 100644
index 0000000..bd8d191
--- /dev/null
+++ b/themes/windows/Privacy-64.png
Binary files differ
diff --git a/themes/windows/Push-16.png b/themes/windows/Push-16.png
new file mode 100644
index 0000000..d710e73
--- /dev/null
+++ b/themes/windows/Push-16.png
Binary files differ
diff --git a/themes/windows/Push-64.png b/themes/windows/Push-64.png
new file mode 100644
index 0000000..27fecb8
--- /dev/null
+++ b/themes/windows/Push-64.png
Binary files differ
diff --git a/themes/windows/Secure24.png b/themes/windows/Secure24.png
new file mode 100644
index 0000000..265d79b
--- /dev/null
+++ b/themes/windows/Secure24.png
Binary files differ
diff --git a/themes/windows/Toolbar-glass.png b/themes/windows/Toolbar-glass.png
new file mode 100644
index 0000000..f8aac24
--- /dev/null
+++ b/themes/windows/Toolbar-glass.png
Binary files differ
diff --git a/themes/windows/Toolbar-glass.svg b/themes/windows/Toolbar-glass.svg
new file mode 100644
index 0000000..9feaac2
--- /dev/null
+++ b/themes/windows/Toolbar-glass.svg
@@ -0,0 +1,3218 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.1"
+ id="PaleMoonToolbarSVG"
+ x="0px"
+ y="0px"
+ width="378"
+ height="38"
+ viewBox="0 0 378 38"
+ enable-background="new 0 0 378 38">
+ <metadata
+ id="metadata146">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs144">
+ <radialGradient
+ xlink:href="#linearGradient4635"
+ id="radialGradient4669"
+ cx="10.529827"
+ cy="14.778796"
+ fx="10.529827"
+ fy="14.778796"
+ r="7.1399999"
+ gradientTransform="matrix(1,0,0,1.1087088,0,-1.2351844)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4635">
+ <stop
+ style="stop-color:#6198cb;stop-opacity:1"
+ offset="0"
+ id="stop4631" />
+ <stop
+ style="stop-color:#3a78b2;stop-opacity:1"
+ offset="1"
+ id="stop4633" />
+ </linearGradient>
+ <radialGradient
+ xlink:href="#linearGradient4635"
+ id="radialGradient4637"
+ cx="11.063469"
+ cy="38.79744"
+ fx="11.063469"
+ fy="38.79744"
+ r="8.7600002"
+ gradientTransform="matrix(1,0,0,1.0853313,0,-3.029369)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ xlink:href="#linearGradient4635"
+ id="radialGradient4677"
+ cx="34.841751"
+ cy="14.552581"
+ fx="34.841751"
+ fy="14.552581"
+ r="7.1399999"
+ gradientTransform="matrix(1,0,0,1.1003056,0,-1.1335797)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ xlink:href="#linearGradient4603"
+ id="radialGradient4605"
+ cx="58.062626"
+ cy="12.761739"
+ fx="58.062626"
+ fy="12.761739"
+ r="7.6799994"
+ gradientTransform="matrix(1,0,0,0.99218759,0,0.09141507)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4603">
+ <stop
+ style="stop-color:#e72b1d;stop-opacity:1"
+ offset="0"
+ id="stop4599" />
+ <stop
+ style="stop-color:#cc4338;stop-opacity:1"
+ offset="1"
+ id="stop4601" />
+ </linearGradient>
+ <radialGradient
+ xlink:href="#linearGradient4523-3"
+ id="radialGradient4525"
+ cx="79.305222"
+ cy="13.939252"
+ fx="79.305222"
+ fy="13.939252"
+ r="7.8000002"
+ gradientTransform="matrix(1,0,0,1.0769231,0,-0.86932835)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4523-3">
+ <stop
+ style="stop-color:#4fb55d;stop-opacity:1"
+ offset="0"
+ id="stop4519" />
+ <stop
+ style="stop-color:#2d8539;stop-opacity:1"
+ offset="1"
+ id="stop4521" />
+ </linearGradient>
+ <radialGradient
+ xlink:href="#linearGradient4527"
+ id="radialGradient4529"
+ cx="103.23091"
+ cy="12.664675"
+ fx="103.23091"
+ fy="12.664675"
+ r="9.5995998"
+ gradientTransform="matrix(1,0,0,0.87507716,0,1.3868386)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4527">
+ <stop
+ style="stop-color:#3f6bbd;stop-opacity:1"
+ offset="0"
+ id="stop4523" />
+ <stop
+ style="stop-color:#29467b;stop-opacity:1"
+ offset="1"
+ id="stop4525" />
+ </linearGradient>
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4709"
+ cx="125.30523"
+ cy="16.659737"
+ fx="125.30523"
+ fy="16.659737"
+ r="8.3726959"
+ gradientTransform="matrix(1,0,0,1.0032611,0,-0.03620244)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4707">
+ <stop
+ style="stop-color:#8c9ba5;stop-opacity:1"
+ offset="0"
+ id="stop4703" />
+ <stop
+ style="stop-color:#607480;stop-opacity:1"
+ offset="1"
+ id="stop4705" />
+ </linearGradient>
+ <radialGradient
+ xlink:href="#linearGradient4727"
+ id="radialGradient4729"
+ cx="149.26262"
+ cy="12.784631"
+ fx="149.26262"
+ fy="12.784631"
+ r="8.6400051"
+ gradientTransform="matrix(1,0,0,0.993055,0,0.07848724)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4727">
+ <stop
+ style="stop-color:#3eb796;stop-opacity:1"
+ offset="0"
+ id="stop4723" />
+ <stop
+ style="stop-color:#31a886;stop-opacity:1"
+ offset="1"
+ id="stop4725" />
+ </linearGradient>
+ <radialGradient
+ xlink:href="#linearGradient5023"
+ id="radialGradient5017"
+ cx="466.94476"
+ cy="12.037849"
+ fx="466.94476"
+ fy="12.037849"
+ r="9.6007004"
+ gradientTransform="matrix(0.79035186,0,0,0.79508811,-0.14216924,6.9389816e-4)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient5023">
+ <stop
+ id="stop5019"
+ offset="0"
+ style="stop-color:#c6cdd2;stop-opacity:1" />
+ <stop
+ id="stop5021"
+ offset="1"
+ style="stop-color:#9cabb4;stop-opacity:1" />
+ </linearGradient>
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4793"
+ cx="194.44176"
+ cy="13.746766"
+ fx="194.44176"
+ fy="13.746766"
+ r="9.5999947"
+ gradientTransform="matrix(1,0,0,0.87500048,0,1.3876528)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4833"
+ cx="239.2"
+ cy="11.101265"
+ fx="239.2"
+ fy="11.101265"
+ r="9.6000004"
+ gradientTransform="matrix(1,0,0,0.87500002,0,1.3876579)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4841"
+ cx="242.0894"
+ cy="12.418613"
+ fx="242.0894"
+ fy="12.418613"
+ r="3.5288758"
+ gradientTransform="matrix(0.79274533,0,0,0.78327978,-0.14435628,0.11758726)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ xlink:href="#linearGradient5037"
+ id="radialGradient5031"
+ cx="466.39926"
+ cy="31.105829"
+ fx="466.39926"
+ fy="31.105829"
+ r="9.7507105"
+ gradientTransform="matrix(1,0,0,0.99992718,0,0.00247197)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient5037">
+ <stop
+ id="stop5033"
+ offset="0"
+ style="stop-color:#e8e1a1;stop-opacity:1" />
+ <stop
+ id="stop5035"
+ offset="1"
+ style="stop-color:#baad3e;stop-opacity:1" />
+ </linearGradient>
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4813"
+ cx="217.95329"
+ cy="16.56296"
+ fx="217.95329"
+ fy="16.56296"
+ r="10.35937"
+ gradientTransform="matrix(1,0,0,0.8160434,0,2.0506693)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4861"
+ cx="262.79288"
+ cy="15.840806"
+ fx="262.79288"
+ fy="15.840806"
+ r="8.5577164"
+ gradientTransform="matrix(1,0,0,0.9969072,0,0.03528241)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4881"
+ cx="286.58698"
+ cy="14.171478"
+ fx="286.58698"
+ fy="14.171478"
+ r="8.53125"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4901"
+ cx="308.97141"
+ cy="14.457072"
+ fx="308.97141"
+ fy="14.457072"
+ r="6.09375"
+ gradientTransform="matrix(1,0,0,1.4,0,-4.4901397)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4921"
+ cx="331.15933"
+ cy="13.119289"
+ fx="331.15933"
+ fy="13.119289"
+ r="8.53125"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4941"
+ cx="353.15076"
+ cy="11.316628"
+ fx="353.15076"
+ fy="11.316628"
+ r="6.09375"
+ gradientTransform="matrix(0.79035186,0,0,0.15902921,-0.14216924,7.1987363)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ gradientTransform="matrix(0.79035186,0,0,0.79514603,-0.14216924,3.8580698e-5)"
+ xlink:href="#linearGradient4707"
+ id="radialGradient4949"
+ cx="375.97003"
+ cy="11.407905"
+ fx="375.97003"
+ fy="11.407905"
+ r="6.09375"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4957"
+ cx="400.5007"
+ cy="13.518586"
+ fx="400.5007"
+ fy="13.518586"
+ r="8.5350475"
+ gradientTransform="matrix(1,0,0,0.99701325,0,0.03407254)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ xlink:href="#linearGradient4975"
+ id="radialGradient4977"
+ cx="417.02075"
+ cy="15.742972"
+ fx="417.02075"
+ fy="15.742972"
+ r="8.53125"
+ gradientTransform="matrix(1.357667,-0.02466618,0.02411975,1.3275908,-149.53429,5.1574131)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4975">
+ <stop
+ style="stop-color:#f79729;stop-opacity:1"
+ offset="0"
+ id="stop4971" />
+ <stop
+ style="stop-color:#d2831f;stop-opacity:1"
+ offset="1"
+ id="stop4973" />
+ </linearGradient>
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4997"
+ cx="444.33652"
+ cy="11.316628"
+ fx="444.33652"
+ fy="11.316628"
+ r="8.53125"
+ gradientTransform="matrix(1,0,0,0.71428563,0,3.2333231)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ xlink:href="#linearGradient4747"
+ id="radialGradient4710"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.265625,0,0,1.1109477,-0.05703897,1.4865748)"
+ cx="134.97461"
+ cy="9"
+ fx="134.97461"
+ fy="9"
+ r="7.9746099" />
+ <linearGradient
+ id="linearGradient4747">
+ <stop
+ style="stop-color:#c5b631;stop-opacity:1"
+ offset="0"
+ id="stop4743" />
+ <stop
+ style="stop-color:#baad3e;stop-opacity:1"
+ offset="1"
+ id="stop4745" />
+ </linearGradient>
+ <radialGradient
+ xlink:href="#linearGradient5037"
+ id="radialGradient4712"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.265625,0,0,1.1109477,-0.05703897,1.4865748)"
+ cx="132.6468"
+ cy="9.0947113"
+ fx="132.6468"
+ fy="9.0947113"
+ r="7.9746099" />
+ <radialGradient
+ xlink:href="#linearGradient4747"
+ id="radialGradient4714"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.265625,0,0,1.1109477,-0.05703897,1.4865748)"
+ cx="134.97461"
+ cy="9"
+ fx="134.97461"
+ fy="9"
+ r="7.9746099" />
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4750"
+ cx="166.37157"
+ cy="11.485105"
+ fx="166.37157"
+ fy="11.485105"
+ r="0.31640625"
+ gradientTransform="matrix(1,0,0,28.000001,0,-310.09784)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ xlink:href="#linearGradient4832"
+ id="radialGradient4709-1"
+ cx="125.30523"
+ cy="16.659737"
+ fx="125.30523"
+ fy="16.659737"
+ r="8.3726959"
+ gradientTransform="matrix(1,0,0,1.0032611,0.11563445,22.233158)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4832">
+ <stop
+ id="stop5029"
+ offset="0"
+ style="stop-color:#22e23d;stop-opacity:1" />
+ <stop
+ id="stop4830"
+ offset="1"
+ style="stop-color:#38a748;stop-opacity:1" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ id="filter4883">
+ <feFlood
+ flood-opacity="1"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4873" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4875" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4877" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset4879" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="fbSourceGraphic"
+ id="feComposite4881" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix6673" />
+ <feFlood
+ id="feFlood6675"
+ flood-opacity="1"
+ flood-color="rgb(255,255,255)"
+ result="flood"
+ in="fbSourceGraphic" />
+ <feComposite
+ in2="fbSourceGraphic"
+ id="feComposite6677"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6679"
+ in="composite1"
+ stdDeviation="1"
+ result="blur" />
+ <feOffset
+ id="feOffset6681"
+ dx="2.77556e-017"
+ dy="0"
+ result="offset" />
+ <feComposite
+ in2="offset"
+ id="feComposite6683"
+ in="fbSourceGraphic"
+ operator="over"
+ result="composite2" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ id="filter4895">
+ <feFlood
+ flood-opacity="1"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4885" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4887" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4889" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset4891" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="fbSourceGraphic"
+ id="feComposite4893" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix6685" />
+ <feFlood
+ id="feFlood6687"
+ flood-opacity="1"
+ flood-color="rgb(255,255,255)"
+ result="flood"
+ in="fbSourceGraphic" />
+ <feComposite
+ in2="fbSourceGraphic"
+ id="feComposite6689"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6691"
+ in="composite1"
+ stdDeviation="1"
+ result="blur" />
+ <feOffset
+ id="feOffset6693"
+ dx="2.77556e-017"
+ dy="0"
+ result="offset" />
+ <feComposite
+ in2="offset"
+ id="feComposite6695"
+ in="fbSourceGraphic"
+ operator="over"
+ result="composite2" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ id="filter4907">
+ <feFlood
+ flood-opacity="1"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4897" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4899" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4901" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset4903" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="fbSourceGraphic"
+ id="feComposite4905" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix6697" />
+ <feFlood
+ id="feFlood6699"
+ flood-opacity="1"
+ flood-color="rgb(255,255,255)"
+ result="flood"
+ in="fbSourceGraphic" />
+ <feComposite
+ in2="fbSourceGraphic"
+ id="feComposite6701"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6703"
+ in="composite1"
+ stdDeviation="1"
+ result="blur" />
+ <feOffset
+ id="feOffset6705"
+ dx="2.77556e-017"
+ dy="0"
+ result="offset" />
+ <feComposite
+ in2="offset"
+ id="feComposite6707"
+ in="fbSourceGraphic"
+ operator="over"
+ result="composite2" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ id="filter4919">
+ <feFlood
+ flood-opacity="1"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4909" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4911" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4913" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset4915" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="fbSourceGraphic"
+ id="feComposite4917" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix6709" />
+ <feFlood
+ id="feFlood6711"
+ flood-opacity="1"
+ flood-color="rgb(255,255,255)"
+ result="flood"
+ in="fbSourceGraphic" />
+ <feComposite
+ in2="fbSourceGraphic"
+ id="feComposite6713"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6715"
+ in="composite1"
+ stdDeviation="1"
+ result="blur" />
+ <feOffset
+ id="feOffset6717"
+ dx="2.77556e-017"
+ dy="0"
+ result="offset" />
+ <feComposite
+ in2="offset"
+ id="feComposite6719"
+ in="fbSourceGraphic"
+ operator="over"
+ result="composite2" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ id="filter4931">
+ <feFlood
+ flood-opacity="1"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4921" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4923" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4925" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset4927" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="fbSourceGraphic"
+ id="feComposite4929" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix6721" />
+ <feFlood
+ id="feFlood6723"
+ flood-opacity="1"
+ flood-color="rgb(255,255,255)"
+ result="flood"
+ in="fbSourceGraphic" />
+ <feComposite
+ in2="fbSourceGraphic"
+ id="feComposite6725"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6727"
+ in="composite1"
+ stdDeviation="1"
+ result="blur" />
+ <feOffset
+ id="feOffset6729"
+ dx="2.77556e-017"
+ dy="0"
+ result="offset" />
+ <feComposite
+ in2="offset"
+ id="feComposite6731"
+ in="fbSourceGraphic"
+ operator="over"
+ result="composite2" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ id="filter4943">
+ <feFlood
+ flood-opacity="1"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4933" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4935" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4937" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset4939" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="fbSourceGraphic"
+ id="feComposite4941" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix6733" />
+ <feFlood
+ id="feFlood6735"
+ flood-opacity="1"
+ flood-color="rgb(255,255,255)"
+ result="flood"
+ in="fbSourceGraphic" />
+ <feComposite
+ in2="fbSourceGraphic"
+ id="feComposite6737"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6739"
+ in="composite1"
+ stdDeviation="1"
+ result="blur" />
+ <feOffset
+ id="feOffset6741"
+ dx="2.77556e-017"
+ dy="0"
+ result="offset" />
+ <feComposite
+ in2="offset"
+ id="feComposite6743"
+ in="fbSourceGraphic"
+ operator="over"
+ result="composite2" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ id="filter4955">
+ <feFlood
+ flood-opacity="1"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4945" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4947" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4949" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset4951" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="fbSourceGraphic"
+ id="feComposite4953" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix6745" />
+ <feFlood
+ id="feFlood6747"
+ flood-opacity="1"
+ flood-color="rgb(255,255,255)"
+ result="flood"
+ in="fbSourceGraphic" />
+ <feComposite
+ in2="fbSourceGraphic"
+ id="feComposite6749"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6751"
+ in="composite1"
+ stdDeviation="1"
+ result="blur" />
+ <feOffset
+ id="feOffset6753"
+ dx="2.77556e-017"
+ dy="0"
+ result="offset" />
+ <feComposite
+ in2="offset"
+ id="feComposite6755"
+ in="fbSourceGraphic"
+ operator="over"
+ result="composite2" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ id="filter4967">
+ <feFlood
+ flood-opacity="1"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4957" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4959" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4961" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset4963" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="fbSourceGraphic"
+ id="feComposite4965" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix6757" />
+ <feFlood
+ id="feFlood6759"
+ flood-opacity="1"
+ flood-color="rgb(255,255,255)"
+ result="flood"
+ in="fbSourceGraphic" />
+ <feComposite
+ in2="fbSourceGraphic"
+ id="feComposite6761"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6763"
+ in="composite1"
+ stdDeviation="1"
+ result="blur" />
+ <feOffset
+ id="feOffset6765"
+ dx="2.77556e-017"
+ dy="0"
+ result="offset" />
+ <feComposite
+ in2="offset"
+ id="feComposite6767"
+ in="fbSourceGraphic"
+ operator="over"
+ result="composite2" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ id="filter4979">
+ <feFlood
+ flood-opacity="1"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4969" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4971" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4973" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset4975" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="fbSourceGraphic"
+ id="feComposite4977" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix6769" />
+ <feFlood
+ id="feFlood6771"
+ flood-opacity="1"
+ flood-color="rgb(255,255,255)"
+ result="flood"
+ in="fbSourceGraphic" />
+ <feComposite
+ in2="fbSourceGraphic"
+ id="feComposite6773"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6775"
+ in="composite1"
+ stdDeviation="1"
+ result="blur" />
+ <feOffset
+ id="feOffset6777"
+ dx="2.77556e-017"
+ dy="0"
+ result="offset" />
+ <feComposite
+ in2="offset"
+ id="feComposite6779"
+ in="fbSourceGraphic"
+ operator="over"
+ result="composite2" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ id="filter4991">
+ <feFlood
+ flood-opacity="1"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4981" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4983" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4985" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset4987" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="fbSourceGraphic"
+ id="feComposite4989" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix6781" />
+ <feFlood
+ id="feFlood6783"
+ flood-opacity="1"
+ flood-color="rgb(255,255,255)"
+ result="flood"
+ in="fbSourceGraphic" />
+ <feComposite
+ in2="fbSourceGraphic"
+ id="feComposite6785"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6787"
+ in="composite1"
+ stdDeviation="1"
+ result="blur" />
+ <feOffset
+ id="feOffset6789"
+ dx="2.77556e-017"
+ dy="0"
+ result="offset" />
+ <feComposite
+ in2="offset"
+ id="feComposite6791"
+ in="fbSourceGraphic"
+ operator="over"
+ result="composite2" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ id="filter5003">
+ <feFlood
+ flood-opacity="1"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4993" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4995" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4997" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset4999" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="fbSourceGraphic"
+ id="feComposite5001" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix6793" />
+ <feFlood
+ id="feFlood6795"
+ flood-opacity="1"
+ flood-color="rgb(255,255,255)"
+ result="flood"
+ in="fbSourceGraphic" />
+ <feComposite
+ in2="fbSourceGraphic"
+ id="feComposite6797"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6799"
+ in="composite1"
+ stdDeviation="1"
+ result="blur" />
+ <feOffset
+ id="feOffset6801"
+ dx="2.77556e-017"
+ dy="0"
+ result="offset" />
+ <feComposite
+ in2="offset"
+ id="feComposite6803"
+ in="fbSourceGraphic"
+ operator="over"
+ result="composite2" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ id="filter5015">
+ <feFlood
+ flood-opacity="1"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood5005" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite5007" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur5009" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset5011" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="fbSourceGraphic"
+ id="feComposite5013" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix6805" />
+ <feFlood
+ id="feFlood6807"
+ flood-opacity="1"
+ flood-color="rgb(255,255,255)"
+ result="flood"
+ in="fbSourceGraphic" />
+ <feComposite
+ in2="fbSourceGraphic"
+ id="feComposite6809"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6811"
+ in="composite1"
+ stdDeviation="1"
+ result="blur" />
+ <feOffset
+ id="feOffset6813"
+ dx="2.77556e-017"
+ dy="0"
+ result="offset" />
+ <feComposite
+ in2="offset"
+ id="feComposite6815"
+ in="fbSourceGraphic"
+ operator="over"
+ result="composite2" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ id="filter5027">
+ <feFlood
+ flood-opacity="1"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood5017" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite5019" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur5021" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset5023" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="fbSourceGraphic"
+ id="feComposite5025" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix6817" />
+ <feFlood
+ id="feFlood6819"
+ flood-opacity="1"
+ flood-color="rgb(255,255,255)"
+ result="flood"
+ in="fbSourceGraphic" />
+ <feComposite
+ in2="fbSourceGraphic"
+ id="feComposite6821"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6823"
+ in="composite1"
+ stdDeviation="1"
+ result="blur" />
+ <feOffset
+ id="feOffset6825"
+ dx="2.77556e-017"
+ dy="0"
+ result="offset" />
+ <feComposite
+ in2="offset"
+ id="feComposite6827"
+ in="fbSourceGraphic"
+ operator="over"
+ result="composite2" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ id="filter5039">
+ <feFlood
+ flood-opacity="1"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood5029" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite5031" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur5033" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset5035" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="fbSourceGraphic"
+ id="feComposite5037" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix6829" />
+ <feFlood
+ id="feFlood6831"
+ flood-opacity="1"
+ flood-color="rgb(255,255,255)"
+ result="flood"
+ in="fbSourceGraphic" />
+ <feComposite
+ in2="fbSourceGraphic"
+ id="feComposite6833"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6835"
+ in="composite1"
+ stdDeviation="1"
+ result="blur" />
+ <feOffset
+ id="feOffset6837"
+ dx="2.77556e-017"
+ dy="0"
+ result="offset" />
+ <feComposite
+ in2="offset"
+ id="feComposite6839"
+ in="fbSourceGraphic"
+ operator="over"
+ result="composite2" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ id="filter5051">
+ <feFlood
+ flood-opacity="1"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood5041" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite5043" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur5045" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset5047" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="fbSourceGraphic"
+ id="feComposite5049" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix6841" />
+ <feFlood
+ id="feFlood6843"
+ flood-opacity="1"
+ flood-color="rgb(255,255,255)"
+ result="flood"
+ in="fbSourceGraphic" />
+ <feComposite
+ in2="fbSourceGraphic"
+ id="feComposite6845"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6847"
+ in="composite1"
+ stdDeviation="1"
+ result="blur" />
+ <feOffset
+ id="feOffset6849"
+ dx="2.77556e-017"
+ dy="0"
+ result="offset" />
+ <feComposite
+ in2="offset"
+ id="feComposite6851"
+ in="fbSourceGraphic"
+ operator="over"
+ result="composite2" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ id="filter5063">
+ <feFlood
+ flood-opacity="1"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood5053" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite5055" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur5057" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset5059" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="fbSourceGraphic"
+ id="feComposite5061" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix6853" />
+ <feFlood
+ id="feFlood6855"
+ flood-opacity="1"
+ flood-color="rgb(255,255,255)"
+ result="flood"
+ in="fbSourceGraphic" />
+ <feComposite
+ in2="fbSourceGraphic"
+ id="feComposite6857"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6859"
+ in="composite1"
+ stdDeviation="1"
+ result="blur" />
+ <feOffset
+ id="feOffset6861"
+ dx="2.77556e-017"
+ dy="0"
+ result="offset" />
+ <feComposite
+ in2="offset"
+ id="feComposite6863"
+ in="fbSourceGraphic"
+ operator="over"
+ result="composite2" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ id="filter5075">
+ <feFlood
+ flood-opacity="1"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood5065" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite5067" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur5069" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset5071" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="fbSourceGraphic"
+ id="feComposite5073" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix6865" />
+ <feFlood
+ id="feFlood6867"
+ flood-opacity="1"
+ flood-color="rgb(255,255,255)"
+ result="flood"
+ in="fbSourceGraphic" />
+ <feComposite
+ in2="fbSourceGraphic"
+ id="feComposite6869"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6871"
+ in="composite1"
+ stdDeviation="1"
+ result="blur" />
+ <feOffset
+ id="feOffset6873"
+ dx="2.77556e-017"
+ dy="0"
+ result="offset" />
+ <feComposite
+ in2="offset"
+ id="feComposite6875"
+ in="fbSourceGraphic"
+ operator="over"
+ result="composite2" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ id="filter5087">
+ <feFlood
+ flood-opacity="1"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood5077" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite5079" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur5081" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset5083" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="fbSourceGraphic"
+ id="feComposite5085" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix6877" />
+ <feFlood
+ id="feFlood6879"
+ flood-opacity="1"
+ flood-color="rgb(255,255,255)"
+ result="flood"
+ in="fbSourceGraphic" />
+ <feComposite
+ in2="fbSourceGraphic"
+ id="feComposite6881"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6883"
+ in="composite1"
+ stdDeviation="1"
+ result="blur" />
+ <feOffset
+ id="feOffset6885"
+ dx="2.77556e-017"
+ dy="0"
+ result="offset" />
+ <feComposite
+ in2="offset"
+ id="feComposite6887"
+ in="fbSourceGraphic"
+ operator="over"
+ result="composite2" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ id="filter5099">
+ <feFlood
+ flood-opacity="1"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood5089" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite5091" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur5093" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset5095" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="fbSourceGraphic"
+ id="feComposite5097" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix6889" />
+ <feFlood
+ id="feFlood6891"
+ flood-opacity="1"
+ flood-color="rgb(255,255,255)"
+ result="flood"
+ in="fbSourceGraphic" />
+ <feComposite
+ in2="fbSourceGraphic"
+ id="feComposite6893"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6895"
+ in="composite1"
+ stdDeviation="1"
+ result="blur" />
+ <feOffset
+ id="feOffset6897"
+ dx="2.77556e-017"
+ dy="0"
+ result="offset" />
+ <feComposite
+ in2="offset"
+ id="feComposite6899"
+ in="fbSourceGraphic"
+ operator="over"
+ result="composite2" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ id="filter5111">
+ <feFlood
+ flood-opacity="1"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood5101" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite5103" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur5105" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset5107" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="fbSourceGraphic"
+ id="feComposite5109" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix6901" />
+ <feFlood
+ id="feFlood6903"
+ flood-opacity="1"
+ flood-color="rgb(255,255,255)"
+ result="flood"
+ in="fbSourceGraphic" />
+ <feComposite
+ in2="fbSourceGraphic"
+ id="feComposite6905"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6907"
+ in="composite1"
+ stdDeviation="1"
+ result="blur" />
+ <feOffset
+ id="feOffset6909"
+ dx="2.77556e-017"
+ dy="0"
+ result="offset" />
+ <feComposite
+ in2="offset"
+ id="feComposite6911"
+ in="fbSourceGraphic"
+ operator="over"
+ result="composite2" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ id="filter5123">
+ <feFlood
+ flood-opacity="1"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood5113" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite5115" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur5117" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset5119" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="fbSourceGraphic"
+ id="feComposite5121" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix6913" />
+ <feFlood
+ id="feFlood6915"
+ flood-opacity="1"
+ flood-color="rgb(255,255,255)"
+ result="flood"
+ in="fbSourceGraphic" />
+ <feComposite
+ in2="fbSourceGraphic"
+ id="feComposite6917"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6919"
+ in="composite1"
+ stdDeviation="1"
+ result="blur" />
+ <feOffset
+ id="feOffset6921"
+ dx="2.77556e-017"
+ dy="0"
+ result="offset" />
+ <feComposite
+ in2="offset"
+ id="feComposite6923"
+ in="fbSourceGraphic"
+ operator="over"
+ result="composite2" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ id="filter5135">
+ <feFlood
+ flood-opacity="1"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood5125" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite5127" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur5129" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset5131" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="fbSourceGraphic"
+ id="feComposite5133" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix6925" />
+ <feFlood
+ id="feFlood6927"
+ flood-opacity="1"
+ flood-color="rgb(255,255,255)"
+ result="flood"
+ in="fbSourceGraphic" />
+ <feComposite
+ in2="fbSourceGraphic"
+ id="feComposite6929"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6931"
+ in="composite1"
+ stdDeviation="1"
+ result="blur" />
+ <feOffset
+ id="feOffset6933"
+ dx="2.77556e-017"
+ dy="0"
+ result="offset" />
+ <feComposite
+ in2="offset"
+ id="feComposite6935"
+ in="fbSourceGraphic"
+ operator="over"
+ result="composite2" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ id="filter5147">
+ <feFlood
+ flood-opacity="1"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood5137" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite5139" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur5141" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset5143" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="fbSourceGraphic"
+ id="feComposite5145" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix6937" />
+ <feFlood
+ id="feFlood6939"
+ flood-opacity="1"
+ flood-color="rgb(255,255,255)"
+ result="flood"
+ in="fbSourceGraphic" />
+ <feComposite
+ in2="fbSourceGraphic"
+ id="feComposite6941"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6943"
+ in="composite1"
+ stdDeviation="1"
+ result="blur" />
+ <feOffset
+ id="feOffset6945"
+ dx="2.77556e-017"
+ dy="0"
+ result="offset" />
+ <feComposite
+ in2="offset"
+ id="feComposite6947"
+ in="fbSourceGraphic"
+ operator="over"
+ result="composite2" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ id="filter5159">
+ <feFlood
+ flood-opacity="1"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood5149" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite5151" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur5153" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset5155" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="fbSourceGraphic"
+ id="feComposite5157" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix6949" />
+ <feFlood
+ id="feFlood6951"
+ flood-opacity="1"
+ flood-color="rgb(255,255,255)"
+ result="flood"
+ in="fbSourceGraphic" />
+ <feComposite
+ in2="fbSourceGraphic"
+ id="feComposite6953"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6955"
+ in="composite1"
+ stdDeviation="1"
+ result="blur" />
+ <feOffset
+ id="feOffset6957"
+ dx="2.77556e-017"
+ dy="0"
+ result="offset" />
+ <feComposite
+ in2="offset"
+ id="feComposite6959"
+ in="fbSourceGraphic"
+ operator="over"
+ result="composite2" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ id="filter5171">
+ <feFlood
+ flood-opacity="1"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood5161" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite5163" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur5165" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset5167" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="fbSourceGraphic"
+ id="feComposite5169" />
+ <feColorMatrix
+ result="fbSourceGraphicAlpha"
+ in="fbSourceGraphic"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ id="feColorMatrix6961" />
+ <feFlood
+ id="feFlood6963"
+ flood-opacity="1"
+ flood-color="rgb(255,255,255)"
+ result="flood"
+ in="fbSourceGraphic" />
+ <feComposite
+ in2="fbSourceGraphic"
+ id="feComposite6965"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6967"
+ in="composite1"
+ stdDeviation="1"
+ result="blur" />
+ <feOffset
+ id="feOffset6969"
+ dx="2.77556e-017"
+ dy="0"
+ result="offset" />
+ <feComposite
+ in2="offset"
+ id="feComposite6971"
+ in="fbSourceGraphic"
+ operator="over"
+ result="composite2" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4635"
+ id="radialGradient4669-9"
+ cx="10.529827"
+ cy="14.778796"
+ fx="10.529827"
+ fy="14.778796"
+ r="7.1399999"
+ gradientTransform="matrix(1,0,0,1.1087088,0,-1.2351844)"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4701">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4691" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4693" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="1"
+ result="blur"
+ id="feGaussianBlur4695" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4697" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4699" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4635"
+ id="radialGradient4637-5"
+ cx="11.063469"
+ cy="38.79744"
+ fx="11.063469"
+ fy="38.79744"
+ r="8.7600002"
+ gradientTransform="matrix(1,0,0,1.0853313,0,-3.029369)"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4661">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4651" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4653" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="1"
+ result="blur"
+ id="feGaussianBlur4655" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4657" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4659" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4635"
+ id="radialGradient4677-0"
+ cx="34.841751"
+ cy="14.552581"
+ fx="34.841751"
+ fy="14.552581"
+ r="7.1399999"
+ gradientTransform="matrix(1,0,0,1.1003056,0,-1.1335797)"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4689">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4679" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4681" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="1"
+ result="blur"
+ id="feGaussianBlur4683" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4685" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4687" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4603"
+ id="radialGradient4605-8"
+ cx="58.062626"
+ cy="12.761739"
+ fx="58.062626"
+ fy="12.761739"
+ r="7.6799994"
+ gradientTransform="matrix(1,0,0,0.99218759,0,0.09141507)"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4629">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4619" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4621" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="1"
+ result="blur"
+ id="feGaussianBlur4623" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4625" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4627" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4523-3"
+ id="radialGradient4525-6"
+ cx="79.305222"
+ cy="13.939252"
+ fx="79.305222"
+ fy="13.939252"
+ r="7.8000002"
+ gradientTransform="matrix(1,0,0,1.0769231,0,-0.86932835)"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4597">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4587" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4589" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="1"
+ result="blur"
+ id="feGaussianBlur4591" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4593" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4595" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4527"
+ id="radialGradient4529-9"
+ cx="103.23091"
+ cy="12.664675"
+ fx="103.23091"
+ fy="12.664675"
+ r="9.5995998"
+ gradientTransform="matrix(1,0,0,0.87507716,0,1.3868386)"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4783">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4773" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4775" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="1"
+ result="blur"
+ id="feGaussianBlur4777" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4779" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4781" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4709-11"
+ cx="125.30523"
+ cy="16.659737"
+ fx="125.30523"
+ fy="16.659737"
+ r="8.3726959"
+ gradientTransform="matrix(1,0,0,1.0032611,0,-0.03620244)"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4721">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4711" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4713" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="1"
+ result="blur"
+ id="feGaussianBlur4715" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4717" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4719" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4727"
+ id="radialGradient4729-5"
+ cx="149.26262"
+ cy="12.784631"
+ fx="149.26262"
+ fy="12.784631"
+ r="8.6400051"
+ gradientTransform="matrix(1,0,0,0.993055,0,0.07848724)"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4741">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4731" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4733" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="1"
+ result="blur"
+ id="feGaussianBlur4735" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4737" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4739" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient5023"
+ id="radialGradient5017-9"
+ cx="466.94476"
+ cy="12.037849"
+ fx="466.94476"
+ fy="12.037849"
+ r="9.6007004"
+ gradientTransform="matrix(0.79035186,0,0,0.79508811,-0.14216924,6.9389816e-4)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4793-8"
+ cx="194.44176"
+ cy="13.746766"
+ fx="194.44176"
+ fy="13.746766"
+ r="9.5999947"
+ gradientTransform="matrix(1,0,0,0.87500048,0,1.3876528)"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4805">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4795" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4797" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4799" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4801" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4803" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4833-4"
+ cx="239.2"
+ cy="11.101265"
+ fx="239.2"
+ fy="11.101265"
+ r="9.6000004"
+ gradientTransform="matrix(1,0,0,0.87500002,0,1.3876579)"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4853">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4843" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4845" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4847" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4849" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4851" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4841-0"
+ cx="242.26164"
+ cy="12.423289"
+ fx="242.26164"
+ fy="12.423289"
+ r="3.5288758"
+ gradientTransform="matrix(0.79274531,0,0,0.78327977,-0.14435628,0.11758726)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient8829"
+ cx="242.26164"
+ cy="12.423289"
+ fx="242.26164"
+ fy="12.423289"
+ r="3.5288758"
+ gradientTransform="matrix(0.79274531,0,0,0.78327977,-0.14435628,0.11758726)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ xlink:href="#linearGradient5037"
+ id="radialGradient5031-1"
+ cx="466.39926"
+ cy="31.105829"
+ fx="466.39926"
+ fy="31.105829"
+ r="9.7507105"
+ gradientTransform="matrix(1,0,0,0.99992718,0,0.00247197)"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter5049">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood5039" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite5041" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur5043" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset5045" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite5047" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4813-2"
+ cx="217.95329"
+ cy="16.56296"
+ fx="217.95329"
+ fy="16.56296"
+ r="10.35937"
+ gradientTransform="matrix(1,0,0,0.8160434,0,2.0506693)"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4825">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4815" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4817" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4819" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4821" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4823" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4861-6"
+ cx="262.79288"
+ cy="15.840806"
+ fx="262.79288"
+ fy="15.840806"
+ r="8.5577164"
+ gradientTransform="matrix(1,0,0,0.9969072,0,0.03528241)"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4873">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4863" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4865" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4867" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4869" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4871" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4881-9"
+ cx="286.58698"
+ cy="14.171478"
+ fx="286.58698"
+ fy="14.171478"
+ r="8.53125"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4893">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4883" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4885" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4887" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4889" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4891" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4901-6"
+ cx="308.97141"
+ cy="14.457072"
+ fx="308.97141"
+ fy="14.457072"
+ r="6.09375"
+ gradientTransform="matrix(1,0,0,1.4,0,-4.4901397)"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4913">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4903" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4905-1" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4907" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4909" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4911-2" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4921-8"
+ cx="331.15933"
+ cy="13.119289"
+ fx="331.15933"
+ fy="13.119289"
+ r="8.53125"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4933">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4923" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4925" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4927" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4929" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4931" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4941-9"
+ cx="353.15076"
+ cy="11.316628"
+ fx="353.15076"
+ fy="11.316628"
+ r="6.09375"
+ gradientTransform="matrix(0.79035186,0,0,0.15902921,-0.14216924,7.1987363)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ gradientTransform="matrix(0.79035186,0,0,0.79514603,-0.14216924,3.8580698e-5)"
+ xlink:href="#linearGradient4707"
+ id="radialGradient4949-5"
+ cx="375.97003"
+ cy="11.407905"
+ fx="375.97003"
+ fy="11.407905"
+ r="6.09375"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4957-2"
+ cx="400.5007"
+ cy="13.518586"
+ fx="400.5007"
+ fy="13.518586"
+ r="8.5350475"
+ gradientTransform="matrix(1,0,0,0.99701325,0,0.03407254)"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4969">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4959" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4961" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4963" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4965" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4967" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4975"
+ id="radialGradient4977-4"
+ cx="417.02075"
+ cy="15.742972"
+ fx="417.02075"
+ fy="15.742972"
+ r="8.53125"
+ gradientTransform="matrix(1.357667,-0.02466618,0.02411975,1.3275908,-149.53429,5.1574131)"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4989">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4979" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4981" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4983" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4985" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4987" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4997-5"
+ cx="444.33652"
+ cy="11.316628"
+ fx="444.33652"
+ fy="11.316628"
+ r="8.53125"
+ gradientTransform="matrix(1,0,0,0.71428563,0,3.2333231)"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter5009">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4999" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite5001-8" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur5003" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset5005" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite5007-3" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4747"
+ id="radialGradient4710-4"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.265625,0,0,1.1109477,-0.05703897,1.4865748)"
+ cx="134.97461"
+ cy="9"
+ fx="134.97461"
+ fy="9"
+ r="7.9746099" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4729">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4719" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4721" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="1"
+ result="blur"
+ id="feGaussianBlur4723" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset4725" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4727" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient5037"
+ id="radialGradient4712-2"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.265625,0,0,1.1109477,-0.05703897,1.4865748)"
+ cx="132.6468"
+ cy="9.0947113"
+ fx="132.6468"
+ fy="9.0947113"
+ r="7.9746099" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4774">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4764" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4766" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4768" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset4770" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4772" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4747"
+ id="radialGradient4714-4"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.265625,0,0,1.1109477,-0.05703897,1.4865748)"
+ cx="134.97461"
+ cy="9"
+ fx="134.97461"
+ fy="9"
+ r="7.9746099" />
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4750-3"
+ cx="166.37157"
+ cy="11.485105"
+ fx="166.37157"
+ fy="11.485105"
+ r="0.31640625"
+ gradientTransform="matrix(0.99998863,-0.00473886,0.08838422,18.426509,-1.0132111,-199.35688)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ xlink:href="#linearGradient4832"
+ id="radialGradient4709-1-2"
+ cx="125.30523"
+ cy="16.659737"
+ fx="125.30523"
+ fy="16.659737"
+ r="8.3726959"
+ gradientTransform="matrix(1,0,0,1.0032611,0.11563445,22.233158)"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4844">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4834" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4836" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4838" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset4840" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4842" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient9039"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.79274531,0,0,0.78327977,-0.14435628,0.11758726)"
+ cx="242.26164"
+ cy="12.423289"
+ fx="242.26164"
+ fy="12.423289"
+ r="3.5288758" />
+ <filter
+ id="filter10217"
+ style="color-interpolation-filters:sRGB;">
+ <feFlood
+ id="feFlood10207"
+ result="flood"
+ flood-color="rgb(0,0,0)"
+ flood-opacity="1" />
+ <feComposite
+ id="feComposite10209"
+ result="composite1"
+ operator="out"
+ in2="SourceGraphic"
+ in="flood" />
+ <feGaussianBlur
+ id="feGaussianBlur10211"
+ result="blur"
+ stdDeviation="0.5"
+ in="composite1" />
+ <feOffset
+ id="feOffset10213"
+ result="offset"
+ dy="0"
+ dx="2.77556e-017" />
+ <feComposite
+ id="feComposite10215"
+ result="fbSourceGraphic"
+ operator="atop"
+ in2="SourceGraphic"
+ in="offset" />
+ <feColorMatrix
+ id="feColorMatrix10219"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ in="fbSourceGraphic"
+ result="fbSourceGraphicAlpha" />
+ <feFlood
+ in="fbSourceGraphic"
+ result="flood"
+ flood-color="rgb(255,255,255)"
+ flood-opacity="1"
+ id="feFlood10221" />
+ <feComposite
+ result="composite1"
+ operator="in"
+ in="flood"
+ id="feComposite10223"
+ in2="fbSourceGraphic" />
+ <feGaussianBlur
+ result="blur"
+ stdDeviation="1"
+ in="composite1"
+ id="feGaussianBlur10225" />
+ <feOffset
+ result="offset"
+ dy="0"
+ dx="2.77556e-017"
+ id="feOffset10227" />
+ <feComposite
+ result="composite2"
+ operator="over"
+ in="fbSourceGraphic"
+ id="feComposite10229"
+ in2="offset" />
+ </filter>
+ </defs>
+ <g
+ style="filter:url(#filter10217)"
+ id="g7757">
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ style="display:inline;fill:url(#radialGradient4669-9);fill-opacity:1;stroke-width:1;filter:url(#filter4701)"
+ d="m 17.870749,13.841269 -6.303534,-0.0074 2.264363,2.148431 c 0.615648,0.584128 0.72,1.44 0.24,1.92 l -0.96,1.08 c -0.48,0.48 -1.32,0.36 -1.92,-0.24 0,0 -6.4200001,-6.6 -6.4800001,-6.6 -0.06,0 -0.36,-0.48 -0.48,-0.84 0,-0.359999 0.36,-0.719999 0.48,-0.839999 l 6.3600001,-6.48 c 0.6,-0.6000001 1.44,-0.7200001 1.92,-0.24 l 0.96,1.0799999 c 0.48,0.48 0.36,1.32 -0.24,1.9200001 l -2.144363,2.0610645 6.359451,0.043374 c 0.719983,0.00491 1.227959,0.50779 1.227959,1.2277905 v 2.483369 c 0,0.72 -0.563877,1.284215 -1.283876,1.28337 z"
+ id="path4" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ style="display:inline;fill:url(#radialGradient4637-5);fill-opacity:1;stroke-width:1;filter:url(#filter4661)"
+ id="path4154"
+ d="m 19.187889,37.765372 -7.620674,-0.0365 3.352509,3.266174 c 0.515294,0.50108 0.829738,1.388534 0.443269,1.764345 l -1.759464,1.992905 c -0.356609,0.403923 -1.52102,-0.108922 -2.036314,-0.735274 L 3.9756591,36.471238 c 0,0 -0.7399492,-0.710192 -0.7399492,-1.211274 0,-0.501081 0.7399492,-1.303987 0.7399492,-1.303987 l 7.5915559,-7.545783 c 0.515294,-0.50108 1.613776,-1.093109 1.980397,-0.698138 l 1.815381,1.955768 c 0.386469,0.375811 0.172889,1.300401 -0.471228,1.801482 l -3.32455,3.229041 7.620674,0.02842 c 0.772936,0.0029 1.344153,0.417712 1.344153,1.169335 v 2.560987 c 0,0.751622 -0.571221,1.311987 -1.344153,1.308283 z" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ style="display:inline;fill:url(#radialGradient4677-0);fill-opacity:1;stroke-width:1;filter:url(#filter4689)"
+ id="path4165"
+ d="m 26.776124,12.612425 v -2.53895 c 0,-0.7200003 0.480206,-1.3282747 1.2,-1.3111602 l 5.76,0.041051 L 31.66,6.7412646 c -0.602042,-0.5979755 -0.72,-1.4399999 -0.24,-1.92 l 0.96,-1.08 c 0.48,-0.48 1.32,-0.36 1.92,0.24 l 6.36,6.4800004 c 0.12,0.12 0.48,0.48 0.48,0.84 0,0.36 -0.36,0.84 -0.48,0.84 l -6.48,6.48 c -0.6,0.6 -1.44,0.72 -1.92,0.24 l -0.96,-1.08 c -0.48,-0.48 -0.36,-1.32 0.24,-1.92 L 33.736124,13.833888 28.06,13.868005 c -0.719987,0.0043 -1.283876,-0.53558 -1.283876,-1.25558 z" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ style="display:inline;fill:url(#radialGradient4605-8);fill-opacity:1;stroke-width:1;filter:url(#filter4629)"
+ id="path4176"
+ d="m 64.708108,6.2881044 -5.061037,5.0305226 5.061037,5.030522 -2.530518,2.515261 -5.061038,-5.030522 -5.061037,5.030522 -2.530519,-2.515261 5.061037,-5.030522 -5.061037,-5.0305226 2.530519,-2.5152612 5.061037,5.0305223 5.061038,-5.0305223 z" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ style="display:inline;fill:url(#radialGradient4525-6);fill-opacity:1;stroke-width:1;filter:url(#filter4597)"
+ id="path4187"
+ d="m 87.482777,11.318627 h -8.856816 l 3.13833,-3.0716727 C 81.15648,7.545735 80.363876,6.712205 79.283876,6.712205 c -2.64,0 -5.107543,1.9376803 -5.107543,4.577681 0,2.64 2.411627,5.050109 5.051627,5.050109 1.68,0 3.619039,-1.066107 4.459039,-2.506107 l 2.530518,1.25763 c -1.32,2.4 -3.961599,4.443007 -6.961599,4.443007 -4.32,0 -8.219378,-3.896849 -8.219378,-8.216849 0,-4.3200008 3.927337,-7.633261 8.247337,-7.633261 1.8,0 3.619792,0.8778997 5.059792,1.9578998 l 3.139108,-3.1271021 z" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ style="display:inline;fill:url(#radialGradient4529-9);fill-opacity:1;stroke:none;stroke-width:1;stroke-opacity:1;filter:url(#filter4783)"
+ d="M 102.66589,2.5152127 92.543814,11.318627 h 3.795778 v 7.545783 h 5.061038 v -5.030522 h 2.53052 v 5.030522 h 5.06104 v -7.545783 h 3.79577 z"
+ id="path4209" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ style="display:inline;fill:url(#radialGradient4709-11);fill-opacity:1;stroke-width:1;filter:url(#filter4721)"
+ id="path4214"
+ d="m 133.03211,12.576257 -6.32629,6.288153 c -0.24,0.36 -0.82401,0.692435 -1.30401,0.692435 -0.48,0 -0.86651,-0.332435 -1.22651,-0.692435 l -6.3263,-6.288153 c -0.79571,-0.72 -0.93921,-1.286268 0.0208,-1.280462 l 3.77501,0.02283 -0.0107,-7.5841022 c -0.001,-0.7199993 0.50796,-1.1722101 1.22796,-1.1722101 h 5.10754 c 0.72,0 1.22426,0.4800094 1.22796,1.2 l 0.0388,7.5563123 3.80118,-0.05062 c 0.95992,-0.01278 0.71459,0.588252 -0.005,1.308252 z" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ style="display:inline;fill:url(#radialGradient4729-5);fill-opacity:1;stroke-width:1;filter:url(#filter4741)"
+ id="path4225"
+ d="m 148.19571,20.103585 c -4.8,0 -8.89163,-3.97895 -8.89163,-8.77895 0,-4.6800005 4.11959,-8.7145303 8.91959,-8.7145303 4.8,0 8.80776,3.9511599 8.80776,8.7511603 0,4.68 -4.03572,8.74232 -8.83572,8.74232 z m 0.0559,-15.04801 c -3.36,0 -6.39142,2.9178996 -6.39142,6.2779 0,3.24 2.9755,6.22232 6.3355,6.22232 3.36,0 6.33551,-2.86232 6.33551,-6.22232 0,-3.3600004 -2.91959,-6.2779 -6.27959,-6.2779 z m -0.63959,7.520682 c -0.48,-0.12 -0.63716,-0.735044 -0.64429,-1.214992 l -0.0559,-3.7667402 c -0.0107,-0.7199217 0.53592,-1.28337 1.25592,-1.28337 0.72,0 1.27834,0.535601 1.28388,1.25558 l 0.0289,3.7518922 c 1.32,1.32 2.53051,3.772891 2.53051,3.772891 0,0 -3.07896,-1.195261 -4.39896,-2.515261 z" />
+ <path
+ style="display:inline;fill:url(#radialGradient5017-9);fill-opacity:1;stroke-width:0.79274529"
+ id="path4355"
+ d="m 369.00476,4.7878231 0.94842,1.9083504 0.47422,0.858758 0.94842,0.190835 2.18136,0.3816699 -1.61231,1.7175156 -0.6639,0.667923 0.0948,0.954175 0.37937,2.290022 -1.89685,-0.954178 -0.85358,-0.477086 -0.85358,0.477086 -1.89685,0.954178 0.37938,-2.290022 0.0948,-0.954175 -0.6639,-0.667923 -1.61232,-1.7175156 2.27622,-0.3816699 0.94842,-0.190835 0.37936,-0.858758 0.94843,-1.9083504 m 0,-3.4350309 c -0.28454,0 -0.56906,0.1908348 -0.75874,0.667922 l -1.89683,3.9121193 -4.07821,0.6679227 c -0.94842,0.190835 -1.13812,0.8587576 -0.47421,1.5266802 l 2.94011,3.1487776 -0.66391,4.389208 c -0.0948,0.572504 0.1897,0.954174 0.66391,0.954174 0.18967,0 0.37936,-0.09542 0.56905,-0.190835 l 3.69885,-2.003768 3.69884,2.003768 c 0.18969,0.09543 0.47421,0.190835 0.56906,0.190835 0.4742,0 0.75873,-0.38167 0.6639,-1.049592 l -0.6639,-4.389207 2.9401,-3.1487781 c 0.66391,-0.6679226 0.37938,-1.3358454 -0.4742,-1.5266803 L 371.66031,5.837416 369.76347,1.9252968 C 369.5738,1.543627 369.28926,1.3527922 369.00474,1.3527922 Z" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ style="display:inline;fill:url(#radialGradient4793-8);fill-opacity:1;stroke-width:1;filter:url(#filter4805)"
+ id="path4366"
+ d="m 202.62125,17.648005 -2.53039,-0.04122 1.26526,2.515261 h -15.18311 l 1.26526,-2.515261 -2.51784,0.02249 c -0.71997,0.0064 -1.31817,-0.563397 -1.31184,-1.28337 l 0.0559,-6.3612693 c 0.006,-0.7199727 0.59192,-1.2112687 1.31183,-1.2000001 l 1.19667,0.018731 0.0313,-2.5298907 c 0.009,-0.7199457 0.51397,-1.243001 1.23397,-1.243001 l 0.022,-1.2403691 c 0.0127,-0.7198886 0.6198,-1.2535885 1.33979,-1.2555799 l 10.04733,-0.02779 c 0.72,-0.00199 1.21931,0.5078413 1.22796,1.22779 l 0.0156,1.295949 c 0.72,0 1.2692,0.5785813 1.26831,1.2985809 l -0.003,2.4743108 1.26513,-1e-7 c 0.71992,-0.010711 1.26205,0.5376375 1.26526,1.2576305 v 6.288153 c 0,0.729621 -0.61509,1.257631 -1.26526,1.257631 z m -15.18298,-0.04122 1.26526,-2.515262 h -1.26526 z m 0.6,-6.288153 h -0.6 -0.6 c -0.36,0 -0.6,0.24 -0.6,0.6 0,0.36 0.24,0.6 0.6,0.6 h 1.2 c 0.36,0 0.6,-0.24 0.6,-0.6 0,-0.36 -0.25348,-0.503016 -0.6,-0.6 z M 198.8256,5.0304738 c 0,-0.72 -0.54528,-1.2523398 -1.26526,-1.2576306 h -7.59155 c -0.71998,-0.00529 -1.28117,0.5378043 -1.26526,1.2576306 v 3.7728917 c 0.0159,0.7198264 0.54528,1.2629215 1.26526,1.2576305 h 7.59155 c 0.71999,-0.0053 1.24942,-0.5078188 1.25592,-1.2277899 z m -1.26526,10.0610482 h -7.59155 l -1.26526,3.772892 h 10.12207 z m 2.53052,0 h -1.26526 l 1.26526,2.515262 z" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ style="display:inline;fill:url(#radialGradient4833-4);fill-opacity:1;stroke-width:1;filter:url(#filter4853)"
+ id="path4388"
+ d="m 248.17426,18.945466 -16.43876,-0.02779 c -0.96,-0.0016 -1.40468,-0.339789 -1.40858,-1.299781 l -0.0559,-13.7557998 c -0.004,-0.959992 0.47654,-1.3847678 1.43653,-1.3831504 l 16.49468,0.02779 c 0.96,0.00162 1.24473,0.3675784 1.24083,1.3275705 l -0.0559,13.7557997 c -0.004,0.959992 -0.25287,1.356983 -1.21287,1.355361 z M 239.88388,3.8178947 c -0.36,0 -0.6,0.24 -0.6,0.6 0,0.36 0.24,0.6 0.6,0.6 0.36,0 0.6,-0.24 0.6,-0.6 0,-0.36 -0.24,-0.6 -0.6,-0.6 z m 2.64346,0 c -0.36,0 -0.6,0.24 -0.6,0.6 0,0.36 0.36,0.6 0.6,0.6 0.36,0 0.6,-0.24 0.6,-0.6 0,-0.36 -0.24,-0.6 -0.6,-0.6 z m 3.72,0 h -1.2 c -0.36,0 -0.6,0.24 -0.6,0.6 0,0.36 0.24,0.6 0.6,0.6 h 1.2 c 0.36,0 0.6,-0.24 0.6,-0.6 0,-0.36 -0.24,-0.6 -0.6,-0.6 z m 0.65592,4.9945298 c 0.005,-0.7199803 -0.53592,-1.2538267 -1.25592,-1.2555798 l -11.41287,-0.02779 c -0.72,-0.00175 -1.22533,0.5077946 -1.22796,1.2277899 l -0.028,7.6724304 c -0.003,0.719994 0.50796,1.175707 1.22795,1.172209 l 11.44083,-0.05558 c 0.71999,-0.0035 1.19464,-0.507809 1.2,-1.227789 z" />
+ <g
+ aria-label="+"
+ transform="scale(0.98484984,1.0153832)"
+ style="font-style:normal;font-weight:normal;font-size:9.51294327px;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;display:inline;fill:url(#radialGradient4841-0);fill-opacity:1;stroke:none;stroke-width:0.79274535px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="text4409">
+ <path
+ d="m 194.95358,9.8484986 h -2.03077 v 1.9696994 h -1.01538 V 9.8484986 h -2.03077 V 8.8636487 h 2.03077 V 6.893949 h 1.01538 v 1.9696997 h 2.03077 z"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:8.55116463px;line-height:125%;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';text-align:start;writing-mode:lr-tb;text-anchor:start;fill:url(#radialGradient9039);fill-opacity:1;stroke-width:0.79274535px"
+ id="path7725" />
+ </g>
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ d="m 467.25784,24.196945 c -0.36562,0 -0.73125,0.24375 -0.975,0.853125 l -2.4375,4.996876 -5.24062,0.853125 c -1.21875,0.24375 -1.4625,1.096875 -0.60938,1.95 l 3.77813,4.021875 -0.85313,5.60625 c -0.12181,0.73125 0.24375,1.21875 0.85313,1.21875 0.24375,0 0.4875,-0.121875 0.73125,-0.24375 l 4.75312,-2.559375 4.75313,2.559375 c 0.24375,0.121875 0.60937,0.24375 0.73125,0.24375 0.60937,0 0.975,-0.4875 0.85312,-1.340625 l -0.85312,-5.60625 3.77812,-4.021875 c 0.85313,-0.853125 0.4875,-1.70625 -0.60937,-1.95 l -5.24063,-0.853125 -2.4375,-4.996876 c -0.24375,-0.4875 -0.60937,-0.73125 -0.975,-0.73125 z"
+ id="path6182"
+ style="display:inline;fill:url(#radialGradient5031-1);fill-opacity:1;stroke-width:1.21875;filter:url(#filter5049)" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ d="m 226.66131,15.091518 -0.0388,3.707848 c -0.007,0.673061 -0.5177,1.245488 -1.19079,1.24654 l -17.77799,0.02779 c -0.6731,0.0011 -1.2416,-0.573462 -1.24671,-1.24654 l -0.028,-3.688049 c -0.005,-0.673078 0.6302,-1.305219 1.30331,-1.305219 1.26531,-1.257631 1.03718,-3.269047 1.26531,-5.0305225 0.4278,-2.8226251 0.0953,-6.2244545 3.80878,-6.2479747 l 7.52682,-0.047673 c 3.73297,-0.023644 3.4173,3.4730226 3.84751,6.2956477 0.41438,2.7153765 0,3.7728915 1.26526,5.0305225 0.70548,0 1.26526,0.515815 1.26526,1.25763 z m -7.68106,-4.410561 h -1.82811 V 8.8528316 c 0,-0.8124995 -1.21875,-0.8124995 -1.21875,0 v 1.8281254 h -1.82814 c -0.8125,0 -0.8125,1.218749 0,1.218749 h 1.82814 v 1.828125 c 0,0.812501 1.21875,0.812501 1.21875,0 v -1.828125 h 1.82811 c 0.8125,0 0.8125,-1.218749 0,-1.218749 z"
+ id="path7318"
+ style="display:inline;fill:url(#radialGradient4813-2);fill-opacity:1;stroke-width:1.21875;filter:url(#filter4825)" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ d="m 269.02823,19.939154 c -0.975,0 -1.82813,-0.24375 -2.80313,-1.584375 -0.975,-1.340625 -2.07187,-3.046875 -2.07187,-3.046875 0,0 -0.85313,-1.096875 -1.34063,-1.95 -0.60937,-0.853125 -1.34062,-0.609375 -1.34062,-0.609375 0,0 -3.53438,-5.7281239 -4.14375,-6.581249 -0.73125,-1.21875 0.73125,-3.290625 0.73125,-3.290625 l 5.3625,8.531249 c 0,0 1.70625,2.315625 2.31562,2.803125 0.60938,0.4875 1.70625,-0.4875 3.4125,1.096875 2.31563,2.19375 1.58438,4.63125 -0.12181,4.63125 z m -0.36563,-3.534375 c -1.09687,-1.21875 -2.07187,-1.096875 -2.31562,-0.73125 -0.24375,0.365625 0,1.4625 0.4875,2.071875 0.4875,0.609375 0.975,0.853125 1.70625,0.853125 0.73125,0.121875 1.34062,-0.853125 0.1218,-2.19375 z m -4.63125,-5.728124 -1.4625,-2.19375 3.53438,-5.60625 c 0,0 1.4625,2.071875 0.73125,3.290625 -0.36563,0.4875001 -1.70625,2.803125 -2.80313,4.509375 z m -5.60625,3.534374 c 0.36563,-0.365625 1.21875,-1.340625 1.70625,-2.071875 l 0.975,1.4625 c -0.4875,0.73125 -1.09687,1.70625 -1.09687,1.70625 0,0 -1.09688,1.70625 -2.07188,3.046875 -0.85312,1.340625 -1.70625,1.584375 -2.80312,1.584375 -1.70625,0 -2.55938,-2.4375 -0.12181,-4.63125 1.70625,-1.4625 2.80312,-0.609375 3.4125,-1.096875 z m -2.925,2.19375 c -1.09687,1.21875 -0.4875,2.19375 0.24375,2.19375 0.73125,0 1.21875,-0.24375 1.70625,-0.853125 0.4875,-0.609375 0.73125,-1.828125 0.4875,-2.071875 -0.36562,-0.365625 -1.34062,-0.4875 -2.4375,0.73125 z"
+ id="path7886"
+ style="display:inline;fill:url(#radialGradient4861-6);fill-opacity:1;stroke-width:1.21875;filter:url(#filter4873)" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ d="m 292.45286,20.11787 h -7.56413 c -0.73125,0 -1.32549,-0.515308 -1.33058,-1.24654 l -0.028,-4.017519 v -3.16875 -2.9250003 c 0,-0.73125 0.4875,-1.21875 1.21875,-1.21875 l 6.44059,0.00442 2.53052,2.5152613 0.0354,8.810334 c 0.003,0.731244 -0.57138,1.24654 -1.30263,1.24654 z M 289.62394,8.7600607 v 2.4375003 h 2.4375 z m -7.3125,-0.6736655 0.0213,0.7169703 v 2.5152615 3.772891 l -5.06403,0.04019 c -0.73123,0.0058 -1.21672,-0.598663 -1.21875,-1.32991 l -0.028,-10.0556901 c -0.002,-0.7312471 0.4875,-1.21875 1.21875,-1.21875 h 6.09375 l 2.79402,2.5031129 v 1.81343 h -2.53052 c -0.67317,-0.017075 -1.31624,0.241688 -1.28654,1.2424914 z m 0.0213,-4.7304016 v 2.4596813 l 2.53052,-0.02779 z"
+ id="path8454"
+ style="display:inline;fill:url(#radialGradient4881-9);fill-opacity:1;stroke-width:1.21875;filter:url(#filter4893)" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ d="m 312.76384,20.20124 -10.1808,-0.02779 c -0.75126,-0.0021 -1.21143,-0.547743 -1.21303,-1.272325 l -0.028,-12.6083588 c -0.002,-0.9749978 0.29395,-1.2762443 1.26895,-1.2723257 l 2.49639,0.010033 c 0,0 0.0587,-2.5309032 2.49616,-2.5309032 2.4375,0 2.56487,2.5309032 2.56487,2.5309032 l 2.48359,0.017757 c 0.97497,0.00697 1.35938,0.3251372 1.35283,1.3001156 l -0.0839,12.4971989 c -0.005,0.808245 -0.51762,1.35744 -1.15713,1.355695 z m -1.86968,-13.8508896 -1.34061,-0.609375 c 0,0 0,-1.8281249 -1.95,-1.8281249 -1.95,0 -1.95,1.8281249 -1.95,1.8281249 l -1.34063,0.609375 -0.47079,1.1953846 h 7.59155 z m 0.53952,2.4530151 h -6.32629 l -2.53052,2.5152615 2.53052,5.030522 7.59155,-5.030522 z"
+ id="path9022"
+ style="display:inline;fill:url(#radialGradient4901-6);fill-opacity:1;stroke-width:1.21875;filter:url(#filter4913)" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ d="M 335.47361,15.091518 V 7.545735 l 3.79578,3.772892 z m -8.85681,1.257631 h 7.59155 l -3.79578,3.772892 z m 6.32629,-1.257631 h -5.06103 c -0.73124,-0.0041 -1.26114,-0.526392 -1.26526,-1.25763 V 8.8033655 c -0.004,-0.7312386 0.53401,-1.2576305 1.26526,-1.2576305 h 5.06103 c 0.73125,0 1.26526,0.5263804 1.26526,1.2576305 v 5.0305225 c 0,0.609375 -0.53402,1.261705 -1.26526,1.25763 z m 0,-3.772891 c 0,-0.73125 -0.53401,-1.257631 -1.26526,-1.257631 h -2.53052 c -0.73125,0 -1.26525,0.526381 -1.26525,1.257631 v 1.25763 c 0,0.73125 0.534,1.257631 1.26525,1.257631 h 2.53052 c 0.73125,0 1.26526,-0.526381 1.26526,-1.257631 z m -2.53052,-8.8034143 3.79578,3.7728917 h -7.59155 z m -5.06103,5.0305223 v 7.545783 l -3.79578,-3.772891 z"
+ id="path9590"
+ style="display:inline;fill:url(#radialGradient4921-8);fill-opacity:1;stroke-width:1.21875;filter:url(#filter4933)" />
+ <path
+ d="m 274,8 h 10 v 2 h -10 z"
+ id="path10158"
+ style="display:inline;fill:url(#radialGradient4941-9);fill-opacity:1;stroke-width:0.96615839" />
+ <path
+ d="m 302,10 h -4 v 4 h -2 v -4 h -4 V 8 h 4 V 4 h 2 v 4 h 4 z"
+ id="path10726"
+ style="display:inline;fill:url(#radialGradient4949-5);fill-opacity:1;stroke-width:0.96615839" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ d="m 406.82037,12.583531 c -0.12181,0.73125 -0.24375,1.4625 -0.4875,2.071875 -0.4875,1.584375 -1.4625,3.046875 -3.04686,4.021874 0.4875,0.4875 1.58436,1.096875 1.58436,1.096875 0,0 -2.4375,0.365625 -4.99686,0.365625 l -0.12181,-0.121875 v 0.121875 c -1.21875,0 -2.4375,-0.365625 -3.65625,-0.73125 0.85311,-0.73125 1.4625,-1.584374 1.95,-2.559374 0.73125,-1.4625 0.73125,-3.65625 0.73125,-3.65625 0,0 1.09686,1.828125 1.70625,2.559375 1.4625,-0.73125 2.4375,-2.19375 2.55936,-3.65625 0.12181,-1.096875 -0.24375,-2.071875 -0.73125,-2.8031253 -0.4875,-0.8531251 -1.21875,-1.3406251 -2.07186,-1.7062501 0.24375,-0.4874999 0.60936,-1.0968749 0.975,-1.584375 0.4875,-0.73125 1.09686,-1.21875 1.58436,-1.4625 2.55939,1.340625 4.3875,4.5093751 4.02189,8.0437504 z m -8.53125,-2.4375 c 0,0 -1.34061,-1.8281254 -1.95,-2.4375004 -1.70625,0.853125 -2.68125,2.4375004 -2.68125,4.1437504 0.12181,1.828125 1.34064,3.290625 2.925,4.021875 -0.36561,0.609375 -0.73125,1.21875 -1.21875,1.70625 -0.4875,0.609375 -1.09686,0.974999 -1.4625,1.340624 -2.80311,-1.706249 -4.50936,-4.874999 -4.02186,-8.287499 0.1218,-0.8531253 0.36561,-1.7062504 0.60936,-2.4375004 0.4875,-1.3406249 1.34064,-2.4375 2.55939,-3.290625 0.1218,-0.121875 0.24375,-0.121875 0.36561,-0.24375 -0.4875,-0.4875 -1.95,-0.975 -1.95,-0.975 0,0 3.04689,-0.975 8.2875,-0.365625 -1.58436,2.315625 -1.4625,6.8250004 -1.4625,6.8250004 z"
+ id="path11294"
+ style="display:inline;fill:url(#radialGradient4957-2);fill-opacity:1;stroke-width:1.21875;filter:url(#filter4969)" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ d="m 429.73975,20.187746 -2.55504,0.05558 c -0.48738,0.0106 -0.61242,-0.769541 -0.61242,-1.378916 0,0 0.31966,-4.276424 -3.79578,-8.803414 -3.20233,-3.5225593 -8.85682,-3.7728916 -8.85682,-3.7728916 -0.60938,0 -1.26177,-0.171597 -1.25602,-0.6590635 l 0.028,-2.3732094 c 0.006,-0.4874666 0.61868,-0.7406188 1.22806,-0.7406188 0,0 7.05132,0.3003217 11.38734,5.0305223 3.72704,4.065871 5.06103,11.318675 5.06103,11.318675 0,0.4875 -0.0191,1.310083 -0.62831,1.323336 z M 413.91969,8.8033655 c 0,0 4.48239,0.7421569 7.0114,3.0905195 2.59016,2.40515 3.11068,6.970525 3.11068,6.970525 0,0.4875 -0.7448,1.278471 -1.23236,1.278471 h -1.35283 c -0.4875,0 -1.21059,-0.669096 -1.21059,-1.278471 0,0 0.4472,-2.304652 -1.91012,-4.45401 -1.78736,-1.629677 -4.41618,-1.834143 -4.41618,-1.834143 -0.60938,0 -1.23946,-0.793663 -1.22806,-1.281032 l 0.028,-1.195314 c 0.0114,-0.4873685 0.59073,-1.2965455 1.20011,-1.2965455 z m 1.20944,6.3426395 c 1.34062,0 2.54933,1.124665 2.54933,2.46529 0,1.340626 -1.09688,2.576451 -2.4375,2.576451 -1.34063,0 -2.54933,-1.208035 -2.54933,-2.548661 0,-1.340625 1.09687,-2.49308 2.4375,-2.49308 z"
+ id="path11862"
+ style="display:inline;fill:url(#radialGradient4977-4);fill-opacity:1;stroke-width:1.21875;filter:url(#filter4989)" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ d="m 448.08169,13.833888 v 2.515261 c 0,0.975 -0.41214,1.257631 -1.26525,1.257631 h -10.12208 c -0.85314,0 -1.26526,-0.282631 -1.26526,-1.257631 V 6.2881044 c 0,-0.9750001 0.41212,-1.2576306 1.26526,-1.2576306 h 10.12208 c 0.85311,0 1.26525,0.2826305 1.26525,1.2576306 v 2.5152611 l 5.06104,-2.5152611 V 16.349149 Z"
+ id="path12430"
+ style="display:inline;fill:url(#radialGradient4997-5);fill-opacity:1;stroke-width:1.21875;filter:url(#filter5009)" />
+ <g
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ style="display:inline"
+ id="g4779">
+ <path
+ id="bookmarks-star-4"
+ d="M 165.92886,2.5152127 V 20.122041 h 8.85681 1.26526 2.53052 c 1.26526,0 2.53052,-1.257631 2.53052,-2.515261 V 15.091518 7.545735 5.0304738 c 0,-1.2576306 -1.26526,-2.5152611 -2.53052,-2.5152611 h -2.53052 -1.26526 z m 7.59155,2.5152611 1.26526,2.5152612 c 0,0 0.39827,0.9972133 0.68613,1.1930622 0.26974,0.1834496 1.07417,0.064568 1.07417,0.064568 h 3.30074 l -2.53052,3.1379238 c 0,0 -0.38069,0.332603 -0.51773,0.634968 -0.20593,0.452534 -0.18971,0.518683 -0.12151,1.257631 0.10732,1.162403 0.63924,3.772892 0.63924,3.772892 l -2.53052,-1.257631 c 0,0 -0.77873,-0.42944 -1.26526,-0.430166 -0.5721,0 -1.26526,0.430166 -1.26526,0.430166 l -2.53051,1.257631 c 0,0 0.56012,-2.658345 0.73559,-3.772892 0.11606,-0.733074 0.10061,-0.753616 -0.15752,-1.257631 -0.13995,-0.274086 -0.57807,-0.782455 -0.57807,-0.782455 l -2.53052,-2.9904368 h 3.02161 c 0,0 0.91024,-0.099407 1.27135,-0.3263622 0.27408,-0.1711231 0.76807,-0.9312683 0.76807,-0.9312683 z"
+ style="fill:url(#radialGradient4710-4);fill-opacity:1;stroke-width:1.265625;filter:url(#filter4729)" />
+ <path
+ id="bookmarks-overlay-1"
+ d="m 163.39834,2.5152127 c -1.26526,0 -2.53052,1.2576305 -2.53052,2.5152611 v 2.5152612 7.545783 2.515262 c 0,1.25763 1.26526,2.515261 2.53052,2.515261 h 2.91839 l 0.0559,-4.840061 V 7.6882296 l 0.028,-5.1730169 z"
+ style="fill:url(#radialGradient4712-2);fill-opacity:1;stroke:none;stroke-width:1.265625;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers stroke fill;filter:url(#filter4774)" />
+ <path
+ id="bookmarks-divider-7"
+ d="M 165.92886,2.5152127 V 20.122041"
+ style="opacity:0.66300001;fill:url(#radialGradient4714-4);fill-opacity:1;stroke:url(#radialGradient4750-3);stroke-width:1.2614392;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ style="display:inline;fill:url(#radialGradient4709-1-2);fill-opacity:1;stroke-width:1;filter:url(#filter4844)"
+ id="path4214-3"
+ d="m 133.03211,35.213608 -6.32629,6.288153 c -0.24,0.36 -0.8202,0.518975 -1.3002,0.518975 -0.48,0 -0.87032,-0.158975 -1.23032,-0.518975 l -6.3263,-6.288153 c -0.6,-0.72 -0.96335,-1.29465 -0.003,-1.287183 l 3.79916,0.02955 -0.0908,-7.535242 c -0.009,-0.719949 0.67572,-1.307177 1.39571,-1.31116 l 5.02367,-0.02779 c 0.71999,-0.004 1.25401,0.619416 1.22796,1.33895 l 0.035,7.535242 3.80499,-0.02955 c 0.95998,-0.0075 0.71078,0.567183 -0.009,1.287183 z" />
+ </g>
+</svg>
diff --git a/themes/windows/Toolbar-inverted.png b/themes/windows/Toolbar-inverted.png
new file mode 100644
index 0000000..54d83bf
--- /dev/null
+++ b/themes/windows/Toolbar-inverted.png
Binary files differ
diff --git a/themes/windows/Toolbar-inverted.svg b/themes/windows/Toolbar-inverted.svg
new file mode 100644
index 0000000..ce59313
--- /dev/null
+++ b/themes/windows/Toolbar-inverted.svg
@@ -0,0 +1,302 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ enable-background="new 0 0 378 38"
+ viewBox="0 0 378 38"
+ height="38"
+ width="378"
+ y="0px"
+ x="0px"
+ id="strataToolbarSVG"
+ version="1.1">
+ <metadata
+ id="metadata146">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs144">
+ <filter
+ id="filter1070"
+ style="color-interpolation-filters:sRGB;">
+ <feFlood
+ id="feFlood1060"
+ result="flood"
+ flood-color="rgb(0,0,0)"
+ flood-opacity="0.498039" />
+ <feComposite
+ id="feComposite1062"
+ result="composite1"
+ operator="in"
+ in2="SourceGraphic"
+ in="flood" />
+ <feGaussianBlur
+ id="feGaussianBlur1064"
+ result="blur"
+ stdDeviation="0.5"
+ in="composite1" />
+ <feOffset
+ id="feOffset1066"
+ result="offset"
+ dy="0"
+ dx="0" />
+ <feComposite
+ id="feComposite1068"
+ result="fbSourceGraphic"
+ operator="over"
+ in2="offset"
+ in="SourceGraphic" />
+ <feColorMatrix
+ id="feColorMatrix1072"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ in="fbSourceGraphic"
+ result="fbSourceGraphicAlpha" />
+ <feFlood
+ in="fbSourceGraphic"
+ result="flood"
+ flood-color="rgb(0,0,0)"
+ flood-opacity="0.498039"
+ id="feFlood1074" />
+ <feComposite
+ result="composite1"
+ operator="in"
+ in="flood"
+ id="feComposite1076"
+ in2="fbSourceGraphic" />
+ <feGaussianBlur
+ result="blur"
+ stdDeviation="0.5"
+ in="composite1"
+ id="feGaussianBlur1078" />
+ <feOffset
+ result="offset"
+ dy="0"
+ dx="0"
+ id="feOffset1080" />
+ <feComposite
+ result="fbSourceGraphic"
+ operator="over"
+ in="fbSourceGraphic"
+ id="feComposite1082"
+ in2="offset" />
+ <feColorMatrix
+ id="feColorMatrix1084"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ in="fbSourceGraphic"
+ result="fbSourceGraphicAlpha" />
+ <feFlood
+ in="fbSourceGraphic"
+ result="flood"
+ flood-color="rgb(0,0,0)"
+ flood-opacity="0.498039"
+ id="feFlood1086" />
+ <feComposite
+ result="composite1"
+ operator="in"
+ in="flood"
+ id="feComposite1088"
+ in2="fbSourceGraphic" />
+ <feGaussianBlur
+ result="blur"
+ stdDeviation="0.5"
+ in="composite1"
+ id="feGaussianBlur1090" />
+ <feOffset
+ result="offset"
+ dy="0"
+ dx="0"
+ id="feOffset1092" />
+ <feComposite
+ result="fbSourceGraphic"
+ operator="over"
+ in="fbSourceGraphic"
+ id="feComposite1094"
+ in2="offset" />
+ <feColorMatrix
+ id="feColorMatrix1096"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ in="fbSourceGraphic"
+ result="fbSourceGraphicAlpha" />
+ <feFlood
+ in="fbSourceGraphic"
+ result="flood"
+ flood-color="rgb(0,0,0)"
+ flood-opacity="0.498039"
+ id="feFlood1098" />
+ <feComposite
+ result="composite1"
+ operator="in"
+ in="flood"
+ id="feComposite1100"
+ in2="fbSourceGraphic" />
+ <feGaussianBlur
+ result="blur"
+ stdDeviation="0.5"
+ in="composite1"
+ id="feGaussianBlur1102" />
+ <feOffset
+ result="offset"
+ dy="0"
+ dx="0"
+ id="feOffset1104" />
+ <feComposite
+ result="fbSourceGraphic"
+ operator="over"
+ in="fbSourceGraphic"
+ id="feComposite1106"
+ in2="offset" />
+ <feColorMatrix
+ id="feColorMatrix1108"
+ values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
+ in="fbSourceGraphic"
+ result="fbSourceGraphicAlpha" />
+ <feFlood
+ in="fbSourceGraphic"
+ result="flood"
+ flood-color="rgb(0,0,0)"
+ flood-opacity="0.498039"
+ id="feFlood1110" />
+ <feComposite
+ result="composite1"
+ operator="in"
+ in="flood"
+ id="feComposite1112"
+ in2="fbSourceGraphic" />
+ <feGaussianBlur
+ result="blur"
+ stdDeviation="0.5"
+ in="composite1"
+ id="feGaussianBlur1114" />
+ <feOffset
+ result="offset"
+ dy="0"
+ dx="0"
+ id="feOffset1116" />
+ <feComposite
+ result="composite2"
+ operator="over"
+ in="fbSourceGraphic"
+ id="feComposite1118"
+ in2="offset" />
+ </filter>
+ </defs>
+ <g
+ style="fill:#ffffff;filter:url(#filter1070)"
+ id="toolbar">
+ <path
+ style="stroke:none;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 14.7,10.7 C 14.464283,10.935746 14.000125,11 14.000125,11 H 9 l 2.132,2.085573 c 0.519423,0.508112 0.595,1.2729 0.198,1.6979 l -0.793,0.9549 c -0.396,0.424 -1.091,0.318 -1.587,-0.212 L 3.595,9.690773 3,8.946873 l 0.595,-0.743 5.256,-5.7295 c 0.496,-0.531 1.19,-0.637 1.587,-0.212 l 0.794,0.9549 c 0.396,0.425 0.297,1.1669 -0.198,1.6979 L 9,7 h 4.999875 c 0,0 0.464437,0.064342 0.700125,0.3 0.235717,0.2356875 0.299875,0.7 0.299875,0.7 l 2.5e-4,2 c 0,0 -0.06444,0.464283 -0.300125,0.7 z"
+ id="back" />
+ <path
+ style="stroke:none;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="back-large"
+ d="m 16.441631,29.733331 c -0.275,0.275034 -0.816512,0.349996 -0.816512,0.349996 H 8.999927 l 2.487308,2.516543 c 0.595913,0.602917 0.694159,1.485034 0.230997,1.980862 l -0.925157,1.114039 C 10.33108,36.189432 9.5202547,36.065767 8.9415947,35.44744 L 2.6941594,28.639311 2,27.771437 2.6941594,26.904612 8.8260957,20.220265 c 0.57866,-0.619493 1.3883193,-0.743159 1.8514803,-0.24733 l 0.926324,1.114038 c 0.461995,0.495828 0.346497,1.361369 -0.230997,1.980863 l -2.37283,2.348836 h 6.6249 c 0,0 0.541837,0.07506 0.816804,0.349997 0.275,0.274966 0.34985,0.816658 0.34985,0.816658 l 1.46e-4,2.333346 c 0,0 -0.07518,0.541658 -0.350142,0.816658 z" />
+ <path
+ style="stroke:none;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="forward"
+ d="m 21.30025,10.7 c 0.235717,0.235746 0.699875,0.3 0.699875,0.3 h 5.000125 l -2.132,2.085573 c -0.519423,0.508112 -0.595,1.2729 -0.198,1.6979 l 0.793,0.9549 c 0.396,0.424 1.091,0.318 1.587,-0.212 l 5.355,-5.8356 0.595,-0.7439 -0.595,-0.743 -5.256,-5.7295 c -0.496,-0.531 -1.19,-0.637 -1.587,-0.212 l -0.794,0.9549 c -0.396,0.425 -0.297,1.1669 0.198,1.6979 L 27.00025,7 h -4.999875 c 0,0 -0.464437,0.064342 -0.700125,0.3 C 21.064533,7.5356875 21.000375,8 21.000375,8 l -2.5e-4,2 c 0,0 0.06444,0.464283 0.300125,0.7 z" />
+ <path
+ style="stroke:none;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 51.5,4.64955 47.233834,8.949625 51.5,13.248616 49.366916,15.398166 45,11.201008 40.733834,15.5 38.60075,13.35045 42.866916,8.9485416 38.5,4.64955 40.633084,2.5 45,6.800075 49.266166,2.5 Z"
+ id="stop" />
+ <path
+ style="stroke:none;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 69,9 H 62 L 64.8,6.2 C 64.1,5.7 63.4,5.5 62.5,5.5 c -2.2,0 -4,1.8 -4,4 0,2.2 1.8,4 4,4 1.399,0 2.7,-0.7 3.399,-1.9 l 2.301,1 C 67.099,14.6 65,16 62.5,16 58.899,16 56,13.1 56,9.5 56,5.9 58.899,3 62.5,3 64,3 65.399,3.5 66.6,4.4 L 69,2 Z"
+ id="reload" />
+ <path
+ style="stroke:none;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 76,9 h -3 l 8,-7 8,7 h -3 v 6.5 H 82 V 12 h -2 v 3.5 h -4 z"
+ id="home" />
+ <path
+ style="stroke:none;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 91.5,9 h 4.453 V 2 h 6.093 V 9 H 106.5 L 99,16 Z"
+ id="download" />
+ <path
+ style="stroke:none;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 117,2 c -3.866,0 -7,3.134 -7,7 0,3.866 3.134,7 7,7 3.866,0 7,-3.134 7,-7 0,-3.866 -3.134,-7 -7,-7 z m 0,2 c 2.761,0 5,2.238 5,5 0,2.762 -2.239,5 -5,5 -2.762,0 -5,-2.238 -5,-5 0,-2.762 2.239,-5 5,-5 z m -0.7,1.2 C 116.0643,5.4357023 116,6 116,6 v 4 l 4,2 -2,-3 V 6 C 118,6 117.9357,5.4357023 117.7,5.2 117.4643,4.9642977 117,5 117,5 c 0,0 -0.4643,-0.035702 -0.7,0.2 z"
+ id="history" />
+ <g
+ style="stroke:none;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="bookmarks">
+ <path
+ style="stroke:none;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 131,2 v 14 h 7.44922 0.55078 1.94922 c 0.99175,-0.258441 1.76717,-1.038297 2,-2 V 12 6 4 c -0.23283,-0.9617034 -1.00825,-1.7415586 -2,-2 h -1.63672 -0.86328 z m 6.11523,2.4160156 1.05469,1.7714844 c 0,0 0.29209,0.4878333 0.51953,0.6425781 0.21313,0.1449479 0.73828,0.2285157 0.73828,0.2285157 h 2.15625 l -1.49804,2.4335937 c 0,0 -0.21789,0.4778906 -0.32617,0.7167965 -0.16271,0.357558 0.005,0.982547 0.0586,1.566407 0.0848,0.918442 0.15039,1.80664 0.15039,1.80664 l -1.75,-0.894531 c 0,0 -0.71714,-0.337318 -1.10156,-0.337891 -0.45203,0 -1.29883,0.392579 -1.29883,0.392579 l -1.75391,0.841796 c 0,0 0.12894,-0.80101 0.26758,-1.68164 0.0917,-0.579219 0.34458,-1.238485 0.14063,-1.636719 -0.11058,-0.216562 -0.33204,-0.6484375 -0.33204,-0.6484375 l -1.7246,-2.5019531 h 2.14257 c 0,0 0.69711,-0.049193 0.98243,-0.2285156 0.21656,-0.1352084 0.48242,-0.5957032 0.48242,-0.5957032 z"
+ id="bookmarks-star" />
+ <path
+ style="stroke:none;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 129,2 c -0.99175,0.2584414 -1.76717,1.0382967 -2,2 v 2 6 2 c 0.23283,0.961703 1.00825,1.741559 2,2 h 2.5 V 12 6 2 Z"
+ id="bookmarks-overlay" />
+ <path
+ style="stroke:#000000;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 131.49914,2 V 16"
+ id="bookmarks-divider" />
+ </g>
+ <path
+ style="stroke:none;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 160,14 h -2 l 1,2 h -12 l 1,-2 h -2 c -0.601,0 -1,-0.4 -1,-1 V 8 c 0,-0.6 0.70113,-0.635443 1,-1 0.38098,-0.4647208 0.61902,-1.0352792 1,-1.5 0.29887,-0.364557 0.399,-1 1,-1 V 3 c 0,-0.6 0.399,-1 1,-1 h 8 c 0.6,0 1,0.4 1,1 v 1.5 c 0.6,0 0.70113,0.635443 1,1 0.38098,0.4647208 0.61902,1.0352792 1,1.5 0.29887,0.364557 1,0.4 1,1 v 5 c 0,0.6 -0.4,1 -1,1 z M 148.5,9 h -0.5 -0.5 c -0.3,0 -0.5,0.2 -0.5,0.5 0,0.3 0.2,0.5 0.5,0.5 h 1 C 148.8,10 149,9.8 149,9.5 149,9.2 148.8,9 148.5,9 Z M 157,4 c 0,-0.6 -0.4,-1 -1,-1 h -6 c -0.601,0 -1,0.4 -1,1 v 3 c 0,0.6 0.399,1 1,1 h 6 c 0.6,0 1,-0.4 1,-1 z m -0.9,9 h -6.2 l -0.899,2 h 8 z"
+ id="print" />
+ <path
+ style="stroke:none;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="newtab"
+ d="m 170.93359,2 -4.01562,0.00195 c 0,0.00178 -0.87681,0.216686 -0.87695,1.4980469 v 2.8164062 c 0,0 -0.0732,0.3643751 -0.19922,0.484375 C 165.69982,6.9357812 165.29102,7 165.29102,7 c 0,0 -0.53242,-0.057 -0.7754,0 -0.36297,0.086 -0.73599,0.236 -1,0.5 -0.26399,0.264 -0.41502,0.637 -0.5,1 -0.038,0.162 0,0.5 0,0.5 v 5.537109 c 0,0 0.24003,0.710891 0.5,0.962891 0.27398,0.267 1.03516,0.5 1.03516,0.5 h 13.0957 c 0,0 0.60287,-0.276 0.83985,-0.5 0.21699,-0.205 0.49023,-0.742188 0.49023,-0.742188 V 9 c 0.026,-0.322 0.038,-0.338 0,-0.5 -0.086,-0.363 -0.22624,-0.736 -0.49023,-1 -0.26398,-0.264 -0.63802,-0.415 -1,-0.5 -0.22799,-0.054 -0.69922,0 -0.69922,0 0,0 -0.44657,-0.056219 -0.60156,-0.1992188 -0.12799,-0.1189999 -0.19922,-0.484375 -0.19922,-0.484375 L 175.98828,3.5 C 175.98843,2.1047456 174.9707,2 174.9707,2 Z m -0.79101,5 h 1.71484 V 9.1425781 H 174 v 1.7148439 h -2.14258 V 13 h -1.71484 V 10.857422 H 168 V 9.1425781 h 2.14258 z" />
+ <path
+ style="stroke:none;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="newwindow"
+ d="M 182.4375,2 C 181.52354,2.2781435 181.28553,2.8114358 181,3.6875 V 14.125 c 0.21642,1.330001 0.75257,1.636052 2.0625,1.875 H 195 c 1.30474,-0.204204 1.70477,-0.57308 2,-1.875 V 4 C 196.8019,2.7723315 196.4998,2.3703399 195.3125,2 Z M 190,3 h 1 v 1 h -1 z m 2,0 h 1 v 1 h -1 z m 2,0 h 2 v 1 h -2 z m -11,3 h 12 v 8 h -12 z m 5.14258,1 V 9.1425781 H 186 v 1.7148439 h 2.14258 V 13 h 1.71484 V 10.857422 H 192 V 9.1425781 h -2.14258 V 7 Z" />
+ <path
+ style="stroke:none;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 212.70159,16 c -0.79707,0 -1.49513,-0.2 -2.2922,-1.3 -0.79807,-1.1 -1.69515,-2.5 -1.69515,-2.5 0,0 -0.69706,-0.9 -1.09709,-1.6 -0.49805,-0.7 -1.0971,-0.5 -1.0971,-0.5 0,0 -2.89125,-4.7 -3.3903,-5.4 -0.59805,-1 0.59906,-2.7 0.59906,-2.7 l 4.38738,7 c 0,0 1.39612,1.9 1.89417,2.3 0.49904,0.4 1.39612,-0.4 2.79224,0.9 1.89417,1.8 1.29611,3.8 -0.10101,3.8 z m -0.29802,-2.9 c -0.89708,-1 -1.69415,-0.9 -1.89417,-0.6 -0.20002,0.3 0,1.2 0.39804,1.7 0.39803,0.5 0.79806,0.7 1.39612,0.7 0.59905,0.1 1.09709,-0.7 0.10001,-1.8 z m -3.78934,-4.7 -1.1961,-1.8 2.89125,-4.6 c 0,0 1.19611,1.7 0.59906,2.7 -0.29903,0.4 -1.39613,2.3 -2.29421,3.7 z m -4.5854,2.899 c 0.29903,-0.3 0.99709,-1.1 1.39613,-1.7 l 0.79806,1.2 c -0.39803,0.6 -0.89707,1.4 -0.89707,1.4 0,0 -0.89608,1.4 -1.69415,2.5 -0.69806,1.1 -1.39612,1.3 -2.2932,1.3 -1.39613,0 -2.09419,-2 -0.10001,-3.8 1.39412,-1.199 2.2912,-0.499 2.79024,-0.9 z m -2.39321,1.801 c -0.89708,1 -0.39803,1.8 0.19902,1.8 0.59905,0 0.99709,-0.2 1.39612,-0.7 0.39804,-0.5 0.59806,-1.5 0.39804,-1.7 -0.29803,-0.3 -1.0961,-0.4 -1.99318,0.6 z"
+ id="cut" />
+ <path
+ style="stroke:none;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 231,16 h -6 c -0.601,0 -1,-0.4 -1,-1 V 12 9.4 7 c 0,-0.6 0.399,-1 1,-1 0,0 3.2,0 5,0 l 2,2 c 0,2.2 0,7 0,7 0,0.6 -0.4,1 -1,1 z m -2,-9 v 2 h 2 z M 223,5.9 V 7 8.9 12 h -4 c -0.601,0 -1,-0.4 -1,-1 V 3 c 0,-0.6 0.399,-1 1,-1 0,0 3.2,0 5,0 l 2,2 c 0,0.4 0,0.8 0,1.3 h -2.5 C 223.2,5.4 223,5.6 223,5.9 Z M 223,3 v 2 h 2 z"
+ id="copy" />
+ <path
+ style="stroke:none;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 246.8507,16 h -7.7014 C 238.26914,16 237.5,15.3 237.5,14.5 v -9 c 0,-0.8 0.77014,-1.5 1.6493,-1.5 h 1.6503 c 0,0 0,-2 2.2004,-2 2.2004,0 2.2004,2 2.2004,2 h 1.6493 c 0.88016,0 1.6503,0.7 1.6503,1.5 v 9 c 0.001,0.799 -0.76914,1.5 -1.6493,1.5 z M 245.97054,5 244.76032,4.5 c 0,0 0,-1.5 -1.76032,-1.5 -1.76032,0 -1.76032,1.5 -1.76032,1.5 l -1.21122,0.5 -0.5501,1 h 2.7505 3.52164 0.88016 z m 0.11002,1.7 h -5.17094 l -3.3016,1.7 3.08056,4.9 7.26232,-3.8 z"
+ id="paste" />
+ <path
+ style="stroke:none;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 265,12 V 6 l 3,3 z m -7,1 h 6 l -3,3 z m 0,-1 V 6 h 6 v 6 z m 5,-4 h -4 v 3 h 4 z m -2,-6 3,3 h -6 z m -4,4 v 6 l -3,-3 z"
+ id="fullscreen" />
+ <path
+ style="stroke:none;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 274,8 h 10 v 2 h -10 z"
+ id="minus" />
+ <path
+ style="stroke:none;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 302,10 h -4 v 4 h -2 v -4 h -4 V 8 h 4 V 4 h 2 v 4 h 4 z"
+ id="plus" />
+ <path
+ style="stroke:none;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 321.96164,9.3589991 c -0.0996,0.5880529 -0.19907,1.1749739 -0.39904,1.6649929 -0.39904,1.272996 -2.49721,3.230982 -2.49721,3.230982 l 1.23012,1.745023 -4.92726,6e-6 V 9.849 c 0,0 0.89889,1.46799 1.39795,2.05599 1.19912,-0.588025 1.99814,-1.762997 2.09818,-2.9380158 0.10109,-0.8810027 -0.19905,-1.6639894 -0.59902,-2.2510039 -0.18813,-0.3229439 -0.42109,-0.5799624 -0.68685,-0.7931908 -0.30005,-0.23897 -1.01208,-0.5779774 -1.01208,-0.5779774 l 0.0174,-3.3306603 c 1.03186,0.2680556 1.4999,0.3864246 2.85781,1.3686643 1.67715,1.2509803 2.78224,3.5019854 2.52022,5.9759989 z m -6.99262,-1.9580198 c 0,0 -1.09884,-1.4681946 -1.59912,-1.9580122 -1.39809,0.6859994 -2.19718,1.957993 -2.19714,3.3290059 0.39151,1.955025 1.15486,2.878079 2.87049,3.728027 V 16 c -1.14823,-0.14515 -2.89657,-1.144129 -3.76062,-2.017966 -1.64214,-1.695027 -2.56324,-3.735 -2.20626,-6.1889776 0.1,-0.6850241 0.30006,-1.3699914 0.49903,-1.9580159 0.40011,-1.0769929 1.10011,-1.957978 2.09921,-2.6429982 0.0997,-0.098323 0.199,-0.098023 0.29929,-0.1955973 -0.39905,-0.3919885 -1.2861,-0.9964444 -1.2861,-0.9964444 l 5.35545,-1.3e-6 z"
+ id="sync" />
+ <path
+ style="stroke:none;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 339.29905,15.9 h -1.49989 c -0.39997,0 -0.79995,-0.3 -0.79995,-0.8 0,0 0.29998,-3.6 -3.19977,-7.2 -2.49982,-3 -6.9995,-3.2 -6.9995,-3.2 C 326.29998,4.7 326,4.4 326,4 V 2.6 c 0,-0.4 0.29998,-0.7 0.79994,-0.7 0,0 6.29955,0.4 9.59932,4.5 3.29976,3.1 3.60074,8.8 3.60074,8.8 -10e-4,0.4 -0.20099,0.7 -0.70095,0.7 z m -12.49911,-9 c 0,0 3.69974,0.5 5.79959,2.4 2.09985,2 2.49982,5.9 2.49982,5.9 0,0.4 -0.1,0.8 -0.49996,0.8 h -1.4999 c -0.39997,0 -0.59995,-0.3 -0.59995,-0.8 0,0 0.1,-2.4 -1.80088,-4.2 C 329.19877,9.7 326.79994,9.6 326.79994,9.6 326.29998,9.6 326,9.3 326,8.9 V 7.6 c 0,-0.4 0.29998,-0.7 0.79994,-0.7 z m 1.19992,5 c 1.09992,0 1.99985,0.9 1.99985,2 0,1.1 -0.89993,2 -1.99985,2 C 326.89894,15.9 326,15 326,13.9 c 0,-1.1 0.89994,-2 1.99986,-2 z"
+ id="rss" />
+ <path
+ style="stroke:none;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 344.5,5 c 0,0 0.0643,-0.4642977 0.3,-0.7 0.2357,-0.2357023 0.7,-0.3 0.7,-0.3 h 7.5 c 0,0 0.4643,0.064298 0.7,0.3 0.2357,0.2357023 0.3,0.7 0.3,0.7 v 2 l 4.0245,-3 -0.049,10 L 354,11 v 2 c 0,0 -0.0643,0.464298 -0.3,0.7 -0.2357,0.235702 -0.7,0.3 -0.7,0.3 h -7.5 c 0,0 -0.4643,-0.0643 -0.7,-0.3 -0.2357,-0.235702 -0.3,-0.7 -0.3,-0.7 z"
+ id="webrtc" />
+ <path
+ style="stroke:none;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="bookmark-none"
+ d="m 369.20312,1 -1.9082,3.2734375 c 0,0 -0.46184,0.8030625 -0.83984,1.0390625 -0.498,0.313 -1.71875,0.3984375 -1.71875,0.3984375 H 361 l 3.00781,4.3652345 c 0,0 0.38708,0.754812 0.58008,1.132812 0.356,0.6951 -0.0841,1.844469 -0.24414,2.855469 -0.242,1.5371 -0.4668,2.935547 -0.4668,2.935547 l 3.06055,-1.466797 c 0,0 1.47663,-0.685547 2.26562,-0.685547 0.671,10e-4 1.92579,0.587891 1.92579,0.587891 l 3.05273,1.5625 c 0,0 -0.11177,-1.549244 -0.25976,-3.152344 -0.093,-1.0191 -0.38752,-2.112228 -0.10352,-2.736328 0.189,-0.417 0.56836,-1.2519531 0.56836,-1.2519531 L 377,5.6113281 h -3.76172 c 0,0 -0.91706,-0.1454375 -1.28906,-0.3984375 -0.397,-0.2701 -0.90625,-1.1210937 -0.90625,-1.1210937 z m -0.0762,3 1.15039,1.9316406 c 0,0 0.31633,0.5323647 0.56446,0.7011719 0.23249,0.1581201 0.80664,0.25 0.80664,0.25 H 374 l -1.63281,2.6542969 c 0,0 -0.23929,0.5206326 -0.35742,0.7812496 -0.17749,0.39005 0.008,1.074021 0.0664,1.710938 C 372.16866,13.031203 372.23828,14 372.23828,14 l -1.9082,-0.978516 c 0,0 -0.78376,-0.366562 -1.20313,-0.367187 -0.4931,0 -1.41601,0.429687 -1.41601,0.429687 L 365.79883,14 c 0,0 0.13977,-0.873327 0.29101,-1.833984 0.1,-0.631855 0.37484,-1.350734 0.15235,-1.785157 -0.12063,-0.236243 -0.36133,-0.708984 -0.36133,-0.708984 L 364,6.9453125 h 2.33594 c 0,0 0.76298,-0.054381 1.07422,-0.25 0.23624,-0.1474953 0.52343,-0.6484375 0.52343,-0.6484375 z" />
+ <path
+ style="stroke:none;stroke-width:0.5;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="bookmark-added"
+ d="m 369.202,19 -1.908,3.2732 c 0,0 -0.461,0.8031 -0.839,1.0391 -0.498,0.313 -1.718,0.399 -1.718,0.399 H 361 l 3.008,4.3643 c 0,0 0.387,0.755 0.58,1.133 0.356,0.6951 -0.084,1.8452 -0.244,2.8562 C 364.102,33.6019 363.877,35 363.877,35 l 3.06,-1.4671 c 0,0 1.477,-0.686 2.266,-0.686 0.671,0.001 1.925,0.588 1.925,0.588 l 3.053,1.5641 c 0,0 -0.112,-1.5501 -0.26,-3.1532 -0.093,-1.0191 -0.388,-2.1121 -0.104,-2.7362 0.189,-0.417 0.57,-1.252 0.57,-1.252 L 377,23.6123 h -3.763 c 0,0 -0.917,-0.146 -1.289,-0.399 -0.397,-0.2701 -0.906,-1.1221 -0.906,-1.1221 z" />
+ </g>
+</svg>
diff --git a/themes/windows/Toolbar.png b/themes/windows/Toolbar.png
new file mode 100644
index 0000000..8ec756e
--- /dev/null
+++ b/themes/windows/Toolbar.png
Binary files differ
diff --git a/themes/windows/Toolbar.svg b/themes/windows/Toolbar.svg
new file mode 100644
index 0000000..7a68c06
--- /dev/null
+++ b/themes/windows/Toolbar.svg
@@ -0,0 +1,1356 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ version="1.1"
+ id="PaleMoonToolbarSVG"
+ x="0px"
+ y="0px"
+ width="378"
+ height="38"
+ viewBox="0 0 378 38"
+ enable-background="new 0 0 378 38">
+ <metadata
+ id="metadata146">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:title></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs
+ id="defs144">
+ <radialGradient
+ id="globalGradient"
+ cy="0.69999999">
+ <stop
+ offset="0.05"
+ id="stop4"
+ style="stop-color:#87939b;stop-opacity:1" />
+ <stop
+ offset="1"
+ id="stop6"
+ style="stop-color:#45555f;stop-opacity:1" />
+ </radialGradient>
+ <filter
+ id="insetShadow">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood1875" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite1877" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="2"
+ result="blur"
+ id="feGaussianBlur1879" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset1881" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite1883" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ id="filter2164">
+ <feFlood
+ flood-opacity="1"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood2154" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite2156" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur2158" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset2160" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite2162" />
+ </filter>
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ r="7.2456884"
+ fy="12.21416"
+ fx="95.643087"
+ cx="95.643087"
+ gradientTransform="matrix(1.0350983,0,0,0.96609178,0,18)"
+ id="globalGradient-8"
+ cy="12.21416">
+ <stop
+ offset="0.05"
+ id="stop4-5"
+ style="stop-color:#12d92d;stop-opacity:1" />
+ <stop
+ offset="1"
+ id="stop6-5"
+ style="stop-color:#01b222;stop-opacity:1" />
+ </radialGradient>
+ <radialGradient
+ xlink:href="#linearGradient4635"
+ id="radialGradient4669"
+ cx="10.529827"
+ cy="14.778796"
+ fx="10.529827"
+ fy="14.778796"
+ r="7.1399999"
+ gradientTransform="matrix(1,0,0,1.1087088,0,-1.2351844)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4635">
+ <stop
+ style="stop-color:#6198cb;stop-opacity:1"
+ offset="0"
+ id="stop4631" />
+ <stop
+ style="stop-color:#3a78b2;stop-opacity:1"
+ offset="1"
+ id="stop4633" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4701">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4691" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4693" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="1"
+ result="blur"
+ id="feGaussianBlur4695" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4697" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4699" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4635"
+ id="radialGradient4637"
+ cx="11.063469"
+ cy="38.79744"
+ fx="11.063469"
+ fy="38.79744"
+ r="8.7600002"
+ gradientTransform="matrix(1,0,0,1.0853313,0,-3.029369)"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4661">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4651" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4653" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="1"
+ result="blur"
+ id="feGaussianBlur4655" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4657" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4659" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4635"
+ id="radialGradient4677"
+ cx="34.841751"
+ cy="14.552581"
+ fx="34.841751"
+ fy="14.552581"
+ r="7.1399999"
+ gradientTransform="matrix(1,0,0,1.1003056,0,-1.1335797)"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4689">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4679" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4681" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="1"
+ result="blur"
+ id="feGaussianBlur4683" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4685" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4687" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4603"
+ id="radialGradient4605"
+ cx="58.062626"
+ cy="12.761739"
+ fx="58.062626"
+ fy="12.761739"
+ r="7.6799994"
+ gradientTransform="matrix(1,0,0,0.99218759,0,0.09141507)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4603">
+ <stop
+ style="stop-color:#e72b1d;stop-opacity:1"
+ offset="0"
+ id="stop4599" />
+ <stop
+ style="stop-color:#cc4338;stop-opacity:1"
+ offset="1"
+ id="stop4601" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4629">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4619" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4621" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="1"
+ result="blur"
+ id="feGaussianBlur4623" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4625" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4627" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4523-3"
+ id="radialGradient4525"
+ cx="79.305222"
+ cy="13.939252"
+ fx="79.305222"
+ fy="13.939252"
+ r="7.8000002"
+ gradientTransform="matrix(1,0,0,1.0769231,0,-0.86932835)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4523-3">
+ <stop
+ style="stop-color:#4fb55d;stop-opacity:1"
+ offset="0"
+ id="stop4519" />
+ <stop
+ style="stop-color:#2d8539;stop-opacity:1"
+ offset="1"
+ id="stop4521" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4597">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4587" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4589" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="1"
+ result="blur"
+ id="feGaussianBlur4591" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4593" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4595" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4527"
+ id="radialGradient4529"
+ cx="103.23091"
+ cy="12.664675"
+ fx="103.23091"
+ fy="12.664675"
+ r="9.5995998"
+ gradientTransform="matrix(1,0,0,0.87507716,0,1.3868386)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4527">
+ <stop
+ style="stop-color:#3f6bbd;stop-opacity:1"
+ offset="0"
+ id="stop4523" />
+ <stop
+ style="stop-color:#29467b;stop-opacity:1"
+ offset="1"
+ id="stop4525" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4783">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4773" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4775" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="1"
+ result="blur"
+ id="feGaussianBlur4777" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4779" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4781" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4709"
+ cx="125.30523"
+ cy="16.659737"
+ fx="125.30523"
+ fy="16.659737"
+ r="8.3726959"
+ gradientTransform="matrix(1,0,0,1.0032611,0,-0.03620244)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4707">
+ <stop
+ style="stop-color:#8c9ba5;stop-opacity:1"
+ offset="0"
+ id="stop4703" />
+ <stop
+ style="stop-color:#607480;stop-opacity:1"
+ offset="1"
+ id="stop4705" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4721">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4711" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4713" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="1"
+ result="blur"
+ id="feGaussianBlur4715" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4717" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4719" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4727"
+ id="radialGradient4729"
+ cx="149.26262"
+ cy="12.784631"
+ fx="149.26262"
+ fy="12.784631"
+ r="8.6400051"
+ gradientTransform="matrix(1,0,0,0.993055,0,0.07848724)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4727">
+ <stop
+ style="stop-color:#3eb796;stop-opacity:1"
+ offset="0"
+ id="stop4723" />
+ <stop
+ style="stop-color:#31a886;stop-opacity:1"
+ offset="1"
+ id="stop4725" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4741">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4731" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4733" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="1"
+ result="blur"
+ id="feGaussianBlur4735" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4737" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4739" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient5023"
+ id="radialGradient5017"
+ cx="466.94476"
+ cy="12.037849"
+ fx="466.94476"
+ fy="12.037849"
+ r="9.6007004"
+ gradientTransform="matrix(0.79035186,0,0,0.79508811,-0.14216924,6.9389816e-4)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient5023">
+ <stop
+ id="stop5019"
+ offset="0"
+ style="stop-color:#c6cdd2;stop-opacity:1" />
+ <stop
+ id="stop5021"
+ offset="1"
+ style="stop-color:#9cabb4;stop-opacity:1" />
+ </linearGradient>
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4793"
+ cx="194.44176"
+ cy="13.746766"
+ fx="194.44176"
+ fy="13.746766"
+ r="9.5999947"
+ gradientTransform="matrix(1,0,0,0.87500048,0,1.3876528)"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4805">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4795" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4797" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4799" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4801" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4803" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4833"
+ cx="239.2"
+ cy="11.101265"
+ fx="239.2"
+ fy="11.101265"
+ r="9.6000004"
+ gradientTransform="matrix(1,0,0,0.87500002,0,1.3876579)"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4853">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4843" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4845" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4847" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4849" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4851" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4841"
+ cx="242.26164"
+ cy="12.423289"
+ fx="242.26164"
+ fy="12.423289"
+ r="3.5288758"
+ gradientTransform="matrix(0.79274531,0,0,0.78327977,-0.14435628,0.11758726)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4858"
+ cx="242.0894"
+ cy="12.418613"
+ fx="242.0894"
+ fy="12.418613"
+ r="3.5288758"
+ gradientTransform="matrix(1,0,0,0.9880597,0,0.14828194)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ xlink:href="#linearGradient5037"
+ id="radialGradient5031"
+ cx="466.39926"
+ cy="31.105829"
+ fx="466.39926"
+ fy="31.105829"
+ r="9.7507105"
+ gradientTransform="matrix(1,0,0,0.99992718,0,0.00247197)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient5037">
+ <stop
+ id="stop5033"
+ offset="0"
+ style="stop-color:#e8e1a1;stop-opacity:1" />
+ <stop
+ id="stop5035"
+ offset="1"
+ style="stop-color:#baad3e;stop-opacity:1" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter5049">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood5039" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite5041" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur5043" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset5045" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite5047" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4813"
+ cx="217.95329"
+ cy="16.56296"
+ fx="217.95329"
+ fy="16.56296"
+ r="10.35937"
+ gradientTransform="matrix(1,0,0,0.8160434,0,2.0506693)"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4825">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4815" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4817" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4819" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4821" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4823" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4861"
+ cx="262.79288"
+ cy="15.840806"
+ fx="262.79288"
+ fy="15.840806"
+ r="8.5577164"
+ gradientTransform="matrix(1,0,0,0.9969072,0,0.03528241)"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4873">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4863" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4865" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4867" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4869" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4871" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4881"
+ cx="286.58698"
+ cy="14.171478"
+ fx="286.58698"
+ fy="14.171478"
+ r="8.53125"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4893">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4883" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4885" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4887" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4889" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4891" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4901"
+ cx="308.97141"
+ cy="14.457072"
+ fx="308.97141"
+ fy="14.457072"
+ r="6.09375"
+ gradientTransform="matrix(1,0,0,1.4,0,-4.4901397)"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4913">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4903" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4905" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4907" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4909" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4911" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4921"
+ cx="331.15933"
+ cy="13.119289"
+ fx="331.15933"
+ fy="13.119289"
+ r="8.53125"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4933">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4923" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4925" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4927" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4929" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4931" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4941"
+ cx="353.15076"
+ cy="11.316628"
+ fx="353.15076"
+ fy="11.316628"
+ r="6.09375"
+ gradientTransform="matrix(0.79035186,0,0,0.15902921,-0.14216924,7.1987363)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ gradientTransform="matrix(0.79035186,0,0,0.79514603,-0.14216924,3.8580698e-5)"
+ xlink:href="#linearGradient4707"
+ id="radialGradient4949"
+ cx="375.97003"
+ cy="11.407905"
+ fx="375.97003"
+ fy="11.407905"
+ r="6.09375"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4957"
+ cx="400.5007"
+ cy="13.518586"
+ fx="400.5007"
+ fy="13.518586"
+ r="8.5350475"
+ gradientTransform="matrix(1,0,0,0.99701325,0,0.03407254)"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4969">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4959" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4961" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4963" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4965" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4967" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4975"
+ id="radialGradient4977"
+ cx="417.02075"
+ cy="15.742972"
+ fx="417.02075"
+ fy="15.742972"
+ r="8.53125"
+ gradientTransform="matrix(1.357667,-0.02466618,0.02411975,1.3275908,-149.53429,5.1574131)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4975">
+ <stop
+ style="stop-color:#f79729;stop-opacity:1"
+ offset="0"
+ id="stop4971" />
+ <stop
+ style="stop-color:#d2831f;stop-opacity:1"
+ offset="1"
+ id="stop4973" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4989">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4979" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4981" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4983" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset4985" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4987" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4997"
+ cx="444.33652"
+ cy="11.316628"
+ fx="444.33652"
+ fy="11.316628"
+ r="8.53125"
+ gradientTransform="matrix(1,0,0,0.71428563,0,3.2333231)"
+ gradientUnits="userSpaceOnUse" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter5009">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4999" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite5001" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur5003" />
+ <feOffset
+ dx="0"
+ dy="0"
+ result="offset"
+ id="feOffset5005" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite5007" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4747"
+ id="radialGradient4710"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.265625,0,0,1.1109477,-0.05703897,1.4865748)"
+ cx="134.97461"
+ cy="9"
+ fx="134.97461"
+ fy="9"
+ r="7.9746099" />
+ <linearGradient
+ id="linearGradient4747">
+ <stop
+ style="stop-color:#c5b631;stop-opacity:1"
+ offset="0"
+ id="stop4743" />
+ <stop
+ style="stop-color:#baad3e;stop-opacity:1"
+ offset="1"
+ id="stop4745" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4729">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4719" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4721" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="1"
+ result="blur"
+ id="feGaussianBlur4723" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset4725" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4727" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient5037"
+ id="radialGradient4712"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.265625,0,0,1.1109477,-0.05703897,1.4865748)"
+ cx="132.6468"
+ cy="9.0947113"
+ fx="132.6468"
+ fy="9.0947113"
+ r="7.9746099" />
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4774">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4764" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4766" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4768" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset4770" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4772" />
+ </filter>
+ <radialGradient
+ xlink:href="#linearGradient4747"
+ id="radialGradient4714"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1.265625,0,0,1.1109477,-0.05703897,1.4865748)"
+ cx="134.97461"
+ cy="9"
+ fx="134.97461"
+ fy="9"
+ r="7.9746099" />
+ <radialGradient
+ xlink:href="#linearGradient4707"
+ id="radialGradient4750"
+ cx="166.37157"
+ cy="11.485105"
+ fx="166.37157"
+ fy="11.485105"
+ r="0.31640625"
+ gradientTransform="matrix(0.99998863,-0.00473886,0.08838422,18.426509,-1.0132111,-199.35688)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ xlink:href="#linearGradient4832"
+ id="radialGradient4709-1"
+ cx="125.30523"
+ cy="16.659737"
+ fx="125.30523"
+ fy="16.659737"
+ r="8.3726959"
+ gradientTransform="matrix(1,0,0,1.0032611,0.11563445,22.233158)"
+ gradientUnits="userSpaceOnUse" />
+ <linearGradient
+ id="linearGradient4832">
+ <stop
+ id="stop5029"
+ offset="0"
+ style="stop-color:#22e23d;stop-opacity:1" />
+ <stop
+ id="stop4830"
+ offset="1"
+ style="stop-color:#38a748;stop-opacity:1" />
+ </linearGradient>
+ <filter
+ style="color-interpolation-filters:sRGB"
+ id="filter4844">
+ <feFlood
+ flood-opacity="0.498039"
+ flood-color="rgb(0,0,0)"
+ result="flood"
+ id="feFlood4834" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="out"
+ result="composite1"
+ id="feComposite4836" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4838" />
+ <feOffset
+ dx="2.77556e-017"
+ dy="0"
+ result="offset"
+ id="feOffset4840" />
+ <feComposite
+ in="offset"
+ in2="SourceGraphic"
+ operator="atop"
+ result="composite2"
+ id="feComposite4842" />
+ </filter>
+ </defs>
+ <g
+ id="g7757">
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ style="display:inline;fill:url(#radialGradient4669);fill-opacity:1;stroke-width:1;filter:url(#filter4701)"
+ d="m 17.870749,13.841269 -6.303534,-0.0074 2.264363,2.148431 c 0.615648,0.584128 0.72,1.44 0.24,1.92 l -0.96,1.08 c -0.48,0.48 -1.32,0.36 -1.92,-0.24 0,0 -6.4200001,-6.6 -6.4800001,-6.6 -0.06,0 -0.36,-0.48 -0.48,-0.84 0,-0.359999 0.36,-0.719999 0.48,-0.839999 l 6.3600001,-6.48 c 0.6,-0.6000001 1.44,-0.7200001 1.92,-0.24 l 0.96,1.0799999 c 0.48,0.48 0.36,1.32 -0.24,1.9200001 l -2.144363,2.0610645 6.359451,0.043374 c 0.719983,0.00491 1.227959,0.50779 1.227959,1.2277905 v 2.483369 c 0,0.72 -0.563877,1.284215 -1.283876,1.28337 z"
+ id="path4" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ style="display:inline;fill:url(#radialGradient4637);fill-opacity:1;stroke-width:1;filter:url(#filter4661)"
+ id="path4154"
+ d="m 19.187889,37.765372 -7.620674,-0.0365 3.352509,3.266174 c 0.515294,0.50108 0.829738,1.388534 0.443269,1.764345 l -1.759464,1.992905 c -0.356609,0.403923 -1.52102,-0.108922 -2.036314,-0.735274 L 3.9756591,36.471238 c 0,0 -0.7399492,-0.710192 -0.7399492,-1.211274 0,-0.501081 0.7399492,-1.303987 0.7399492,-1.303987 l 7.5915559,-7.545783 c 0.515294,-0.50108 1.613776,-1.093109 1.980397,-0.698138 l 1.815381,1.955768 c 0.386469,0.375811 0.172889,1.300401 -0.471228,1.801482 l -3.32455,3.229041 7.620674,0.02842 c 0.772936,0.0029 1.344153,0.417712 1.344153,1.169335 v 2.560987 c 0,0.751622 -0.571221,1.311987 -1.344153,1.308283 z" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ style="display:inline;fill:url(#radialGradient4677);fill-opacity:1;stroke-width:1;filter:url(#filter4689)"
+ id="path4165"
+ d="m 26.776124,12.612425 v -2.53895 c 0,-0.7200003 0.480206,-1.3282747 1.2,-1.3111602 l 5.76,0.041051 L 31.66,6.7412646 c -0.602042,-0.5979755 -0.72,-1.4399999 -0.24,-1.92 l 0.96,-1.08 c 0.48,-0.48 1.32,-0.36 1.92,0.24 l 6.36,6.4800004 c 0.12,0.12 0.48,0.48 0.48,0.84 0,0.36 -0.36,0.84 -0.48,0.84 l -6.48,6.48 c -0.6,0.6 -1.44,0.72 -1.92,0.24 l -0.96,-1.08 c -0.48,-0.48 -0.36,-1.32 0.24,-1.92 L 33.736124,13.833888 28.06,13.868005 c -0.719987,0.0043 -1.283876,-0.53558 -1.283876,-1.25558 z" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ style="display:inline;fill:url(#radialGradient4605);fill-opacity:1;stroke-width:1;filter:url(#filter4629)"
+ id="path4176"
+ d="m 64.708108,6.2881044 -5.061037,5.0305226 5.061037,5.030522 -2.530518,2.515261 -5.061038,-5.030522 -5.061037,5.030522 -2.530519,-2.515261 5.061037,-5.030522 -5.061037,-5.0305226 2.530519,-2.5152612 5.061037,5.0305223 5.061038,-5.0305223 z" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ style="display:inline;fill:url(#radialGradient4525);fill-opacity:1;stroke-width:1;filter:url(#filter4597)"
+ id="path4187"
+ d="m 87.482777,11.318627 h -8.856816 l 3.13833,-3.0716727 C 81.15648,7.545735 80.363876,6.712205 79.283876,6.712205 c -2.64,0 -5.107543,1.9376803 -5.107543,4.577681 0,2.64 2.411627,5.050109 5.051627,5.050109 1.68,0 3.619039,-1.066107 4.459039,-2.506107 l 2.530518,1.25763 c -1.32,2.4 -3.961599,4.443007 -6.961599,4.443007 -4.32,0 -8.219378,-3.896849 -8.219378,-8.216849 0,-4.3200008 3.927337,-7.633261 8.247337,-7.633261 1.8,0 3.619792,0.8778997 5.059792,1.9578998 l 3.139108,-3.1271021 z" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ style="display:inline;fill:url(#radialGradient4529);fill-opacity:1;stroke:none;stroke-width:1;stroke-opacity:1;filter:url(#filter4783)"
+ d="M 102.66589,2.5152127 92.543814,11.318627 h 3.795778 v 7.545783 h 5.061038 v -5.030522 h 2.53052 v 5.030522 h 5.06104 v -7.545783 h 3.79577 z"
+ id="path4209" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ style="display:inline;fill:url(#radialGradient4709);fill-opacity:1;stroke-width:1;filter:url(#filter4721)"
+ id="path4214"
+ d="m 133.03211,12.576257 -6.32629,6.288153 c -0.24,0.36 -0.82401,0.692435 -1.30401,0.692435 -0.48,0 -0.86651,-0.332435 -1.22651,-0.692435 l -6.3263,-6.288153 c -0.79571,-0.72 -0.93921,-1.286268 0.0208,-1.280462 l 3.77501,0.02283 -0.0107,-7.5841022 c -0.001,-0.7199993 0.50796,-1.1722101 1.22796,-1.1722101 h 5.10754 c 0.72,0 1.22426,0.4800094 1.22796,1.2 l 0.0388,7.5563123 3.80118,-0.05062 c 0.95992,-0.01278 0.71459,0.588252 -0.005,1.308252 z" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ style="display:inline;fill:url(#radialGradient4729);fill-opacity:1;stroke-width:1;filter:url(#filter4741)"
+ id="path4225"
+ d="m 148.19571,20.103585 c -4.8,0 -8.89163,-3.97895 -8.89163,-8.77895 0,-4.6800005 4.11959,-8.7145303 8.91959,-8.7145303 4.8,0 8.80776,3.9511599 8.80776,8.7511603 0,4.68 -4.03572,8.74232 -8.83572,8.74232 z m 0.0559,-15.04801 c -3.36,0 -6.39142,2.9178996 -6.39142,6.2779 0,3.24 2.9755,6.22232 6.3355,6.22232 3.36,0 6.33551,-2.86232 6.33551,-6.22232 0,-3.3600004 -2.91959,-6.2779 -6.27959,-6.2779 z m -0.63959,7.520682 c -0.48,-0.12 -0.63716,-0.735044 -0.64429,-1.214992 l -0.0559,-3.7667402 c -0.0107,-0.7199217 0.53592,-1.28337 1.25592,-1.28337 0.72,0 1.27834,0.535601 1.28388,1.25558 l 0.0289,3.7518922 c 1.32,1.32 2.53051,3.772891 2.53051,3.772891 0,0 -3.07896,-1.195261 -4.39896,-2.515261 z" />
+ <path
+ style="display:inline;fill:url(#radialGradient5017);fill-opacity:1;stroke-width:0.79274529"
+ id="path4355"
+ d="m 369.00476,4.7878231 0.94842,1.9083504 0.47422,0.858758 0.94842,0.190835 2.18136,0.3816699 -1.61231,1.7175156 -0.6639,0.667923 0.0948,0.954175 0.37937,2.290022 -1.89685,-0.954178 -0.85358,-0.477086 -0.85358,0.477086 -1.89685,0.954178 0.37938,-2.290022 0.0948,-0.954175 -0.6639,-0.667923 -1.61232,-1.7175156 2.27622,-0.3816699 0.94842,-0.190835 0.37936,-0.858758 0.94843,-1.9083504 m 0,-3.4350309 c -0.28454,0 -0.56906,0.1908348 -0.75874,0.667922 l -1.89683,3.9121193 -4.07821,0.6679227 c -0.94842,0.190835 -1.13812,0.8587576 -0.47421,1.5266802 l 2.94011,3.1487776 -0.66391,4.389208 c -0.0948,0.572504 0.1897,0.954174 0.66391,0.954174 0.18967,0 0.37936,-0.09542 0.56905,-0.190835 l 3.69885,-2.003768 3.69884,2.003768 c 0.18969,0.09543 0.47421,0.190835 0.56906,0.190835 0.4742,0 0.75873,-0.38167 0.6639,-1.049592 l -0.6639,-4.389207 2.9401,-3.1487781 c 0.66391,-0.6679226 0.37938,-1.3358454 -0.4742,-1.5266803 L 371.66031,5.837416 369.76347,1.9252968 C 369.5738,1.543627 369.28926,1.3527922 369.00474,1.3527922 Z" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ style="display:inline;fill:url(#radialGradient4793);fill-opacity:1;stroke-width:1;filter:url(#filter4805)"
+ id="path4366"
+ d="m 202.62125,17.648005 -2.53039,-0.04122 1.26526,2.515261 h -15.18311 l 1.26526,-2.515261 -2.51784,0.02249 c -0.71997,0.0064 -1.31817,-0.563397 -1.31184,-1.28337 l 0.0559,-6.3612693 c 0.006,-0.7199727 0.59192,-1.2112687 1.31183,-1.2000001 l 1.19667,0.018731 0.0313,-2.5298907 c 0.009,-0.7199457 0.51397,-1.243001 1.23397,-1.243001 l 0.022,-1.2403691 c 0.0127,-0.7198886 0.6198,-1.2535885 1.33979,-1.2555799 l 10.04733,-0.02779 c 0.72,-0.00199 1.21931,0.5078413 1.22796,1.22779 l 0.0156,1.295949 c 0.72,0 1.2692,0.5785813 1.26831,1.2985809 l -0.003,2.4743108 1.26513,-10e-8 c 0.71992,-0.010711 1.26205,0.5376375 1.26526,1.2576305 v 6.288153 c 0,0.729621 -0.61509,1.257631 -1.26526,1.257631 z m -15.18298,-0.04122 1.26526,-2.515262 h -1.26526 z m 0.6,-6.288153 h -0.6 -0.6 c -0.36,0 -0.6,0.24 -0.6,0.6 0,0.36 0.24,0.6 0.6,0.6 h 1.2 c 0.36,0 0.6,-0.24 0.6,-0.6 0,-0.36 -0.25348,-0.503016 -0.6,-0.6 z M 198.8256,5.0304738 c 0,-0.72 -0.54528,-1.2523398 -1.26526,-1.2576306 h -7.59155 c -0.71998,-0.00529 -1.28117,0.5378043 -1.26526,1.2576306 v 3.7728917 c 0.0159,0.7198264 0.54528,1.2629215 1.26526,1.2576305 h 7.59155 c 0.71999,-0.0053 1.24942,-0.5078188 1.25592,-1.2277899 z m -1.26526,10.0610482 h -7.59155 l -1.26526,3.772892 h 10.12207 z m 2.53052,0 h -1.26526 l 1.26526,2.515262 z" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ style="display:inline;fill:url(#radialGradient4833);fill-opacity:1;stroke-width:1;filter:url(#filter4853)"
+ id="path4388"
+ d="m 248.17426,18.945466 -16.43876,-0.02779 c -0.96,-0.0016 -1.40468,-0.339789 -1.40858,-1.299781 l -0.0559,-13.7557998 c -0.004,-0.959992 0.47654,-1.3847678 1.43653,-1.3831504 l 16.49468,0.02779 c 0.96,0.00162 1.24473,0.3675784 1.24083,1.3275705 l -0.0559,13.7557997 c -0.004,0.959992 -0.25287,1.356983 -1.21287,1.355361 z M 239.88388,3.8178947 c -0.36,0 -0.6,0.24 -0.6,0.6 0,0.36 0.24,0.6 0.6,0.6 0.36,0 0.6,-0.24 0.6,-0.6 0,-0.36 -0.24,-0.6 -0.6,-0.6 z m 2.64346,0 c -0.36,0 -0.6,0.24 -0.6,0.6 0,0.36 0.36,0.6 0.6,0.6 0.36,0 0.6,-0.24 0.6,-0.6 0,-0.36 -0.24,-0.6 -0.6,-0.6 z m 3.72,0 h -1.2 c -0.36,0 -0.6,0.24 -0.6,0.6 0,0.36 0.24,0.6 0.6,0.6 h 1.2 c 0.36,0 0.6,-0.24 0.6,-0.6 0,-0.36 -0.24,-0.6 -0.6,-0.6 z m 0.65592,4.9945298 c 0.005,-0.7199803 -0.53592,-1.2538267 -1.25592,-1.2555798 l -11.41287,-0.02779 c -0.72,-0.00175 -1.22533,0.5077946 -1.22796,1.2277899 l -0.028,7.6724304 c -0.003,0.719994 0.50796,1.175707 1.22795,1.172209 l 11.44083,-0.05558 c 0.71999,-0.0035 1.19464,-0.507809 1.2,-1.227789 z" />
+ <g
+ aria-label="+"
+ transform="scale(0.98484984,1.0153832)"
+ style="font-style:normal;font-weight:normal;font-size:9.51294327px;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;display:inline;fill:url(#radialGradient4841);fill-opacity:1;stroke:none;stroke-width:0.79274535px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ id="text4409">
+ <path
+ d="m 194.95358,9.8484986 h -2.03077 v 1.9696994 h -1.01538 V 9.8484986 h -2.03077 V 8.8636487 h 2.03077 V 6.893949 h 1.01538 v 1.9696997 h 2.03077 z"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:8.55116463px;line-height:125%;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Bold';text-align:start;writing-mode:lr-tb;text-anchor:start;fill:url(#radialGradient4841);fill-opacity:1;stroke-width:0.79274535px"
+ id="path7725" />
+ </g>
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ d="m 467.25784,24.196945 c -0.36562,0 -0.73125,0.24375 -0.975,0.853125 l -2.4375,4.996876 -5.24062,0.853125 c -1.21875,0.24375 -1.4625,1.096875 -0.60938,1.95 l 3.77813,4.021875 -0.85313,5.60625 c -0.12181,0.73125 0.24375,1.21875 0.85313,1.21875 0.24375,0 0.4875,-0.121875 0.73125,-0.24375 l 4.75312,-2.559375 4.75313,2.559375 c 0.24375,0.121875 0.60937,0.24375 0.73125,0.24375 0.60937,0 0.975,-0.4875 0.85312,-1.340625 l -0.85312,-5.60625 3.77812,-4.021875 c 0.85313,-0.853125 0.4875,-1.70625 -0.60937,-1.95 l -5.24063,-0.853125 -2.4375,-4.996876 c -0.24375,-0.4875 -0.60937,-0.73125 -0.975,-0.73125 z"
+ id="path6182"
+ style="display:inline;fill:url(#radialGradient5031);fill-opacity:1;stroke-width:1.21875;filter:url(#filter5049)" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ d="m 226.66131,15.091518 -0.0388,3.707848 c -0.007,0.673061 -0.5177,1.245488 -1.19079,1.24654 l -17.77799,0.02779 c -0.6731,0.0011 -1.2416,-0.573462 -1.24671,-1.24654 l -0.028,-3.688049 c -0.005,-0.673078 0.6302,-1.305219 1.30331,-1.305219 1.26531,-1.257631 1.03718,-3.269047 1.26531,-5.0305225 0.4278,-2.8226251 0.0953,-6.2244545 3.80878,-6.2479747 l 7.52682,-0.047673 c 3.73297,-0.023644 3.4173,3.4730226 3.84751,6.2956477 0.41438,2.7153765 0,3.7728915 1.26526,5.0305225 0.70548,0 1.26526,0.515815 1.26526,1.25763 z m -7.68106,-4.410561 h -1.82811 V 8.8528316 c 0,-0.8124995 -1.21875,-0.8124995 -1.21875,0 v 1.8281254 h -1.82814 c -0.8125,0 -0.8125,1.218749 0,1.218749 h 1.82814 v 1.828125 c 0,0.812501 1.21875,0.812501 1.21875,0 v -1.828125 h 1.82811 c 0.8125,0 0.8125,-1.218749 0,-1.218749 z"
+ id="path7318"
+ style="display:inline;fill:url(#radialGradient4813);fill-opacity:1;stroke-width:1.21875;filter:url(#filter4825)" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ d="m 269.02823,19.939154 c -0.975,0 -1.82813,-0.24375 -2.80313,-1.584375 -0.975,-1.340625 -2.07187,-3.046875 -2.07187,-3.046875 0,0 -0.85313,-1.096875 -1.34063,-1.95 -0.60937,-0.853125 -1.34062,-0.609375 -1.34062,-0.609375 0,0 -3.53438,-5.7281239 -4.14375,-6.581249 -0.73125,-1.21875 0.73125,-3.290625 0.73125,-3.290625 l 5.3625,8.531249 c 0,0 1.70625,2.315625 2.31562,2.803125 0.60938,0.4875 1.70625,-0.4875 3.4125,1.096875 2.31563,2.19375 1.58438,4.63125 -0.12181,4.63125 z m -0.36563,-3.534375 c -1.09687,-1.21875 -2.07187,-1.096875 -2.31562,-0.73125 -0.24375,0.365625 0,1.4625 0.4875,2.071875 0.4875,0.609375 0.975,0.853125 1.70625,0.853125 0.73125,0.121875 1.34062,-0.853125 0.1218,-2.19375 z m -4.63125,-5.728124 -1.4625,-2.19375 3.53438,-5.60625 c 0,0 1.4625,2.071875 0.73125,3.290625 -0.36563,0.4875001 -1.70625,2.803125 -2.80313,4.509375 z m -5.60625,3.534374 c 0.36563,-0.365625 1.21875,-1.340625 1.70625,-2.071875 l 0.975,1.4625 c -0.4875,0.73125 -1.09687,1.70625 -1.09687,1.70625 0,0 -1.09688,1.70625 -2.07188,3.046875 -0.85312,1.340625 -1.70625,1.584375 -2.80312,1.584375 -1.70625,0 -2.55938,-2.4375 -0.12181,-4.63125 1.70625,-1.4625 2.80312,-0.609375 3.4125,-1.096875 z m -2.925,2.19375 c -1.09687,1.21875 -0.4875,2.19375 0.24375,2.19375 0.73125,0 1.21875,-0.24375 1.70625,-0.853125 0.4875,-0.609375 0.73125,-1.828125 0.4875,-2.071875 -0.36562,-0.365625 -1.34062,-0.4875 -2.4375,0.73125 z"
+ id="path7886"
+ style="display:inline;fill:url(#radialGradient4861);fill-opacity:1;stroke-width:1.21875;filter:url(#filter4873)" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ d="m 292.45286,20.11787 -7.56413,0 c -0.73125,0 -1.32549,-0.515308 -1.33058,-1.24654 l -0.028,-4.017519 v -3.16875 -2.9250003 c 0,-0.73125 0.4875,-1.21875 1.21875,-1.21875 l 6.44059,0.00442 2.53052,2.515261 0.0354,8.810334 c 0.003,0.731244 -0.57138,1.24654 -1.30263,1.24654 z M 289.62394,8.7600607 v 2.4375003 h 2.4375 z m -7.3125,-0.6736655 0.0213,0.7169703 v 2.5152615 3.772891 l -5.06403,0.04019 c -0.73123,0.0058 -1.21672,-0.598663 -1.21875,-1.32991 l -0.028,-10.0556901 c -0.002,-0.7312471 0.4875,-1.21875 1.21875,-1.21875 h 6.09375 l 2.79402,2.5031129 v 1.81343 h -2.53052 c -0.67317,-0.017075 -1.31624,0.241688 -1.28654,1.2424914 z m 0.0213,-4.7304016 v 2.4596813 l 2.53052,-0.02779 z"
+ id="path8454"
+ style="display:inline;fill:url(#radialGradient4881);fill-opacity:1;stroke-width:1.21875;filter:url(#filter4893)" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ d="m 312.76384,20.20124 -10.1808,-0.02779 c -0.75126,-0.0021 -1.21143,-0.547743 -1.21303,-1.272325 l -0.028,-12.6083588 c -0.002,-0.9749978 0.29395,-1.2762443 1.26895,-1.2723257 l 2.49639,0.010033 c 0,0 0.0587,-2.5309032 2.49616,-2.5309032 2.4375,0 2.56487,2.5309032 2.56487,2.5309032 l 2.48359,0.017757 c 0.97497,0.00697 1.35938,0.3251372 1.35283,1.3001156 l -0.0839,12.4971989 c -0.005,0.808245 -0.51762,1.35744 -1.15713,1.355695 z m -1.86968,-13.8508896 -1.34061,-0.609375 c 0,0 0,-1.8281249 -1.95,-1.8281249 -1.95,0 -1.95,1.8281249 -1.95,1.8281249 l -1.34063,0.609375 -0.47079,1.1953846 h 7.59155 z m 0.53952,2.4530151 h -6.32629 l -2.53052,2.5152615 2.53052,5.030522 7.59155,-5.030522 z"
+ id="path9022"
+ style="display:inline;fill:url(#radialGradient4901);fill-opacity:1;stroke-width:1.21875;filter:url(#filter4913)" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ d="M 335.47361,15.091518 V 7.545735 l 3.79578,3.772892 z m -8.85681,1.257631 h 7.59155 l -3.79578,3.772892 z m 6.32629,-1.257631 h -5.06103 c -0.73124,-0.0041 -1.26114,-0.526392 -1.26526,-1.25763 V 8.8033655 c -0.004,-0.7312386 0.53401,-1.2576305 1.26526,-1.2576305 h 5.06103 c 0.73125,0 1.26526,0.5263804 1.26526,1.2576305 v 5.0305225 c 0,0.609375 -0.53402,1.261705 -1.26526,1.25763 z m 0,-3.772891 c 0,-0.73125 -0.53401,-1.257631 -1.26526,-1.257631 h -2.53052 c -0.73125,0 -1.26525,0.526381 -1.26525,1.257631 v 1.25763 c 0,0.73125 0.534,1.257631 1.26525,1.257631 h 2.53052 c 0.73125,0 1.26526,-0.526381 1.26526,-1.257631 z m -2.53052,-8.8034143 3.79578,3.7728917 h -7.59155 z m -5.06103,5.0305223 v 7.545783 l -3.79578,-3.772891 z"
+ id="path9590"
+ style="display:inline;fill:url(#radialGradient4921);fill-opacity:1;stroke-width:1.21875;filter:url(#filter4933)" />
+ <path
+ d="m 274,8 h 10 v 2 h -10 z"
+ id="path10158"
+ style="display:inline;fill:url(#radialGradient4941);fill-opacity:1;stroke-width:0.96615839" />
+ <path
+ d="m 302,10 h -4 v 4 h -2 v -4 h -4 V 8 h 4 V 4 h 2 v 4 h 4 z"
+ id="path10726"
+ style="display:inline;fill:url(#radialGradient4949);fill-opacity:1;stroke-width:0.96615839" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ d="m 406.82037,12.583531 c -0.12181,0.73125 -0.24375,1.4625 -0.4875,2.071875 -0.4875,1.584375 -1.4625,3.046875 -3.04686,4.021874 0.4875,0.4875 1.58436,1.096875 1.58436,1.096875 0,0 -2.4375,0.365625 -4.99686,0.365625 l -0.12181,-0.121875 v 0.121875 c -1.21875,0 -2.4375,-0.365625 -3.65625,-0.73125 0.85311,-0.73125 1.4625,-1.584374 1.95,-2.559374 0.73125,-1.4625 0.73125,-3.65625 0.73125,-3.65625 0,0 1.09686,1.828125 1.70625,2.559375 1.4625,-0.73125 2.4375,-2.19375 2.55936,-3.65625 0.12181,-1.096875 -0.24375,-2.071875 -0.73125,-2.8031253 -0.4875,-0.8531251 -1.21875,-1.3406251 -2.07186,-1.7062501 0.24375,-0.4874999 0.60936,-1.0968749 0.975,-1.584375 0.4875,-0.73125 1.09686,-1.21875 1.58436,-1.4625 2.55939,1.340625 4.3875,4.5093751 4.02189,8.0437504 z m -8.53125,-2.4375 c 0,0 -1.34061,-1.8281254 -1.95,-2.4375004 -1.70625,0.853125 -2.68125,2.4375004 -2.68125,4.1437504 0.12181,1.828125 1.34064,3.290625 2.925,4.021875 -0.36561,0.609375 -0.73125,1.21875 -1.21875,1.70625 -0.4875,0.609375 -1.09686,0.974999 -1.4625,1.340624 -2.80311,-1.706249 -4.50936,-4.874999 -4.02186,-8.287499 0.1218,-0.8531253 0.36561,-1.7062504 0.60936,-2.4375004 0.4875,-1.3406249 1.34064,-2.4375 2.55939,-3.290625 0.1218,-0.121875 0.24375,-0.121875 0.36561,-0.24375 -0.4875,-0.4875 -1.95,-0.975 -1.95,-0.975 0,0 3.04689,-0.975 8.2875,-0.365625 -1.58436,2.315625 -1.4625,6.8250004 -1.4625,6.8250004 z"
+ id="path11294"
+ style="display:inline;fill:url(#radialGradient4957);fill-opacity:1;stroke-width:1.21875;filter:url(#filter4969)" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ d="m 429.73975,20.187746 -2.55504,0.05558 c -0.48738,0.0106 -0.61242,-0.769541 -0.61242,-1.378916 0,0 0.31966,-4.276424 -3.79578,-8.803414 -3.20233,-3.5225593 -8.85682,-3.7728916 -8.85682,-3.7728916 -0.60938,0 -1.26177,-0.171597 -1.25602,-0.6590635 l 0.028,-2.3732094 c 0.006,-0.4874666 0.61868,-0.7406188 1.22806,-0.7406188 0,0 7.05132,0.3003217 11.38734,5.0305223 3.72704,4.065871 5.06103,11.318675 5.06103,11.318675 0,0.4875 -0.0191,1.310083 -0.62831,1.323336 z M 413.91969,8.8033655 c 0,0 4.48239,0.7421569 7.0114,3.0905195 2.59016,2.40515 3.11068,6.970525 3.11068,6.970525 0,0.4875 -0.7448,1.278471 -1.23236,1.278471 h -1.35283 c -0.4875,0 -1.21059,-0.669096 -1.21059,-1.278471 0,0 0.4472,-2.304652 -1.91012,-4.45401 -1.78736,-1.629677 -4.41618,-1.834143 -4.41618,-1.834143 -0.60938,0 -1.23946,-0.793663 -1.22806,-1.281032 l 0.028,-1.195314 c 0.0114,-0.4873685 0.59073,-1.2965455 1.20011,-1.2965455 z m 1.20944,6.3426395 c 1.34062,0 2.54933,1.124665 2.54933,2.46529 0,1.340626 -1.09688,2.576451 -2.4375,2.576451 -1.34063,0 -2.54933,-1.208035 -2.54933,-2.548661 0,-1.340625 1.09687,-2.49308 2.4375,-2.49308 z"
+ id="path11862"
+ style="display:inline;fill:url(#radialGradient4977);fill-opacity:1;stroke-width:1.21875;filter:url(#filter4989)" />
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ d="m 448.08169,13.833888 v 2.515261 c 0,0.975 -0.41214,1.257631 -1.26525,1.257631 h -10.12208 c -0.85314,0 -1.26526,-0.282631 -1.26526,-1.257631 V 6.2881044 c 0,-0.9750001 0.41212,-1.2576306 1.26526,-1.2576306 h 10.12208 c 0.85311,0 1.26525,0.2826305 1.26525,1.2576306 v 2.5152611 l 5.06104,-2.5152611 V 16.349149 Z"
+ id="path12430"
+ style="display:inline;fill:url(#radialGradient4997);fill-opacity:1;stroke-width:1.21875;filter:url(#filter5009)" />
+ <g
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ style="display:inline"
+ id="g4779">
+ <path
+ id="bookmarks-star-4"
+ d="M 165.92886,2.5152127 V 20.122041 h 8.85681 1.26526 2.53052 c 1.26526,0 2.53052,-1.257631 2.53052,-2.515261 V 15.091518 7.545735 5.0304738 c 0,-1.2576306 -1.26526,-2.5152611 -2.53052,-2.5152611 h -2.53052 -1.26526 z m 7.59155,2.5152611 1.26526,2.5152612 c 0,0 0.39827,0.9972133 0.68613,1.1930622 0.26974,0.1834496 1.07417,0.064568 1.07417,0.064568 h 3.30074 l -2.53052,3.1379238 c 0,0 -0.38069,0.332603 -0.51773,0.634968 -0.20593,0.452534 -0.18971,0.518683 -0.12151,1.257631 0.10732,1.162403 0.63924,3.772892 0.63924,3.772892 l -2.53052,-1.257631 c 0,0 -0.77873,-0.42944 -1.26526,-0.430166 -0.5721,0 -1.26526,0.430166 -1.26526,0.430166 l -2.53051,1.257631 c 0,0 0.56012,-2.658345 0.73559,-3.772892 0.11606,-0.733074 0.10061,-0.753616 -0.15752,-1.257631 -0.13995,-0.274086 -0.57807,-0.782455 -0.57807,-0.782455 l -2.53052,-2.9904368 h 3.02161 c 0,0 0.91024,-0.099407 1.27135,-0.3263622 0.27408,-0.1711231 0.76807,-0.9312683 0.76807,-0.9312683 z"
+ style="fill:url(#radialGradient4710);fill-opacity:1;stroke-width:1.265625;filter:url(#filter4729)" />
+ <path
+ id="bookmarks-overlay-1"
+ d="m 163.39834,2.5152127 c -1.26526,0 -2.53052,1.2576305 -2.53052,2.5152611 v 2.5152612 7.545783 2.515262 c 0,1.25763 1.26526,2.515261 2.53052,2.515261 h 2.91839 l 0.0559,-4.840061 V 7.6882296 l 0.028,-5.1730169 z"
+ style="fill:url(#radialGradient4712);fill-opacity:1;stroke:none;stroke-width:1.265625;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers stroke fill;filter:url(#filter4774)" />
+ <path
+ id="bookmarks-divider-7"
+ d="M 165.92886,2.5152127 V 20.122041"
+ style="opacity:0.66300001;fill:url(#radialGradient4714);fill-opacity:1;stroke:url(#radialGradient4750);stroke-width:1.2614392;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <path
+ transform="matrix(0.79035179,0,0,0.79514606,-0.14216927,3.8570695e-5)"
+ style="display:inline;fill:url(#radialGradient4709-1);fill-opacity:1;stroke-width:1;filter:url(#filter4844)"
+ id="path4214-3"
+ d="m 133.03211,35.213608 -6.32629,6.288153 c -0.24,0.36 -0.8202,0.518975 -1.3002,0.518975 -0.48,0 -0.87032,-0.158975 -1.23032,-0.518975 l -6.3263,-6.288153 c -0.6,-0.72 -0.96335,-1.29465 -0.003,-1.287183 l 3.79916,0.02955 -0.0908,-7.535242 c -0.009,-0.719949 0.67572,-1.307177 1.39571,-1.31116 l 5.02367,-0.02779 c 0.71999,-0.004 1.25401,0.619416 1.22796,1.33895 l 0.035,7.535242 3.80499,-0.02955 c 0.95998,-0.0075 0.71078,0.567183 -0.009,1.287183 z" />
+ </g>
+</svg>
diff --git a/themes/windows/aboutCertError.css b/themes/windows/aboutCertError.css
new file mode 100644
index 0000000..dbb3530
--- /dev/null
+++ b/themes/windows/aboutCertError.css
@@ -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/. */
+
+
+html {
+ background: #833;
+}
+
+body {
+ margin: 0;
+ padding: 0 1em;
+ color: -moz-FieldText;
+ font: message-box;
+}
+
+h1 {
+ margin: 0 0 .6em 0;
+ border-bottom: 1px solid ThreeDLightShadow;
+ font-size: 160%;
+}
+
+h2 {
+ font-size: 130%;
+}
+
+#errorPageContainer {
+ position: relative;
+ min-width: 13em;
+ max-width: 52em;
+ margin: 4em auto;
+ border: 2px solid #DD0D09;
+ border-radius: 10px;
+ box-shadow: 0px 0px 8px red;
+ padding: 3em;
+ -moz-padding-start: 30px;
+ background: url("chrome://global/skin/icons/sslWarning.png") left 0 no-repeat -moz-Field;
+ background-origin: content-box;
+}
+
+#errorPageContainer:-moz-dir(rtl) {
+ background-position: right 0;
+}
+
+#errorTitle {
+ -moz-margin-start: 80px;
+}
+
+#errorLongContent {
+ -moz-margin-start: 80px;
+}
+
+.expander > button {
+ -moz-padding-start: 20px;
+ -moz-margin-start: -20px;
+ background: url("chrome://browser/skin/aboutCertError_sectionExpanded.png") left center no-repeat;
+ border: none;
+ font: inherit;
+ color: inherit;
+ cursor: pointer;
+}
+
+.expander > button:-moz-dir(rtl) {
+ background-position: right center;
+}
+
+.expander[collapsed] > button {
+ background-image: url("chrome://browser/skin/aboutCertError_sectionCollapsed.png");
+}
+
+.expander[collapsed] > button:-moz-dir(rtl) {
+ background-image: url("chrome://browser/skin/aboutCertError_sectionCollapsed-rtl.png");
+}
diff --git a/themes/windows/aboutCertError_sectionCollapsed-rtl.png b/themes/windows/aboutCertError_sectionCollapsed-rtl.png
new file mode 100644
index 0000000..84ba18c
--- /dev/null
+++ b/themes/windows/aboutCertError_sectionCollapsed-rtl.png
Binary files differ
diff --git a/themes/windows/aboutCertError_sectionCollapsed.png b/themes/windows/aboutCertError_sectionCollapsed.png
new file mode 100644
index 0000000..c9805f6
--- /dev/null
+++ b/themes/windows/aboutCertError_sectionCollapsed.png
Binary files differ
diff --git a/themes/windows/aboutCertError_sectionExpanded.png b/themes/windows/aboutCertError_sectionExpanded.png
new file mode 100644
index 0000000..128cef9
--- /dev/null
+++ b/themes/windows/aboutCertError_sectionExpanded.png
Binary files differ
diff --git a/themes/windows/aboutPrivateBrowsing.css b/themes/windows/aboutPrivateBrowsing.css
new file mode 100644
index 0000000..cd6026b
--- /dev/null
+++ b/themes/windows/aboutPrivateBrowsing.css
@@ -0,0 +1,47 @@
+%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
+
+body.private > #errorPageContainer {
+ background-image: url("chrome://browser/skin/Privacy-48.png");
+}
+
+body.normal > #errorPageContainer {
+ background-image: url("chrome://global/skin/icons/question-48.png");
+}
+
+#clearRecentHistoryDesc {
+ margin-top: 2em;
+}
+
+#clearRecentHistoryDesc > p {
+ font-size: 110%; /* to match the value set in chrome://global/skin/netError.css */
+}
+
+#startPrivateBrowsingDesc > button {
+ -moz-margin-start: 0;
+}
+
+#footerDesc > p {
+ font-size: 110%; /* to match the value set in chrome://global/skin/netError.css */
+}
+
+#moreInfo {
+ font-size: 110%; /* to match the value set in chrome://global/skin/netError.css */
+ -moz-padding-start: 25px;
+ background: url("chrome://global/skin/icons/information-16.png") no-repeat top left;
+}
+
+#moreInfo:-moz-dir(rtl) {
+ background-position: top right;
+}
+
+#moreInfoText {
+ margin-bottom: 0;
+}
+
+#moreInfoLinkContainer {
+ margin-top: 0.5em;
+}
diff --git a/themes/windows/aboutSessionRestore-window-icon.png b/themes/windows/aboutSessionRestore-window-icon.png
new file mode 100644
index 0000000..0077405
--- /dev/null
+++ b/themes/windows/aboutSessionRestore-window-icon.png
Binary files differ
diff --git a/themes/windows/aboutSessionRestore.css b/themes/windows/aboutSessionRestore.css
new file mode 100644
index 0000000..4fa4907
--- /dev/null
+++ b/themes/windows/aboutSessionRestore.css
@@ -0,0 +1,73 @@
+%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 {
+ background: #f8ffd0;
+ height: 100%;
+}
+
+body {
+ height: 100%;
+ text-align: center;
+}
+
+#errorPageContainer {
+ background-image: url("chrome://global/skin/icons/warning-large.png");
+ display: -moz-box;
+ width: -moz-available;
+ max-width: 85%;
+ height: 75%;
+ max-height: 85%;
+ -moz-box-orient: vertical;
+ text-align: start;
+ border: 2px solid #efc;
+ box-shadow: 0px 0px 8px #aaa;
+}
+
+#errorShortDesc > p {
+ margin-top: 0.4em;
+ margin-bottom: 0;
+}
+
+#errorLongContent, #errorTrailerDesc {
+ display: -moz-box;
+ -moz-box-flex: 1;
+ -moz-box-orient: vertical;
+}
+
+#tabList {
+ margin-top: 2.5em;
+ width: 100%;
+ min-height: 12em;
+}
+
+treechildren::-moz-tree-image(icon),
+treechildren::-moz-tree-image(noicon) {
+ padding-right: 2px;
+ margin: 0px 2px;
+ width: 16px;
+ height: 16px;
+}
+
+treechildren::-moz-tree-image(noicon) {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+treechildren::-moz-tree-image(container, noicon) {
+ list-style-image: url("chrome://browser/skin/aboutSessionRestore-window-icon.png");
+}
+treechildren::-moz-tree-image(checked) {
+ list-style-image: url("chrome://global/skin/checkbox/cbox-check.gif");
+}
+treechildren::-moz-tree-image(partial) {
+ list-style-image: url("chrome://global/skin/checkbox/cbox-check-dis.gif");
+}
+
+#buttons {
+ width: 100%;
+}
+#buttons > button {
+ margin-top: 2em;
+}
diff --git a/themes/windows/aboutSyncTabs.css b/themes/windows/aboutSyncTabs.css
new file mode 100644
index 0000000..4f21a9d
--- /dev/null
+++ b/themes/windows/aboutSyncTabs.css
@@ -0,0 +1,101 @@
+/* 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/. */
+
+#tabs-display,
+#tabsList {
+ background-color: transparent;
+ -moz-appearance: none;
+ margin: 0;
+}
+
+#tabsList {
+ width: 100%;
+}
+
+#tabs-display {
+ background: #fff url(chrome://browser/skin/sync-bg.png) repeat-x center -80px;
+}
+
+#headers {
+ background: url(chrome://browser/skin/sync-32.png) no-repeat;
+ margin-top: 4px;
+ width: 45em;
+ height: 32px;
+ -moz-margin-start: 2em;
+ -moz-margin-end: 2em;
+}
+
+#tabsListHeading {
+ font-size: 140%;
+ font-weight: bold;
+ -moz-margin-start: 40px;
+}
+
+richlistitem {
+ -moz-margin-end: 2em;
+}
+
+richlistitem[selected="true"],
+richlistitem:focus {
+ outline-style: none;
+}
+
+richlistitem[type="tab"] {
+ min-height: 3em;
+ border: #999999 1px solid !important;
+ padding: 2px 5px;
+ margin-bottom: 4px;
+ -moz-margin-start: 4em;
+ border-radius: 6px;
+ background-color: menu;
+ width: 44em;
+ opacity: 0.9;
+ box-shadow:
+ inset rgba(255, 255, 255, 0.5) 0 1px 0px,
+ inset rgba(0, 0, 0, 0.1) 0 -2px 0px,
+ rgba(0, 0, 0, 0.1) 0px 1px 0px;
+}
+
+richlistitem[type="tab"][selected="true"] {
+ background-color: -moz-MenuHover;
+}
+
+richlistitem[type="client"] {
+ min-height: 2em;
+ color: #000000;
+ -moz-margin-start: 2em;
+ margin-top: 2px;
+ margin-bottom: 3px;
+ width: 42em;
+ border-radius: 6px;
+ background-color: transparent;
+ -moz-user-focus: ignore !important;
+}
+richlistitem.mobile[type="client"] {
+ list-style-image: url("chrome://browser/skin/sync-mobileIcon.png");
+}
+richlistitem.desktop[type="client"] {
+ list-style-image: url("chrome://browser/skin/sync-desktopIcon.png");
+}
+
+.title,
+.clientName {
+ color: #000000;
+ font-size: 1.1em;
+}
+
+.title[selected="true"],
+.url[selected="true"] {
+ color: inherit;
+}
+
+.url {
+ color: -moz-nativehyperlinktext;
+ font-size: 0.95em;
+}
+
+.tabIcon {
+ -moz-padding-start: 2px;
+ padding-top: 2px;
+}
diff --git a/themes/windows/actionicon-tab.png b/themes/windows/actionicon-tab.png
new file mode 100644
index 0000000..ced958e
--- /dev/null
+++ b/themes/windows/actionicon-tab.png
Binary files differ
diff --git a/themes/windows/appmenu-dropmarker.png b/themes/windows/appmenu-dropmarker.png
new file mode 100644
index 0000000..27deaff
--- /dev/null
+++ b/themes/windows/appmenu-dropmarker.png
Binary files differ
diff --git a/themes/windows/appmenu-icons.png b/themes/windows/appmenu-icons.png
new file mode 100644
index 0000000..78f3658
--- /dev/null
+++ b/themes/windows/appmenu-icons.png
Binary files differ
diff --git a/themes/windows/autocomplete.css b/themes/windows/autocomplete.css
new file mode 100644
index 0000000..b3cab44
--- /dev/null
+++ b/themes/windows/autocomplete.css
@@ -0,0 +1,238 @@
+/* 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/. */
+
+/* ===== autocomplete.css =================================================
+ == Styles used by the autocomplete widget.
+ ======================================================================= */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
+
+/* ::::: autocomplete ::::: */
+
+/* .padded is used by autocomplete widgets that don't have an icon. Gross. -dwh */
+textbox:not(.padded) {
+ cursor: default;
+ padding: 0;
+}
+
+textbox[nomatch="true"][highlightnonmatches="true"] {
+ color: red;
+}
+
+.private-autocomplete-textbox-container {
+ -moz-box-align: center;
+}
+
+textbox:not(.padded) .textbox-input-box {
+ margin: 0 3px;
+}
+
+.textbox-input-box {
+ -moz-box-align: center;
+}
+
+/* ::::: autocomplete popups ::::: */
+
+panel[type="private-autocomplete"],
+panel[type="private-autocomplete-richlistbox"],
+.private-autocomplete-history-popup {
+ -moz-appearance: none;
+ border-width: 1px;
+ -moz-border-top-colors: ThreeDShadow;
+ -moz-border-right-colors: ThreeDShadow;
+ -moz-border-bottom-colors: ThreeDShadow;
+ -moz-border-left-colors: ThreeDShadow;
+ padding: 0;
+ color: -moz-FieldText;
+ background-color: -moz-Field;
+}
+
+.private-autocomplete-history-popup {
+ max-height: 180px;
+}
+
+/* ::::: tree ::::: */
+
+.private-autocomplete-tree {
+ -moz-appearance: none !important;
+ border: none !important;
+ background-color: transparent !important;
+}
+
+.private-autocomplete-treecol {
+ -moz-appearance: none !important;
+ margin: 0 !important;
+ border: none !important;
+ padding: 0 !important;
+}
+
+/* GTK calculates space for a sort arrow */
+.private-autocomplete-treecol > .treecol-sortdirection {
+ -moz-appearance: none !important;
+}
+
+.private-autocomplete-treebody::-moz-tree-cell-text {
+ -moz-padding-start: 8px;
+}
+
+treechildren.private-autocomplete-treebody::-moz-tree-row(selected) {
+ background-color: Highlight;
+}
+
+treechildren.private-autocomplete-treebody::-moz-tree-cell-text(selected) {
+ color: HighlightText !important;
+}
+
+.private-autocomplete-treebody::-moz-tree-image(treecolAutoCompleteValue) {
+ max-width: 16px;
+ height: 16px;
+}
+
+/* ::::: richlistbox autocomplete ::::: */
+
+.private-autocomplete-richlistbox {
+ -moz-appearance: none;
+ margin: 0;
+}
+
+.private-autocomplete-richlistitem {
+ padding: 1px;
+}
+
+.private-autocomplete-richlistitem[selected="true"] {
+ background-color: Highlight;
+ color: HighlightText;
+}
+
+%ifdef XP_WIN
+@media (-moz-os-version: windows-vista) and (-moz-windows-default-theme),
+ (-moz-os-version: windows-win7) and (-moz-windows-default-theme) {
+ .private-autocomplete-richlistitem[selected="true"] {
+ color: inherit;
+ background-color: transparent;
+ /* four gradients for the bevel highlights on each edge, one for blue background */
+ background-image:
+ linear-gradient(to bottom, rgba(255,255,255,0.9) 3px, transparent 3px),
+ linear-gradient(to right, rgba(255,255,255,0.5) 3px, transparent 3px),
+ linear-gradient(to left, rgba(255,255,255,0.5) 3px, transparent 3px),
+ linear-gradient(to top, rgba(255,255,255,0.4) 3px, transparent 3px),
+ linear-gradient(to bottom, rgba(163,196,247,0.3), rgba(122,180,246,0.3));
+ background-clip: content-box;
+ border-radius: 6px;
+ outline: 1px solid rgb(124,163,206);
+ -moz-outline-radius: 3px;
+ outline-offset: -2px;
+ }
+}
+%endif
+
+.ac-title-box {
+ margin-top: 4px;
+}
+
+.ac-url-box {
+ /* When setting a vertical margin here, half of that needs to be added
+ .ac-title-box's translateY for when .ac-url-box is hidden (see below). */
+ margin: 1px 0 4px;
+}
+
+.private-autocomplete-richlistitem[actiontype="keyword"] .ac-url-box,
+.private-autocomplete-richlistitem[actiontype="searchengine"] .ac-url-box,
+.private-autocomplete-richlistitem[actiontype="visiturl"] .ac-url-box,
+.private-autocomplete-richlistitem[type~="autofill"] .ac-url-box {
+ visibility: hidden;
+}
+
+.private-autocomplete-richlistitem[actiontype="keyword"] .ac-title-box,
+.private-autocomplete-richlistitem[actiontype="searchengine"] .ac-title-box,
+.private-autocomplete-richlistitem[actiontype="visiturl"] .ac-title-box,
+.private-autocomplete-richlistitem[type~="autofill"] .ac-title-box {
+ /* Center the title by moving it down by half of .ac-url-box's height,
+ including vertical margins (if any). */
+ transform: translateY(calc(.5em + 2px));
+}
+
+.ac-site-icon {
+ width: 16px;
+ height: 16px;
+ margin: 0 5px -2px;
+}
+
+.ac-type-icon {
+ width: 16px;
+ height: 16px;
+ -moz-margin-start: 6px;
+ -moz-margin-end: 4px;
+ margin-bottom: -1px;
+}
+
+.ac-url-box > .ac-site-icon,
+.ac-url-box > .ac-type-icon {
+ /* Otherwise the spacer is big enough to stretch its container */
+ height: auto;
+}
+
+.ac-extra > .ac-result-type-tag {
+ margin: 0 4px;
+}
+
+.ac-extra > .ac-comment {
+ padding-right: 4px;
+}
+
+.ac-ellipsis-after {
+ margin: 0 !important;
+ padding: 0;
+ min-width: 1em;
+}
+
+.ac-normal-text {
+ margin: 0 !important;
+ padding: 0;
+}
+
+.ac-normal-text > html|span {
+ margin: 0 !important;
+ padding: 0;
+}
+
+html|span.ac-emphasize-text {
+ box-shadow: inset 0 0 1px 1px rgba(208,208,208,0.5);
+ background-color: rgba(208,208,208,0.3);
+ border-radius: 2px;
+ text-shadow: 0 0 currentColor;
+}
+
+@media (-moz-windows-default-theme) {
+ @media not all and (-moz-os-version: windows-xp) {
+ html|span.ac-emphasize-text {
+ box-shadow: inset 0 0 1px 1px rgba(0,0,0,0.1);
+ background-color: rgba(0,0,0,0.05);
+ }
+ }
+
+ @media (-moz-os-version: windows-xp) {
+ .ac-url-text > html|span.ac-emphasize-text,
+ .ac-action-text > html|span.ac-emphasize-text {
+ box-shadow: inset 0 0 1px 1px rgba(202,214,201,0.3);
+ background-color: rgba(202,214,201,0.2);
+ }
+ }
+}
+
+.ac-title, .ac-url {
+ overflow: hidden;
+}
+
+/* ::::: textboxes inside toolbarpaletteitems ::::: */
+
+toolbarpaletteitem > toolbaritem > textbox > hbox > hbox > html|*.textbox-input {
+ visibility: hidden;
+}
+
+toolbarpaletteitem > toolbaritem > * > textbox > hbox > hbox > html|*.textbox-input {
+ visibility: hidden;
+}
+
diff --git a/themes/windows/browser.css b/themes/windows/browser.css
new file mode 100644
index 0000000..88c3087
--- /dev/null
+++ b/themes/windows/browser.css
@@ -0,0 +1,3856 @@
+/* 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/. */
+
+@import url("chrome://global/skin/");
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
+
+%include ../shared/browser.inc
+%filter substitution
+%define toolbarShadowColor rgba(10%,10%,10%,.4)
+%define toolbarShadowOnTab linear-gradient(to top, rgba(10%,10%,10%,.4) 1px, transparent 1px)
+%define navbarTextboxCustomBorder border-color: rgba(0,0,0,.32);
+%define navbarLargeIcons #navigator-toolbox[iconsize=large][mode=icons] > #nav-bar
+%define forwardTransitionLength 150ms
+%define conditionalForwardWithUrlbar window:not([chromehidden~=toolbar]) #navigator-toolbox[iconsize=large][mode=icons] > :-moz-any(#nav-bar[currentset*="unified-back-forward-button"],#nav-bar:not([currentset])) > #unified-back-forward-button
+%define conditionalForwardWithUrlbar2 window:not([chromehidden~=toolbar]) #navigator-toolbox[iconsize=large][mode=icons] > :-moz-any(#nav-bar[currentset*="unified-back-forward-button"]:not([currentset*="unified-back-forward-button,urlbar-container"]),#nav-bar:not([currentset])) > #unified-back-forward-button
+%define conditionalForwardWithUrlbarWidth 27
+%define glassActiveBorderColor rgb(37, 44, 51)
+%define glassInactiveBorderColor rgb(102, 102, 102)
+
+%ifdef MOZ_OFFICIAL_BRANDING
+%define appMenuButtonBorderColor rgba(255,255,255,.5) rgba(6,42,83,.9)
+%else
+%if MOZ_UPDATE_CHANNEL == aurora
+%define appMenuButtonBorderColor hsla(0,0%,100%,.5) hsla(214,89%,21%,.9)
+%else
+%define appMenuButtonBorderColor hsla(0,0%,100%,.5) hsla(210,59%,13%,.9)
+%endif
+%endif
+
+:root {
+ --toolbox-after-color: ThreeDShadow;
+
+ --toolbar-custom-color: hsl(210,75%,92%);
+ --toolbar-highlight-top: rgba(255,255,255,.5);
+ --toolbar-highlight-bottom: transparent;
+
+ --toolbarbutton-background-color: hsla(210,32%,93%,.3);
+ --toolbarbutton-border-radius: 2.5px;
+ --toolbarbutton-border-color: hsla(210,54%,20%,.2);
+
+ --toolbarbutton-image: url("chrome://browser/skin/Toolbar.png");
+ --toolbarbutton-glass-image: url("chrome://browser/skin/Toolbar-glass.png");
+ --toolbarbutton-inverted-image: url("chrome://browser/skin/Toolbar-inverted.png");
+
+ --tab-background: linear-gradient(transparent, hsla(0,0%,45%,.1) 1px, hsla(0,0%,32%,.2) 80%, hsla(0,0%,0%,.2));
+ --tab-background-hover: linear-gradient(hsla(0,0%,100%,.3) 1px, hsla(0,0%,75%,.2) 80%, hsla(0,0%,60%,.2));
+ --tab-border-radius: 6px;
+ --tab-box-shadow: inset 0.5px 1px 1px var(--tab-selected-highlight);
+ --tab-selected-highlight: rgba(255,255,255,.7);
+
+ --window-text-color: currentColor;
+}
+
+/* Use SVG for HiDPI 133%+ */
+@media (min-resolution: 1.33dppx) {
+ :root {
+ --toolbarbutton-image: url("chrome://browser/skin/Toolbar.svg");
+ --toolbarbutton-glass-image: url("chrome://browser/skin/Toolbar-glass.svg");
+ --toolbarbutton-inverted-image: url("chrome://browser/skin/Toolbar-inverted.svg");
+ }
+}
+
+:root:-moz-lwtheme-brighttext {
+ --toolbar-highlight-top: rgba(32,32,32,.8);
+ --toolbar-highlight-bottom: rgba(32,32,32,0);
+}
+
+:root:-moz-lwtheme-darktext {
+ --toolbar-highlight-top: rgba(255,255,255,.8);
+
+ --tab-selected-highlight: rgba(255,255,255,.6);
+}
+
+#menubar-items {
+ -moz-box-orient: vertical; /* for flex hack */
+}
+
+#main-menubar {
+ -moz-box-flex: 1; /* make menu items expand to fill toolbar height */
+}
+
+#navigator-toolbox {
+ -moz-appearance: none;
+ background-color: transparent;
+ border-top: none;
+}
+
+#navigator-toolbox::after {
+ content: "";
+ display: -moz-box;
+ -moz-box-ordinal-group: 101; /* tabs toolbar is 100 */
+ height: 1px;
+ background-color: var(--toolbox-after-color);
+}
+#navigator-toolbox[tabsontop=false]::after,
+#main-window[disablechrome] #navigator-toolbox::after {
+ visibility: collapse;
+}
+
+#navigator-toolbox > toolbar:not(:-moz-lwtheme) {
+ -moz-appearance: none;
+ border-style: none;
+ background-color: -moz-Dialog;
+}
+
+@media not all and (-moz-windows-compositor) {
+ #main-window[tabsintitlebar]:not([inFullscreen]) #toolbar-menubar[inactive] ~ #TabsToolbar:not(:-moz-lwtheme) {
+ background: linear-gradient(to top, @toolbarShadowColor@ 1px, transparent 1px),
+ linear-gradient(rgba(50%,50%,50%,0), ActiveCaption 85%);
+ color: CaptionText;
+ }
+ #main-window[tabsintitlebar]:not([inFullscreen]) #toolbar-menubar[inactive] ~ #TabsToolbar:not(:-moz-lwtheme):-moz-window-inactive {
+ background: linear-gradient(to top, @toolbarShadowColor@ 1px, transparent 1px),
+ linear-gradient(rgba(50%,50%,50%,0), InactiveCaption 85%);
+ color: InactiveCaptionText;
+ }
+
+ #main-window[tabsintitlebar] #titlebar:-moz-lwtheme {
+ visibility: hidden;
+ }
+ #main-window[tabsintitlebar] #titlebar-content:-moz-lwtheme {
+ -moz-binding: url("chrome://global/content/bindings/general.xml#windowdragbox");
+ visibility: visible;
+ }
+
+ #main-menubar > menu:not(:-moz-lwtheme) {
+ color: inherit; /* allow menubar items to be styled */
+ }
+}
+
+#nav-bar[tabsontop=true],
+#nav-bar[tabsontop=true][collapsed=true]:not([customizing]) + toolbar,
+#nav-bar[tabsontop=true][collapsed=true]:not([customizing]) + #customToolbars + #PersonalToolbar {
+ background-image: linear-gradient(var(--toolbar-highlight-top), var(--toolbar-highlight-bottom));
+}
+
+#personal-bookmarks {
+ min-height: 24px;
+}
+
+#print-preview-toolbar:not(:-moz-lwtheme) {
+ -moz-appearance: toolbox;
+}
+
+#browser-bottombox:not(:-moz-lwtheme) {
+ background-color: -moz-dialog;
+}
+
+/* ::::: app menu button ::::: */
+
+#appmenu-button {
+ -moz-appearance: none;
+ background-clip: padding-box;
+ border: 1px solid;
+ border-top: none;
+ color: white;
+ text-shadow: 0 0 1px rgba(0,0,0,.7),
+ 0 1px 1.5px rgba(0,0,0,.5);
+ font-weight: bold;
+ padding: 0 1.5em .05em;
+ margin: 0 0 2px;
+}
+
+@media (-moz-os-version: windows-vista),
+ (-moz-os-version: windows-win7) {
+ #appmenu-button {
+ border-radius: 0 0 4px 4px;
+ }
+}
+
+@media (-moz-windows-classic) {
+ #appmenu-button {
+ margin-bottom: 1px;
+ }
+}
+
+#appmenu-button:hover:active,
+#appmenu-button[open] {
+ border-radius: 0;
+}
+
+%ifdef MOZ_OFFICIAL_BRANDING
+#appmenu-button {
+ background-image: linear-gradient(rgb(82,182,247), rgb(10,98,215) 95%);
+ border-color: rgba(6,42,83,.9);
+ box-shadow: 0 1px 0 rgba(255,255,255,.25) inset,
+ 0 0 0 1px rgba(255,255,255,.25) inset;
+}
+#appmenu-button:hover:not(:active):not([open]) {
+ background-image: radial-gradient(farthest-side at bottom, rgba(89,240,252,.5) 10%, rgba(89,240,252,0) 70%),
+ radial-gradient(farthest-side at bottom, rgb(60,68,236), rgba(172,229,255,0)),
+ linear-gradient(rgb(69,170,246), rgb(30,40,209) 95%);
+ border-color: rgba(6,42,83,.9);
+ box-shadow: 0 1px 0 rgba(255,255,255,.1) inset,
+ 0 0 2px 1px rgba(169,234,250,.7) inset,
+ 0 -1px 0 rgba(169,234,250,.5) inset;
+}
+#appmenu-button:hover:active,
+#appmenu-button[open] {
+ background-image: linear-gradient(rgb(69,170,246), rgb(0,74,209) 95%);
+ box-shadow: 0 2px 3px rgba(0,0,0,.4) inset,
+ 0 1px 1px rgba(0,0,0,.2) inset;
+}
+%else
+%if MOZ_UPDATE_CHANNEL == aurora
+#appmenu-button {
+ background-image: linear-gradient(hsl(208,99%,37%), hsl(214,90%,23%) 95%);
+ border-color: hsla(214,89%,21%,.9);
+ box-shadow: 0 1px 0 hsla(205,100%,72%,.2) inset,
+ 0 0 2px 1px hsla(205,100%,72%,.25) inset;
+}
+#appmenu-button:hover:not(:active):not([open]) {
+ background-image: radial-gradient(farthest-side at bottom, hsla(202,100%,85%,.5) 10%, hsla(202,100%,85%,0) 70%),
+ radial-gradient(farthest-side at bottom, hsla(205,100%,72%,.7), hsla(205,100%,72%,0)),
+ linear-gradient(hsl(208,98%,34%), hsl(213,87%,20%) 95%);
+ border-color: hsla(214,89%,21%,.9);
+ box-shadow: 0 1px 0 hsla(205,100%,72%,.15) inset,
+ 0 0 2px 1px hsla(205,100%,72%,.5) inset,
+ 0 -1px 0 hsla(205,100%,72%,.2) inset;
+}
+#appmenu-button:hover:active,
+#appmenu-button[open] {
+ background-image: linear-gradient(hsl(208,95%,30%), hsl(214,85%,17%) 95%);
+ box-shadow: 0 2px 3px rgba(0,0,0,.4) inset,
+ 0 1px 1px rgba(0,0,0,.2) inset;
+}
+%else
+#appmenu-button {
+ background-image: linear-gradient(hsl(211,33%,32%), hsl(209,53%,10%) 95%);
+ border-color: hsla(210,59%,13%,.9);
+ box-shadow: 0 1px 0 hsla(210,48%,90%,.15) inset,
+ 0 0 2px 1px hsla(211,65%,85%,.15) inset;
+}
+#appmenu-button:hover:not(:active):not([open]) {
+ background-image: radial-gradient(farthest-side at bottom, hsla(210,48%,90%,.5) 10%, hsla(210,48%,90%,0) 70%),
+ radial-gradient(farthest-side at bottom, hsla(211,70%,83%,.5), hsla(211,70%,83%,0)),
+ linear-gradient(hsl(211,33%,32%), hsl(209,53%,10%) 95%);
+ border-color: hsla(210,59%,13%,.9);
+ box-shadow: 0 1px 0 hsla(210,48%,90%,.15) inset,
+ 0 0 2px 1px hsla(210,48%,90%,.4) inset,
+ 0 -1px 0 hsla(210,48%,90%,.2) inset;
+}
+#appmenu-button:hover:active,
+#appmenu-button[open] {
+ background-image: linear-gradient(hsl(211,33%,26%), hsl(209,53%,6%) 95%);
+ box-shadow: 0 2px 3px rgba(0,0,0,.4) inset,
+ 0 1px 1px rgba(0,0,0,.2) inset;
+}
+%endif
+%endif
+
+#main-window[privatebrowsingmode=temporary] #appmenu-button {
+ background-image: linear-gradient(rgb(153,38,211), rgb(105,19,163) 95%);
+ border-color: rgba(43,8,65,.9);
+}
+
+#main-window[privatebrowsingmode=temporary] #appmenu-button:hover:not(:active):not([open]) {
+ background-image: radial-gradient(farthest-side at bottom, rgba(240,193,255,.5) 10%, rgba(240,193,255,0) 70%),
+ radial-gradient(farthest-side at bottom, rgb(192,81,247), rgba(236,172,255,0)),
+ linear-gradient(rgb(144,20,207), rgb(95,0,158) 95%);
+ border-color: rgba(43,8,65,.9);
+ box-shadow: 0 1px 0 rgba(255,255,255,.1) inset,
+ 0 0 2px 1px rgba(240,193,255,.7) inset,
+ 0 -1px 0 rgba(240,193,255,.5) inset;
+}
+
+#main-window[privatebrowsingmode=temporary] #appmenu-button:hover:active,
+#main-window[privatebrowsingmode=temporary] #appmenu-button[open] {
+ background-image: linear-gradient(rgb(144,20,207), rgb(95,0,158) 95%);
+}
+
+
+
+#appmenu-button > .button-box {
+ border-style: none;
+ padding: 0;
+}
+
+#appmenu-button > .button-box > .button-menu-dropmarker {
+ list-style-image: url(appmenu-dropmarker.png);
+ width: auto;
+ height: auto;
+ padding: 0;
+ margin: 0;
+ -moz-margin-start: .5em;
+}
+
+.splitmenu-menuitem {
+ -moz-margin-end: 1px;
+ -moz-padding-end: 0.5em;
+}
+
+.splitmenu-menu {
+ -moz-box-pack: end;
+}
+
+.appmenu-edit-button {
+ -moz-appearance: none;
+ border: 1px solid transparent;
+ padding: 2px;
+ background: transparent;
+ border-radius: 3px;
+}
+
+.appmenu-edit-button[disabled="true"] {
+ opacity: .3;
+}
+
+#appmenuPrimaryPane {
+ -moz-border-end: 1px solid ThreeDShadow;
+}
+
+@media (-moz-windows-default-theme) {
+ #appmenu-popup {
+ -moz-appearance: none;
+ background: white;
+ border: 1px solid ThreeDShadow;
+ }
+ #appmenuPrimaryPane {
+ background-color: rgba(255,255,255,0.5);
+ padding: 2px;
+ -moz-border-end: none;
+ }
+ #appmenuSecondaryPane {
+ background-color: #f1f5fb;
+ box-shadow: 1px 0 2px rgb(204,214,234) inset;
+ -moz-padding-start: 3px;
+ -moz-padding-end: 2px;
+ padding-top: 2px;
+ padding-bottom: 2px;
+ font-family: "Segoe UI Semibold", "Segoe UI", sans-serif;
+ }
+ #appmenuSecondaryPane:-moz-locale-dir(rtl) {
+ box-shadow: -1px 0 2px rgb(204,214,234) inset;
+ }
+
+ #appmenuPrimaryPane menupopup {
+ -moz-appearance: none;
+ background-image: linear-gradient(to right, white 26px, ThreeDLightShadow 26px,
+ ThreeDLightShadow 27px, ThreeDHighlight 27px,
+ ThreeDHighlight 28px, white 28px);
+ border: 3px solid;
+ -moz-border-top-colors: ThreeDShadow white;
+ -moz-border-bottom-colors: ThreeDShadow white;
+ -moz-border-left-colors: ThreeDShadow white;
+ -moz-border-right-colors: ThreeDShadow white;
+ }
+
+ #appmenuSecondaryPane menupopup {
+ -moz-appearance: none;
+ background-image: linear-gradient(to right, #f1f5fb 26px, ThreeDLightShadow 26px,
+ ThreeDLightShadow 27px, ThreeDHighlight 27px,
+ ThreeDHighlight 28px, #f1f5fb 28px);
+ border: 3px solid;
+ -moz-border-top-colors: ThreeDShadow #f1f5fb;
+ -moz-border-bottom-colors: ThreeDShadow #f1f5fb;
+ -moz-border-left-colors: ThreeDShadow #f1f5fb;
+ -moz-border-right-colors: ThreeDShadow #f1f5fb;
+ }
+
+ #appmenuPrimaryPane menupopup:-moz-locale-dir(rtl) {
+ background-image: linear-gradient(to left, white 26px, ThreeDLightShadow 26px,
+ ThreeDLightShadow 27px, ThreeDHighlight 27px,
+ ThreeDHighlight 28px, white 28px);
+ }
+ #appmenuSecondaryPane menupopup:-moz-locale-dir(rtl) {
+ background-image: linear-gradient(to left, #f1f5fb 26px, ThreeDLightShadow 26px,
+ ThreeDLightShadow 27px, ThreeDHighlight 27px,
+ ThreeDHighlight 28px, #f1f5fb 28px);
+ }
+
+ /* Hi-DPI overrides of the menu backgrounds, to adjust where the gutter line falls */
+ @media (min-resolution: 1.25dppx) {
+ #appmenuPrimaryPane menupopup {
+ background-image: linear-gradient(to right, white 22.4px, ThreeDLightShadow 22.4px,
+ ThreeDLightShadow 23.2px, ThreeDHighlight 23.2px,
+ ThreeDHighlight 24px, white 24px);
+ }
+ #appmenuSecondaryPane menupopup {
+ background-image: linear-gradient(to right, #f1f5fb 22.4px, ThreeDLightShadow 22.4px,
+ ThreeDLightShadow 23.2px, ThreeDHighlight 23.2px,
+ ThreeDHighlight 24px, #f1f5fb 24px);
+ }
+ #appmenuPrimaryPane menupopup:-moz-locale-dir(rtl) {
+ background-image: linear-gradient(to left, white 22.4px, ThreeDLightShadow 22.4px,
+ ThreeDLightShadow 23.2px, ThreeDHighlight 23.2px,
+ ThreeDHighlight 24px, white 24px);
+ }
+ #appmenuSecondaryPane menupopup:-moz-locale-dir(rtl) {
+ background-image: linear-gradient(to left, #f1f5fb 22.4px, ThreeDLightShadow 22.4px,
+ ThreeDLightShadow 23.2px, ThreeDHighlight 23.2px,
+ ThreeDHighlight 24px, #f1f5fb 24px);
+ }
+ }
+
+ @media (min-resolution: 1.5dppx) {
+ #appmenuPrimaryPane menupopup {
+ background-image: linear-gradient(to right, white 20.6667px, ThreeDLightShadow 20.6667px,
+ ThreeDLightShadow 21.3333px, ThreeDHighlight 21.3333px,
+ ThreeDHighlight 22px, white 22px);
+ }
+ #appmenuSecondaryPane menupopup {
+ background-image: linear-gradient(to right, #f1f5fb 20.6667px, ThreeDLightShadow 20.6667px,
+ ThreeDLightShadow 21.3333px, ThreeDHighlight 21.3333px,
+ ThreeDHighlight 22px, #f1f5fb 22px);
+ }
+ #appmenuPrimaryPane menupopup:-moz-locale-dir(rtl) {
+ background-image: linear-gradient(to left, white 20.6667px, ThreeDLightShadow 20.6667px,
+ ThreeDLightShadow 21.3333px, ThreeDHighlight 21.3333px,
+ ThreeDHighlight 22px, white 22px);
+ }
+ #appmenuSecondaryPane menupopup:-moz-locale-dir(rtl) {
+ background-image: linear-gradient(to left, #f1f5fb 20.6667px, ThreeDLightShadow 20.6667px,
+ ThreeDLightShadow 21.3333px, ThreeDHighlight 21.3333px,
+ ThreeDHighlight 22px, #f1f5fb 22px);
+ }
+ }
+
+ @media (min-resolution: 2dppx) {
+ #appmenuPrimaryPane menupopup {
+ background-image: linear-gradient(to right, white 19.5px, ThreeDLightShadow 19.5px,
+ ThreeDLightShadow 20px, ThreeDHighlight 20px,
+ ThreeDHighlight 20.5px, white 20.5px);
+ }
+ #appmenuSecondaryPane menupopup {
+ background-image: linear-gradient(to right, #f1f5fb 19.5px, ThreeDLightShadow 19.5px,
+ ThreeDLightShadow 20px, ThreeDHighlight 20px,
+ ThreeDHighlight 20.5px, #f1f5fb 20.5px);
+ }
+ #appmenuPrimaryPane menupopup:-moz-locale-dir(rtl) {
+ background-image: linear-gradient(to left, white 19.5px, ThreeDLightShadow 19.5px,
+ ThreeDLightShadow 20px, ThreeDHighlight 20px,
+ ThreeDHighlight 20.5px, white 20.5px);
+ }
+ #appmenuSecondaryPane menupopup:-moz-locale-dir(rtl) {
+ background-image: linear-gradient(to left, #f1f5fb 19.5px, ThreeDLightShadow 19.5px,
+ ThreeDLightShadow 20px, ThreeDHighlight 20px,
+ ThreeDHighlight 20.5px, #f1f5fb 20.5px);
+ }
+ }
+
+ .appmenu-menuseparator {
+ -moz-appearance: none;
+ margin-top: 3px;
+ margin-bottom: 3px;
+ -moz-margin-start: 30px;
+ padding: 0;
+ border-top: 1px solid #d6e5f5;
+ border-bottom: none;
+ }
+
+ @media (min-resolution: 1.25dppx) {
+ .appmenu-menuseparator {
+ -moz-margin-start: 25px;
+ }
+ }
+ @media (min-resolution: 1.5dppx) {
+ .appmenu-menuseparator {
+ -moz-margin-start: 24px;
+ }
+ }
+ @media (min-resolution: 2dppx) {
+ .appmenu-menuseparator {
+ -moz-margin-start: 22px;
+ }
+ }
+
+ .appmenu-edit-button:not([disabled]):hover {
+ border: 1px solid #b8d6fb;
+ box-shadow: inset 0 0 1px white;
+ background: linear-gradient(#fafbfd, #ebf3fd);
+ transition: .2s ease-in;
+ }
+}
+
+#appmenuSecondaryPane-spacer {
+ min-height: 1em;
+}
+
+#appmenu-editmenu {
+ -moz-box-pack: end;
+}
+
+#appmenu_print,
+#appmenu_print_popup,
+.appmenu-edit-button,
+#appmenu-editmenu-cut,
+#appmenu-editmenu-copy,
+#appmenu-editmenu-paste,
+#appmenu-quit {
+ list-style-image: url("appmenu-icons.png");
+}
+
+#appmenu-cut,
+#appmenu-editmenu-cut {
+ -moz-image-region: rect(0 16px 16px 0);
+}
+
+#appmenu-copy,
+#appmenu-editmenu-copy {
+ -moz-image-region: rect(0 32px 16px 16px);
+}
+
+#appmenu-paste,
+#appmenu-editmenu-paste {
+ -moz-image-region: rect(0 48px 16px 32px);
+}
+
+#appmenu_print,
+#appmenu_print_popup {
+ -moz-image-region: rect(0 64px 16px 48px);
+}
+
+#appmenu-quit {
+ -moz-image-region: rect(0 80px 16px 64px);
+}
+
+#appmenu-edit-label {
+ -moz-appearance: none;
+ background: transparent;
+ font-style: italic;
+}
+
+#appmenu_bookmarks {
+ list-style-image: url("chrome://browser/skin/places/bookmark.png");
+ -moz-image-region: rect(0px 48px 16px 32px);
+}
+
+#appmenu_privateBrowsing,
+#appmenu_newPrivateWindow {
+ list-style-image: url("chrome://browser/skin/Privacy-16.png");
+}
+
+@media (min-resolution: 1.25dppx) {
+ #appmenu_privateBrowsing,
+ #appmenu_newPrivateWindow {
+ list-style-image: url("chrome://browser/skin/Privacy-32.png");
+ }
+}
+
+#appmenu_addons {
+ list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric-16.png");
+}
+
+@media (min-resolution: 1.25dppx) {
+ #appmenu_addons {
+ list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric.png");
+ }
+}
+
+#appmenu_showAllBookmarks,
+#bookmarksShowAll,
+#BMB_bookmarksShowAll {
+ list-style-image: url("chrome://browser/skin/places/allBookmarks.png");
+}
+
+#appmenu_bookmarkThisPage,
+#menu_bookmarkThisPage,
+#BMB_bookmarkThisPage {
+ list-style-image: url("chrome://browser/skin/places/bookmark.png");
+ -moz-image-region: rect(0 16px 16px 0);
+}
+
+#appmenu_showAllHistory,
+#menu_showAllHistory,
+#HMB_showAllHistory {
+ list-style-image: url("chrome://browser/skin/places/history.png");
+}
+
+#appmenu_sanitizeHistory,
+#sanitizeItem,
+#HMB_sanitizeItem {
+ list-style-image: url("chrome://browser/skin/sanitize.png");
+}
+
+/* ::::: titlebar ::::: */
+
+#main-window[sizemode="normal"] > #titlebar {
+ -moz-appearance: -moz-window-titlebar;
+}
+
+#main-window[sizemode="maximized"] > #titlebar {
+ -moz-appearance: -moz-window-titlebar-maximized;
+}
+
+@media (-moz-windows-classic) {
+ #main-window[sizemode="normal"] > #titlebar > #titlebar-content > #appmenu-button-container {
+ margin-top: 4px;
+ }
+}
+
+#titlebar-buttonbox {
+ -moz-appearance: -moz-window-button-box;
+}
+
+#main-window[sizemode="maximized"] #titlebar-buttonbox {
+ -moz-appearance: -moz-window-button-box-maximized;
+}
+
+.titlebar-placeholder[type="appmenu-button"] {
+ margin-left: 4px;
+}
+
+.titlebar-placeholder[type="caption-buttons"] {
+ margin-left: 22px; /* additional space for Aero Snap */
+}
+
+/* titlebar command buttons */
+
+#titlebar-min {
+ -moz-appearance: -moz-window-button-minimize;
+}
+
+#titlebar-max {
+ -moz-appearance: -moz-window-button-maximize;
+}
+
+#main-window[sizemode="maximized"] #titlebar-max {
+ -moz-appearance: -moz-window-button-restore;
+}
+
+#titlebar-close {
+ -moz-appearance: -moz-window-button-close;
+}
+
+@media not all and (-moz-windows-classic) {
+ #titlebar-min {
+ -moz-margin-end: 2px;
+ }
+}
+
+/* ::::: bookmark buttons ::::: */
+
+toolbarbutton.bookmark-item {
+ margin: 0;
+ padding: 2px 3px;
+}
+
+toolbarbutton.bookmark-item:hover:active:not([disabled="true"]),
+toolbarbutton.bookmark-item[open="true"] {
+ padding-top: 3px;
+ padding-bottom: 1px;
+ -moz-padding-start: 4px;
+ -moz-padding-end: 2px;
+}
+
+.bookmark-item:not(#bookmarks-menu-button) > .toolbarbutton-icon {
+ width: 16px;
+ height: 16px;
+}
+
+/* Prevent [mode="icons"] from hiding the label */
+.bookmark-item > .toolbarbutton-text {
+ display: -moz-box !important;
+}
+
+.bookmark-item > .toolbarbutton-menu-dropmarker {
+ display: none;
+}
+
+#wrapper-personal-bookmarks[place="palette"] > .toolbarpaletteitem-box {
+ background: url("chrome://browser/skin/places/bookmarksToolbar.png") no-repeat center;
+}
+
+.bookmarks-toolbar-customize {
+ max-width: 15em !important;
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png") !important;
+}
+
+/* ::::: bookmark menus ::::: */
+
+menu.bookmark-item,
+menuitem.bookmark-item {
+ min-width: 0;
+ max-width: 32em;
+}
+
+.bookmark-item > .menu-iconic-left {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+.bookmark-item > .menu-iconic-left > .menu-iconic-icon {
+ -moz-padding-start: 0px;
+}
+
+/* ::::: bookmark items ::::: */
+
+.bookmark-item {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+
+.bookmark-item[container] {
+ list-style-image: url("chrome://global/skin/icons/folder-item.png");
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+.bookmark-item[container][open] {
+ -moz-image-region: rect(16px, 32px, 32px, 16px);
+}
+
+.bookmark-item[container][livemark] {
+ list-style-image: url("chrome://browser/skin/livemark-folder.png");
+ -moz-image-region: auto;
+}
+
+.bookmark-item[container][livemark] .bookmark-item {
+ list-style-image: url("chrome://browser/skin/places/livemark-item.png");
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+}
+
+.bookmark-item[container][livemark] .bookmark-item[visited] {
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+.bookmark-item[container][query] {
+ list-style-image: url("chrome://browser/skin/places/query.png");
+ -moz-image-region: auto;
+}
+
+.bookmark-item[query][tagContainer] {
+ list-style-image: url("chrome://browser/skin/places/tag.png");
+ -moz-image-region: auto;
+}
+
+.bookmark-item[query][dayContainer] {
+ list-style-image: url("chrome://browser/skin/places/calendar.png");
+ -moz-image-region: auto;
+}
+
+.bookmark-item[query][hostContainer] {
+ list-style-image: url("chrome://global/skin/icons/folder-item.png");
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+.bookmark-item[query][hostContainer][open] {
+ list-style-image: url("chrome://global/skin/icons/folder-item.png");
+ -moz-image-region: rect(16px, 32px, 32px, 16px);
+}
+
+.bookmark-item[cutting] > .toolbarbutton-icon,
+.bookmark-item[cutting] > .menu-iconic-left > .menu-iconic-icon {
+ opacity: 0.5;
+}
+
+.bookmark-item[cutting] > .toolbarbutton-text,
+.bookmark-item[cutting] > .menu-iconic-left > .menu-iconic-text {
+ opacity: 0.7;
+}
+
+/* ::::: primary toolbar buttons ::::: */
+
+.toolbarbutton-1 {
+ list-style-image: var(--toolbarbutton-image);
+}
+
+toolbar[brighttext] .toolbarbutton-1 {
+ list-style-image: var(--toolbarbutton-inverted-image);
+}
+
+.toolbarbutton-1:not(:-moz-lwtheme) {
+ list-style-image: var(--toolbarbutton-glass-image);
+}
+
+.toolbarbutton-1[disabled=true] > .toolbarbutton-icon,
+.toolbarbutton-1[disabled=true] > .toolbarbutton-menu-dropmarker,
+.toolbarbutton-1[disabled=true] > .toolbarbutton-menubutton-dropmarker,
+.toolbarbutton-1[disabled=true] > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
+.toolbarbutton-1 > .toolbarbutton-menubutton-button[disabled] > .toolbarbutton-icon {
+ opacity: .4;
+}
+
+.toolbarbutton-1 > .toolbarbutton-menu-dropmarker,
+.toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+ list-style-image: url("chrome://browser/skin/toolbarbutton-dropdown-arrow.png");
+}
+
+toolbar[brighttext] .toolbarbutton-1 > .toolbarbutton-menu-dropmarker,
+toolbar[brighttext] .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+ list-style-image: url("chrome://browser/skin/toolbarbutton-dropdown-arrow-inverted.png");
+}
+
+.toolbarbutton-1 > .toolbarbutton-icon,
+.toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
+ -moz-margin-end: 0;
+}
+
+toolbar[mode=full] .toolbarbutton-1:not([type=menu-button]) {
+ -moz-box-orient: vertical;
+}
+
+toolbar[mode=full] .toolbarbutton-1,
+toolbar[mode=full] .toolbarbutton-1 > .toolbarbutton-menubutton-button {
+ min-width: 57px;
+}
+
+#nav-bar {
+ /* force iconsize="small" on this toolbar */
+ counter-reset: smallicons;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1,
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-button {
+ -moz-appearance: none;
+ border: none;
+ padding: 0;
+ background: none;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1:not([type=menu-button]),
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-button,
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+ padding: 5px 2px;
+ -moz-box-pack: center;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1[type=menu]:not(#back-button):not(#forward-button):not(#feed-button) {
+ padding-left: 5px;
+ padding-right: 5px;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1 > menupopup {
+ margin-top: -3px;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-button {
+ -moz-padding-end: 0;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
+ -moz-padding-start: 0;
+ -moz-box-align: center;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-icon,
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-badge-stack,
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ padding: 2px 6px;
+ background: var(--toolbarbutton-background-color) padding-box;
+ background-image: linear-gradient(hsla(0,0%,100%,.4), hsla(0,0%,100%,.1));
+ background-clip: padding-box;
+ border-radius: var(--toolbarbutton-border-radius);
+ border: 1px solid;
+ border-color: var(--toolbarbutton-border-color) var(--toolbarbutton-border-color) var(--toolbarbutton-border-color);
+ box-shadow: 0 1px hsla(0,0%,100%,.05) inset,
+ 0 1px hsla(210,54%,20%,.05),
+ 0 0 2px hsla(210,54%,20%,.05);
+}
+
+@media (-moz-os-version: windows-win10) {
+ /* Square is the new round, courtesy of microsoft */
+ :root {
+ --toolbarbutton-border-radius: 0px;
+ }
+}
+
+@navbarLargeIcons@ .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@)) > .toolbarbutton-icon,
+@navbarLargeIcons@ .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@)) > .toolbarbutton-badge-stack,
+@navbarLargeIcons@ .toolbarbutton-1:not(:-moz-any(@primaryToolbarButtons@)) > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
+ padding: 3px 7px;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1[type=menu]:not(#back-button):not(#forward-button):not(#feed-button) > .toolbarbutton-icon,
+@navbarLargeIcons@ .toolbarbutton-1[type=menu] > .toolbarbutton-text /* hack for add-ons that forcefully display the label */ {
+ -moz-padding-end: 17px;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menu-dropmarker {
+ -moz-margin-start: -15px;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
+ -moz-border-end: none;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ padding: 8px 5px 7px;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1:not(:hover):not(:active):not([open]) > .toolbarbutton-menubutton-dropmarker::before {
+ content: "";
+ display: -moz-box;
+ width: 1px;
+ height: 18px;
+ -moz-margin-end: -1px;
+ background-image: linear-gradient(var(--toolbarbutton-border-color) 0, var(--toolbarbutton-border-color) 18px);
+ background-clip: padding-box;
+ background-position: center;
+ background-repeat: no-repeat;
+ background-size: 1px 18px;
+ box-shadow: 0 0 0 1px hsla(0,0%,100%,.2);
+}
+
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon:-moz-locale-dir(ltr),
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon:-moz-locale-dir(rtl) {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-icon:-moz-locale-dir(rtl),
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon:-moz-locale-dir(ltr) {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1:not([disabled]):not([checked]):not([open]):not(:active):hover > .toolbarbutton-icon,
+@navbarLargeIcons@ .toolbarbutton-1:not([disabled]):not([checked]):not([open]):not(:active):hover > .toolbarbutton-badge-stack,
+@navbarLargeIcons@ .toolbarbutton-1:not([disabled]):-moz-any(:hover,[open]) > .toolbarbutton-menubutton-button > .toolbarbutton-icon,
+@navbarLargeIcons@ .toolbarbutton-1:not([disabled]):hover > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+ background-image: linear-gradient(hsla(0,0%,100%,.8), hsla(0,0%,100%,.5));
+ border-color: hsla(210,54%,20%,.25) hsla(210,54%,20%,.3) hsla(210,54%,20%,.35);
+ box-shadow: 0 1px hsla(0,0%,100%,.3) inset,
+ 0 1px hsla(210,54%,20%,.03),
+ 0 0 2px hsla(210,54%,20%,.1);
+}
+
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-button:not([disabled]):not([open]):not(:active):hover > .toolbarbutton-icon,
+@navbarLargeIcons@ .toolbarbutton-1:not([buttonover]):not([open]):not(:active):hover > .toolbarbutton-menubutton-dropmarker:not([disabled]) > .dropmarker-icon {
+ background-color: hsla(210,48%,96%,.75);
+ border-color: hsla(210,54%,20%,.3) hsla(210,54%,20%,.35) hsla(210,54%,20%,.4);
+ box-shadow: 0 0 1px hsla(210,54%,20%,.03),
+ 0 0 2px hsla(210,54%,20%,.1);
+}
+
+@navbarLargeIcons@ .toolbarbutton-1:not([disabled]):-moz-any([open],[checked],:hover:active) > .toolbarbutton-icon,
+@navbarLargeIcons@ .toolbarbutton-1:not([disabled]):-moz-any([open],[checked],:hover:active) > .toolbarbutton-badge-stack,
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-menubutton-button:not([disabled]):hover:active > .toolbarbutton-icon,
+@navbarLargeIcons@ .toolbarbutton-1[open] > .toolbarbutton-menubutton-dropmarker:not([disabled]) > .dropmarker-icon {
+ background-image: linear-gradient(hsla(0,0%,100%,.6), hsla(0,0%,100%,.1));
+ background-color: hsla(210,54%,20%,.15);
+ border-color: hsla(210,54%,20%,.3) hsla(210,54%,20%,.35) hsla(210,54%,20%,.4);
+ box-shadow: 0 1px 1px hsla(210,54%,20%,.1) inset,
+ 0 0 1px var(--toolbarbutton-border-color) inset,
+ /* allows windows-keyhole-forward-clip-path to be used for non-hover as well as hover: */
+ 0 1px 0 hsla(210,54%,20%,0),
+ 0 0 2px hsla(210,54%,20%,0);
+ text-shadow: none;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1:-moz-any(:hover,[open]) > .toolbarbutton-menubutton-dropmarker:not([disabled]) > .dropmarker-icon {
+ -moz-border-start-color: hsla(210,54%,20%,.35);
+}
+
+@navbarLargeIcons@ .toolbarbutton-1[checked]:not(:active):hover > .toolbarbutton-icon {
+ background-color: rgba(90%,90%,90%,.4);
+ transition: background-color .4s;
+}
+
+:-moz-any(#TabsToolbar, #addon-bar) .toolbarbutton-1,
+:-moz-any(#TabsToolbar, #addon-bar) .toolbarbutton-1 > .toolbarbutton-menubutton-button,
+.tabbrowser-arrowscrollbox > .scrollbutton-up,
+.tabbrowser-arrowscrollbox > .scrollbutton-down {
+ -moz-appearance: none;
+ border-style: none;
+ padding: 0 3px;
+}
+
+#TabsToolbar .toolbarbutton-1:not([disabled]):hover,
+#TabsToolbar .toolbarbutton-1[open],
+#TabsToolbar .toolbarbutton-1 > .toolbarbutton-menubutton-button:not([disabled]):hover,
+.tabbrowser-arrowscrollbox > .scrollbutton-up:not([disabled]):hover,
+.tabbrowser-arrowscrollbox > .scrollbutton-down:not([disabled]):hover {
+ background-image: linear-gradient(var(--toolbar-highlight-bottom), var(--toolbar-highlight-top)),
+ linear-gradient(transparent, rgba(0,0,0,.25) 30%),
+ linear-gradient(transparent, rgba(0,0,0,.25) 30%);
+ background-position: 1px -1px, 0 -1px, 100% -1px;
+ background-size: calc(100% - 2px) 100%, 1px 100%, 1px 100%;
+ background-repeat: no-repeat;
+}
+
+#addon-bar .toolbarbutton-1:not([disabled]):hover,
+#addon-bar .toolbarbutton-1[open],
+#addon-bar .toolbarbutton-1 > .toolbarbutton-menubutton-button:not([disabled]):hover {
+ background-image: linear-gradient(to top, transparent, rgba(0,0,0,.15)),
+ linear-gradient(to top, transparent, rgba(0,0,0,.15) 30%),
+ linear-gradient(to top, transparent, rgba(0,0,0,.15) 30%);
+ background-position: left, left, right;
+ background-size: auto, 1px 100%, 1px 100%;
+ background-repeat: no-repeat;
+}
+
+/* unified back/forward button */
+
+#back-button {
+ -moz-image-region: rect(0, 18px, 18px, 0);
+}
+
+#forward-button {
+ -moz-image-region: rect(0, 36px, 18px, 18px);
+}
+
+#back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
+#forward-button:-moz-locale-dir(rtl),
+#forward-button:-moz-locale-dir(rtl) > .toolbarbutton-text {
+ transform: scaleX(-1);
+}
+
+@conditionalForwardWithUrlbar@ {
+ -moz-box-align: center;
+}
+
+@conditionalForwardWithUrlbar@ > .toolbarbutton-1:-moz-any([disabled],:not([disabled]):not([open]):not(:active)) > .toolbarbutton-icon {
+ border-color: hsla(210,54%,20%,.25) hsla(210,54%,20%,.3) hsla(210,54%,20%,.35);
+ box-shadow: 0 1px hsla(0,0%,100%,.3) inset,
+ 0 1px hsla(210,54%,20%,.03),
+ 0 0 2px hsla(210,54%,20%,.1);
+}
+
+@conditionalForwardWithUrlbar@ > #forward-button {
+ padding: 0;
+}
+
+@conditionalForwardWithUrlbar@ > #forward-button > menupopup {
+ margin-top: 1px;
+}
+
+@conditionalForwardWithUrlbar@ > #forward-button > .toolbarbutton-icon {
+ clip-path: url(chrome://browser/content/browser.xul#windows-keyhole-forward-clip-path);
+ -moz-margin-start: -6px !important;
+ border-left-style: none;
+ border-radius: 0;
+ padding-left: 7px;
+ padding-right: 3px;
+}
+
+@conditionalForwardWithUrlbar2@ > #forward-button:-moz-locale-dir(ltr) > .toolbarbutton-icon {
+ border-top-right-radius: var(--toolbarbutton-border-radius);
+ border-bottom-right-radius: var(--toolbarbutton-border-radius);
+}
+
+@conditionalForwardWithUrlbar2@ > #forward-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+ border-top-left-radius: var(--toolbarbutton-border-radius);
+ border-bottom-left-radius: var(--toolbarbutton-border-radius);
+}
+
+@conditionalForwardWithUrlbar@ > #forward-button:not([disabled]):not([open]):not(:active):hover > .toolbarbutton-icon {
+ background-color: hsla(210,48%,96%,.75);
+ border-color: hsla(210,54%,20%,.3) hsla(210,54%,20%,.35) hsla(210,54%,20%,.4);
+ box-shadow: 0 0 1px hsla(210,54%,20%,.03),
+ 0 0 2px hsla(210,54%,20%,.1);
+}
+
+@conditionalForwardWithUrlbar@ > #back-button {
+ -moz-image-region: rect(18px, 20px, 38px, 0);
+ padding-top: 3px;
+ padding-bottom: 3px;
+ -moz-padding-start: 5px;
+ -moz-padding-end: 0;
+ position: relative;
+ z-index: 1;
+ border-radius: 0 10000px 10000px 0;
+}
+
+@conditionalForwardWithUrlbar@ > #back-button:-moz-locale-dir(rtl) {
+ border-radius: 10000px 0 0 10000px;
+}
+
+@conditionalForwardWithUrlbar@ > #back-button > menupopup {
+ margin-top: -1px;
+}
+
+@conditionalForwardWithUrlbar@ > #back-button > .toolbarbutton-icon {
+ border-radius: 10000px;
+ padding: 5px;
+ border: none;
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset,
+ 0 0 0 1px hsla(0,0%,100%,.3) inset,
+ 0 0 0 1px hsla(210,54%,20%,.25),
+ 0 1px 0 hsla(210,54%,20%,.35);
+}
+
+@conditionalForwardWithUrlbar@ > #back-button:not([disabled="true"]):not([open="true"]):not(:active):hover > .toolbarbutton-icon {
+ background-color: hsla(210,48%,96%,.75);
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.3) inset,
+ 0 0 0 1px hsla(0,0%,100%,.3) inset,
+ 0 0 0 1px hsla(210,54%,20%,.3),
+ 0 1px 0 hsla(210,54%,20%,.4),
+ 0 0 4px var(--toolbarbutton-border-color);
+}
+
+@conditionalForwardWithUrlbar@ > #back-button:not([disabled="true"]):hover:active > .toolbarbutton-icon,
+@conditionalForwardWithUrlbar@ > #back-button[open="true"] > .toolbarbutton-icon {
+ background-color: hsla(210,54%,20%,.15);
+ box-shadow: 0 1px 1px hsla(210,54%,20%,.1) inset,
+ 0 0 1px var(--toolbarbutton-border-color) inset,
+ 0 0 0 1px hsla(210,54%,20%,.4),
+ 0 1px 0 var(--toolbarbutton-border-color);
+}
+
+@conditionalForwardWithUrlbar@ > #back-button[disabled] > .toolbarbutton-icon {
+ box-shadow: 0 0 0 1px hsla(210,54%,20%,.55),
+ 0 1px 0 hsla(210,54%,20%,.65);
+}
+
+.unified-nav-back[_moz-menuactive]:-moz-locale-dir(ltr),
+.unified-nav-forward[_moz-menuactive]:-moz-locale-dir(rtl) {
+ list-style-image: url("chrome://browser/skin/menu-back.png") !important;
+}
+
+.unified-nav-forward[_moz-menuactive]:-moz-locale-dir(ltr),
+.unified-nav-back[_moz-menuactive]:-moz-locale-dir(rtl) {
+ list-style-image: url("chrome://browser/skin/menu-forward.png") !important;
+}
+
+#stop-button {
+ -moz-image-region: rect(0, 54px, 18px, 36px);
+}
+
+#reload-button {
+ -moz-image-region: rect(0, 72px, 18px, 54px);
+}
+
+#home-button.bookmark-item {
+ list-style-image: var(--toolbarbutton-image);
+}
+toolbar[brighttext] #home-button.bookmark-item {
+ list-style-image: var(--toolbarbutton-inverted-image);
+}
+
+#home-button.bookmark-item:not(:-moz-lwtheme) {
+ list-style-image: var(--toolbarbutton-glass-image);
+}
+
+#home-button {
+ -moz-image-region: rect(0, 90px, 18px, 72px);
+}
+
+#downloads-button {
+ -moz-image-region: rect(0, 108px, 18px, 90px);
+}
+
+#history-button,
+#history-menu-button {
+ -moz-image-region: rect(0, 126px, 18px, 108px);
+}
+
+#bookmarks-button,
+#bookmarks-menu-button {
+ -moz-image-region: rect(0, 144px, 18px, 126px);
+}
+
+#bookmarks-menu-button.bookmark-item {
+ list-style-image: var(--toolbarbutton-image);
+}
+
+toolbar[brighttext] #bookmarks-menu-button.bookmark-item {
+ list-style-image: var(--toolbarbutton-inverted-image);
+}
+
+#bookmarks-menu-button.bookmark-item:not(:-moz-lwtheme) {
+ list-style-image: var(--toolbarbutton-glass-image);
+}
+
+#print-button {
+ -moz-image-region: rect(0, 162px, 18px, 144px);
+}
+
+#new-tab-button {
+ -moz-image-region: rect(0, 180px, 18px, 162px);
+}
+
+#new-window-button {
+ -moz-image-region: rect(0, 198px, 18px, 180px);
+}
+
+#cut-button {
+ -moz-image-region: rect(0, 216px, 18px, 198px);
+}
+
+#copy-button {
+ -moz-image-region: rect(0, 234px, 18px, 216px);
+}
+
+#paste-button {
+ -moz-image-region: rect(0, 252px, 18px, 234px);
+}
+
+#fullscreen-button {
+ -moz-image-region: rect(0, 270px, 18px, 252px);
+}
+
+#zoom-out-button {
+ -moz-image-region: rect(0, 288px, 18px, 270px);
+}
+
+#zoom-in-button {
+ -moz-image-region: rect(0, 306px, 18px, 288px);
+}
+
+#sync-button {
+ -moz-image-region: rect(0, 324px, 18px, 306px);
+}
+#sync-button[status="active"] {
+ list-style-image: url("chrome://browser/skin/sync-throbber.png");
+ -moz-image-region: rect(0, 18px, 18px, 0);
+}
+
+#feed-button {
+ -moz-image-region: rect(0, 342px, 18px, 324px);
+}
+
+%ifdef MOZ_WEBRTC
+#webrtc-status-button {
+ -moz-image-region: rect(0, 360px, 18px, 342px);
+}
+%endif
+
+/* ::::: fullscreen window controls ::::: */
+
+#window-controls {
+ -moz-margin-start: 4px;
+}
+
+#minimize-button,
+#restore-button,
+#close-button {
+ list-style-image: url("chrome://global/skin/icons/windowControls.png");
+ padding: 0;
+}
+
+#minimize-button {
+ -moz-image-region: rect(0, 16px, 16px, 0);
+}
+#minimize-button:hover {
+ -moz-image-region: rect(16px, 16px, 32px, 0);
+}
+#minimize-button:hover:active {
+ -moz-image-region: rect(32px, 16px, 48px, 0);
+}
+#restore-button {
+ -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+#restore-button:hover {
+ -moz-image-region: rect(16px, 32px, 32px, 16px);
+}
+#restore-button:hover:active {
+ -moz-image-region: rect(32px, 32px, 48px, 16px);
+}
+#close-button {
+ -moz-image-region: rect(0, 48px, 16px, 32px);
+ -moz-appearance: none;
+ border-style: none;
+ margin: 2px;
+}
+#close-button:hover {
+ -moz-image-region: rect(16px, 48px, 32px, 32px);
+}
+#close-button:hover:active {
+ -moz-image-region: rect(32px, 48px, 48px, 32px);
+}
+
+/* ::::: Location Bar ::::: */
+
+#urlbar,
+.searchbar-textbox {
+ -moz-appearance: none;
+ margin: 1px 3px;
+ padding: 0;
+ background-clip: padding-box;
+ border: 1px solid ThreeDShadow;
+ border-radius: 2px;
+}
+
+#urlbar {
+ width: 7em;
+ -moz-padding-end: 2px;
+}
+
+@media (-moz-windows-default-theme) {
+ #urlbar,
+ .searchbar-textbox {
+ @navbarTextboxCustomBorder@
+ }
+}
+
+#urlbar:-moz-lwtheme,
+.searchbar-textbox:-moz-lwtheme {
+ background-color: rgba(255,255,255,.8);
+ @navbarTextboxCustomBorder@
+ color: black;
+}
+
+@conditionalForwardWithUrlbar@ + #urlbar-container {
+ padding-left: @conditionalForwardWithUrlbarWidth@px;
+ -moz-margin-start: -@conditionalForwardWithUrlbarWidth@px;
+ position: relative;
+ pointer-events: none;
+}
+
+@conditionalForwardWithUrlbar@ + #urlbar-container > #urlbar {
+ -moz-border-start: none;
+ margin-left: 0;
+ pointer-events: all;
+}
+
+@conditionalForwardWithUrlbar@:not([switchingtabs]) + #urlbar-container > #urlbar {
+ transition: margin-left @forwardTransitionLength@ ease-out;
+}
+
+@conditionalForwardWithUrlbar@ + #urlbar-container > #urlbar:-moz-locale-dir(ltr) {
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+}
+
+@conditionalForwardWithUrlbar@ + #urlbar-container > #urlbar:-moz-locale-dir(rtl) {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+@conditionalForwardWithUrlbar@[forwarddisabled] + #urlbar-container {
+ clip-path: url("chrome://browser/content/browser.xul#windows-urlbar-back-button-clip-path");
+}
+
+@conditionalForwardWithUrlbar@ + #urlbar-container:-moz-locale-dir(rtl),
+@conditionalForwardWithUrlbar@ + #urlbar-container > #urlbar:-moz-locale-dir(rtl) {
+ /* let windows-urlbar-back-button-mask clip the urlbar's right side for RTL */
+ transform: scaleX(-1);
+}
+
+html|*.urlbar-input:-moz-lwtheme::-moz-placeholder,
+.searchbar-textbox:-moz-lwtheme > .autocomplete-textbox-container > .textbox-input-box > html|*.textbox-input::-moz-placeholder {
+ opacity: 1.0;
+ color: #777;
+}
+
+#urlbar:-moz-lwtheme[focused="true"],
+.searchbar-textbox:-moz-lwtheme[focused="true"] {
+ background-color: white;
+}
+
+#urlbar-container {
+ -moz-box-orient: horizontal;
+ -moz-box-align: stretch;
+}
+
+.urlbar-textbox-container {
+ -moz-box-align: stretch;
+}
+
+.urlbar-input-box {
+ -moz-margin-start: 0;
+ min-width: 4em;
+}
+
+#urlbar-icons {
+ -moz-box-align: center;
+}
+
+.urlbar-icon {
+ padding: 0 3px;
+}
+
+.searchbar-engine-button,
+.search-go-container {
+ padding: 2px 2px;
+}
+
+.urlbar-icon:hover {
+ background-image: radial-gradient(circle closest-side, hsla(200,100%,70%,.3), hsla(200,100%,70%,0));
+}
+
+.urlbar-icon[open="true"],
+.urlbar-icon:hover:active {
+ background-image: radial-gradient(circle closest-side, hsla(200,100%,70%,.1), hsla(200,100%,70%,0));
+}
+
+#urlbar-search-splitter {
+ min-width: 6px;
+ -moz-margin-start: -3px;
+ border: none;
+ background: transparent;
+}
+
+#urlbar-search-splitter + #urlbar-container > #urlbar ,
+#urlbar-search-splitter + #search-container > #searchbar > .searchbar-textbox {
+ -moz-margin-start: 0;
+}
+
+#urlbar-display-box {
+ -moz-border-end: 1px solid #AAA;
+ -moz-margin-end: 3px;
+}
+
+#urlbar-display {
+ margin-top: 0;
+ margin-bottom: 0;
+ -moz-margin-start: 0;
+ color: GrayText;
+}
+
+/* identity box */
+
+#identity-box {
+ padding: 2px;
+ font-size: .9em;
+}
+
+#identity-box:-moz-locale-dir(ltr) {
+ border-top-left-radius: 1.5px;
+ border-bottom-left-radius: 1.5px;
+}
+
+#identity-box:-moz-locale-dir(rtl) {
+ border-top-right-radius: 1.5px;
+ border-bottom-right-radius: 1.5px;
+}
+
+#notification-popup-box:not([hidden]) + #identity-box {
+ -moz-padding-start: 10px;
+ border-radius: 0;
+}
+
+@conditionalForwardWithUrlbar@ + #urlbar-container > #urlbar > #identity-box {
+ border-radius: 0;
+}
+
+#urlbar[pageproxystate="valid"] > #identity-box.verifiedIdentity {
+ color: hsl(92,100%,20%);
+ -moz-margin-end: 4px;
+ background-image: -moz-linear-gradient(hsla(92,81%,16%,0),
+ hsla(92,81%,16%,.08) 25%,
+ hsla(92,81%,16%,.08) 75%,
+ hsla(92,81%,16%,0));
+ background-position: right;
+ background-repeat: no-repeat;
+ border-right: 1px solid hsla(92,100%,20%,0.5);
+}
+
+#urlbar[pageproxystate="valid"] > #identity-box.verifiedDomain {
+ color: rgb(0,79,168);
+ -moz-margin-end: 4px;
+ background-image: -moz-linear-gradient(rgba(0,79,168,0),
+ rgba(0,79,168,.08) 25%,
+ rgba(0,79,168,.08) 75%,
+ rgba(0,79,168,0));
+ background-position: right;
+ background-repeat: no-repeat;
+ border-right: 1px solid rgba(0,79,168,0.5);
+}
+
+#identity-box.verifiedIdentity:-moz-locale-dir(rtl) {
+ background-position: left;
+ border-right: none;
+ border-left: 1px solid hsla(92,100%,20%,0.5);
+}
+
+#identity-box.verifiedDomain:-moz-locale-dir(rtl) {
+ background-position: left;
+ border-right: none;
+ border-left: 1px solid rgba(0,79,168,0.5);
+}
+
+#identity-box:-moz-focusring {
+ outline: 1px dotted #000;
+ outline-offset: -3px;
+}
+
+#identity-icon-labels {
+ -moz-padding-start: 2px;
+ -moz-padding-end: 5px;
+}
+
+/* Address bar shading for SSL */
+
+#urlbar[https_color="all"][security_level="broken"],
+#urlbar[https_color="all"][security_level="low"] {
+ box-shadow: inset 0 0 2px rgb(168,0,0);
+}
+
+#urlbar[https_color="all"][security_level="mixed"],
+#urlbar[https_color="secure-mixed"][security_level="mixed"] {
+ box-shadow: inset 0 0 2px rgb(168,79,0);
+}
+
+#urlbar[https_color="all"][security_level="high"],
+#urlbar[https_color="secure-mixed"][security_level="high"],
+#urlbar[https_color="secure-only"][security_level="high"] {
+ box-shadow: inset 0 0 2px rgb(0,79,168);
+}
+
+#urlbar[https_color="all"][security_level="ev"],
+#urlbar[https_color="secure-mixed"][security_level="ev"],
+#urlbar[https_color="secure-only"][security_level="ev"] {
+ box-shadow: inset 0 0 2px rgb(0,168,0);
+}
+
+#urlbar[https_color="all"][security_level="broken"]:not(:-moz-lwtheme),
+#urlbar[https_color="all"][security_level="low"]:not(:-moz-lwtheme) {
+ box-shadow: inset 0 0 3px rgba(168,0,0,0.8);
+}
+
+#urlbar[https_color="all"][security_level="mixed"]:not(:-moz-lwtheme),
+#urlbar[https_color="secure-mixed"][security_level="mixed"]:not(:-moz-lwtheme) {
+ box-shadow: inset 0 0 3px rgba(168,79,0,0.8);
+}
+
+#urlbar[https_color="all"][security_level="high"]:not(:-moz-lwtheme),
+#urlbar[https_color="secure-mixed"][security_level="high"]:not(:-moz-lwtheme),
+#urlbar[https_color="secure-only"][security_level="high"]:not(:-moz-lwtheme) {
+ box-shadow: inset 0 0 3px rgba(0,79,168,0.8);
+}
+
+#urlbar[https_color="all"][security_level="ev"]:not(:-moz-lwtheme),
+#urlbar[https_color="secure-mixed"][security_level="ev"]:not(:-moz-lwtheme),
+#urlbar[https_color="secure-only"][security_level="ev"]:not(:-moz-lwtheme) {
+ box-shadow: inset 0 0 3px rgba(0,168,0,0.8);
+}
+
+/* Location bar dropmarker */
+
+.urlbar-history-dropmarker {
+ -moz-appearance: none;
+ padding: 0 3px;
+ background-color: transparent;
+ border: none;
+ width: auto;
+ list-style-image: url("chrome://browser/skin/urlbar-history-dropmarker.png");
+ -moz-image-region: rect(0px, 11px, 14px, 0px);
+}
+
+.urlbar-history-dropmarker:hover {
+ background-image: radial-gradient(circle closest-side, hsla(205,100%,70%,.3), hsla(205,100%,70%,0));
+ -moz-image-region: rect(0px, 22px, 14px, 11px);
+}
+
+.urlbar-history-dropmarker:hover:active,
+.urlbar-history-dropmarker[open="true"] {
+ background-image: radial-gradient(circle closest-side, hsla(205,100%,70%,.1), hsla(205,100%,70%,0));
+ -moz-image-region: rect(0px, 33px, 14px, 22px);
+}
+
+/* page proxy icon */
+
+#page-proxy-favicon {
+ width: 16px;
+ height: 16px;
+ margin-top: 1px;
+ margin-bottom: 1px;
+ -moz-margin-start: 3px;
+ -moz-margin-end: 2px;
+ list-style-image: url(chrome://browser/skin/identity-icons-generic.png);
+ -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+@conditionalForwardWithUrlbar@ + #urlbar-container > #urlbar > #identity-box > #page-proxy-favicon {
+ -moz-margin-end: 1px;
+}
+
+/* Since we already have a padlock, always use the generic icon until the favicon loads
+.verifiedDomain > #page-proxy-favicon[pageproxystate="valid"] {
+ list-style-image: url(chrome://browser/skin/identity-icons-https.png);
+}
+
+.verifiedIdentity > #page-proxy-favicon[pageproxystate="valid"] {
+ list-style-image: url(chrome://browser/skin/identity-icons-https-ev.png);
+}
+
+.mixedActiveContent > #page-proxy-favicon[pageproxystate="valid"] {
+ list-style-image: url(chrome://browser/skin/identity-icons-https-mixed-active.png);
+}
+*/
+
+#identity-box:hover > #page-proxy-favicon {
+ -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
+#identity-box:hover:active > #page-proxy-favicon,
+#identity-box[open=true] > #page-proxy-favicon {
+ -moz-image-region: rect(0, 48px, 16px, 32px);
+}
+
+#page-proxy-favicon[pageproxystate="invalid"] {
+ opacity: 0.3;
+}
+
+/* autocomplete */
+
+#treecolAutoCompleteImage {
+ max-width: 36px;
+}
+
+.ac-result-type-bookmark,
+.autocomplete-treebody::-moz-tree-image(bookmark, treecolAutoCompleteImage) {
+ list-style-image: url("chrome://browser/skin/places/bookmark.png");
+ -moz-image-region: rect(0px 48px 16px 32px);
+ width: 16px;
+ height: 16px;
+}
+
+.ac-result-type-keyword,
+.autocomplete-treebody::-moz-tree-image(keyword, treecolAutoCompleteImage) {
+ list-style-image: url(chrome://global/skin/icons/Search-glass.png);
+ -moz-image-region: rect(0px 32px 16px 16px);
+ width: 16px;
+ height: 16px;
+}
+
+.ac-result-type-tag,
+.autocomplete-treebody::-moz-tree-image(tag, treecolAutoCompleteImage) {
+ list-style-image: url("chrome://browser/skin/places/tag.png");
+ width: 16px;
+ height: 16px;
+}
+
+.ac-comment {
+ font-size: 1.06em;
+}
+
+.ac-extra > .ac-comment {
+ font-size: 1em;
+}
+
+.ac-url-text,
+.ac-action-text {
+ font-size: 1em;
+ color: -moz-nativehyperlinktext;
+}
+
+richlistitem[type~="action"][actiontype="switchtab"] > .ac-url-box > .ac-action-icon {
+ list-style-image: url("chrome://browser/skin/actionicon-tab.png");
+ -moz-image-region: rect(0, 16px, 11px, 0);
+ padding: 0 3px;
+}
+
+@media not all and (-moz-os-version: windows-vista),
+ not all and (-moz-windows-default-theme) {
+ @media not all and (-moz-os-version: windows-win7),
+ not all and (-moz-windows-default-theme) {
+ richlistitem[type~="action"][actiontype$="tab"][selected="true"] > .ac-url-box > .ac-action-icon {
+ -moz-image-region: rect(11px, 16px, 22px, 0);
+ }
+
+ .ac-comment[selected="true"],
+ .ac-url-text[selected="true"],
+ .ac-action-text[selected="true"] {
+ color: inherit !important;
+ }
+ }
+}
+
+.autocomplete-treebody::-moz-tree-cell-text(treecolAutoCompleteComment) {
+ color: GrayText;
+}
+
+.autocomplete-treebody::-moz-tree-cell-text(suggesthint, treecolAutoCompleteComment),
+.autocomplete-treebody::-moz-tree-cell-text(suggestfirst, treecolAutoCompleteComment)
+{
+ color: GrayText;
+ font-size: smaller;
+}
+
+.autocomplete-treebody::-moz-tree-cell(suggesthint) {
+ border-top: 1px solid GrayText;
+}
+
+/* combined go/reload/stop button in location bar */
+
+#go-button,
+#urlbar > toolbarbutton {
+ -moz-appearance: none;
+ padding: 0 2px;
+ background-origin: border-box;
+ border: none;
+ list-style-image: url("chrome://browser/skin/reload-stop-go.png");
+}
+
+#go-button {
+ padding: 0 3px;
+}
+
+#urlbar-reload-button {
+ -moz-image-region: rect(0, 14px, 14px, 0);
+}
+
+#urlbar-reload-button:not([disabled]):hover {
+ background-image: radial-gradient(circle closest-side, hsla(200,100%,70%,.2), hsla(200,100%,70%,0));
+ -moz-image-region: rect(14px, 14px, 28px, 0);
+}
+
+#urlbar-reload-button:not([disabled]):hover:active {
+ background-image: radial-gradient(circle closest-side, hsla(200,100%,60%,.1), hsla(200,100%,60%,0));
+ -moz-image-region: rect(28px, 14px, 42px, 0);
+}
+
+#urlbar-reload-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+ transform: scaleX(-1);
+}
+
+#go-button,
+#urlbar-go-button {
+ -moz-image-region: rect(0, 42px, 14px, 28px);
+}
+
+#go-button:hover,
+#urlbar-go-button:hover {
+ background-image: radial-gradient(circle closest-side, hsla(110,70%,50%,.2), hsla(110,70%,50%,0));
+ -moz-image-region: rect(14px, 42px, 28px, 28px);
+}
+
+#go-button:hover:active,
+#urlbar-go-button:hover:active {
+ background-image: radial-gradient(circle closest-side, hsla(110,70%,50%,.1), hsla(110,70%,50%,0));
+ -moz-image-region: rect(28px, 42px, 42px, 28px);
+}
+
+#go-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
+#urlbar-go-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+ transform: scaleX(-1);
+}
+
+#urlbar-stop-button {
+ -moz-image-region: rect(0, 28px, 14px, 14px);
+}
+
+#urlbar-stop-button:not([disabled]):hover {
+ background-image: radial-gradient(circle closest-side, hsla(5,100%,75%,.3), hsla(5,100%,75%,0));
+ -moz-image-region: rect(14px, 28px, 28px, 14px);
+}
+
+#urlbar-stop-button:hover:active {
+ background-image: radial-gradient(circle closest-side, hsla(5,100%,75%,.1), hsla(5,100%,75%,0));
+ -moz-image-region: rect(28px, 28px, 42px, 14px);
+}
+
+/* popup blocker button */
+
+#page-report-button {
+ list-style-image: url("chrome://browser/skin/urlbar-popup-blocked.png");
+ -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+#page-report-button:hover {
+ -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
+#page-report-button:hover:active,
+#page-report-button[open="true"] {
+ -moz-image-region: rect(0, 48px, 16px, 32px);
+}
+
+/* star button */
+
+#star-button {
+ list-style-image: url("chrome://browser/skin/places/bookmark.png");
+ -moz-image-region: rect(0px 16px 16px 0px);
+}
+
+#star-button:hover {
+ background-image: radial-gradient(circle closest-side, hsla(45,100%,73%,.3), hsla(45,100%,73%,0));
+ -moz-image-region: rect(0px 32px 16px 16px);
+}
+
+#star-button:hover:active {
+ background-image: radial-gradient(circle closest-side, hsla(45,100%,73%,.1), hsla(45,100%,73%,0));
+ -moz-image-region: rect(0px 48px 16px 32px);
+}
+
+#star-button[starred] {
+ list-style-image: url("chrome://browser/skin/places/editBookmark.png");
+}
+
+/* bookmarking panel */
+
+#editBookmarkPanelStarIcon {
+ list-style-image: url("chrome://browser/skin/places/starred48.png");
+ width: 48px;
+ height: 48px;
+}
+
+#editBookmarkPanelStarIcon[unstarred] {
+ list-style-image: url("chrome://browser/skin/places/unstarred48.png");
+}
+
+#editBookmarkPanelTitle {
+ font-size: 130%;
+}
+
+#editBookmarkPanelHeader,
+#editBookmarkPanelContent {
+ margin-bottom: .5em;
+}
+
+/* Implements editBookmarkPanel resizing on folderTree un-collapse. */
+#editBMPanel_folderTree {
+ min-width: 27em;
+}
+
+/* ::::: content area ::::: */
+
+#sidebar {
+ background-color: Window;
+}
+
+#sidebar-title {
+ -moz-padding-start: 0px;
+}
+
+/* ::::: throbber ::::: */
+
+#navigator-throbber {
+ width: 16px;
+ min-height: 16px;
+ margin: 0 3px;
+}
+
+#navigator-throbber[busy="true"] {
+ list-style-image: url("chrome://global/skin/icons/loading_16.png");
+}
+
+#navigator-throbber,
+#wrapper-navigator-throbber > #navigator-throbber {
+ list-style-image: url("chrome://global/skin/icons/notloading_16.png");
+}
+
+/* Tabstrip */
+
+#TabsToolbar {
+ min-height: 0;
+ padding: 0;
+}
+
+/* Make sure the Navigation toolbar buttons are more or less
+ vertically centered between the tabs and the AppMenu button
+ when the tabs are not on top and the Bookmarks toolbar is
+ disabled */
+#nav-bar + #customToolbars + #PersonalToolbar:-moz-any([collapsed=true],[moz-collapsed=true]) + #TabsToolbar[tabsontop=false] {
+ margin-top: 1px;
+}
+
+/* Make sure the elements on the Tab bar are not "glued" right
+ up against the AppMenu button / the caption when the tabs are
+ on top and the window is unmaximized */
+#main-window[sizemode="normal"] #TabsToolbar[tabsontop=true] {
+ margin-top: 1px;
+}
+
+#TabsToolbar:not(:-moz-lwtheme),
+#TabsToolbar[tabsontop=false] {
+ background-image: linear-gradient(to top, @toolbarShadowColor@ 1px, rgba(0,0,0,.05) 1px, transparent 50%);
+}
+
+/* When the tab bar is collapsed, show a 1px border in its place. */
+#TabsToolbar[tabsontop="false"][collapsed="true"]:not([customizing="true"]) {
+ visibility: visible;
+ height: 1px;
+ border-bottom-width: 1px;
+ /* !important here to override border-style: none on the toolbar */
+ border-bottom-style: solid !important;
+ border-bottom-color: var(--toolbox-after-color);
+ overflow: hidden;
+}
+
+.tabbrowser-tab,
+.tabs-newtab-button {
+ -moz-appearance: none;
+ background: @toolbarShadowOnTab@, var(--tab-background),
+ linear-gradient(-moz-dialog, -moz-dialog);
+ background-clip: padding-box;
+ padding: 3px 1px 4px;
+ /* Setting a transparent outer border allows us to have a 1px gap
+ between the tabs and the top edge of the screen, even when the
+ tabs have a top margin of 0, which is important for Fitts' law
+ compliance */
+ border: 1.6px solid;
+ border-bottom: none;
+ border-radius: var(--tab-border-radius) var(--tab-border-radius) 0px 0px;
+ -moz-border-top-colors: transparent #929292;
+ -moz-border-left-colors: transparent #929292;
+ -moz-border-right-colors: transparent #929292;
+ /* Hide the transparent top border by default */
+ margin-top: -1px;
+ /* Reduce the gap between the tabs */
+ -moz-margin-start: -1px;
+ box-shadow: var(--tab-box-shadow);
+}
+
+.tabbrowser-tab {
+ -moz-padding-end: 3px;
+}
+
+/* Override the default (globally-set) tab width values; increase
+ by 2px to compensate for the transparent outer border of the tabs */
+.tabbrowser-tab:not([pinned]) {
+ max-width: 252px;
+ min-width: 102px;
+}
+
+/* When the tabs are on top and the window is maximized or in full-
+ screen mode, unhide the transparent top border of the tabs so we
+ have a 1px gap between the tabs and the top edge of the screen */
+#main-window[sizemode="maximized"][tabsontop=true] .tabbrowser-tab,
+#main-window[sizemode="maximized"][tabsontop=true] .tabs-newtab-button,
+#main-window[sizemode="fullscreen"][tabsontop=true] .tabbrowser-tab,
+#main-window[sizemode="fullscreen"][tabsontop=true] .tabs-newtab-button {
+ margin-top: 0px;
+}
+
+@media (-moz-os-version: windows-win8) {
+ /* Square is the new round, courtesy of microsoft */
+ /* We keep the hinting at round here because that's the hybrid in use
+ on our other controls in the navigation toolbars */
+ :root {
+ --tab-border-radius: 3.5px;
+ }
+}
+
+@media (-moz-os-version: windows-win10) {
+ /* Square is the new round, courtesy of microsoft */
+ :root {
+ --tab-border-radius: 0px;
+ --tab-box-shadow: none;
+ }
+}
+
+.tabbrowser-tab:hover,
+.tabs-newtab-button:hover {
+ background-image: @toolbarShadowOnTab@, var(--tab-background-hover),
+ linear-gradient(-moz-dialog, -moz-dialog);
+}
+
+.tabbrowser-tab[selected="true"] {
+ background-image: linear-gradient(var(--tab-selected-highlight), var(--toolbar-highlight-top) 50%),
+ linear-gradient(-moz-dialog, -moz-dialog);
+}
+
+#main-window[tabsontop=false]:not([disablechrome]) .tabbrowser-tab[selected=true]:not(:-moz-lwtheme) {
+ background-image: @toolbarShadowOnTab@,
+ linear-gradient(var(--tab-selected-highlight), var(--toolbar-highlight-top) 50%),
+ linear-gradient(-moz-dialog, -moz-dialog);
+}
+
+.tabbrowser-tab:-moz-lwtheme {
+ color: inherit;
+ /* 0.99 opacity rquired to force an active layer, see bug #1028369 */
+ opacity: 0.99;
+}
+
+.tabbrowser-tab:-moz-lwtheme:not([selected="true"]) {
+ opacity: 0.9;
+}
+
+/* Remove highlight fuzz on dark themes */
+.tabbrowser-tab:-moz-lwtheme-brighttext,
+.tabs-newtab-button:-moz-lwtheme-brighttext {
+ box-shadow:none;
+ -moz-border-top-colors: transparent #707070;
+ -moz-border-left-colors: transparent #707070;
+ -moz-border-right-colors: transparent #707070;
+}
+
+.tabbrowser-tab[selected="true"]:-moz-lwtheme {
+ background-image: linear-gradient(var(--tab-selected-highlight), var(--toolbar-highlight-top) 50%);
+}
+
+.tabbrowser-tab[selected="true"]:-moz-lwtheme-brighttext {
+ background-image: linear-gradient(rgba(128,128,128,.9), rgba(32,32,32,.9) 50%, rgba(32,32,32,.9) 80%, var(--toolbar-highlight-top) 100%);
+ -moz-border-top-colors: transparent #D0D0D0;
+ -moz-border-left-colors: transparent #D0D0D0;
+ -moz-border-right-colors: transparent #D0D0D0;
+}
+
+.tabbrowser-tab:-moz-lwtheme-brighttext:not([selected="true"]),
+.tabs-newtab-button:-moz-lwtheme-brighttext {
+ background-image: linear-gradient(hsla(0,0%,25%,.4), hsla(0,0%,15%,.6) 80%);
+}
+
+.tabbrowser-tab:-moz-lwtheme-brighttext:not([selected="true"]):hover,
+.tabs-newtab-button:-moz-lwtheme-brighttext:hover {
+ background-image: linear-gradient(hsla(0,0%,60%,.4), hsla(0,0%,10%,.8) 80%);
+}
+
+.tabbrowser-tab:-moz-lwtheme-darktext:not([selected="true"]),
+.tabs-newtab-button:-moz-lwtheme-darktext {
+ background-image: linear-gradient(hsla(0,0%,75%,.4), hsla(0,0%,85%,.6) 80%);
+}
+
+.tabbrowser-tab:-moz-lwtheme-darktext:not([selected="true"]):hover,
+.tabs-newtab-button:-moz-lwtheme-darktext:hover {
+ background-image: linear-gradient(hsla(0,0%,60%,.4), hsla(0,0%,90%,.8) 80%);
+}
+
+.tabbrowser-tab[pinned][titlechanged]:not([selected="true"]) {
+ background-image: radial-gradient(circle farthest-corner at 50% 3px, rgba(255,255,255,1) 3%, rgba(186,221,251,.75) 40%, rgba(127,179,255,.5) 80%, rgba(127,179,255,.25));
+}
+.tabbrowser-tab[pinned][titlechanged]:not([selected="true"]):hover {
+ background-image: linear-gradient(hsla(0,0%,100%,.4), hsla(0,0%,75%,.4) 80%),
+ radial-gradient(circle farthest-corner at 50% 3px, rgba(255,255,255,1) 3%, rgba(186,221,251,.75) 40%, rgba(127,179,255,.5) 80%, rgba(127,179,255,.25));
+}
+
+.tab-throbber,
+.tab-icon-image {
+ width: 16px;
+ height: 16px;
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+ -moz-margin-start: 2px;
+ -moz-margin-end: 3px;
+}
+
+.tab-throbber {
+ list-style-image: url("chrome://browser/skin/tabbrowser/connecting.png");
+}
+
+.tab-throbber[progress] {
+ list-style-image: url("chrome://browser/skin/tabbrowser/loading.png");
+}
+
+.tab-throbber[pinned],
+.tab-icon-image[pinned] {
+ -moz-margin-start: 5px;
+ -moz-margin-end: 5px;
+}
+
+/* tabbrowser-tab focus ring */
+.tabbrowser-tab:focus > .tab-stack {
+ outline: 1px dotted;
+}
+
+/* Tab DnD indicator */
+.tab-drop-indicator {
+ list-style-image: url(chrome://browser/skin/tabbrowser/tabDragIndicator.png);
+ margin-bottom: -11px;
+}
+
+/* Tab close button */
+.tab-close-button {
+ -moz-appearance: none;
+ border: none;
+ padding: 0px;
+}
+
+.tab-close-button:not([selected]):not(:hover):not(:active) {
+ -moz-image-region: rect(0, 64px, 16px, 48px);
+}
+
+.tab-close-button:-moz-lwtheme-brighttext {
+ list-style-image: url("chrome://global/skin/icons/close-inverted.svg");
+}
+
+/* Tab sound indicator */
+.tab-icon-sound {
+ -moz-margin-start: 4px;
+ width: 16px;
+ height: 16px;
+ padding: 0;
+}
+
+.allTabs-endimage[soundplaying],
+.tab-icon-sound[soundplaying] {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio");
+}
+
+.allTabs-endimage[muted],
+.tab-icon-sound[muted] {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-muted");
+}
+
+.allTabs-endimage[blocked],
+.tab-icon-sound[blocked] {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio.svg#tab-audio-blocked");
+}
+
+#TabsToolbar:-moz-lwtheme-brighttext .tab-icon-sound[soundplaying],
+#TabsToolbar:-moz-lwtheme-brighttext .tab-icon-sound[blocked],
+#TabsToolbar:-moz-lwtheme-brighttext .tab-icon-sound[muted] {
+ filter: invert(1);
+}
+
+.tab-icon-sound[soundplaying-scheduledremoval]:not([muted]):not(:hover),
+.tab-icon-overlay[soundplaying-scheduledremoval]:not([muted]):not(:hover) {
+ transition: opacity .3s linear var(--soundplaying-removal-delay);
+ opacity: 0;
+}
+
+/* Tab icon overlay */
+.tab-icon-overlay {
+ width: 16px;
+ height: 16px;
+ margin-top: -8px;
+ margin-inline-start: -15px;
+ margin-inline-end: -1px;
+ position: relative;
+}
+
+.tab-icon-overlay[soundplaying],
+.tab-icon-overlay[muted]:not([crashed]),
+.tab-icon-overlay[blocked]:not([crashed]) {
+ border-radius: 10px;
+}
+
+.tab-icon-overlay[soundplaying]:hover,
+.tab-icon-overlay[muted]:not([crashed]):hover,
+.tab-icon-overlay[blocked]:not([crashed]):hover {
+ background-color: white;
+}
+
+.tab-icon-overlay[soundplaying] {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio");
+}
+
+.tab-icon-overlay[muted] {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-muted");
+}
+
+.tab-icon-overlay[blocked] {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-blocked");
+}
+
+#TabsToolbar:-moz-lwtheme-brighttext .tab-icon-overlay[soundplaying]:not([selected]):not(:hover),
+.tab-icon-overlay[soundplaying][selected]:-moz-lwtheme-brighttext:not(:hover) {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-white");
+}
+
+#TabsToolbar:-moz-lwtheme-brighttext .tab-icon-overlay[muted]:not([crashed]):not([selected]):not(:hover),
+.tab-icon-overlay[muted][selected]:-moz-lwtheme-brighttext:not(:hover) {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-white-muted");
+}
+
+#TabsToolbar:-moz-lwtheme-brighttext .tab-icon-overlay[blocked]:not([crashed]):not([selected]):not(:hover),
+.tab-icon-overlay[blocked][selected]:-moz-lwtheme-brighttext:not(:hover) {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-audio-small.svg#tab-audio-white-blocked");
+}
+
+/* Tab scrollbox arrow, tabstrip new tab and all-tabs buttons */
+
+.tabbrowser-arrowscrollbox > .scrollbutton-up,
+.tabbrowser-arrowscrollbox > .scrollbutton-down {
+ list-style-image: url("chrome://browser/skin/tabbrowser/tab-arrow-left.png");
+ margin: 0;
+ padding-right: 2px;
+ border-right: 2px solid transparent;
+ background-origin: border-box;
+}
+
+/* Prevent the icon from being vertically stretched when we unhide
+ the transparent top border of the tabs (when the tabs are on top
+ and the window is maximized or in full-screen mode) */
+#main-window[sizemode="maximized"][tabsontop=true] .tabbrowser-arrowscrollbox > .scrollbutton-up > .toolbarbutton-icon,
+#main-window[sizemode="maximized"][tabsontop=true] .tabbrowser-arrowscrollbox > .scrollbutton-down > .toolbarbutton-icon,
+#main-window[sizemode="fullscreen"][tabsontop=true] .tabbrowser-arrowscrollbox > .scrollbutton-up > .toolbarbutton-icon,
+#main-window[sizemode="fullscreen"][tabsontop=true] .tabbrowser-arrowscrollbox > .scrollbutton-down > .toolbarbutton-icon {
+ margin-bottom: 1px;
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-up:-moz-system-metric(windows-compositor):not(:-moz-lwtheme),
+.tabbrowser-arrowscrollbox > .scrollbutton-down:-moz-system-metric(windows-compositor):not(:-moz-lwtheme) {
+ list-style-image: url(chrome://browser/skin/tabbrowser/tab-arrow-left-glass.png);
+}
+
+toolbar[brighttext] .tabbrowser-arrowscrollbox > .scrollbutton-up,
+toolbar[brighttext] .tabbrowser-arrowscrollbox > .scrollbutton-down {
+ list-style-image: url(chrome://browser/skin/tabbrowser/tab-arrow-left-inverted.png);
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-up[disabled],
+.tabbrowser-arrowscrollbox > .scrollbutton-down[disabled] {
+ opacity: .4;
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-up:-moz-locale-dir(rtl),
+.tabbrowser-arrowscrollbox > .scrollbutton-down:-moz-locale-dir(ltr) {
+ transform: scaleX(-1);
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-down {
+ transition: 1s background-color ease-out;
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-down[notifybgtab] {
+ background-color: Highlight;
+ transition: none;
+}
+
+.tabbrowser-arrowscrollbox > .scrollbutton-up:not([disabled]),
+.tabbrowser-arrowscrollbox > .scrollbutton-down:not([disabled]) {
+ border-width: 0 2px 0 0;
+ border-style: solid;
+ border-image: url("chrome://browser/skin/tabbrowser/tab-overflow-border.png") 0 2 0 2 fill;
+}
+
+.tabs-newtab-button > .toolbarbutton-icon {
+ margin-top: -1px;
+ margin-bottom: -1px;
+}
+
+.tabs-newtab-button,
+#TabsToolbar > #new-tab-button,
+#TabsToolbar > toolbarpaletteitem > #new-tab-button {
+ list-style-image: url(chrome://browser/skin/tabbrowser/newtab.png);
+ -moz-image-region: auto;
+}
+
+#TabsToolbar > #new-tab-button:-moz-system-metric(windows-compositor):not(:-moz-lwtheme),
+#TabsToolbar > toolbarpaletteitem > #new-tab-button:-moz-system-metric(windows-compositor):not(:-moz-lwtheme) {
+ list-style-image: url(chrome://browser/skin/tabbrowser/newtab-glass.png);
+}
+
+.tabs-newtab-button:-moz-lwtheme-brighttext,
+#TabsToolbar[brighttext] > #new-tab-button,
+#TabsToolbar[brighttext] > toolbarpaletteitem > #new-tab-button {
+ list-style-image: url(chrome://browser/skin/tabbrowser/newtab-inverted.png);
+}
+
+.tabs-newtab-button {
+/* The button has a transparent outer border, so it will appear
+ to be 2px narrower than the width we set for it here */
+ width: 30px;
+}
+
+#TabsToolbar > #new-tab-button {
+ width: 26px;
+}
+
+#alltabs-button {
+ list-style-image: url("chrome://browser/skin/tabbrowser/alltabs.png");
+ -moz-image-region: rect(0, 14px, 16px, 0);
+}
+
+#alltabs-button[type="menu"] {
+ list-style-image: url("chrome://browser/skin/toolbarbutton-dropdown-arrow.png");
+ -moz-image-region: auto;
+}
+
+toolbar[brighttext] #alltabs-button {
+ list-style-image: url("chrome://browser/skin/tabbrowser/alltabs-inverted.png");
+}
+
+:-moz-any(#TabsToolbar, #nav-bar[tabsontop=false], #toolbar-menubar) > #alltabs-button[type=menu]:-moz-system-metric(windows-compositor):not(:-moz-lwtheme),
+:-moz-any(#TabsToolbar, #nav-bar[tabsontop=false], #toolbar-menubar) > toolbarpaletteitem > #alltabs-button[type=menu]:-moz-system-metric(windows-compositor):not(:-moz-lwtheme),
+toolbar[brighttext] #alltabs-button[type="menu"] {
+ list-style-image: url("chrome://browser/skin/toolbarbutton-dropdown-arrow-inverted.png");
+}
+
+#alltabs-button[type="menu"] > .toolbarbutton-icon {
+ margin: 0 2px;
+}
+
+#alltabs-button[type="menu"] > .toolbarbutton-menu-dropmarker {
+ display: none;
+}
+
+/* All tabs menupopup */
+.alltabs-item > .menu-iconic-left > .menu-iconic-icon {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+
+.alltabs-item[selected="true"] {
+ font-weight: bold;
+}
+
+.alltabs-item[busy] > .menu-iconic-left > .menu-iconic-icon {
+ list-style-image: url("chrome://global/skin/icons/loading_16.png");
+}
+
+.alltabs-item[tabIsVisible] {
+ /* box-shadow instead of background-color to work around native styling */
+ box-shadow: inset -5px 0 ThreeDShadow;
+}
+
+/* Tabstrip close button */
+.tabs-closebutton {
+ -moz-appearance: none;
+ padding: 4px 2px;
+ margin: 0px;
+ border: none;
+}
+
+toolbar[brighttext] .tabs-closebutton {
+ list-style-image: url("chrome://global/skin/icons/close-inverted.svg");
+}
+
+.tabs-closebutton > .toolbarbutton-icon {
+ -moz-margin-end: 0px !important;
+ -moz-padding-end: 2px !important;
+ -moz-padding-start: 2px !important;
+}
+
+toolbarbutton.chevron {
+ list-style-image: url("chrome://global/skin/toolbar/chevron.gif") !important;
+}
+
+toolbarbutton.chevron:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+ transform: scaleX(-1);
+}
+
+toolbarbutton.chevron > .toolbarbutton-text,
+toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
+ display: none;
+}
+
+toolbarbutton.chevron > .toolbarbutton-icon {
+ margin: 0;
+}
+
+toolbar[mode="text"] toolbarbutton.chevron > .toolbarbutton-icon {
+ display: -moz-box; /* display chevron icon in text mode */
+}
+
+#sidebar-throbber[loading="true"] {
+ list-style-image: url("chrome://global/skin/icons/loading_16.png");
+ -moz-margin-end: 4px;
+}
+
+/* Pale Moon: Feed icon */
+#ub-feed-button,
+#ub-feed-button > .button-box,
+#ub-feed-button:hover:active > .button-box {
+ padding: 0px;
+ margin: 0px;
+ border: 0px;
+ background-color: transparent !important;
+}
+
+#ub-feed-button {
+ -moz-appearance: none;
+ min-width: 0px;
+ list-style-image: url("chrome://browser/skin/feeds/feed-icons-16.png");
+ -moz-image-region: rect(0px 16px 16px 0px);
+}
+
+#ub-feed-button:hover {
+ -moz-image-region: rect(0px 32px 16px 16px);
+}
+
+#ub-feed-button[open="true"],
+#ub-feed-button:hover:active {
+ -moz-image-region: rect(0px 48px 16px 32px);
+}
+
+
+/* Bookmarks toolbar */
+#PlacesToolbarDropIndicator {
+ list-style-image: url(chrome://browser/skin/places/toolbarDropMarker.png);
+}
+
+toolbarbutton.bookmark-item[dragover="true"][open="true"] {
+ -moz-appearance: none;
+ background: Highlight !important;
+ color: HighlightText !important;
+}
+
+/* rules for menupopup drop indicators */
+.menupopup-drop-indicator-bar {
+ position: relative;
+ /* these two margins must together compensate the indicator's height */
+ margin-top: -1px;
+ margin-bottom: -1px;
+}
+
+.menupopup-drop-indicator {
+ list-style-image: none;
+ height: 2px;
+ -moz-margin-end: -4em;
+ background-color: Highlight;
+}
+
+/* ::::: Identity Indicator Styling ::::: */
+
+/* Popup Icons */
+#identity-popup-icon {
+ height: 64px;
+ width: 64px;
+ padding: 0;
+ list-style-image: url("chrome://browser/skin/identity.png");
+ -moz-image-region: rect(0px, 64px, 64px, 0px);
+}
+
+#identity-popup.verifiedDomain > #identity-popup-container > #identity-popup-icon {
+ -moz-image-region: rect(64px, 64px, 128px, 0px);
+}
+
+#identity-popup.verifiedIdentity > #identity-popup-container > #identity-popup-icon {
+ -moz-image-region: rect(128px, 64px, 192px, 0px);
+}
+
+/* Popup Body Text */
+.identity-popup-description {
+ white-space: pre-wrap;
+ -moz-padding-start: 15px;
+ margin: 2px 0 4px;
+}
+
+.identity-popup-label {
+ white-space: pre-wrap;
+ -moz-padding-start: 15px;
+ margin: 0;
+}
+
+#identity-popup-content-host,
+#identity-popup-content-box.verifiedIdentity > #identity-popup-content-owner {
+ font-size: 1.2em;
+}
+
+#identity-popup-content-host {
+ margin-top: 3px;
+ margin-bottom: 5px;
+ font-weight: bold;
+ max-width: 300px;
+}
+
+#identity-popup-content-owner {
+ margin-top: 4px;
+ margin-bottom: 0 !important;
+ font-weight: bold;
+ max-width: 300px;
+}
+
+.verifiedDomain > #identity-popup-content-owner {
+ font-weight: normal;
+}
+
+#identity-popup-content-verifier {
+ margin: 4px 0 2px;
+}
+
+#identity-popup-content-box.verifiedIdentity > #identity-popup-encryption ,
+#identity-popup-content-box.verifiedDomain > #identity-popup-encryption {
+ margin-top: 10px;
+ -moz-margin-start: -24px;
+}
+
+#identity-popup-content-box.verifiedIdentity > #identity-popup-encryption > vbox > #identity-popup-encryption-icon ,
+#identity-popup-content-box.verifiedDomain > #identity-popup-encryption > vbox > #identity-popup-encryption-icon {
+ list-style-image: url("chrome://browser/skin/Secure24.png");
+}
+
+#identity-popup-more-info-button {
+ margin-top: 6px;
+ margin-bottom: 0;
+ -moz-margin-end: 0;
+}
+
+.popup-notification-icon {
+ width: 64px;
+ height: 64px;
+ -moz-margin-end: 10px;
+}
+
+.popup-notification-icon[popupid="geolocation"] {
+ list-style-image: url(chrome://browser/skin/Geolocation-64.png);
+}
+
+.popup-notification-icon[popupid="xpinstall-disabled"],
+.popup-notification-icon[popupid="addon-progress"],
+.popup-notification-icon[popupid="addon-install-cancelled"],
+.popup-notification-icon[popupid="addon-install-blocked"],
+.popup-notification-icon[popupid="addon-install-origin-blocked"],
+.popup-notification-icon[popupid="addon-install-failed"],
+.popup-notification-icon[popupid="addon-install-complete"] {
+ list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric.png);
+ width: 32px;
+ height: 32px;
+}
+
+.popup-notification-icon[popupid="click-to-play-plugins"] {
+ list-style-image: url(chrome://mozapps/skin/plugins/pluginBlocked-64.png);
+}
+
+.popup-notification-icon[popupid="web-notifications"] {
+ list-style-image: url(chrome://browser/skin/notification-64.png);
+}
+
+.addon-progress-description {
+ width: 350px;
+ max-width: 350px;
+}
+
+.popup-progress-label,
+.popup-progress-meter {
+ -moz-margin-start: 0;
+ -moz-margin-end: 0;
+}
+
+.popup-progress-cancel {
+ -moz-appearance: none;
+ background: transparent;
+ border: none;
+ padding: 0;
+ margin: 0;
+ min-height: 0;
+ min-width: 0;
+ list-style-image: url(chrome://mozapps/skin/downloads/downloadButtons.png);
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+.popup-progress-cancel:hover {
+ -moz-image-region: rect(16px, 32px, 32px, 16px);
+}
+
+.popup-progress-cancel:active {
+ -moz-image-region: rect(32px, 32px, 48px, 16px);
+}
+
+.popup-notification-icon[popupid="indexedDB-permissions-prompt"],
+.popup-notification-icon[popupid="indexedDB-quota-prompt"],
+.popup-notification-icon[popupid*="offline-app-requested"],
+.popup-notification-icon[popupid="offline-app-usage"] {
+ list-style-image: url(chrome://global/skin/icons/question-64.png);
+}
+
+.popup-notification-icon[popupid="password"] {
+ list-style-image: url(chrome://mozapps/skin/passwordmgr/key-64.png);
+}
+
+.popup-notification-icon[popupid="mixed-content-blocked"] {
+ list-style-image: url(chrome://browser/skin/mixed-content-blocked-64.png);
+}
+
+%ifdef MOZ_WEBRTC
+.popup-notification-icon[popupid="webRTC-sharingDevices"],
+.popup-notification-icon[popupid="webRTC-shareDevices"] {
+ list-style-image: url(chrome://browser/skin/webRTC-shareDevice-64.png);
+}
+%endif
+
+.popup-notification-icon[popupid="pointerLock"] {
+ list-style-image: url(chrome://browser/skin/pointerLock-64.png);
+}
+
+/* Notification icon box */
+#notification-popup-box {
+ position: relative;
+ background-color: #fff;
+ background-clip: padding-box;
+ padding-left: 3px;
+ border-radius: var(--toolbarbutton-border-radius) 0 0 var(--toolbarbutton-border-radius);
+ border-width: 0 8px 0 0;
+ border-style: solid;
+ border-image: url("chrome://browser/skin/urlbar-arrow.png") 0 8 0 0 fill;
+ -moz-margin-end: -8px;
+}
+
+@conditionalForwardWithUrlbar@[forwarddisabled] + #urlbar-container > #urlbar > #notification-popup-box {
+ padding-left: 5px;
+}
+
+#notification-popup-box:-moz-locale-dir(rtl),
+.notification-anchor-icon:-moz-locale-dir(rtl) {
+ transform: scaleX(-1);
+}
+
+.notification-anchor-icon {
+ width: 16px;
+ height: 16px;
+ margin: 0 2px;
+}
+
+.notification-anchor-icon:-moz-focusring {
+ outline: 1px dotted -moz-DialogText;
+ outline-offset: -3px;
+}
+
+.default-notification-icon,
+#default-notification-icon {
+ list-style-image: url(chrome://global/skin/icons/information-16.png);
+}
+
+.geo-notification-icon,
+#geo-notification-icon {
+ list-style-image: url(chrome://browser/skin/Geolocation-16.png);
+}
+
+#addons-notification-icon {
+ list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric-16.png);
+}
+
+.indexedDB-notification-icon,
+#indexedDB-notification-icon {
+ list-style-image: url(chrome://global/skin/icons/question-16.png);
+}
+
+#password-notification-icon {
+ list-style-image: url(chrome://mozapps/skin/passwordmgr/key-16.png);
+}
+
+#plugins-notification-icon {
+ list-style-image: url(chrome://browser/skin/notification-pluginNormal.png);
+}
+
+#alert-plugins-notification-icon {
+ list-style-image: url(chrome://browser/skin/notification-pluginAlert.png);
+}
+
+#blocked-plugins-notification-icon {
+ list-style-image: url(chrome://browser/skin/notification-pluginBlocked.png);
+}
+
+#plugins-notification-icon,
+#alert-plugins-notification-icon,
+#blocked-plugins-notification-icon {
+ -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+#plugins-notification-icon:hover,
+#alert-plugins-notification-icon:hover,
+#blocked-plugins-notification-icon:hover {
+ -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
+#plugins-notification-icon:active,
+#alert-plugins-notification-icon:active,
+#blocked-plugins-notification-icon:active {
+ -moz-image-region: rect(0, 48px, 16px, 32px);
+}
+
+#notification-popup-box[hidden] {
+ /* Override display:none to make the pluginBlockedNotification animation work
+ when showing the notification repeatedly. */
+ display: -moz-box;
+ visibility: collapse;
+}
+
+#blocked-plugins-notification-icon[showing] {
+ animation: pluginBlockedNotification 500ms ease 0s 5 alternate both;
+}
+
+@keyframes pluginBlockedNotification {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
+.mixed-content-blocked-notification-icon,
+#mixed-content-blocked-notification-icon {
+ list-style-image: url(chrome://browser/skin/mixed-content-blocked-16.png);
+}
+
+%ifdef MOZ_WEBRTC
+.webRTC-shareDevices-notification-icon,
+#webRTC-shareDevices-notification-icon {
+ list-style-image: url(chrome://browser/skin/webRTC-shareDevice-16.png);
+}
+
+.webRTC-sharingDevices-notification-icon,
+#webRTC-sharingDevices-notification-icon {
+ list-style-image: url(chrome://browser/skin/webRTC-sharingDevice-16.png);
+}
+%endif
+
+.web-notifications-notification-icon,
+#web-notifications-notification-icon {
+ list-style-image: url(chrome://browser/skin/web-notifications-tray.svg);
+ -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+.web-notifications-notification-icon:hover,
+#web-notifications-notification-icon:hover {
+ -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
+.web-notifications-notification-icon:hover:active,
+#web-notifications-notification-icon:hover:active {
+ -moz-image-region: rect(0, 48px, 16px, 32px);
+}
+
+#pointerLock-notification-icon {
+ list-style-image: url(chrome://browser/skin/pointerLock-16.png);
+}
+#pointerLock-cancel {
+ margin: 0px;
+}
+
+#identity-popup-container {
+ min-width: 280px;
+}
+
+/* Bookmarks roots menu-items */
+#appmenu_subscribeToPage:not([disabled]),
+#appmenu_subscribeToPageMenu,
+#subscribeToPageMenuitem:not([disabled]),
+#subscribeToPageMenupopup,
+#BMB_subscribeToPageMenuitem:not([disabled]),
+#BMB_subscribeToPageMenupopup {
+ list-style-image: url("chrome://browser/skin/feeds/feedIcon16.png");
+}
+
+#appmenu_bookmarksToolbar,
+#bookmarksToolbarFolderMenu,
+#BMB_bookmarksToolbar {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png");
+ -moz-image-region: auto;
+}
+
+#appmenu_unsortedBookmarks,
+#menu_unsortedBookmarks,
+#BMB_unsortedBookmarks {
+ list-style-image: url("chrome://browser/skin/places/unsortedBookmarks.png");
+ -moz-image-region: auto;
+}
+
+/* ::::: Keyboard UI Panel ::::: */
+
+.KUI-panel {
+ -moz-appearance: none;
+ background: rgba(27%,27%,27%,.9) url(KUI-background.png) repeat-x;
+ color: white;
+ border-style: none;
+ border-radius: 20px;
+}
+
+.KUI-panel[level="top"] {
+ background-color: rgba(27%,27%,27%,.65);
+}
+
+.KUI-panel-closebutton {
+ list-style-image: url(KUI-close.png);
+ -moz-appearance: none;
+ border: none;
+ padding: 0;
+ width: 24px;
+ height: 24px;
+}
+
+.KUI-panel-closebutton:not(:hover) {
+ opacity: .6;
+}
+
+.KUI-panel-closebutton > .toolbarbutton-icon {
+ margin: 0;
+}
+
+/* ::::: Ctrl-Tab and All Tabs Panels ::::: */
+
+/* Ctrl-Tab */
+
+#ctrlTab-panel {
+ padding: 20px 10px 10px;
+ font-weight: bold;
+ text-shadow: 0 0 1px rgb(27%,27%,27%), 0 0 2px rgb(27%,27%,27%);
+}
+
+.ctrlTab-favicon[src] {
+ background-color: white;
+ width: 20px;
+ height: 20px;
+ padding: 2px;
+}
+
+.ctrlTab-preview-inner > .tabPreview-canvas {
+ box-shadow: 1px 1px 2px rgb(12%,12%,12%);
+}
+
+.ctrlTab-preview:not(#ctrlTab-showAll) > * > .ctrlTab-preview-inner > .tabPreview-canvas {
+ margin-bottom: 2px;
+}
+
+.ctrlTab-preview-inner {
+ padding-bottom: 10px;
+}
+
+#ctrlTab-showAll:not(:focus) > * > .ctrlTab-preview-inner {
+ padding: 10px;
+ background-color: rgba(255,255,255,.2);
+ border-radius: .5em;
+}
+
+.ctrlTab-preview:focus > * > .ctrlTab-preview-inner {
+ color: white;
+ background-color: rgba(0,0,0,.6);
+ text-shadow: none;
+ padding: 8px;
+ border: 2px solid white;
+ border-radius: .5em;
+}
+
+.ctrlTab-preview:not(#ctrlTab-showAll):focus > * > .ctrlTab-preview-inner {
+ margin: -10px -10px 0;
+}
+
+#ctrlTab-showAll {
+ margin-top: .5em;
+}
+
+/* All Tabs */
+
+#allTabs-panel {
+ padding-bottom: 10px;
+ -moz-appearance: none;
+ border: none;
+ background: -moz-dialog;
+ color: -moz-dialogText;
+}
+
+#allTabs-meta {
+ margin: 10px;
+}
+
+#allTabs-filter {
+ -moz-margin-start: 24px;
+ -moz-margin-end: 0;
+}
+
+#allTabs-tab-close-button > .toolbarbutton-icon {
+ margin: 0;
+}
+
+/* Make sure the allTab previews always have regular close buttons */
+#allTabs-tab-close-button:-moz-lwtheme-brighttext {
+ list-style-image: url("chrome://global/skin/icons/close.svg");
+}
+
+.allTabs-favicon[src] {
+ background-color: -moz-dialog;
+ width: 22px;
+ height: 22px;
+ padding-top: 1px;
+ padding-bottom: 5px;
+ -moz-padding-start: 1px;
+ -moz-padding-end: 5px;
+ margin-top: -2px;
+ -moz-margin-start: -2px;
+ border-bottom-right-radius: 4px;
+}
+
+.allTabs-favicon[src]:-moz-locale-dir(rtl) {
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 4px;
+}
+
+.allTabs-preview-inner > .tabPreview-canvas {
+ background-color: rgb(60%,60%,60%);
+ box-shadow: 0 0 1.5px ThreeDShadow;
+}
+
+.allTabs-preview:not(:hover):not([closebuttonhover]) > html|canvas {
+ opacity: .8;
+}
+
+.allTabs-preview:focus > * > .allTabs-preview-inner {
+ outline: 1px dotted -moz-dialogText;
+}
+
+/* Add-on bar */
+
+#addon-bar {
+ -moz-appearance: none;
+ min-height: 20px;
+ border-top-style: none;
+ border-bottom-style: none;
+ padding-top: 1px;
+ background-image: linear-gradient(rgba(0,0,0,.15) 1px, rgba(255,255,255,.15) 1px);
+ background-size: 100% 2px;
+ background-repeat: no-repeat;
+}
+
+#status-bar {
+ -moz-appearance: none;
+ background-color: transparent;
+ border: none;
+ min-height: 0;
+}
+
+#addon-bar[customizing] > #status-bar {
+ opacity: .5;
+ background-image: repeating-linear-gradient(135deg,
+ rgba(255,255,255,.3), rgba(255,255,255,.3) 5px,
+ rgba(0,0,0,.3) 5px, rgba(0,0,0,.3) 10px);
+}
+
+#status-bar > statusbarpanel {
+ border-width: 0;
+ -moz-appearance: none;
+}
+
+#addonbar-closebutton {
+ border: none;
+ padding: 0 5px;
+ -moz-appearance: none;
+}
+
+toolbar[brighttext] #addonbar-closebutton {
+ list-style-image: url("chrome://global/skin/icons/close-inverted.svg");
+}
+
+/* Status panel */
+
+.statuspanel-label {
+ margin: 0;
+ padding: 2px 4px;
+ background: linear-gradient(#fff, #ddd);
+ border: 1px none #ccc;
+ border-top-style: solid;
+ color: #333;
+ text-shadow: none;
+}
+
+.statuspanel-label:-moz-locale-dir(ltr):not([mirror]),
+.statuspanel-label:-moz-locale-dir(rtl)[mirror] {
+ border-right-style: solid;
+ /* disabled for triggering grayscale AA (bug 659213)
+ border-top-right-radius: .3em;
+ */
+ margin-right: 1em;
+}
+
+.statuspanel-label:-moz-locale-dir(rtl):not([mirror]),
+.statuspanel-label:-moz-locale-dir(ltr)[mirror] {
+ border-left-style: solid;
+ /* disabled for triggering grayscale AA (bug 659213)
+ border-top-left-radius: .3em;
+ */
+ margin-left: 1em;
+}
+
+#full-screen-warning-message {
+ background-color: hsl(0,0%,15%);
+ color: white;
+ border-radius: 8px;
+ margin-top: 30px;
+ padding: 30px 50px;
+ box-shadow: 0 0 2px white;
+}
+
+.full-screen-description {
+ font-size: 150%;
+}
+
+#full-screen-domain-text {
+ font-size: 300%;
+}
+
+%ifdef MOZ_DEVTOOLS
+%include ../../../../devtools/client/themes/responsivedesign.inc.css
+%include ../../../../devtools/client/themes/commandline.inc.css
+%endif
+%include ../shared/plugin-doorhanger.inc.css
+
+%ifdef MOZ_DEVTOOLS
+/* Error counter */
+
+#developer-toolbar-toolbox-button[error-count]:before {
+ color: #FDF3DE;
+ min-width: 16px;
+ text-shadow: none;
+ background-image: linear-gradient(#B4211B, #8A1915);
+ border-radius: 1px;
+ -moz-margin-end: 5px;
+}
+%endif
+
+.toolbarbutton-badge-stack {
+ margin: 0;
+ padding: 0;
+ position: relative;
+}
+
+@navbarLargeIcons@ .toolbarbutton-1 > .toolbarbutton-badge-stack {
+ padding: 2px 5px;
+}
+
+.toolbarbutton-1 > .toolbarbutton-badge-stack > .toolbar-icon {
+ position: absolute;
+ top: 2px;
+ right: 2px;
+}
+
+.toolbarbutton-badge-stack > .toolbarbutton-icon[label]:not([label=""]) {
+ -moz-margin-end: 0;
+}
+
+@navbarLargeIcons@ *|* > .toolbarbutton-badge[badge]:not([badge=""])::after {
+ top: 1px;
+ right: 1px;
+}
+
+.toolbarbutton-badge[badge]:not([badge=""]):-moz-locale-dir(rtl)::after {
+ left: 0;
+ right: auto;
+}
+
+@navbarLargeIcons@ *|* > .toolbarbutton-badge[badge]:not([badge=""]):-moz-locale-dir(rtl)::after {
+ left: 1px;
+ right: auto;
+}
+
+#main-window[privatebrowsingmode=temporary] #toolbar-menubar {
+ background-image: url("chrome://browser/skin/privatebrowsing-dark.png");
+ background-position: top right;
+ background-repeat: no-repeat;
+}
+
+#main-window[privatebrowsingmode=temporary] #toolbar-menubar:-moz-locale-dir(rtl) {
+ background-position: top left;
+}
+
+#main-window[privatebrowsingmode=temporary] #appmenu-button > .button-box > .box-inherit > .button-icon {
+ list-style-image: url("chrome://browser/skin/privatebrowsing-light.png");
+ width: 20px;
+ height: 16px;
+}
+
+@media not all and (-moz-windows-classic) {
+ #main-window[sizemode="normal"] > #titlebar > #titlebar-content > #appmenu-button-container {
+ margin-top: 1px;
+ }
+
+ #appmenu-button {
+ border-width: 2px;
+ -moz-border-left-colors: @appMenuButtonBorderColor@;
+ -moz-border-bottom-colors: @appMenuButtonBorderColor@;
+ -moz-border-right-colors: @appMenuButtonBorderColor@;
+ margin-bottom: 1px; /* compensate white outer border */
+ box-shadow: 0 1px 0 rgba(255,255,255,.25) inset,
+ 0 0 2px 1px rgba(255,255,255,.25) inset;
+ }
+
+ #main-window[privatebrowsingmode=temporary] #appmenu-button {
+ -moz-border-left-colors: rgba(255,255,255,.5) rgba(43,8,65,.9);
+ -moz-border-bottom-colors: rgba(255,255,255,.5) rgba(43,8,65,.9);
+ -moz-border-right-colors: rgba(255,255,255,.5) rgba(43,8,65,.9);
+ }
+
+ #appmenu-popup {
+ margin-top: -1px;
+ -moz-margin-start: 1px;
+ }
+
+ .panel-promo-message {
+ font-style: italic;
+ }
+}
+
+@media (-moz-windows-default-theme) {
+ #navigator-toolbox > toolbar:not(:-moz-lwtheme),
+ #browser-bottombox:not(:-moz-lwtheme) {
+ background-color: var(--toolbar-custom-color);
+ }
+
+ .tabbrowser-tab:not(:-moz-lwtheme),
+ .tabs-newtab-button:not(:-moz-lwtheme) {
+ background-image: @toolbarShadowOnTab@, var(--tab-background),
+ linear-gradient(var(--toolbar-custom-color), var(--toolbar-custom-color));
+ }
+
+ .tabbrowser-tab:not(:-moz-lwtheme):hover,
+ .tabs-newtab-button:not(:-moz-lwtheme):hover {
+ background-image: @toolbarShadowOnTab@, var(--tab-background-hover),
+ linear-gradient(var(--toolbar-custom-color), var(--toolbar-custom-color));
+ }
+
+ .tabbrowser-tab[selected="true"]:not(:-moz-lwtheme) {
+ background-image: linear-gradient(#fff, var(--toolbar-highlight-top) 50%),
+ linear-gradient(var(--toolbar-custom-color), var(--toolbar-custom-color));
+ }
+
+ #main-window[tabsontop=false]:not([disablechrome]) .tabbrowser-tab[selected=true]:not(:-moz-lwtheme) {
+ background-image: @toolbarShadowOnTab@,
+ linear-gradient(#fff, var(--toolbar-highlight-top) 50%),
+ linear-gradient(var(--toolbar-custom-color), var(--toolbar-custom-color));
+ }
+
+ @media (-moz-os-version: windows-vista),
+ (-moz-os-version: windows-win7) {
+ :root:not(:-moz-lwtheme) {
+ --toolbox-after-color: #aabccf;
+ }
+ }
+
+ @media (-moz-os-version: windows-win8),
+ (-moz-os-version: windows-win10) {
+ :root {
+ --toolbar-custom-color: hsl(210,0%,92%);
+ }
+
+ :root:not(:-moz-lwtheme) {
+ --toolbox-after-color: #bcbcbc;
+ }
+ }
+
+ #navigator-toolbox[tabsontop=true] #urlbar:not(:-moz-lwtheme),
+ #navigator-toolbox[tabsontop=true] .searchbar-textbox:not(:-moz-lwtheme) {
+ border-color: hsla(210,54%,20%,.25) hsla(210,54%,20%,.27) hsla(210,54%,20%,.3);
+ }
+
+ #navigator-toolbox[tabsontop=true] #urlbar:not(:-moz-lwtheme):not([focused]):hover,
+ #navigator-toolbox[tabsontop=true] .searchbar-textbox:not(:-moz-lwtheme):not([focused]):hover {
+ border-color: hsla(210,54%,20%,.35) hsla(210,54%,20%,.37) hsla(210,54%,20%,.4);
+ }
+
+ #navigator-toolbox[tabsontop=true] #urlbar:not(:-moz-lwtheme)[focused],
+ #navigator-toolbox[tabsontop=true] .searchbar-textbox:not(:-moz-lwtheme)[focused] {
+ border-color: hsla(206,100%,60%,.65) hsla(206,100%,55%,.65) hsla(206,100%,50%,.65);
+ }
+
+ .sidebar-splitter {
+ border: 0;
+ -moz-border-end: 1px solid #A9B7C9;
+ min-width: 0;
+ width: 3px;
+ background-color: transparent;
+ -moz-margin-start: -3px;
+ position: relative;
+ }
+
+ #appcontent ~ .sidebar-splitter {
+ -moz-border-start: 1px solid #A9B7C9;
+ -moz-border-end: none;
+ -moz-margin-start: 0;
+ -moz-margin-end: -3px;
+ }
+
+ .menu-accel,
+ .menu-iconic-accel {
+ color: graytext;
+ }
+
+ .chatbar-button,
+ chatbar > chatbox {
+ border-color: #A9B7C9;
+ }
+}
+
+@media (-moz-windows-compositor) {
+ #main-window {
+ background-color: transparent;
+ -moz-appearance: -moz-win-glass;
+ }
+
+ /* On win 10, if we don't set this on the entire browser container, including
+ * the sidebar, then the accent color bleeds through in the titlebar
+ * if the sidebar is open. */
+ #browser {
+ -moz-appearance: -moz-win-exclude-glass;
+ }
+
+/* ==== Windows 10 styling ==== */
+
+ @media (-moz-os-version: windows-win10) {
+ /* Draw XUL caption buttons and background on Win10 */
+ @media (-moz-windows-accent-color-applies: 0) {
+ /* Default styling for when no accent color is applied */
+ #main-window:not(:-moz-window-inactive):not(:-moz-lwtheme) {
+ background-color: white;
+ }
+
+ :root:not(:-moz-window-inactive):not(:-moz-lwtheme) {
+ --window-text-color: black;
+ }
+
+ #titlebar-min:not(:-moz-window-inactive):not(:-moz-lwtheme) {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#minimize);
+ }
+
+ #titlebar-max:not(:-moz-window-inactive):not(:-moz-lwtheme) {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#maximize);
+ }
+
+ #main-window[sizemode="maximized"] #titlebar-max:not(:-moz-window-inactive):not(:-moz-lwtheme) {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#restore);
+ }
+
+ #titlebar-close:not(:-moz-window-inactive):not(:-moz-lwtheme) {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#close);
+ }
+
+ .titlebar-button:not(#titlebar-close):not(:-moz-window-inactive):not(:-moz-lwtheme):hover {
+ background-color: hsla(0, 0%, 0%, .17);
+ }
+
+ .titlebar-button:not(#titlebar-close):not(:-moz-window-inactive):not(:-moz-lwtheme):hover:active {
+ background-color: hsla(0, 0%, 0%, .27);
+ transition: none;
+ }
+
+ #titlebar-close:not(:-moz-window-inactive):not(:-moz-lwtheme):hover {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-highlight);
+ background-color: hsla(0, 86%, 49%, 1);
+ }
+
+ #titlebar-close:not(:-moz-window-inactive):not(:-moz-lwtheme):hover:active {
+ background-color: hsla(0, 60%, 49%, 0.6);
+ transition: none;
+ }
+ }
+
+ @media (-moz-windows-accent-color-applies) {
+ /* Styling for when an accent color is applied to the titlebar */
+ #main-window:not(:-moz-window-inactive):not(:-moz-lwtheme) {
+ background-color: -moz-win-accentcolor;
+ }
+
+ :root:not(:-moz-window-inactive):not(:-moz-lwtheme) {
+ --window-text-color: -moz-win-accentcolortext;
+ }
+
+ #titlebar-min {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#minimize);
+ }
+
+ #titlebar-max {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#maximize);
+ }
+
+ #main-window[sizemode="maximized"] #titlebar-max {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#restore);
+ }
+
+ #titlebar-close {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#close);
+ }
+
+ .titlebar-button:hover {
+ background-color: hsla(0, 0%, 0%, .17);
+ }
+
+ .titlebar-button:hover:active {
+ background-color: hsla(0, 0%, 0%, .27);
+ transition: none;
+ }
+
+ @media (-moz-windows-accent-color-is-dark) {
+ /* dark accent color */
+ #titlebar-min {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#minimize-highlight);
+ }
+
+ #titlebar-max {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#maximize-highlight);
+ }
+
+ #main-window[sizemode="maximized"] #titlebar-max {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#restore-highlight);
+ }
+
+ #titlebar-close {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-highlight);
+ }
+
+ .titlebar-button:not(#titlebar-close):not(:-moz-window-inactive):not(:-moz-lwtheme):hover {
+ background-color: hsla(0, 0%, 100%, .17);
+ }
+
+ .titlebar-button:not(#titlebar-close):not(:-moz-window-inactive):not(:-moz-lwtheme):hover:active {
+ background-color: hsla(0, 0%, 100%, .27);
+ transition: none;
+ }
+
+ #titlebar-close:not(:-moz-window-inactive):not(:-moz-lwtheme):hover {
+ background-color: hsla(0, 86%, 49%, 1);
+ }
+
+ #titlebar-close:not(:-moz-window-inactive):not(:-moz-lwtheme):hover:active {
+ background-color: hsla(0, 60%, 39%, 1);
+ transition: none;
+ }
+ }
+ }
+
+ #main-window:-moz-window-inactive:not(:-moz-lwtheme) {
+ background-color: hsl(0, 0%, 95%);
+ }
+
+ /* If we don't have [chromemargin], it means the menubar is active; set the
+ window background to transparent in that case to match it with the dwm
+ color and prevent a drawing delay between title bar and UI region */
+ #main-window:not([chromemargin]):not(:-moz-lwtheme) {
+ background-color: transparent;
+ }
+
+ #titlebar-buttonbox,
+ .titlebar-button {
+ -moz-appearance: none !important;
+ }
+
+ .titlebar-button {
+ border: none;
+ margin: 0 !important;
+ padding: 9px 17px;
+ transition: background-color linear 120ms;
+ }
+
+ #main-window[sizemode="maximized"][tabsontop=true] #tabbrowser-tabs {
+ min-height: 28px;
+ }
+
+ #main-window[sizemode=maximized] .titlebar-button {
+ padding-top: 8px;
+ padding-bottom: 8px;
+ }
+
+ .titlebar-button > .toolbarbutton-icon {
+ width: 12px;
+ height: 12px;
+ }
+
+ .titlebar-button:not(:hover) > .toolbarbutton-icon:-moz-window-inactive {
+ opacity: 0.5;
+ }
+
+ #main-window[chromemargin^="0,"][sizemode=normal] #navigator-toolbox {
+ margin-top: -4px;
+ }
+
+ #main-window[sizemode="maximized"] #titlebar-close {
+ padding-right: 19px;
+ }
+
+ #titlebar-close:hover {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-highlight);
+ background-color: hsla(0, 86%, 49%, 1);
+ transition: background-color linear 160ms;
+ }
+
+ #titlebar-close:hover:active {
+ background-color: hsla(0, 86%, 49%, 0.6);
+ transition: none;
+ }
+
+ /* inactive window */
+
+ #titlebar-min:-moz-window-inactive:not(:-moz-lwtheme) {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#minimize-inactive);
+ }
+
+ #titlebar-max:-moz-window-inactive:not(:-moz-lwtheme) {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#maximize-inactive);
+ }
+
+ #main-window[sizemode="maximized"] #titlebar-max:-moz-window-inactive:not(:-moz-lwtheme) {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#restore-inactive);
+ }
+
+ #titlebar-close:-moz-window-inactive:not(:-moz-lwtheme):not(:hover) {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-inactive);
+ }
+
+ .titlebar-button:-moz-window-inactive:not(:-moz-lwtheme):hover {
+ background-color: hsla(0, 0%, 0%, .17);
+ }
+
+ .titlebar-button:-moz-window-inactive:not(:-moz-lwtheme):hover:active {
+ background-color: hsla(0, 0%, 0%, .27);
+ transition: none;
+ }
+
+ /* light persona */
+
+ .titlebar-button:-moz-lwtheme-darktext:hover {
+ background-color: hsla(0, 0%, 0%, .17);
+ }
+
+ .titlebar-button:-moz-lwtheme-darktext:hover:active {
+ background-color: hsla(0, 0%, 0%, .27);
+ transition: none;
+ }
+
+ #titlebar-min:-moz-lwtheme-darktext {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#minimize-outline);
+ }
+
+ #titlebar-max:-moz-lwtheme-darktext {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#maximize-outline);
+ }
+
+ #main-window[sizemode="maximized"]:-moz-lwtheme-darktext #titlebar-max:-moz-lwtheme-darktext {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#restore-outline);
+ }
+
+ #titlebar-close:-moz-lwtheme-darktext {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-outline);
+ }
+ #titlebar-close:hover:-moz-lwtheme-darktext {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-outline);
+ }
+
+ /* dark persona */
+
+ .titlebar-button:-moz-lwtheme-brighttext:hover {
+ background-color: hsla(0, 0%, 100%, .27);
+ }
+
+ .titlebar-button:-moz-lwtheme-brighttext:hover:active {
+ background-color: hsla(0, 0%, 100%, .37);
+ transition: none;
+ }
+
+ #titlebar-min:-moz-lwtheme-brighttext {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#minimize-outline-inverted);
+ }
+
+ #titlebar-max:-moz-lwtheme-brighttext {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#maximize-outline-inverted);
+ }
+
+ #main-window[sizemode="maximized"]:-moz-lwtheme-brighttext #titlebar-max:-moz-lwtheme-brighttext {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#restore-outline-inverted);
+ }
+
+ #titlebar-close:-moz-lwtheme-brighttext {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-outline-inverted);
+ }
+ #titlebar-close:hover:-moz-lwtheme-brighttext {
+ list-style-image: url(chrome://browser/skin/caption-buttons.svg#close-outline-inverted);
+ }
+
+ /* the 12px image renders a 10px icon, and the 10px upscaled gets rounded to 12.5, which
+ * rounds up to 13px, which makes the icon one pixel too big on 1.25dppx. Fix: */
+ @media (min-resolution: 1.20dppx) and (max-resolution: 1.45dppx) {
+ .titlebar-button > .toolbarbutton-icon {
+ width: 11.5px;
+ height: 11.5px;
+ }
+ }
+
+ /* 175% dpi should result in the same device pixel sizes as 150% dpi. */
+ @media (min-resolution: 1.70dppx) and (max-resolution: 1.95dppx) {
+ .titlebar-button {
+ padding-left: 14.1px;
+ padding-right: 14.1px;
+ }
+
+ .titlebar-button > .toolbarbutton-icon {
+ width: 10.8px;
+ height: 10.8px;
+ }
+ }
+
+ /* 225% dpi should result in the same device pixel sizes as 200% dpi. */
+ @media (min-resolution: 2.20dppx) and (max-resolution: 2.45dppx) {
+ .titlebar-button {
+ padding-left: 15.3333px;
+ padding-right: 15.3333px;
+ }
+
+ .titlebar-button > .toolbarbutton-icon {
+ width: 10.8px;
+ height: 10.8px;
+ }
+ }
+
+ /* 275% dpi should result in the same device pixel sizes as 250% dpi. */
+ @media (min-resolution: 2.70dppx) and (max-resolution: 2.95dppx) {
+ .titlebar-button > .toolbarbutton-icon {
+ width: 10.8px;
+ height: 10.8px;
+ }
+ }
+
+ #appmenu-button {
+ margin-top: -1px;
+ margin-bottom: 5px;
+ }
+ }
+
+/* ==== Windows Vista/7/8 styling ==== */
+
+ @media (-moz-os-version: windows-vista),
+ (-moz-os-version: windows-win7),
+ (-moz-os-version: windows-win8) {
+ /* Make sure the native margins are correct on Win Vista/7/8.
+ * We can't use -moz-win-glass there because the border sizing would
+ * not be correct. */
+ #main-window {
+ -moz-appearance: -moz-win-borderless-glass;
+ }
+
+ /* These should be hidden w/ glass enabled. Windows draws its own buttons. */
+ .titlebar-button {
+ display: none;
+ }
+
+ /* The borders on the glass frame are ours, and inside #browser, and on
+ * vista and win7 we want to make sure they are "glassy", so we can't use
+ * #browser as the exclude-glass container. We use #appcontent instead. */
+ #browser {
+ -moz-appearance: none;
+ }
+
+ #appcontent {
+ -moz-appearance: -moz-win-exclude-glass;
+ }
+
+ #main-window[chromemargin^="0,"][sizemode=normal] #navigator-toolbox {
+ margin-top: -7px;
+ }
+
+ /* Artificially draw window borders that are covered by lwtheme, see bug 591930. */
+ #main-window[sizemode="normal"] > #titlebar > #titlebar-content:-moz-lwtheme {
+ border-top: 2px solid;
+ -moz-border-top-colors: @glassActiveBorderColor@ rgba(255,255,255,.6);
+ }
+
+ #main-window[sizemode="normal"] > #titlebar > #titlebar-content:-moz-lwtheme:-moz-window-inactive {
+ -moz-border-top-colors: @glassInactiveBorderColor@ rgba(255,255,255,.6);
+ }
+
+ #main-window[sizemode="normal"] > #titlebar > #titlebar-content > #appmenu-button-container:-moz-lwtheme {
+ margin-top: -1px;
+ }
+
+ #main-window[sizemode="normal"] #titlebar-buttonbox:-moz-lwtheme {
+ margin-top: -2px;
+ }
+
+ #appmenu-button {
+ margin-bottom: -1px; /* compensate white outer border */
+ }
+
+ }
+
+/* ==== Windows Vista/7 (true glass) styling ==== */
+
+ @media (-moz-os-version: windows-vista),
+ (-moz-os-version: windows-win7) {
+ #toolbar-menubar:not(:-moz-lwtheme),
+ #TabsToolbar[tabsontop=true]:not(:-moz-lwtheme),
+ #nav-bar[tabsontop=false]:not(:-moz-lwtheme),
+ #nav-bar[tabsontop=false]:not(:-moz-lwtheme) .toolbarbutton-text,
+ #nav-bar + #customToolbars + #PersonalToolbar[collapsed=true] + #TabsToolbar[tabsontop=false]:last-child:not(:-moz-lwtheme) {
+ text-shadow: 0 0 .5em white, 0 0 .5em white, 0 1px 0 rgba(255,255,255,.4);
+ }
+
+ #main-menubar:not(:-moz-lwtheme):not(:-moz-window-inactive) {
+ background-color: rgba(255,255,255,.7);
+ border-radius: var(--toolbarbutton-border-radius);
+ color: black;
+ }
+
+ :-moz-any(#toolbar-menubar, #TabsToolbar[tabsontop=true], #nav-bar[tabsontop=false]) .toolbarbutton-1 > .toolbarbutton-menu-dropmarker:not(:-moz-lwtheme),
+ :-moz-any(#toolbar-menubar, #TabsToolbar[tabsontop=true], #nav-bar[tabsontop=false]) .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker:not(:-moz-lwtheme),
+ #nav-bar + #customToolbars + #PersonalToolbar[collapsed=true] + #TabsToolbar[tabsontop=false]:last-child .toolbarbutton-1 > .toolbarbutton-menu-dropmarker:not(:-moz-lwtheme),
+ #nav-bar + #customToolbars + #PersonalToolbar[collapsed=true] + #TabsToolbar[tabsontop=false]:last-child .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker:not(:-moz-lwtheme) {
+ list-style-image: url("chrome://browser/skin/toolbarbutton-dropdown-arrow-inverted.png");
+ }
+ }
+
+/* ==== Windows 8/10 (flat color) styling ==== */
+
+ @media (-moz-os-version: windows-win8) {
+ /* Use a light text styling on dark window frames */
+ :root[darkwindowframe="true"]:not(:-moz-lwtheme):not(:-moz-window-inactive) {
+ --window-text-color: white;
+ }
+ }
+
+ @media (-moz-os-version: windows-win8),
+ (-moz-os-version: windows-win10) {
+ /* Fade text stylings on window inactivity */
+ :root:not(:-moz-lwtheme):-moz-window-inactive {
+ --window-text-color: rgba(0, 0, 0, 0.5);
+ }
+ }
+
+/* ==== ==== */
+
+ #main-window[sizemode=fullscreen]:not(:-moz-lwtheme) {
+ -moz-appearance: none;
+ background-color: #556;
+ }
+
+ #toolbar-menubar:not(:-moz-lwtheme),
+ #TabsToolbar[tabsontop=true]:not(:-moz-lwtheme),
+ #nav-bar[tabsontop=false]:not(:-moz-lwtheme),
+ #nav-bar[tabsontop=false]:not(:-moz-lwtheme) .toolbarbutton-text,
+ #nav-bar + #customToolbars + #PersonalToolbar:-moz-any([collapsed=true],[moz-collapsed=true]) + #TabsToolbar[tabsontop=false]:last-child:not(:-moz-lwtheme) {
+ background-color: transparent !important;
+ color: var(--window-text-color);
+ border-left-style: none !important;
+ border-right-style: none !important;
+ }
+
+ #main-menubar > menu:not(:-moz-lwtheme) {
+ color: inherit;
+ }
+
+ :-moz-any(#toolbar-menubar, #nav-bar[tabsontop=false]) :-moz-any(@primaryToolbarButtons@):not(:-moz-any(#alltabs-button,#sync-button[status])) > .toolbarbutton-icon:not(:-moz-lwtheme),
+ :-moz-any(#toolbar-menubar, #nav-bar[tabsontop=false]) :-moz-any(@primaryToolbarButtons@) > toolbarbutton > .toolbarbutton-icon:not(:-moz-lwtheme),
+ #TabsToolbar[tabsontop=true] :-moz-any(@primaryToolbarButtons@):not(:-moz-any(#alltabs-button,#new-tab-button,#sync-button[status])) > .toolbarbutton-icon:not(:-moz-lwtheme),
+ #TabsToolbar[tabsontop=true] :-moz-any(@primaryToolbarButtons@) > toolbarbutton > .toolbarbutton-icon:not(:-moz-lwtheme),
+ #nav-bar + #customToolbars + #PersonalToolbar[collapsed=true] + #TabsToolbar[tabsontop=false]:last-child :-moz-any(@primaryToolbarButtons@):not(:-moz-any(#alltabs-button,#new-tab-button,#sync-button[status])) > .toolbarbutton-icon:not(:-moz-lwtheme),
+ #nav-bar + #customToolbars + #PersonalToolbar[collapsed=true] + #TabsToolbar[tabsontop=false]:last-child :-moz-any(@primaryToolbarButtons@) > toolbarbutton > .toolbarbutton-icon:not(:-moz-lwtheme) {
+ list-style-image: var(--toolbarbutton-glass-image);
+ }
+
+/* Show toolbar borders on vista through win8, but not on win10 and later: */
+@media (-moz-os-version: windows-vista),
+ (-moz-os-version: windows-win7),
+ (-moz-os-version: windows-win8) {
+ /* Vertical toolbar border */
+ #main-window[sizemode=normal] #navigator-toolbox::after,
+ #main-window[sizemode=normal] #navigator-toolbox[tabsontop=true] > toolbar:not(#toolbar-menubar):not(#TabsToolbar),
+ #main-window[sizemode=normal] #navigator-toolbox[tabsontop=false] > toolbar:not(#toolbar-menubar):not(#nav-bar) {
+ border-left: 1px solid @toolbarShadowColor@;
+ border-right: 1px solid @toolbarShadowColor@;
+ background-clip: padding-box;
+ }
+ #main-window[sizemode=normal] #navigator-toolbox > toolbar:-moz-lwtheme {
+ border-color: transparent !important;
+ }
+ #main-window[sizemode=normal] #browser-border-start,
+ #main-window[sizemode=normal] #browser-border-end {
+ display: -moz-box;
+ background-color: @toolbarShadowColor@;
+ width: 1px;
+ }
+ #main-window[sizemode=normal] #browser-bottombox {
+ border: 1px solid @toolbarShadowColor@;
+ border-top-style: none;
+ background-clip: padding-box;
+ }
+}
+
+ #main-window[sizemode=normal][tabsontop=false] #PersonalToolbar:not(:-moz-lwtheme) {
+ border-top-left-radius: var(--toolbarbutton-border-radius);
+ border-top-right-radius: var(--toolbarbutton-border-radius);
+ }
+
+ #main-window[sizemode=normal] #nav-bar[tabsontop=true]:not(:-moz-lwtheme),
+ #main-window[sizemode=normal] #nav-bar[tabsontop=true][collapsed=true]:not([customizing]) + toolbar:not(:-moz-lwtheme),
+ #main-window[sizemode=normal] #nav-bar[tabsontop=true][collapsed=true]:not([customizing]) + #customToolbars + #PersonalToolbar:not(:-moz-lwtheme),
+ #main-window[sizemode=normal][disablechrome] #navigator-toolbox[tabsontop=true]:not(:-moz-lwtheme)::after {
+ border-top-left-radius: var(--toolbarbutton-border-radius);
+ border-top-right-radius: var(--toolbarbutton-border-radius);
+ }
+
+ /* Toolbar shadow behind tabs */
+ /* This code is only needed for restored windows (i.e. sizemode=normal)
+ because of the border radius on the toolbar below the tab bar. */
+ #main-window[sizemode=normal] #nav-bar[tabsontop=true]:not(:-moz-lwtheme),
+ #main-window[sizemode=normal] #nav-bar[tabsontop=true][collapsed=true]:not([customizing]) + toolbar:not(:-moz-lwtheme),
+ #main-window[sizemode=normal] #nav-bar[tabsontop=true][collapsed=true]:not([customizing]) + #customToolbars + #PersonalToolbar:not(:-moz-lwtheme),
+ #main-window[sizemode=normal][disablechrome] #navigator-toolbox[tabsontop=true]:not(:-moz-lwtheme)::after {
+ border-top: 1px solid @toolbarShadowColor@;
+ background-clip: padding-box;
+ }
+ #main-window[sizemode=normal] #TabsToolbar[tabsontop=true]:not(:-moz-lwtheme) {
+ margin-bottom: -1px;
+ background-image: none !important;
+ }
+ #main-window[sizemode=normal] #tabbrowser-tabs[tabsontop=true] > .tabbrowser-arrowscrollbox > .arrowscrollbox-scrollbox > .scrollbox-innerbox:not(:-moz-lwtheme) {
+ position: relative;
+ }
+
+ #navigator-toolbox[tabsontop=false] > #PersonalToolbar {
+ margin-top: 3px;
+ }
+
+ #navigator-toolbox[tabsontop=false] > #PersonalToolbar:not(:-moz-lwtheme) {
+ margin-top: 2px;
+ border-top: 1px solid @toolbarShadowColor@;
+ background-image: linear-gradient(var(--toolbar-highlight-top), var(--toolbar-highlight-bottom));
+ }
+
+ @media (-moz-os-version: windows-win10) {
+ /* Remove gradient and make border faded */
+ #navigator-toolbox[tabsontop=false] > #PersonalToolbar:not(:-moz-lwtheme) {
+ border-top: 1px solid rgba(10%,10%,10%,.2);
+ background-image: none;
+ }
+ }
+
+ #main-window[sizemode=normal] #TabsToolbar[tabsontop=true] {
+ padding-left: 4px;
+ padding-right: 4px;
+ }
+
+ #main-window[sizemode=normal] #TabsToolbar[tabsontop=false] {
+ padding-left: 2px;
+ padding-right: 2px;
+ }
+
+ /* Rounded corners for when chrome is disabled */
+ #main-window[sizemode=normal][disablechrome] #navigator-toolbox[tabsontop=true]:not(:-moz-lwtheme)::after {
+ visibility: visible;
+ background-color: var(--toolbar-custom-color);
+ background-image: linear-gradient(var(--toolbar-highlight-top), var(--toolbar-highlight-top));
+ height: 4px;
+ }
+
+ /* Make the window draggable by glassed toolbars (bug 555081) */
+ #toolbar-menubar:not([autohide="true"]),
+ #TabsToolbar[tabsontop="true"],
+ #nav-bar[tabsontop=false],
+ #nav-bar + #customToolbars + #PersonalToolbar[collapsed="true"] + #TabsToolbar[tabsontop="false"]:last-child,
+ #navigator-toolbox > toolbar:not(#toolbar-menubar):-moz-lwtheme {
+ -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbar-drag");
+ }
+
+ #appcontent:not(:-moz-lwtheme) {
+ background-color: -moz-dialog;
+ }
+
+ #navigator-toolbox[tabsontop=false] #urlbar:not(:-moz-lwtheme),
+ #navigator-toolbox[tabsontop=false] .searchbar-textbox:not(:-moz-lwtheme) {
+ background-color: rgba(255,255,255,.725);
+ @navbarTextboxCustomBorder@
+ color: black;
+ }
+
+ #navigator-toolbox[tabsontop=false] html|*.urlbar-input:not(:-moz-lwtheme)::-moz-placeholder,
+ #navigator-toolbox[tabsontop=false] .searchbar-textbox > .autocomplete-textbox-container > .textbox-input-box > html|*.textbox-input:not(:-moz-lwtheme)::-moz-placeholder {
+ opacity: 1.0;
+ color: #777;
+ }
+
+ #navigator-toolbox[tabsontop=false] #urlbar:not(:-moz-lwtheme):hover,
+ #navigator-toolbox[tabsontop=false] .searchbar-textbox:not(:-moz-lwtheme):hover {
+ background-color: rgba(255,255,255,.898);
+ }
+
+ #navigator-toolbox[tabsontop=false] #urlbar:not(:-moz-lwtheme)[focused],
+ #navigator-toolbox[tabsontop=false] .searchbar-textbox:not(:-moz-lwtheme)[focused] {
+ background-color: white;
+ }
+
+ .tabbrowser-tab:not(:-moz-lwtheme) {
+ text-shadow: none;
+ }
+
+ #main-window[sizemode=normal] .statuspanel-inner {
+ /* align with the browser's side borders */
+ padding-left: 1px;
+ padding-right: 1px;
+ }
+
+ #allTabs-panel,
+ #ctrlTab-panel {
+ background: transparent;
+ -moz-appearance: -moz-win-glass;
+ border-radius: 0;
+ border: none;
+ font: normal 1.2em "Segoe UI";
+ color: black;
+ text-shadow: white -1px -1px .35em, white -1px 1px .35em, white 1px 1px .35em, white 1px -1px .35em;
+ }
+}
+
+@media not all and (-moz-windows-compositor) {
+ @media (-moz-windows-default-theme) {
+ #main-window {
+ background-color: rgb(185,209,234);
+ }
+ #main-window:-moz-window-inactive {
+ background-color: rgb(215,228,242);
+ }
+
+ #toolbar-menubar:not([autohide=true]):not(:-moz-lwtheme),
+ #TabsToolbar[tabsontop=true]:not(:-moz-lwtheme),
+ #navigator-toolbox[tabsontop=false] > toolbar:not(#toolbar-menubar):not(:-moz-lwtheme) {
+ -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbar-drag");
+ background-color: transparent;
+ }
+ #toolbar-menubar[autohide=true] {
+ background-color: transparent !important;
+ }
+ }
+
+ #print-preview-toolbar:not(:-moz-lwtheme) {
+ -moz-appearance: -moz-win-browsertabbar-toolbox;
+ }
+}
+
+/* ::::: fullscreen window controls ::::: */
+
+#window-controls {
+ -moz-box-align: start;
+}
+
+#minimize-button,
+#restore-button,
+#close-button {
+ -moz-appearance: none;
+ border-style: none;
+ margin: 0;
+}
+#close-button {
+ -moz-image-region: rect(0, 49px, 16px, 32px);
+}
+#close-button:hover {
+ -moz-image-region: rect(16px, 49px, 32px, 32px);
+}
+#close-button:hover:active {
+ -moz-image-region: rect(32px, 49px, 48px, 32px);
+}
+
+#minimize-button:-moz-locale-dir(rtl),
+#restore-button:-moz-locale-dir(rtl),
+#close-button:-moz-locale-dir(rtl) {
+ transform: scaleX(-1);
+}
+
+/* ::::: splitmenu highlight style that imitates Windows 7 start menu ::::: */
+@media (-moz-os-version: windows-vista) and (-moz-windows-default-theme),
+ (-moz-os-version: windows-win7) and (-moz-windows-default-theme) {
+ .splitmenu-menuitem,
+ .splitmenu-menu {
+ -moz-appearance: none;
+ padding-top: 2px;
+ padding-bottom: 2px;
+ border: 1px solid transparent;
+ }
+ .splitmenu-menuitem {
+ -moz-margin-end: 0;
+ }
+ .splitmenu-menu {
+ -moz-margin-start: -1px;
+ }
+ .splitmenu-menuitem:-moz-locale-dir(ltr),
+ .splitmenu-menu:-moz-locale-dir(rtl) {
+ border-top-left-radius: 3px;
+ border-bottom-left-radius: 3px;
+ }
+ .splitmenu-menu:-moz-locale-dir(ltr),
+ .splitmenu-menuitem:-moz-locale-dir(rtl) {
+ border-top-right-radius: 3px;
+ border-bottom-right-radius: 3px;
+ }
+
+ .splitmenu-menuitem > .menu-text {
+ -moz-margin-start: 1px !important;
+ -moz-margin-end: 3px !important;
+ }
+ .splitmenu-menu > .menu-right {
+ -moz-margin-end: -3px;
+ }
+
+ .splitmenu-menuitem[iconic],
+ .splitmenu-menu[iconic] {
+ padding-bottom: 1px;
+ }
+ .splitmenu-menuitem[iconic] > .menu-iconic-left {
+ margin-top: -3px;
+ margin-bottom: -2px;
+ -moz-margin-start: -1px;
+ }
+ .splitmenu-menuitem[iconic] > .menu-iconic-text {
+ -moz-margin-start: 2px !important;
+ -moz-margin-end: 3px !important;
+ }
+ .splitmenu-menu[iconic] > .menu-right {
+ margin-top: -1px;
+ }
+
+ .splitmenu-menuitem[_moz-menuactive],
+ .splitmenu-menu[_moz-menuactive] {
+ background-color: transparent;
+ background-image: linear-gradient(#fafbfd, #ebf3fd);
+ border-color: #aeccf1;
+ }
+
+ .splitmenu-menuitem[disabled][_moz-menuactive],
+ .splitmenu-menu[disabled][_moz-menuactive] {
+ background-image: linear-gradient(#f8f9f9, #eaeaea);
+ border-color: #d8d7d7;
+ }
+
+ .splitmenu-menu[_moz-menuactive]:not(:hover):not([open]) {
+ background-image: none;
+ }
+}
diff --git a/themes/windows/caption-buttons.svg b/themes/windows/caption-buttons.svg
new file mode 100644
index 0000000..9342aca
--- /dev/null
+++ b/themes/windows/caption-buttons.svg
@@ -0,0 +1,121 @@
+<svg width="12" height="12" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<!-- 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/. -->
+ <style>
+ g {
+ stroke: -moz-win-accentcolortext;
+ stroke-width: 0.9px;
+ fill: none;
+ }
+
+ g:not(:target) {
+ display: none;
+ }
+
+ use:target > g {
+ display: initial;
+ }
+
+ .highlight > g {
+ stroke: HighlightText;
+ }
+
+ .inactive > g {
+ stroke: black;
+ }
+
+ .bolder {
+ stroke-width: 1.6px;
+ stroke: black;
+ }
+
+ .outline {
+ stroke-width: 4px;
+ stroke: white;
+ opacity: 0.75;
+ }
+
+ .inverted {
+ stroke-width: 1.6px;
+ stroke: white;
+ }
+
+ .outline-inverted {
+ stroke-width: 4px;
+ stroke: black;
+ opacity: 0.75;
+ }
+
+ .outline-thinner {
+ stroke-width: 3.6px;
+ }
+
+ </style>
+
+ <g id="close">
+ <line x1="1" y1="1" x2="11" y2="11" stroke-width="1px"/>
+ <line x1="11" y1="1" x2="1" y2="11" stroke-width="1px"/>
+ </g>
+ <g id="maximize">
+ <rect x="1.5" y="1.5" width="9" height="9"/>
+ </g>
+ <g id="minimize">
+ <line x1="1" y1="5.5" x2="11" y2="5.5"/>
+ </g>
+ <g id="restore">
+ <rect x="1.5" y="3.5" width="7" height="7"/>
+ <polyline points="3.5,3.5 3.5,1.5 10.5,1.5 10.5,8.5 8.5,8.5"/>
+ </g>
+
+ <g id="close-outline">
+ <line x1="1" y1="1" x2="11" y2="11" stroke-linecap="round" class="outline"/>
+ <line x1="11" y1="1" x2="1" y2="11" stroke-linecap="round" class="outline"/>
+ <line x1="1" y1="1" x2="11" y2="11" class="bolder"/>
+ <line x1="11" y1="1" x2="1" y2="11" class="bolder"/>
+ </g>
+ <g id="maximize-outline">
+ <rect x="1.2" y="1.2" width="9.6" height="9.6" stroke-linecap="round" class="outline"/>
+ <rect x="1.5" y="1.5" width="9" height="9" class="bolder"/>
+ </g>
+ <g id="minimize-outline">
+ <line x1="1" y1="5.5" x2="11" y2="5.5" stroke-linecap="round" class="outline outline-thinner"/>
+ <line x1="1" y1="5.5" x2="11" y2="5.5" class="bolder"/>
+ </g>
+ <g id="restore-outline">
+ <rect x="1.5" y="3.5" width="7" height="7" stroke-linecap="round" class="outline"/>
+ <polyline points="3.5,3.5 3.5,1.5 10.5,1.5 10.5,8.5 8.5,8.5" stroke-linecap="round" class="outline"/>
+ <rect x="1.5" y="3.5" width="7" height="7" class="bolder"/>
+ <polyline points="3.5,3.5 3.5,1.5 10.5,1.5 10.5,8.5 8.5,8.5" class="bolder"/>
+ </g>
+
+ <g id="close-outline-inverted">
+ <line x1="1" y1="1" x2="11" y2="11" stroke-linecap="round" class="outline-inverted"/>
+ <line x1="11" y1="1" x2="1" y2="11" stroke-linecap="round" class="outline-inverted"/>
+ <line x1="1" y1="1" x2="11" y2="11" class="inverted"/>
+ <line x1="11" y1="1" x2="1" y2="11" class="inverted"/>
+ </g>
+ <g id="maximize-outline-inverted">
+ <rect x="1.2" y="1.2" width="9.6" height="9.6" stroke-linecap="round" class="outline-inverted"/>
+ <rect x="1.5" y="1.5" width="9" height="9" class="inverted"/>
+ </g>
+ <g id="minimize-outline-inverted">
+ <line x1="1" y1="5.5" x2="11" y2="5.5" stroke-linecap="round" class="outline-inverted outline-thinner"/>
+ <line x1="1" y1="5.5" x2="11" y2="5.5" class="inverted"/>
+ </g>
+ <g id="restore-outline-inverted">
+ <rect x="1.5" y="3.5" width="7" height="7" stroke-linecap="round" class="outline-inverted"/>
+ <polyline points="3.5,3.5 3.5,1.5 10.5,1.5 10.5,8.5 8.5,8.5" stroke-linecap="round" class="outline-inverted"/>
+ <rect x="1.5" y="3.5" width="7" height="7" class="inverted"/>
+ <polyline points="3.5,3.5 3.5,1.5 10.5,1.5 10.5,8.5 8.5,8.5" class="inverted"/>
+ </g>
+
+ <use id="close-highlight" class="highlight" xlink:href="#close"/>
+ <use id="maximize-highlight" class="highlight" xlink:href="#maximize"/>
+ <use id="minimize-highlight" class="highlight" xlink:href="#minimize"/>
+ <use id="restore-highlight" class="highlight" xlink:href="#restore"/>
+ <use id="close-inactive" class="inactive" xlink:href="#close"/>
+ <use id="maximize-inactive" class="inactive" xlink:href="#maximize"/>
+ <use id="minimize-inactive" class="inactive" xlink:href="#minimize"/>
+ <use id="restore-inactive" class="inactive" xlink:href="#restore"/>
+</svg> \ No newline at end of file
diff --git a/themes/windows/click-to-play-warning-stripes.png b/themes/windows/click-to-play-warning-stripes.png
new file mode 100644
index 0000000..29f15f7
--- /dev/null
+++ b/themes/windows/click-to-play-warning-stripes.png
Binary files differ
diff --git a/themes/windows/communicator/communicator.css b/themes/windows/communicator/communicator.css
new file mode 100644
index 0000000..0b57574
--- /dev/null
+++ b/themes/windows/communicator/communicator.css
@@ -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/. */
+
+@import url("chrome://global/skin/");
+
diff --git a/themes/windows/communicator/jar.mn b/themes/windows/communicator/jar.mn
new file mode 100644
index 0000000..612d133
--- /dev/null
+++ b/themes/windows/communicator/jar.mn
@@ -0,0 +1,7 @@
+# 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:
+% skin communicator classic/1.0 %skin/classic/communicator/
+ skin/classic/communicator/communicator.css
diff --git a/themes/windows/communicator/moz.build b/themes/windows/communicator/moz.build
new file mode 100644
index 0000000..c97072b
--- /dev/null
+++ b/themes/windows/communicator/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/.
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/themes/windows/downloads/allDownloadsViewOverlay.css b/themes/windows/downloads/allDownloadsViewOverlay.css
new file mode 100644
index 0000000..bd3b789
--- /dev/null
+++ b/themes/windows/downloads/allDownloadsViewOverlay.css
@@ -0,0 +1,178 @@
+/* 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/. */
+
+#downloadsRichListBox {
+ /** The default listbox appearance comes with an unwanted margin. **/
+ -moz-appearance: none;
+ margin: 0;
+}
+
+#downloadsRichListBox > richlistitem.download {
+ height: 6em;
+}
+
+.downloadTypeIcon {
+ -moz-margin-start: 8px;
+ -moz-margin-end: 8px;
+ /* explicitly size the icon, so size doesn't vary on hidpi systems */
+ height: 32px;
+ width: 32px;
+}
+
+.blockedIcon {
+ list-style-image: url("chrome://global/skin/icons/Error.png");
+}
+
+.downloadTarget {
+ margin-bottom: 3px;
+ cursor: inherit;
+}
+
+.downloadDetails {
+ opacity: 0.7;
+ font-size: 95%;
+ cursor: inherit;
+}
+
+.downloadButton {
+ -moz-appearance: none;
+ background: transparent;
+ min-width: 0;
+ min-height: 0;
+ margin: 3px;
+ border: none;
+ padding: 5px;
+ list-style-image: url("chrome://browser/skin/downloads/buttons.png");
+}
+
+/*** Button icons ***/
+
+.downloadButton.downloadCancel {
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+}
+
+richlistitem.download:hover > .downloadButton.downloadCancel {
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+richlistitem.download:hover > .downloadButton.downloadCancel:hover {
+ -moz-image-region: rect(0px, 48px, 16px, 32px);
+}
+
+richlistitem.download:hover > .downloadButton.downloadCancel:active {
+ -moz-image-region: rect(0px, 64px, 16px, 48px);
+}
+
+.downloadButton.downloadShow {
+ -moz-image-region: rect(16px, 16px, 32px, 0px);
+}
+
+richlistitem.download:hover > .downloadButton.downloadShow {
+ -moz-image-region: rect(16px, 32px, 32px, 16px);
+}
+
+richlistitem.download:hover > .downloadButton.downloadShow:hover {
+ -moz-image-region: rect(16px, 48px, 32px, 32px);
+}
+
+richlistitem.download:hover > .downloadButton.downloadShow:active {
+ -moz-image-region: rect(16px, 64px, 32px, 48px);
+}
+
+.downloadButton.downloadRetry {
+ -moz-image-region: rect(32px, 16px, 48px, 0px);
+}
+
+richlistitem.download:hover > .downloadButton.downloadRetry {
+ -moz-image-region: rect(32px, 32px, 48px, 16px);
+}
+
+richlistitem.download:hover > .downloadButton.downloadRetry:hover {
+ -moz-image-region: rect(32px, 48px, 48px, 32px);
+}
+
+richlistitem.download:hover > .downloadButton.downloadRetry:active {
+ -moz-image-region: rect(32px, 64px, 48px, 48px);
+}
+
+@media not all and (-moz-os-version: windows-vista) and (-moz-windows-default-theme) {
+ @media not all and (-moz-os-version: windows-win7) and (-moz-windows-default-theme) {
+ richlistitem.download[selected] > .downloadButton.downloadCancel {
+ -moz-image-region: rect(0px, 80px, 16px, 64px);
+ }
+
+ richlistitem.download[selected]:hover > .downloadButton.downloadCancel {
+ -moz-image-region: rect(0px, 96px, 16px, 80px);
+ }
+
+ richlistitem.download[selected]:hover > .downloadButton.downloadCancel:hover {
+ -moz-image-region: rect(0px, 112px, 16px, 96px);
+ }
+
+ richlistitem.download[selected]:hover > .downloadButton.downloadCancel:active {
+ -moz-image-region: rect(0px, 128px, 16px, 112px);
+ }
+
+ richlistitem.download[selected] > .downloadButton.downloadShow {
+ -moz-image-region: rect(16px, 80px, 32px, 64px);
+ }
+
+ richlistitem.download[selected]:hover > .downloadButton.downloadShow {
+ -moz-image-region: rect(16px, 96px, 32px, 80px);
+ }
+
+ richlistitem.download[selected]:hover > .downloadButton.downloadShow:hover {
+ -moz-image-region: rect(16px, 112px, 32px, 96px);
+ }
+
+ richlistitem.download[selected]:hover > .downloadButton.downloadShow:active {
+ -moz-image-region: rect(16px, 128px, 32px, 112px);
+ }
+
+ richlistitem.download[selected] > .downloadButton.downloadRetry {
+ -moz-image-region: rect(32px, 80px, 48px, 64px);
+ }
+
+ richlistitem.download[selected]:hover > .downloadButton.downloadRetry {
+ -moz-image-region: rect(32px, 96px, 48px, 80px);
+ }
+
+ richlistitem.download[selected]:hover > .downloadButton.downloadRetry:hover {
+ -moz-image-region: rect(32px, 112px, 48px, 96px);
+ }
+
+ richlistitem.download[selected]:hover > .downloadButton.downloadRetry:active {
+ -moz-image-region: rect(32px, 128px, 48px, 112px);
+ }
+ }
+}
+
+@media (-moz-os-version: windows-vista) and (-moz-windows-default-theme),
+ (-moz-os-version: windows-win7) and (-moz-windows-default-theme) {
+ /*
+ -moz-appearance: menuitem is almost right, but the hover effect is not
+ transparent and is lighter than desired.
+
+ Copied from the autocomplete richlistbox styling in
+ toolkit/themes/windows/global/autocomplete.css
+
+ This styling should be kept in sync with the style from the above file.
+ */
+ #downloadsRichListBox > richlistitem.download[selected] {
+ color: inherit;
+ background-color: transparent;
+ /* four gradients for the bevel highlights on each edge, one for blue background */
+ background-image:
+ linear-gradient(to bottom, rgba(255,255,255,0.9) 3px, transparent 3px),
+ linear-gradient(to right, rgba(255,255,255,0.5) 3px, transparent 3px),
+ linear-gradient(to left, rgba(255,255,255,0.5) 3px, transparent 3px),
+ linear-gradient(to top, rgba(255,255,255,0.4) 3px, transparent 3px),
+ linear-gradient(to bottom, rgba(163,196,247,0.3), rgba(122,180,246,0.3));
+ background-clip: content-box;
+ border-radius: 6px;
+ outline: 1px solid rgb(124,163,206);
+ -moz-outline-radius: 3px;
+ outline-offset: -2px;
+ }
+}
diff --git a/themes/windows/downloads/buttons.png b/themes/windows/downloads/buttons.png
new file mode 100644
index 0000000..ca87b40
--- /dev/null
+++ b/themes/windows/downloads/buttons.png
Binary files differ
diff --git a/themes/windows/downloads/contentAreaDownloadsView.css b/themes/windows/downloads/contentAreaDownloadsView.css
new file mode 100644
index 0000000..ece99ea
--- /dev/null
+++ b/themes/windows/downloads/contentAreaDownloadsView.css
@@ -0,0 +1,22 @@
+/* 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/. */
+
+@import url("chrome://global/skin/inContentUI.css");
+
+.downloadButton {
+ box-shadow: none;
+}
+
+.downloadButton:not([disabled="true"]):hover:active,
+.downloadButton:not([disabled]):hover:active {
+ background: transparent;
+ border: none;
+ box-shadow: none;
+}
+
+#downloadsListEmptyDescription {
+ margin: 1em;
+ text-align: center;
+ color: GrayText;
+}
diff --git a/themes/windows/downloads/download-notification-finish.png b/themes/windows/downloads/download-notification-finish.png
new file mode 100644
index 0000000..5194f5d
--- /dev/null
+++ b/themes/windows/downloads/download-notification-finish.png
Binary files differ
diff --git a/themes/windows/downloads/download-notification-start.png b/themes/windows/downloads/download-notification-start.png
new file mode 100644
index 0000000..bd548b1
--- /dev/null
+++ b/themes/windows/downloads/download-notification-start.png
Binary files differ
diff --git a/themes/windows/downloads/download-summary.png b/themes/windows/downloads/download-summary.png
new file mode 100644
index 0000000..67003c7
--- /dev/null
+++ b/themes/windows/downloads/download-summary.png
Binary files differ
diff --git a/themes/windows/downloads/downloads.css b/themes/windows/downloads/downloads.css
new file mode 100644
index 0000000..f169896
--- /dev/null
+++ b/themes/windows/downloads/downloads.css
@@ -0,0 +1,487 @@
+/* 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/. */
+
+/*** Panel and outer controls ***/
+
+#downloadsPanel > .panel-arrowcontainer > .panel-arrowcontent {
+ padding: 0;
+}
+
+#downloadsListBox {
+ background-color: transparent;
+ padding: 4px;
+ color: inherit;
+}
+
+#downloadsPanel:not([hasdownloads]) > #downloadsListBox {
+ display: none;
+}
+
+#downloadsPanel[hasdownloads] > #emptyDownloads {
+ display: none;
+}
+
+#emptyDownloads {
+ padding: 10px 20px;
+ max-width: 40ch;
+}
+
+#downloadsHistory {
+ background: transparent;
+ cursor: pointer;
+}
+
+@media (-moz-os-version: windows-vista),
+ (-moz-os-version: windows-win7) {
+ #downloadsHistory {
+ color: -moz-nativehyperlinktext;
+ }
+}
+
+#downloadsPanel[keyfocus] > #downloadsFooter > #downloadsHistory:focus {
+ outline: 1px -moz-dialogtext dotted;
+ outline-offset: -1px;
+}
+
+#downloadsHistory > .button-box {
+ border: none;
+ margin: 1em;
+}
+
+#downloadsFooter {
+ background-color: hsla(210,4%,10%,.04);
+ box-shadow: 0 1px 0 hsla(210,4%,10%,.08) inset;
+ transition-duration: 150ms;
+ transition-property: background-color;
+}
+
+#downloadsFooter:hover {
+ background-color: hsla(210,4%,10%,.05);
+}
+
+#downloadsFooter:hover:active {
+ background-color: hsla(210,4%,10%,.1);
+ box-shadow: 0 2px 0 0 hsla(210,4%,10%,.1) inset;
+}
+
+@media (-moz-os-version: windows-vista),
+ (-moz-os-version: windows-win7) {
+ @media (-moz-windows-default-theme) {
+ #downloadsFooter {
+ border-bottom-left-radius: 3px;
+ border-bottom-right-radius: 3px;
+ transition-duration: 0s;
+ }
+
+ #downloadsFooter,
+ #downloadsFooter:hover,
+ #downloadsFooter:hover:active {
+ background-color: #f1f5fb;
+ box-shadow: 0px 1px 2px rgb(204,214,234) inset;
+ }
+ }
+}
+
+/*** Downloads Summary and List items ***/
+
+#downloadsSummary,
+richlistitem[type="download"] {
+ height: 7em;
+ -moz-padding-end: 0;
+ color: inherit;
+}
+
+#downloadsSummary {
+ padding: 8px 38px 8px 12px;
+ cursor: pointer;
+ -moz-user-focus: normal;
+}
+
+#downloadsPanel[keyfocus] > #downloadsFooter > #downloadsSummary:focus {
+ outline: 1px -moz-dialogtext dotted;
+ outline-offset: -5px;
+}
+
+#downloadsSummary > .downloadTypeIcon {
+ list-style-image: url("chrome://browser/skin/downloads/download-summary.png");
+}
+
+#downloadsSummaryDescription {
+ color: -moz-nativehyperlinktext;
+}
+
+richlistitem[type="download"] {
+ margin: 0;
+ border-top: 1px solid hsla(0,0%,100%,.3);
+ border-bottom: 1px solid hsla(220,18%,51%,.25);
+ background: transparent;
+ padding: 8px;
+}
+
+@media (-moz-windows-default-theme) and (-moz-os-version: windows-vista),
+ (-moz-windows-default-theme) and (-moz-os-version: windows-win7) {
+ richlistitem[type="download"] {
+ border: 1px solid transparent;
+ border-bottom: 1px solid hsl(213,40%,90%);
+ }
+}
+
+richlistitem[type="download"]:first-child {
+ border-top: 1px solid transparent;
+}
+
+@media (-moz-windows-default-theme) {
+ richlistitem[type="download"]:last-child {
+ border-bottom: 1px solid transparent;
+ }
+}
+
+#downloadsPanel[keyfocus] > #downloadsListBox:focus > richlistitem[type="download"][selected] {
+ outline: 1px -moz-dialogtext dotted;
+ outline-offset: -1px;
+}
+
+.downloadTypeIcon {
+ -moz-margin-end: 8px;
+ /* Prevent flickering when changing states. */
+ height: 32px;
+ width: 32px;
+}
+
+.blockedIcon {
+ list-style-image: url("chrome://global/skin/icons/Error.png");
+}
+
+/* We hold .downloadDisplayName, .downloadProgress and .downloadDetails
+ inside of a vbox with class .downloadContainer. We set the font-size of
+ the entire container to 90% because:
+
+ 1) This is the size that we want .downloadDetails to be
+ 2) The container's width is set by localizers by &downloadDetails.width;,
+ which is a ch unit. Since this is the value that should control the
+ panel width, we apply it to the outer container to constrain
+ .downloadDisplayName and .downloadProgress.
+
+ Finally, since we want .downloadDisplayName's font-size to be at 100% of
+ the font-size of .downloadContainer's parent, we use calc to go from the
+ smaller font-size back to the original font-size.
+ */
+#downloadsSummaryDetails,
+.downloadContainer {
+ font-size: 90%;
+}
+
+#downloadsSummaryDescription,
+.downloadDisplayName {
+ margin-bottom: 6px;
+ cursor: inherit;
+}
+
+.downloadDisplayName {
+ font-size: calc(100%/0.9);
+}
+
+#downloadsSummaryDetails,
+.downloadDetails {
+ opacity: 0.6;
+ cursor: inherit;
+}
+
+.downloadButton {
+ -moz-appearance: none;
+ min-width: 0;
+ min-height: 0;
+ margin: 3px;
+ border: none;
+ background: transparent;
+ padding: 5px;
+ list-style-image: url("chrome://browser/skin/downloads/buttons.png");
+}
+
+.downloadButton > .button-box {
+ border: 1px solid transparent;
+ padding: 0;
+}
+
+#downloadsPanel[keyfocus] .downloadButton:focus > .button-box {
+ border: 1px dotted ThreeDDarkShadow;
+}
+
+/*** Highlighted list items ***/
+
+#downloadsPanel:not([keyfocus]) > #downloadsListBox > richlistitem[type="download"][state="1"][exists]:hover {
+ background-color: hsla(210,4%,10%,.08);
+ outline: 1px solid hsla(210,4%,10%,.1);
+ outline-offset: -1px;
+ cursor: pointer;
+}
+
+#downloadsPanel:not([keyfocus]) > #downloadsListBox > richlistitem[type="download"][state="1"][exists]:hover:active {
+ background-color: hsla(210,4%,10%,.15);
+ outline: 1px solid hsla(210,4%,10%,.15);
+ box-shadow: 0 1px 0 0 hsla(210,4%,10%,.05) inset;
+}
+
+@media (-moz-os-version: windows-vista),
+ (-moz-os-version: windows-win7) {
+ #downloadsPanel:not([keyfocus]) > #downloadsListBox > richlistitem[type="download"][state="1"][exists]:hover {
+ border-radius: 3px;
+ outline: 0;
+ border-top: 1px solid hsla(0,0%,100%,.2);
+ border-bottom: 1px solid hsla(0,0%,0%,.2);
+ background-color: Highlight;
+ color: HighlightText;
+ }
+
+ #downloadsPanel:not([keyfocus]) > #downloadsListBox > richlistitem[type="download"][state="1"][exists]:hover:active {
+ background-color: Highlight;
+ outline: 0;
+ box-shadow: none;
+ }
+}
+
+@media (-moz-windows-default-theme) and (-moz-os-version: windows-vista),
+ (-moz-windows-default-theme) and (-moz-os-version: windows-win7) {
+ #downloadsPanel:not([keyfocus]) > #downloadsListBox > richlistitem[type="download"][state="1"][exists]:hover {
+ border: 1px solid hsl(213,45%,65%);
+ box-shadow: 0 0 0 1px hsla(0,0%,100%,.5) inset,
+ 0 1px 0 hsla(0,0%,100%,.3) inset;
+ background-image: linear-gradient(hsl(212,86%,92%), hsl(212,91%,86%));
+ color: black;
+ }
+}
+
+/*** Button icons ***/
+
+.downloadButton.downloadCancel {
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+}
+richlistitem[type="download"]:hover > stack > .downloadButton.downloadCancel {
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+richlistitem[type="download"]:hover > stack > .downloadButton.downloadCancel:hover {
+ -moz-image-region: rect(0px, 48px, 16px, 32px);
+}
+richlistitem[type="download"]:hover > stack > .downloadButton.downloadCancel:active {
+ -moz-image-region: rect(0px, 64px, 16px, 48px);
+}
+
+.downloadButton.downloadShow {
+ -moz-image-region: rect(16px, 16px, 32px, 0px);
+}
+richlistitem[type="download"]:hover > stack > .downloadButton.downloadShow {
+ -moz-image-region: rect(16px, 32px, 32px, 16px);
+}
+richlistitem[type="download"]:hover > stack > .downloadButton.downloadShow:hover {
+ -moz-image-region: rect(16px, 48px, 32px, 32px);
+}
+richlistitem[type="download"]:hover > stack > .downloadButton.downloadShow:active {
+ -moz-image-region: rect(16px, 64px, 32px, 48px);
+}
+
+.downloadButton.downloadRetry {
+ -moz-image-region: rect(32px, 16px, 48px, 0px);
+}
+richlistitem[type="download"]:hover > stack > .downloadButton.downloadRetry {
+ -moz-image-region: rect(32px, 32px, 48px, 16px);
+}
+richlistitem[type="download"]:hover > stack > .downloadButton.downloadRetry:hover {
+ -moz-image-region: rect(32px, 48px, 48px, 32px);
+}
+richlistitem[type="download"]:hover > stack > .downloadButton.downloadRetry:active {
+ -moz-image-region: rect(32px, 64px, 48px, 48px);
+}
+
+/*** Status and progress indicator ***/
+
+#downloads-indicator-anchor {
+ /* Makes the outermost stack element positioned, so that its contents are
+ rendered over the main browser window in the Z order. This is required by
+ the animated event notification. */
+ position: relative;
+}
+
+#navigator-toolbox[iconsize=large][mode=icons] > #nav-bar[brighttext] #downloads-indicator[counter] > #downloads-indicator-anchor {
+ /* Use a dark download button when appropriate to improve text legibility */
+ background: hsla(94,56%,18%,.3) padding-box;
+ background-image: linear-gradient(hsla(0,0%,0%,.1), hsla(0,0%,0%,.4));
+ border-color: hsla(29,12%,90%,.2) hsla(29,12%,90%,.2) hsla(29,12%,90%,.2);
+ box-shadow: 0 1px hsla(0,0%,0%,.05) inset,
+ 0 1px hsla(29,12%,90%,.05),
+ 0 0 2px hsla(29,12%,90%,.05);
+}
+
+/*** Main indicator icon ***/
+
+#downloads-indicator-icon {
+ background: -moz-image-rect(var(--toolbarbutton-image),
+ 0, 108, 18, 90) center no-repeat;
+ min-width: 18px;
+ min-height: 18px;
+}
+
+toolbar[brighttext] #downloads-indicator-icon {
+ background: -moz-image-rect(var(--toolbarbutton-inverted-image),
+ 0, 108, 18, 90) center no-repeat;
+}
+
+#downloads-indicator[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
+ background: -moz-image-rect(var(--toolbarbutton-image),
+ 19, 108, 36, 90) center no-repeat;
+}
+
+@media (-moz-windows-compositor) {
+ :-moz-any(#toolbar-menubar, #nav-bar[tabsontop=false]) #downloads-indicator-icon:not(:-moz-lwtheme),
+ #TabsToolbar[tabsontop=true] #downloads-indicator-icon:not(:-moz-lwtheme),
+ #nav-bar + #customToolbars + #PersonalToolbar[collapsed=true] + #TabsToolbar[tabsontop=false]:last-child #downloads-indicator-icon:not(:-moz-lwtheme) {
+ background: -moz-image-rect(var(--toolbarbutton-glass-image),
+ 0, 108, 18, 90) center no-repeat;
+ }
+ #downloads-indicator[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
+ background: -moz-image-rect(var(--toolbarbutton-glass-image),
+ 19, 108, 36, 90) center no-repeat;
+}
+
+
+}
+
+/* In the next few rules, we use :not([counter]) as a shortcut that is
+ equivalent to -moz-any([progress], [paused]). */
+
+#downloads-indicator:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+ background: -moz-image-rect(var(--toolbarbutton-image),
+ 0, 108, 18, 90) center no-repeat;
+ background-size: 12px;
+}
+
+toolbar[brighttext] #downloads-indicator:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+ background: -moz-image-rect(var(--toolbarbutton-inverted-image),
+ 0, 108, 18, 90) center no-repeat;
+}
+
+@media (-moz-windows-compositor) {
+ :-moz-any(#toolbar-menubar, #nav-bar[tabsontop=false]) #downloads-indicator:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter:not(:-moz-lwtheme),
+ #TabsToolbar[tabsontop=true] #downloads-indicator:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter:not(:-moz-lwtheme),
+ #nav-bar + #customToolbars + #PersonalToolbar[collapsed=true] + #TabsToolbar[tabsontop=false]:last-child #downloads-indicator:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter:not(:-moz-lwtheme) {
+ background: -moz-image-rect(var(--toolbarbutton-glass-image),
+ 0, 108, 18, 90) center no-repeat;
+ }
+ #downloads-indicator:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+ background: -moz-image-rect(var(--toolbarbutton-glass-image),
+ 19, 108, 36, 90) center no-repeat;
+ }
+}
+
+/*** Download notifications ***/
+
+#downloads-indicator-notification {
+ opacity: 0;
+ background-size: 16px;
+ background-position: center;
+ background-repeat: no-repeat;
+}
+
+@keyframes downloadsIndicatorNotificationStartRight {
+ from { opacity: 0; transform: translate(-128px, 128px) scale(8); }
+ 20% { opacity: .85; animation-timing-function: ease-out; }
+ to { opacity: 0; transform: translate(0) scale(1); }
+}
+
+@keyframes downloadsIndicatorNotificationStartLeft {
+ from { opacity: 0; transform: translate(128px, 128px) scale(8); }
+ 20% { opacity: .85; animation-timing-function: ease-out; }
+ to { opacity: 0; transform: translate(0) scale(1); }
+}
+
+#downloads-indicator[notification="start"] > #downloads-indicator-anchor > #downloads-indicator-notification {
+ background-image: url("chrome://browser/skin/downloads/download-notification-start.png");
+ animation-name: downloadsIndicatorNotificationStartRight;
+ animation-duration: 1s;
+}
+
+#downloads-indicator[notification="start"]:-moz-locale-dir(rtl) > #downloads-indicator-anchor > #downloads-indicator-notification {
+ animation-name: downloadsIndicatorNotificationStartLeft;
+}
+
+@keyframes downloadsIndicatorNotificationFinish {
+ from { opacity: 0; transform: scale(1); }
+ 20% { opacity: .65; animation-timing-function: ease-in; }
+ to { opacity: 0; transform: scale(8); }
+}
+
+#downloads-indicator[notification="finish"] > #downloads-indicator-anchor > #downloads-indicator-notification {
+ background-image: url("chrome://browser/skin/downloads/download-notification-finish.png");
+ animation-name: downloadsIndicatorNotificationFinish;
+ animation-duration: 1s;
+}
+
+/*** Progress bar and text ***/
+
+#downloads-indicator-counter {
+ height: 9px;
+ margin: -3px 0px 0px 0px;
+ color: hsl(0,0%,30%);
+ text-shadow: hsla(0,0%,100%,.5) 0 1px;
+ font-size: 9px;
+ line-height: 9px;
+ text-align: center;
+}
+
+toolbar[brighttext] #downloads-indicator-counter {
+ color: white;
+ text-shadow: 0 0 1px rgba(0,0,0,.7),
+ 0 1px 1.5px rgba(0,0,0,.5);
+}
+
+#downloads-indicator-progress {
+ width: 16px;
+ height: 5px;
+ min-width: 0;
+ min-height: 0;
+ margin-top: 1px;
+ margin-bottom: 2px;
+ border-radius: 2px;
+ box-shadow: 0 1px 0 hsla(0,0%,100%,.4);
+}
+
+#downloads-indicator-progress > .progress-bar {
+ -moz-appearance: none;
+ min-width: 0;
+ min-height: 0;
+ /* The background-clip: border-box; and background-image: none; are there to expand the background-color behind the border */
+ background-clip: padding-box, border-box;
+ background-color: rgb(90, 201, 66);
+ background-image: linear-gradient(transparent 1px, rgba(255, 255, 255, 0.4) 1px, rgba(255, 255, 255, 0.4) 2px, transparent 2px), none;
+ border: 1px solid;
+ border-color: rgba(0,43,86,.6) rgba(0,43,86,.4) rgba(0,43,86,.4);
+ border-radius: 2px 0 0 2px;
+}
+
+#downloads-indicator-progress > .progress-remainder {
+ -moz-appearance: none;
+ min-width: 0;
+ min-height: 0;
+ background-image: linear-gradient(#505050, #575757);
+ border: 1px solid;
+ border-color: hsla(0,0%,0%,.6) hsla(0,0%,0%,.4) hsla(0,0%,0%,.4);
+ -moz-border-start: none;
+ border-radius: 0 2px 2px 0;
+}
+
+#downloads-indicator[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-bar {
+ background-color: rgb(220, 230, 81);
+}
+
+#downloads-indicator[paused] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-progress > .progress-remainder {
+ background-image: linear-gradient(#4b5000, #515700);
+}
+
+toolbar[mode="full"] > #downloads-indicator > .toolbarbutton-text {
+ margin: 0;
+ text-align: center;
+}
+
+#downloads-indicator-counter {
+ margin-bottom: -1px;
+}
diff --git a/themes/windows/engineManager.css b/themes/windows/engineManager.css
new file mode 100644
index 0000000..18817cd
--- /dev/null
+++ b/themes/windows/engineManager.css
@@ -0,0 +1,16 @@
+%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
+
+#engineList treechildren::-moz-tree-image(engineName) {
+ -moz-margin-end: 4px;
+ -moz-margin-start: 1px;
+ width: 16px;
+ height: 16px;
+}
+
+#engineList treechildren::-moz-tree-row {
+ height: 20px;
+}
diff --git a/themes/windows/feeds/feed-icons-16.png b/themes/windows/feeds/feed-icons-16.png
new file mode 100644
index 0000000..b822aa4
--- /dev/null
+++ b/themes/windows/feeds/feed-icons-16.png
Binary files differ
diff --git a/themes/windows/feeds/feedIcon.png b/themes/windows/feeds/feedIcon.png
new file mode 100644
index 0000000..b4d5994
--- /dev/null
+++ b/themes/windows/feeds/feedIcon.png
Binary files differ
diff --git a/themes/windows/feeds/feedIcon16.png b/themes/windows/feeds/feedIcon16.png
new file mode 100644
index 0000000..7c3aceb
--- /dev/null
+++ b/themes/windows/feeds/feedIcon16.png
Binary files differ
diff --git a/themes/windows/feeds/subscribe-ui.css b/themes/windows/feeds/subscribe-ui.css
new file mode 100644
index 0000000..8ca5328
--- /dev/null
+++ b/themes/windows/feeds/subscribe-ui.css
@@ -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/. */
+
+.alwaysUse {
+ padding: 5px;
+}
+
+.handlersMenuPopup > menuitem {
+ -moz-padding-start: 23px;
+}
+
+.handlersMenuPopup > menuitem.menuitem-iconic {
+ -moz-padding-start: 2px;
+}
+
+.handlersMenuPopup > .menuitem-iconic > .menu-iconic-left {
+ display: -moz-box;
+ min-width: 16px;
+ -moz-padding-end: 2px;
+}
+
+.chooseApplicationMenuItem {
+ list-style-image: url("chrome://browser/skin/preferences/application.png");
+}
+
+#feedHeader[dir="rtl"] .handlersMenuList > menupopup {
+ direction: rtl;
+}
diff --git a/themes/windows/feeds/subscribe.css b/themes/windows/feeds/subscribe.css
new file mode 100644
index 0000000..dc9304b
--- /dev/null
+++ b/themes/windows/feeds/subscribe.css
@@ -0,0 +1,163 @@
+/* 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 {
+ background: -moz-Dialog;
+ font: 3mm tahoma,arial,helvetica,sans-serif;
+}
+
+#feedBody {
+ border: 1px solid THreeDShadow;
+ padding: 3em;
+ -moz-padding-start: 30px;
+ margin: 2em auto;
+ background: -moz-Field;
+}
+
+#feedHeaderContainer {
+ border: 1px solid ThreeDShadow;
+ border-radius: 10px;
+ margin: -4em auto 0 auto;
+ background-color: InfoBackground;
+}
+
+#feedHeader {
+ margin-top: 4.9em;
+ margin-bottom: 1em;
+ -moz-margin-start: 1.4em;
+ -moz-margin-end: 1em;
+ -moz-padding-start: 2.9em;
+ font-size: 110%;
+ color: InfoText;
+}
+
+#feedIntroText {
+ display: none;
+}
+
+.feedBackground {
+ background: url("chrome://browser/skin/feeds/feedIcon.png") 0% 10% no-repeat InfoBackground;
+}
+
+.videoPodcastBackground {
+ background: url("chrome://browser/skin/feeds/videoFeedIcon.png") 0% 10% no-repeat InfoBackground;
+}
+
+.audioPodcastBackground {
+ background: url("chrome://browser/skin/feeds/audioFeedIcon.png") 0% 10% no-repeat InfoBackground;
+}
+
+#feedHeader[dir="rtl"] {
+ background-position: 100% 10%;
+}
+
+#feedHeader[firstrun="true"] #feedIntroText {
+ padding-top: 0.1em;
+ -moz-padding-start: 0.6em;
+ display: block;
+}
+
+#feedHeader[firstrun="true"] > #feedSubscribeLine {
+ -moz-padding-start: 1.8em;
+}
+
+#feedSubscribeLine {
+ padding-top: 0.2em;
+}
+
+img {
+ max-width: 100%;
+}
+
+/* Don't print subscription UI */
+@media print {
+ #feedHeaderContainer {
+ display: none;
+ }
+}
+
+body {
+ margin: 0;
+ padding: 0 3em;
+ color: -moz-fieldText;
+ font: message-box;
+}
+
+h1 {
+ font-size: 160%;
+ border-bottom: 2px solid ThreeDLightShadow;
+ margin: 0 0 .2em 0;
+}
+
+h2 {
+ color: ThreeDDarkShadow;
+ font-size: 110%;
+ font-weight: normal;
+ margin: 0 0 .6em 0;
+}
+
+#feedTitleLink {
+ float: right;
+ -moz-margin-start: .6em;
+ -moz-margin-end: 0;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+a[href] img {
+ border: none;
+}
+
+#feedTitleContainer {
+ -moz-margin-start: 0;
+ -moz-margin-end: .6em;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+#feedTitleImage {
+ -moz-margin-start: .6em;
+ -moz-margin-end: 0;
+ margin-top: 0;
+ margin-bottom: 0;
+ max-width: 300px;
+ max-height: 150px;
+}
+
+.feedEntryContent {
+ font-size: 110%;
+}
+
+.link {
+ color: #0000FF;
+ text-decoration: underline;
+ cursor: pointer;
+}
+
+.link:hover:active {
+ color: #FF0000;
+}
+
+.lastUpdated {
+ font-size: 85%;
+ font-weight: normal;
+}
+
+.type-icon {
+ vertical-align: bottom;
+ height: 16px;
+ width: 16px;
+}
+
+.enclosures {
+ border: 1px solid THreeDShadow;
+ padding: 1em;
+ margin: 1em auto;
+ background: -moz-Dialog;
+}
+
+.enclosure {
+ vertical-align: middle;
+ margin-left: 2px;
+}
diff --git a/themes/windows/icon.png b/themes/windows/icon.png
new file mode 100644
index 0000000..ff4f21f
--- /dev/null
+++ b/themes/windows/icon.png
Binary files differ
diff --git a/themes/windows/identity-icons-generic.png b/themes/windows/identity-icons-generic.png
new file mode 100644
index 0000000..a39e493
--- /dev/null
+++ b/themes/windows/identity-icons-generic.png
Binary files differ
diff --git a/themes/windows/identity-icons-https-ev.png b/themes/windows/identity-icons-https-ev.png
new file mode 100644
index 0000000..d49be13
--- /dev/null
+++ b/themes/windows/identity-icons-https-ev.png
Binary files differ
diff --git a/themes/windows/identity-icons-https-mixed-active.png b/themes/windows/identity-icons-https-mixed-active.png
new file mode 100644
index 0000000..3c77bc8
--- /dev/null
+++ b/themes/windows/identity-icons-https-mixed-active.png
Binary files differ
diff --git a/themes/windows/identity-icons-https.png b/themes/windows/identity-icons-https.png
new file mode 100644
index 0000000..ffd6694
--- /dev/null
+++ b/themes/windows/identity-icons-https.png
Binary files differ
diff --git a/themes/windows/identity.png b/themes/windows/identity.png
new file mode 100644
index 0000000..0dcccd4
--- /dev/null
+++ b/themes/windows/identity.png
Binary files differ
diff --git a/themes/windows/imagedocument.png b/themes/windows/imagedocument.png
new file mode 100644
index 0000000..ff4f21f
--- /dev/null
+++ b/themes/windows/imagedocument.png
Binary files differ
diff --git a/themes/windows/jar.mn b/themes/windows/jar.mn
new file mode 100644
index 0000000..4422bb6
--- /dev/null
+++ b/themes/windows/jar.mn
@@ -0,0 +1,168 @@
+# 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:
+% skin browser classic/1.0 %skin/classic/browser/
+ skin/classic/browser/sanitizeDialog.css
+* skin/classic/browser/aboutPrivateBrowsing.css
+* skin/classic/browser/aboutSessionRestore.css
+ skin/classic/browser/aboutSessionRestore-window-icon.png
+ skin/classic/browser/aboutCertError.css
+ skin/classic/browser/aboutCertError_sectionCollapsed.png
+ skin/classic/browser/aboutCertError_sectionCollapsed-rtl.png
+ skin/classic/browser/aboutCertError_sectionExpanded.png
+#ifdef MOZ_SERVICES_SYNC
+ skin/classic/browser/aboutSyncTabs.css
+#endif
+* skin/classic/browser/autocomplete.css
+ skin/classic/browser/actionicon-tab.png
+ skin/classic/browser/appmenu-icons.png
+ skin/classic/browser/appmenu-dropmarker.png
+* skin/classic/browser/browser.css
+ skin/classic/browser/caption-buttons.svg
+ skin/classic/browser/click-to-play-warning-stripes.png
+* skin/classic/browser/engineManager.css
+ skin/classic/browser/Geolocation-16.png
+ skin/classic/browser/Geolocation-64.png
+ skin/classic/browser/Info.png
+ skin/classic/browser/identity.png
+ skin/classic/browser/imagedocument.png
+ skin/classic/browser/identity-icons-generic.png
+ skin/classic/browser/identity-icons-https.png
+ skin/classic/browser/identity-icons-https-ev.png
+ skin/classic/browser/identity-icons-https-mixed-active.png
+ skin/classic/browser/keyhole-forward-mask.svg
+ skin/classic/browser/KUI-background.png
+ skin/classic/browser/KUI-close.png
+ skin/classic/browser/livemark-folder.png
+ skin/classic/browser/menu-back.png
+ skin/classic/browser/menu-forward.png
+ skin/classic/browser/mixed-content-blocked-16.png
+ skin/classic/browser/mixed-content-blocked-64.png
+ skin/classic/browser/monitor.png
+ skin/classic/browser/monitor_16-10.png
+ skin/classic/browser/pageInfo.css
+ skin/classic/browser/pageInfo.png
+ skin/classic/browser/page-livemarks.png (feeds/feedIcon16.png)
+ skin/classic/browser/pointerLock-16.png
+ skin/classic/browser/pointerLock-64.png
+ skin/classic/browser/Privacy-16.png
+ skin/classic/browser/Privacy-32.png
+ skin/classic/browser/Privacy-48.png
+ skin/classic/browser/Privacy-64.png
+ skin/classic/browser/privatebrowsing-light.png
+ skin/classic/browser/privatebrowsing-dark.png
+ skin/classic/browser/reload-stop-go.png
+ skin/classic/browser/sanitize.png
+ skin/classic/browser/searchbar.css
+ skin/classic/browser/searchbar-dropdown-arrow.png
+ skin/classic/browser/Secure24.png
+ skin/classic/browser/setDesktopBackground.css
+ skin/classic/browser/slowStartup-16.png
+ skin/classic/browser/Toolbar.png
+ skin/classic/browser/Toolbar-glass.png
+ skin/classic/browser/Toolbar-inverted.png
+ skin/classic/browser/Toolbar.svg
+ skin/classic/browser/Toolbar-glass.svg
+ skin/classic/browser/Toolbar-inverted.svg
+ skin/classic/browser/toolbarbutton-dropdown-arrow.png
+ skin/classic/browser/toolbarbutton-dropdown-arrow-inverted.png
+ skin/classic/browser/urlbar-arrow.png
+ skin/classic/browser/urlbar-popup-blocked.png
+ skin/classic/browser/urlbar-history-dropmarker.png
+ skin/classic/browser/web-notifications-icon.svg
+ skin/classic/browser/web-notifications-tray.svg
+ skin/classic/browser/notification-pluginNormal.png (../shared/plugins/notification-pluginNormal.png)
+ skin/classic/browser/notification-pluginAlert.png (../shared/plugins/notification-pluginAlert.png)
+ skin/classic/browser/notification-pluginBlocked.png (../shared/plugins/notification-pluginBlocked.png)
+#ifdef MOZ_WEBRTC
+ skin/classic/browser/webRTC-shareDevice-16.png
+ skin/classic/browser/webRTC-shareDevice-64.png
+ skin/classic/browser/webRTC-sharingDevice-16.png
+#endif
+ skin/classic/browser/downloads/buttons.png (downloads/buttons.png)
+ skin/classic/browser/downloads/download-notification-finish.png (downloads/download-notification-finish.png)
+ skin/classic/browser/downloads/download-notification-start.png (downloads/download-notification-start.png)
+ skin/classic/browser/downloads/download-summary.png (downloads/download-summary.png)
+ skin/classic/browser/downloads/downloads.css (downloads/downloads.css)
+ skin/classic/browser/downloads/allDownloadsViewOverlay.css (downloads/allDownloadsViewOverlay.css)
+ skin/classic/browser/downloads/contentAreaDownloadsView.css (downloads/contentAreaDownloadsView.css)
+ skin/classic/browser/feeds/feedIcon.png (feeds/feedIcon.png)
+ skin/classic/browser/feeds/feedIcon16.png (feeds/feedIcon16.png)
+ skin/classic/browser/feeds/videoFeedIcon.png (feeds/feedIcon.png)
+ skin/classic/browser/feeds/videoFeedIcon16.png (feeds/feedIcon16.png)
+ skin/classic/browser/feeds/audioFeedIcon.png (feeds/feedIcon.png)
+ skin/classic/browser/feeds/audioFeedIcon16.png (feeds/feedIcon16.png)
+ skin/classic/browser/feeds/feed-icons-16.png (feeds/feed-icons-16.png)
+ skin/classic/browser/feeds/subscribe.css (feeds/subscribe.css)
+ skin/classic/browser/feeds/subscribe-ui.css (feeds/subscribe-ui.css)
+* skin/classic/browser/newtab/newTab.css (newtab/newTab.css)
+ skin/classic/browser/newtab/controls.png (../shared/newtab/controls.png)
+ skin/classic/browser/newtab/noise.png (../shared/newtab/noise.png)
+ skin/classic/browser/newtab/pinned.png (../shared/newtab/pinned.png)
+ skin/classic/browser/places/places.css (places/places.css)
+* skin/classic/browser/places/organizer.css (places/organizer.css)
+ skin/classic/browser/places/editBookmark.png (places/editBookmark.png)
+ skin/classic/browser/places/bookmark.png (places/bookmark.png)
+ skin/classic/browser/places/query.png (places/query.png)
+ skin/classic/browser/places/bookmarksMenu.png (places/bookmarksMenu.png)
+ skin/classic/browser/places/bookmarksToolbar.png (places/bookmarksToolbar.png)
+ skin/classic/browser/places/calendar.png (places/calendar.png)
+ skin/classic/browser/places/toolbarDropMarker.png (places/toolbarDropMarker.png)
+ skin/classic/browser/places/editBookmarkOverlay.css (places/editBookmarkOverlay.css)
+ skin/classic/browser/places/libraryToolbar.png (places/libraryToolbar.png)
+ skin/classic/browser/places/starred48.png (places/starred48.png)
+ skin/classic/browser/places/unstarred48.png (places/unstarred48.png)
+ skin/classic/browser/places/tag.png (places/tag.png)
+ skin/classic/browser/places/history.png (places/history.png)
+ skin/classic/browser/places/allBookmarks.png (places/allBookmarks.png)
+ skin/classic/browser/places/unsortedBookmarks.png (places/unsortedBookmarks.png)
+ skin/classic/browser/places/downloads.png (places/downloads.png)
+ skin/classic/browser/places/livemark-item.png (places/livemark-item.png)
+ skin/classic/browser/permissions/aboutPermissions.css (permissions/aboutPermissions.css)
+ skin/classic/browser/preferences/alwaysAsk.png (preferences/alwaysAsk.png)
+ skin/classic/browser/preferences/application.png (preferences/application.png)
+ skin/classic/browser/preferences/mail.png (preferences/mail.png)
+ skin/classic/browser/preferences/Options.png (preferences/Options.png)
+#ifdef MOZ_SERVICES_SYNC
+ skin/classic/browser/preferences/Options-sync.png (preferences/Options-sync.png)
+#endif
+ skin/classic/browser/preferences/saveFile.png (preferences/saveFile.png)
+* skin/classic/browser/preferences/preferences.css (preferences/preferences.css)
+ skin/classic/browser/preferences/applications.css (preferences/applications.css)
+ skin/classic/browser/statusbar/dynamic.css (../shared/statusbar/dynamic.css)
+* skin/classic/browser/statusbar/overlay.css (statusbar/overlay.css)
+* skin/classic/browser/statusbar/prefs.css (statusbar/prefs.css)
+ skin/classic/browser/statusbar/pulse.png (../shared/statusbar/pulse.png)
+ skin/classic/browser/statusbar/pms16.png (../shared/statusbar/pms16.png)
+ skin/classic/browser/statusbar/pms24.png (../shared/statusbar/pms24.png)
+ skin/classic/browser/statusbar/throbber-idle.png (../shared/statusbar/throbber-idle.png)
+ skin/classic/browser/statusbar/throbberStatic.png (../shared/statusbar/throbberStatic.png)
+ skin/classic/browser/tabbrowser/alltabs.png (tabbrowser/alltabs.png)
+ skin/classic/browser/tabbrowser/alltabs-inverted.png (tabbrowser/alltabs-inverted.png)
+ skin/classic/browser/tabbrowser/newtab.png (tabbrowser/newtab.png)
+ skin/classic/browser/tabbrowser/newtab-glass.png (tabbrowser/newtab-glass.png)
+ skin/classic/browser/tabbrowser/newtab-inverted.png (tabbrowser/newtab-inverted.png)
+ skin/classic/browser/tabbrowser/connecting.png (tabbrowser/connecting.png)
+ skin/classic/browser/tabbrowser/loading.png (tabbrowser/loading.png)
+ skin/classic/browser/tabbrowser/tab-arrow-left.png (tabbrowser/tab-arrow-left.png)
+ skin/classic/browser/tabbrowser/tab-arrow-left-glass.png (tabbrowser/tab-arrow-left-glass.png)
+ skin/classic/browser/tabbrowser/tab-arrow-left-inverted.png (tabbrowser/tab-arrow-left-inverted.png)
+ skin/classic/browser/tabbrowser/tab-overflow-border.png (tabbrowser/tab-overflow-border.png)
+ skin/classic/browser/tabbrowser/tabDragIndicator.png (tabbrowser/tabDragIndicator.png)
+ skin/classic/browser/tabbrowser/tab-audio.svg (../shared/tabbrowser/tab-audio.svg)
+ skin/classic/browser/tabbrowser/tab-audio-small.svg (../shared/tabbrowser/tab-audio-small.svg)
+#ifdef MOZ_SERVICES_SYNC
+ skin/classic/browser/sync-throbber.png
+ skin/classic/browser/sync-16.png
+ skin/classic/browser/sync-32.png
+ skin/classic/browser/sync-128.png
+ skin/classic/browser/sync-bg.png
+ skin/classic/browser/sync-desktopIcon.png
+ skin/classic/browser/sync-mobileIcon.png
+ skin/classic/browser/syncSetup.css
+ skin/classic/browser/syncCommon.css
+ skin/classic/browser/syncQuota.css
+ skin/classic/browser/syncProgress.css
+#endif
diff --git a/themes/windows/keyhole-forward-mask.svg b/themes/windows/keyhole-forward-mask.svg
new file mode 100644
index 0000000..8355447
--- /dev/null
+++ b/themes/windows/keyhole-forward-mask.svg
@@ -0,0 +1,15 @@
+<?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/. -->
+
+<svg xmlns="http://www.w3.org/2000/svg">
+ <mask id="mask" maskContentUnits="objectBoundingBox">
+ <rect x="0" y="0" width="1" height="1" fill="white"/>
+ <circle cx="-0.46" cy="0.5" r="0.63"/>
+ </mask>
+ <mask id="mask-hover" maskContentUnits="objectBoundingBox">
+ <rect x="0" y="0" width="1" height="1" fill="white"/>
+ <circle cx="-0.35" cy="0.5" r="0.58"/>
+ </mask>
+</svg>
diff --git a/themes/windows/livemark-folder.png b/themes/windows/livemark-folder.png
new file mode 100644
index 0000000..79e3329
--- /dev/null
+++ b/themes/windows/livemark-folder.png
Binary files differ
diff --git a/themes/windows/menu-back.png b/themes/windows/menu-back.png
new file mode 100644
index 0000000..2f99ea7
--- /dev/null
+++ b/themes/windows/menu-back.png
Binary files differ
diff --git a/themes/windows/menu-forward.png b/themes/windows/menu-forward.png
new file mode 100644
index 0000000..82cd874
--- /dev/null
+++ b/themes/windows/menu-forward.png
Binary files differ
diff --git a/themes/windows/mixed-content-blocked-16.png b/themes/windows/mixed-content-blocked-16.png
new file mode 100644
index 0000000..7cf33ec
--- /dev/null
+++ b/themes/windows/mixed-content-blocked-16.png
Binary files differ
diff --git a/themes/windows/mixed-content-blocked-64.png b/themes/windows/mixed-content-blocked-64.png
new file mode 100644
index 0000000..cac4415
--- /dev/null
+++ b/themes/windows/mixed-content-blocked-64.png
Binary files differ
diff --git a/themes/windows/monitor.png b/themes/windows/monitor.png
new file mode 100644
index 0000000..35e7b20
--- /dev/null
+++ b/themes/windows/monitor.png
Binary files differ
diff --git a/themes/windows/monitor_16-10.png b/themes/windows/monitor_16-10.png
new file mode 100644
index 0000000..4195034
--- /dev/null
+++ b/themes/windows/monitor_16-10.png
Binary files differ
diff --git a/themes/windows/moz.build b/themes/windows/moz.build
new file mode 100644
index 0000000..6a7af20
--- /dev/null
+++ b/themes/windows/moz.build
@@ -0,0 +1,9 @@
+# -*- 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 += ['communicator']
+
+JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file
diff --git a/themes/windows/newtab/newTab.css b/themes/windows/newtab/newTab.css
new file mode 100644
index 0000000..b8b0fd6
--- /dev/null
+++ b/themes/windows/newtab/newTab.css
@@ -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/. */
+
+%include ../../shared/newtab/newTab.css.inc
+
+.newtab-undo-button {
+ -moz-appearance: none;
+ color: -moz-nativehyperlinktext;
+ color: rgb(0,102,204);
+ cursor: pointer;
+ padding: 0;
+ margin: 0 4px;
+ border: 0;
+ background: transparent;
+ text-decoration: none;
+ min-width: 0;
+}
+
+.newtab-undo-button > .button-box {
+ padding: 0;
+}
+
+#newtab-undo-close-button {
+ -moz-appearance: none;
+ padding: 0;
+ border: none;
+ -moz-user-focus: normal;
+}
diff --git a/themes/windows/pageInfo.css b/themes/windows/pageInfo.css
new file mode 100644
index 0000000..ec65cc4
--- /dev/null
+++ b/themes/windows/pageInfo.css
@@ -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/. */
+
+@import "chrome://global/skin/";
+
+/* View buttons */
+#viewGroup {
+ -moz-padding-start: 10px;
+}
+
+#viewGroup > radio {
+ list-style-image: url("chrome://browser/skin/pageInfo.png");
+ -moz-box-orient: vertical;
+ -moz-box-align: center;
+ -moz-appearance: none;
+ padding: 5px 3px 1px 3px;
+ margin: 0 1px;
+ min-width: 4.5em;
+}
+
+#viewGroup > radio:hover {
+ background-color: #E0E8F6;
+ color: black;
+}
+
+#viewGroup > radio[selected="true"] {
+ background-color: #C1D2EE;
+ color: black;
+}
+
+#topBar {
+ border-bottom: 2px groove ThreeDFace;
+ -moz-padding-start: 10px;
+ background-color: -moz-Field;
+ color: -moz-FieldText;
+}
+
+#generalTab {
+ -moz-image-region: rect(0px, 32px, 32px, 0px)
+}
+
+#generalTab:hover, #generalTab[selected="true"] {
+ -moz-image-region: rect(32px, 32px, 64px, 0px)
+}
+
+#mediaTab {
+ -moz-image-region: rect(0px, 64px, 32px, 32px)
+}
+
+#mediaTab:hover, #mediaTab[selected="true"] {
+ -moz-image-region: rect(32px, 64px, 64px, 32px)
+}
+
+#feedTab {
+ -moz-image-region: rect(0px, 96px, 32px, 64px)
+}
+
+#feedTab:hover, #feedTab[selected="true"] {
+ -moz-image-region: rect(32px, 96px, 64px, 64px)
+}
+
+#permTab {
+ -moz-image-region: rect(0px, 128px, 32px, 96px)
+}
+
+#permTab:hover, #permTab[selected="true"] {
+ -moz-image-region: rect(32px, 128px, 64px, 96px)
+}
+
+#securityTab {
+ -moz-image-region: rect(0px, 160px, 32px, 128px)
+}
+
+#securityTab:hover, #securityTab[selected="true"] {
+ -moz-image-region: rect(32px, 160px, 64px, 128px)
+}
+
+deck {
+ padding: 10px 10px 10px 10px;
+}
+
+/* Misc */
+tree {
+ margin: .5em;
+}
+
+.gridSeparator {
+ width: .5em;
+}
+
+textbox {
+ background: transparent !important;
+ border: none;
+ padding: 0px;
+ margin-top: 1px;
+ -moz-appearance: none;
+}
+
+textbox.header {
+ -moz-margin-start: 0;
+}
+
+.iframe {
+ margin: .5em;
+ background: white;
+ overflow: auto;
+}
+
+.fixedsize {
+ height: 8.5em;
+}
+
+textbox[disabled] {
+ font-style: italic;
+}
+
+/* General Tab */
+groupbox.collapsable caption .caption-icon {
+ width: 9px;
+ height: 9px;
+ background-repeat: no-repeat;
+ background-position: center;
+ -moz-margin-start: 2px;
+ -moz-margin-end: 2px;
+ background-image: url("chrome://global/skin/tree/twisty.svg#open");
+}
+
+groupbox.collapsable[closed="true"] {
+ border: none;
+ margin-bottom: 9px;
+ -moz-appearance: none;
+}
+
+groupbox.collapsable[closed="true"] caption .caption-icon {
+ background-image: url("chrome://global/skin/tree/twisty.svg#clsd");
+}
+
+groupbox tree {
+ margin: 0 3px;
+ border: none;
+}
+
+#securityBox description {
+ -moz-margin-start: 10px;
+}
+
+#general-security-identity {
+ white-space: pre-wrap;
+ line-height: 2em;
+}
+
+@media (-moz-os-version: windows-win10) {
+ groupbox.collapsable caption .caption-icon {
+ background-image: url("chrome://global/skin/tree/twisty-10.svg#open");
+ }
+
+ groupbox.collapsable[closed="true"] caption .caption-icon {
+ background-image: url("chrome://global/skin/tree/twisty-10.svg#clsd");
+ }
+}
+
+/* Media Tab */
+#imagetree {
+ min-height: 10em;
+ margin-bottom: 0;
+}
+
+#mediaSplitter {
+ border-style: none;
+ background: none;
+ height: .8em;
+}
+
+#mediaGrid {
+ min-height: 9em;
+}
+
+#mediaLabelColumn {
+ min-width: 10em;
+}
+
+#thepreviewimage {
+ margin: 1em;
+}
+
+treechildren::-moz-tree-cell-text(broken) {
+ font-style: italic;
+ color: graytext;
+}
+
+/* Feeds Tab */
+#feedtree {
+ margin-bottom: 0px;
+}
+
+#feedListbox richlistitem {
+ padding-top: 6px;
+ padding-bottom: 6px;
+ -moz-padding-start: 7px;
+ -moz-padding-end: 7px;
+ min-height: 25px;
+ border-bottom: 1px dotted #C0C0C0;
+}
+
+#feedListbox richlistitem[selected="true"] {
+ background-color: -moz-Dialog;
+ color: -moz-DialogText;
+}
+
+#feedListbox {
+ border: 2px solid;
+ -moz-border-top-colors: ThreeDShadow ThreeDDarkShadow;
+ -moz-border-right-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-bottom-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-left-colors: ThreeDShadow ThreeDDarkShadow;
+}
+
+.feedTitle {
+ font-weight: bold;
+}
+
+/* Permissions Tab */
+#permList {
+ margin-top: .5em;
+ overflow: auto;
+ border: 2px solid;
+ -moz-border-top-colors: ThreeDShadow ThreeDDarkShadow;
+ -moz-border-right-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-bottom-colors: ThreeDHighlight ThreeDLightShadow;
+ -moz-border-left-colors: ThreeDShadow ThreeDDarkShadow;
+ background-color: -moz-field;
+}
+
+.permission {
+ padding-top: 6px;
+ padding-bottom: 6px;
+ -moz-padding-start: 7px;
+ -moz-padding-end: 7px;
+ min-height: 25px;
+ border-bottom: 1px dotted #C0C0C0;
+}
+
+.permissionLabel {
+ font-weight: bold;
+}
+
+.permission:hover {
+ background-color: -moz-dialog;
+}
+
+/* Security Tab */
+#securityPanel .caption-icon {
+ display: none;
+}
+
+#securityPanel .header {
+ font-size: 120%;
+}
+
+#securityPanel .fieldLabel {
+ margin: 2px 10px 3px 10px;
+}
+
+#securityPanel .fieldValue {
+ font-weight: bold;
+ margin: 2px 10px 0px 10px;
+}
diff --git a/themes/windows/pageInfo.png b/themes/windows/pageInfo.png
new file mode 100644
index 0000000..fcc713b
--- /dev/null
+++ b/themes/windows/pageInfo.png
Binary files differ
diff --git a/themes/windows/permissions/aboutPermissions.css b/themes/windows/permissions/aboutPermissions.css
new file mode 100644
index 0000000..60ee816
--- /dev/null
+++ b/themes/windows/permissions/aboutPermissions.css
@@ -0,0 +1,153 @@
+/* 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/. */
+
+@import url("chrome://global/skin/inContentUI.css");
+
+/* header */
+
+#permissions-pagetitle {
+ font-size: 200%;
+ font-weight: bold;
+ padding-bottom: 0.5em;
+}
+
+/* content box */
+#permissions-content {
+ height: 100%;
+}
+
+/* sites box */
+
+#sites-box {
+ padding: 10px;
+ width: 25em;
+}
+
+#sites-filter {
+ margin: 0;
+}
+
+#sites-list {
+ -moz-appearance: none;
+ border: 1px solid rgba(0, 0, 0, 0.32);
+ background-color: rgba(255, 255, 255, 0.4);
+ margin: 5px 0 0 0;
+}
+
+.site {
+ padding: 4px;
+ border-bottom: 1px solid ThreeDLightShadow;
+}
+
+.site-favicon {
+ height: 16px;
+ width: 16px;
+ -moz-margin-end: 4px;
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+
+#all-sites-item > .site-container > .site-favicon {
+ list-style-image: none;
+}
+
+/* permissions box */
+
+#permissions-box {
+ padding-top: 10px;
+ overflow-y: auto;
+}
+
+#site-description {
+ font-size: 125%;
+ -moz-margin-start: 6px; /* to match button margin */
+}
+
+#site-label {
+ font-weight: bold;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+#defaults-description {
+ font-size: 125%;
+ font-weight: bold;
+ -moz-margin-start: 6px;
+}
+
+.pref-item {
+ margin-bottom: 10px;
+}
+
+.pref-icon {
+ width: 48px;
+ height: 48px;
+ -moz-margin-end: 10px;
+}
+
+.pref-icon[type="password"] {
+ list-style-image: url(chrome://mozapps/skin/passwordmgr/key-64.png);
+}
+.pref-icon[type="image"] {
+ list-style-image: url(chrome://global/skin/icons/question-48.png);
+}
+.pref-icon[type="popup"] {
+ list-style-image: url(chrome://global/skin/icons/question-48.png);
+}
+.pref-icon[type="cookie"] {
+ list-style-image: url(chrome://global/skin/icons/question-48.png);
+}
+.pref-icon[type="desktop-notification"] {
+ list-style-image: url(chrome://browser/skin/web-notifications-icon.svg);
+}
+.pref-icon[type="install"] {
+ list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric-48.png);
+}
+.pref-icon[type="geo"] {
+ list-style-image: url(chrome://browser/skin/Geolocation-64.png);
+}
+.pref-icon[type="plugins"] {
+ list-style-image: url(chrome://mozapps/skin/plugins/pluginGeneric-48.png);
+}
+
+.pref-title {
+ font-size: 125%;
+ margin-bottom: 0;
+ font-weight: bold;
+ margin-right: 0;
+ padding-right: 0;
+}
+
+.pref-default {
+ margin-left: 0.5em;
+ padding-left: 0;
+}
+
+.pref-set-default {
+ visibility: collapse;
+}
+
+.pref-menulist {
+ margin-left: 6px;
+ margin-right: 6px;
+ min-width: 10em; /* native menulists ellipsize their longest entries by default on many themes */
+}
+
+.plugins-label {
+ margin-right: 0;
+ padding-right: 0;
+}
+
+.plugins-vulnerable {
+ margin-left: 0;
+ padding-left: 0;
+ margin-right: 0;
+ padding-right: 0;
+}
+
+.plugins-default {
+ margin-left: 0.5em;
+ padding-left: 0;
+ margin-right: 1em;
+ padding-right: 0;
+}
diff --git a/themes/windows/places/allBookmarks.png b/themes/windows/places/allBookmarks.png
new file mode 100644
index 0000000..177c31c
--- /dev/null
+++ b/themes/windows/places/allBookmarks.png
Binary files differ
diff --git a/themes/windows/places/bookmark.png b/themes/windows/places/bookmark.png
new file mode 100644
index 0000000..2e9a206
--- /dev/null
+++ b/themes/windows/places/bookmark.png
Binary files differ
diff --git a/themes/windows/places/bookmarksMenu.png b/themes/windows/places/bookmarksMenu.png
new file mode 100644
index 0000000..14c9601
--- /dev/null
+++ b/themes/windows/places/bookmarksMenu.png
Binary files differ
diff --git a/themes/windows/places/bookmarksToolbar.png b/themes/windows/places/bookmarksToolbar.png
new file mode 100644
index 0000000..5a4a693
--- /dev/null
+++ b/themes/windows/places/bookmarksToolbar.png
Binary files differ
diff --git a/themes/windows/places/calendar.png b/themes/windows/places/calendar.png
new file mode 100644
index 0000000..c0d1071
--- /dev/null
+++ b/themes/windows/places/calendar.png
Binary files differ
diff --git a/themes/windows/places/downloads.png b/themes/windows/places/downloads.png
new file mode 100644
index 0000000..d37bc40
--- /dev/null
+++ b/themes/windows/places/downloads.png
Binary files differ
diff --git a/themes/windows/places/editBookmark.png b/themes/windows/places/editBookmark.png
new file mode 100644
index 0000000..fbca052
--- /dev/null
+++ b/themes/windows/places/editBookmark.png
Binary files differ
diff --git a/themes/windows/places/editBookmarkOverlay.css b/themes/windows/places/editBookmarkOverlay.css
new file mode 100644
index 0000000..be3ea83
--- /dev/null
+++ b/themes/windows/places/editBookmarkOverlay.css
@@ -0,0 +1,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/. */
+
+/**** folder menulist ****/
+.folder-icon > .menulist-label-box > .menulist-icon {
+ width: 16px;
+ height: 16px;
+}
+
+.folder-icon > .menu-iconic-left {
+ display: -moz-box;
+}
+
+.folder-icon {
+ list-style-image: url("chrome://global/skin/icons/folder-item.png") !important;
+ -moz-image-region: rect(0px, 32px, 16px, 16px) !important;
+}
+
+
+/**** expanders ****/
+
+.expander-up,
+.expander-down {
+ min-width: 0;
+ margin: 0;
+ -moz-margin-end: 4px;
+}
+
+.expander-up > .button-box,
+.expander-down > .button-box {
+ padding: 0;
+}
+
+.expander-up {
+ list-style-image: url("chrome://global/skin/icons/collapse.png");
+}
+
+.expander-down {
+ list-style-image: url("chrome://global/skin/icons/expand.png");
+}
+
+#editBookmarkPanelContent {
+ min-width: 23em;
+}
+
+#editBMPanel_folderTree {
+ margin-top: 2px;
+ margin-bottom: 2px;
+}
+
+/* Hide the value column of the tag autocomplete popup
+ * leaving only the comment column visible. This is
+ * so that only the tag being edited is shown in the
+ * popup.
+ */
+#editBMPanel_tagsField #treecolAutoCompleteValue {
+ visibility: collapse;
+}
+
+
+/* ::::: bookmark panel dropdown icons ::::: */
+
+#editBMPanel_folderMenuList[selectedIndex="0"],
+#editBMPanel_toolbarFolderItem {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png") !important;
+ -moz-image-region: auto !important;
+}
+
+#editBMPanel_folderMenuList[selectedIndex="1"],
+#editBMPanel_bmRootItem {
+ list-style-image: url("chrome://browser/skin/places/bookmarksMenu.png") !important;
+ -moz-image-region: auto !important;
+}
+
+#editBMPanel_folderMenuList[selectedIndex="2"],
+#editBMPanel_unfiledRootItem {
+ list-style-image: url("chrome://browser/skin/places/unsortedBookmarks.png") !important;
+ -moz-image-region: auto !important;
+}
diff --git a/themes/windows/places/history.png b/themes/windows/places/history.png
new file mode 100644
index 0000000..cc3b067
--- /dev/null
+++ b/themes/windows/places/history.png
Binary files differ
diff --git a/themes/windows/places/libraryToolbar.png b/themes/windows/places/libraryToolbar.png
new file mode 100644
index 0000000..f5f3654
--- /dev/null
+++ b/themes/windows/places/libraryToolbar.png
Binary files differ
diff --git a/themes/windows/places/livemark-item.png b/themes/windows/places/livemark-item.png
new file mode 100644
index 0000000..9184b95
--- /dev/null
+++ b/themes/windows/places/livemark-item.png
Binary files differ
diff --git a/themes/windows/places/organizer.css b/themes/windows/places/organizer.css
new file mode 100644
index 0000000..45851d0
--- /dev/null
+++ b/themes/windows/places/organizer.css
@@ -0,0 +1,253 @@
+/* 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/. */
+
+:root {
+ --toolbar-custom-color: hsl(210,75%,92%);
+ --toolbar-highlight-top: rgba(255,255,255,.5);
+ --toolbarbutton-image: url("chrome://browser/skin/Toolbar.png");
+}
+
+/* Use SVG for HiDPI 133%+ */
+@media (min-resolution: 1.33dppx) {
+ :root {
+ --toolbarbutton-image: url("chrome://browser/skin/Toolbar.svg");
+ }
+}
+
+/* Toolbar */
+#placesToolbar {
+ padding: 3px;
+ -moz-padding-end: 6px;
+}
+
+#placesToolbar > toolbarbutton[disabled] > .toolbarbutton-icon {
+ opacity: .4;
+}
+
+#back-button,
+#forward-button {
+ list-style-image: var(--toolbarbutton-image);
+}
+
+#back-button {
+ -moz-image-region: rect(0, 18px, 18px, 0);
+}
+
+#forward-button {
+ -moz-image-region: rect(0, 36px, 18px, 18px);
+}
+
+#back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
+#forward-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
+ transform: scaleX(-1);
+}
+
+/* Menu */
+#placesMenu {
+ -moz-margin-start: 6px;
+ -moz-appearance: none;
+ border: none;
+}
+
+#placesMenu > menu {
+ -moz-padding-start: 4px;
+ -moz-padding-end: 1px;
+ padding-top: 2px;
+ padding-bottom: 2px;
+ -moz-appearance: toolbarbutton;
+%ifdef XP_WIN
+% use standard menu colors on OS/2
+ color: -moz-DialogText;
+%endif
+ border: 1px solid transparent;
+}
+
+#placesMenu > menu[_moz-menuactive="true"] {
+ background-color: transparent;
+}
+
+#placesMenu > menu:hover {
+ border-color: ThreeDHighlight ThreeDShadow ThreeDShadow ThreeDHighlight;
+}
+
+#placesMenu > menu[open="true"] {
+ border-color: ThreeDShadow ThreeDHighlight ThreeDHighlight ThreeDShadow;
+ -moz-padding-start: 5px;
+ -moz-padding-end: 0px;
+ padding-top: 3px;
+ padding-bottom: 1px;
+}
+
+#placesMenu > menu > .menubar-text {
+ -moz-padding-end: 8px;
+ background: url(chrome://global/skin/arrow/arrow-dn.gif) right center no-repeat;
+}
+
+#placesMenu > menu > .menubar-text:-moz-locale-dir(rtl) {
+ background-position: left center;
+}
+
+/* organize, view and maintenance buttons icons */
+#organizeButton,
+#viewMenu,
+#maintenanceButton {
+ list-style-image: url("chrome://browser/skin/places/libraryToolbar.png");
+}
+
+/* organize button */
+#organizeButton {
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+}
+#organizeButton:hover,
+#organizeButton[open="true"] {
+ -moz-image-region: rect(16px, 16px, 32px, 0px);
+}
+
+/* view button */
+#viewMenu {
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+#viewMenu:hover,
+#viewMenu[open="true"] {
+ -moz-image-region: rect(16px, 32px, 32px, 16px);
+}
+
+/* maintenance button */
+#maintenanceButton {
+ -moz-image-region: rect(0px, 48px, 16px, 32px);
+}
+#maintenanceButton:hover,
+#maintenanceButton[open="true"] {
+ -moz-image-region: rect(16px, 48px, 32px, 32px);
+}
+
+/* Root View */
+#placesView {
+ border-top: 1px solid ThreeDDarkShadow;
+}
+
+/* Info box */
+#detailsDeck {
+ border-top: 1px solid ThreeDShadow;
+ padding: 5px;
+}
+
+#infoBoxExpanderLabel {
+ -moz-padding-start: 2px;
+}
+
+#organizerScopeBar {
+ padding: 2px 0;
+ -moz-padding-end: 3px;
+}
+
+#organizerScopeBar > toolbarbutton {
+ -moz-appearance: none;
+ border: 1px solid transparent;
+ border-radius: 2px;
+ padding: 0 !important;
+ margin: 0 1px;
+}
+
+#organizerScopeBar > toolbarbutton > .toolbarbutton-icon {
+ padding: 0;
+ margin: 0;
+}
+
+#organizerScopeBar > toolbarbutton > .toolbarbutton-text {
+ margin: 0;
+ padding: 2px 5px;
+}
+
+#organizerScopeBar > toolbarbutton:not([disabled="true"]):not([checked="true"]):hover {
+ border-color: ThreeDShadow;
+}
+
+#organizerScopeBar > toolbarbutton[checked="true"] {
+ border-color: ThreeDDarkShadow !important;
+}
+
+#searchFilter {
+ margin: 0;
+}
+
+/**
+ * Downloads pane
+ */
+
+#clearDownloadsButton > .toolbarbutton-icon {
+ display: none;
+}
+
+#clearDownloadsButton {
+ -moz-padding-start: 9px;
+ -moz-padding-end: 9px;
+}
+
+#placesView {
+ border-top: none;
+}
+
+@media not all and (-moz-windows-classic) {
+ #placesToolbox {
+ -moz-appearance: none;
+ background-color: transparent;
+ }
+
+ #placesToolbar {
+ -moz-appearance: none;
+ background-color: -moz-Dialog;
+ color: -moz-dialogText;
+ }
+}
+
+@media (-moz-windows-default-theme) {
+ #placesView > splitter {
+ border: 0;
+ -moz-border-end: 1px solid #A9B7C9;
+ min-width: 0;
+ width: 3px;
+ background-color: transparent;
+ -moz-margin-start: -3px;
+ position: relative;
+ }
+}
+
+@media (-moz-windows-glass) {
+ #placesToolbox {
+ border-top: none;
+ }
+
+ #placesToolbar {
+ background-image: linear-gradient(var(--toolbar-highlight-top), transparent);
+ }
+}
+
+@media (-moz-windows-default-theme) and (-moz-os-version: windows-vista),
+ (-moz-windows-default-theme) and (-moz-os-version: windows-win7) {
+ #placesView,
+ #infoPane,
+ #placesList,
+ #placeContent {
+ background-color: #EEF3FA;
+ }
+
+ #placesToolbar {
+ background-color: var(--toolbar-custom-color);
+ color: black;
+ }
+
+ #detailsDeck {
+ border-top-color: #A9B7C9;
+ }
+
+ #searchFilter {
+ -moz-appearance: none;
+ padding: 2px;
+ -moz-padding-start: 4px;
+ background-clip: padding-box;
+ border: 1px solid rgba(0,0,0,.32);
+ border-radius: 2px;
+ }
+}
diff --git a/themes/windows/places/places.css b/themes/windows/places/places.css
new file mode 100644
index 0000000..bb16046
--- /dev/null
+++ b/themes/windows/places/places.css
@@ -0,0 +1,172 @@
+/* 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/. */
+
+/* Sidebars */
+.sidebar-placesTree {
+ -moz-appearance: none;
+ border: 0;
+ margin: 0;
+ border-top: 1px solid ThreeDShadow;
+}
+
+.sidebar-placesTreechildren::-moz-tree-cell(leaf) ,
+.sidebar-placesTreechildren::-moz-tree-image(leaf) {
+ cursor: pointer;
+}
+
+.sidebar-placesTreechildren::-moz-tree-cell-text(leaf, hover) {
+ cursor: pointer;
+ text-decoration: underline;
+}
+
+.sidebar-placesTreechildren::-moz-tree-cell(separator) {
+ cursor: default;
+}
+
+@media (-moz-windows-default-theme) {
+ .sidebar-placesTree {
+ background-color: transparent;
+ border-top: none;
+ }
+
+ .sidebar-placesTreechildren::-moz-tree-cell-text(leaf, hover) {
+ text-decoration: none;
+ }
+
+ @media (-moz-os-version: windows-vista),
+ (-moz-os-version: windows-win7) {
+ #bookmarksPanel,
+ #history-panel {
+ background-color: #EEF3FA;
+ }
+ }
+}
+
+/* Trees */
+treechildren::-moz-tree-image(title) {
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+ padding-right: 2px;
+ margin: 0px 2px;
+ width: 16px;
+ height: 16px;
+}
+
+treechildren::-moz-tree-image(title, livemarkItem) {
+ list-style-image: url("chrome://browser/skin/places/livemark-item.png");
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+}
+
+treechildren::-moz-tree-image(title, livemarkItem, visited) {
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+treechildren::-moz-tree-image(title, separator) {
+ list-style-image: none;
+ width: 0;
+ height: 0;
+}
+
+treechildren::-moz-tree-image(title, container) {
+ list-style-image: url("chrome://global/skin/icons/folder-item.png");
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+treechildren::-moz-tree-image(title, open) {
+ -moz-image-region: rect(16px, 32px, 32px, 16px);
+}
+
+treechildren::-moz-tree-image(title, container, livemark) {
+ list-style-image: url("chrome://browser/skin/livemark-folder.png");
+ -moz-image-region: auto;
+}
+
+treechildren::-moz-tree-image(container, OrganizerQuery_AllBookmarks) {
+ list-style-image: url("chrome://browser/skin/places/allBookmarks.png");
+ -moz-image-region: auto;
+}
+
+treechildren::-moz-tree-image(container, OrganizerQuery_BookmarksToolbar) {
+ list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png");
+ -moz-image-region: auto;
+}
+
+treechildren::-moz-tree-image(container, OrganizerQuery_BookmarksMenu) {
+ list-style-image: url("chrome://browser/skin/places/bookmarksMenu.png");
+ -moz-image-region: auto;
+}
+
+treechildren::-moz-tree-image(container, OrganizerQuery_UnfiledBookmarks) {
+ list-style-image: url("chrome://browser/skin/places/unsortedBookmarks.png");
+ -moz-image-region: auto;
+}
+
+/* query-nodes should be styled even if they're not expandable */
+treechildren::-moz-tree-image(title, query) {
+ list-style-image: url("chrome://browser/skin/places/query.png");
+ -moz-image-region: auto;
+}
+
+treechildren::-moz-tree-image(title, query, tagContainer),
+treechildren::-moz-tree-image(query, OrganizerQuery_Tags) {
+ list-style-image: url("chrome://browser/skin/places/tag.png");
+ -moz-image-region: auto;
+}
+
+treechildren::-moz-tree-image(query, OrganizerQuery_Downloads) {
+ list-style-image: url("chrome://browser/skin/places/downloads.png");
+ -moz-image-region: auto;
+}
+
+/* calendar icon for folders grouping items by date */
+treechildren::-moz-tree-image(title, query, dayContainer) {
+ list-style-image: url("chrome://browser/skin/places/calendar.png");
+ -moz-image-region: auto;
+}
+
+treechildren::-moz-tree-image(title, query, hostContainer) {
+ list-style-image: url("chrome://global/skin/icons/folder-item.png");
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+treechildren::-moz-tree-image(title, query, hostContainer, open) {
+ list-style-image: url("chrome://global/skin/icons/folder-item.png");
+ -moz-image-region: rect(16px, 32px, 32px, 16px);
+}
+
+treechildren::-moz-tree-image(title, query, OrganizerQuery_History) {
+ list-style-image: url("chrome://browser/skin/places/history.png");
+}
+
+/* We want some queries to look like ordinary folders. This must come
+ after the (title, query) selector, or it would get overridden. */
+treechildren::-moz-tree-image(title, query, folder) {
+ list-style-image: url("chrome://global/skin/icons/folder-item.png");
+ -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+treechildren::-moz-tree-image(title, query, folder, open) {
+ -moz-image-region: rect(16px, 32px, 32px, 16px);
+}
+
+treechildren::-moz-tree-image(cutting) {
+ opacity: 0.5;
+}
+
+treechildren::-moz-tree-cell-text(cutting) {
+ opacity: 0.7;
+}
+
+/* Browser Sidebars */
+
+/* Default button vert. margins are 1px/2px, and this can cause misalignment */
+#viewButton {
+ margin-top: 2px;
+ margin-bottom: 2px;
+}
+
+#viewButton > .button-box > .button-menu-dropmarker {
+ height: auto;
+ width: auto;
+ -moz-margin-end: -3px;
+}
diff --git a/themes/windows/places/query.png b/themes/windows/places/query.png
new file mode 100644
index 0000000..fff0fb0
--- /dev/null
+++ b/themes/windows/places/query.png
Binary files differ
diff --git a/themes/windows/places/starred48.png b/themes/windows/places/starred48.png
new file mode 100644
index 0000000..2f7e878
--- /dev/null
+++ b/themes/windows/places/starred48.png
Binary files differ
diff --git a/themes/windows/places/tag.png b/themes/windows/places/tag.png
new file mode 100644
index 0000000..da90624
--- /dev/null
+++ b/themes/windows/places/tag.png
Binary files differ
diff --git a/themes/windows/places/toolbarDropMarker.png b/themes/windows/places/toolbarDropMarker.png
new file mode 100644
index 0000000..3abb7c2
--- /dev/null
+++ b/themes/windows/places/toolbarDropMarker.png
Binary files differ
diff --git a/themes/windows/places/unsortedBookmarks.png b/themes/windows/places/unsortedBookmarks.png
new file mode 100644
index 0000000..d18a501
--- /dev/null
+++ b/themes/windows/places/unsortedBookmarks.png
Binary files differ
diff --git a/themes/windows/places/unstarred48.png b/themes/windows/places/unstarred48.png
new file mode 100644
index 0000000..8b82aab
--- /dev/null
+++ b/themes/windows/places/unstarred48.png
Binary files differ
diff --git a/themes/windows/pointerLock-16.png b/themes/windows/pointerLock-16.png
new file mode 100644
index 0000000..862cd11
--- /dev/null
+++ b/themes/windows/pointerLock-16.png
Binary files differ
diff --git a/themes/windows/pointerLock-64.png b/themes/windows/pointerLock-64.png
new file mode 100644
index 0000000..a35ce04
--- /dev/null
+++ b/themes/windows/pointerLock-64.png
Binary files differ
diff --git a/themes/windows/preferences/Options-sync.png b/themes/windows/preferences/Options-sync.png
new file mode 100644
index 0000000..89901fb
--- /dev/null
+++ b/themes/windows/preferences/Options-sync.png
Binary files differ
diff --git a/themes/windows/preferences/Options.png b/themes/windows/preferences/Options.png
new file mode 100644
index 0000000..2cb1f50
--- /dev/null
+++ b/themes/windows/preferences/Options.png
Binary files differ
diff --git a/themes/windows/preferences/alwaysAsk.png b/themes/windows/preferences/alwaysAsk.png
new file mode 100644
index 0000000..d12805e
--- /dev/null
+++ b/themes/windows/preferences/alwaysAsk.png
Binary files differ
diff --git a/themes/windows/preferences/application.png b/themes/windows/preferences/application.png
new file mode 100644
index 0000000..d67993d
--- /dev/null
+++ b/themes/windows/preferences/application.png
Binary files differ
diff --git a/themes/windows/preferences/applications.css b/themes/windows/preferences/applications.css
new file mode 100644
index 0000000..80699b4
--- /dev/null
+++ b/themes/windows/preferences/applications.css
@@ -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/. */
+
+/**
+ * Line up the actions menu with action labels above and below it.
+ * Equalize the distance from the left side of the action box to the left side
+ * of the icon for both the menu and the non-menu versions of the action box.
+ * Also make sure the labels are the same distance away from the icons.
+ */
+.actionsMenu {
+ margin-top: 0;
+ margin-bottom: 0;
+ -moz-margin-start: -2px;
+ -moz-margin-end: 0;
+}
+
+.typeIcon,
+.actionIcon {
+ -moz-margin-start: 3px;
+ -moz-margin-end: 3px;
+}
+
+richlistitem label {
+ -moz-margin-start: 1px;
+ margin-top: 2px;
+}
+
+richlistitem {
+ min-height: 22px;
+}
+
+richlistitem[appHandlerIcon="ask"],
+menuitem[appHandlerIcon="ask"] {
+ list-style-image: url("chrome://browser/skin/preferences/alwaysAsk.png");
+}
+
+richlistitem[appHandlerIcon="save"],
+menuitem[appHandlerIcon="save"] {
+ list-style-image: url("chrome://browser/skin/preferences/application.png");
+}
+
+richlistitem[appHandlerIcon="feed"],
+menuitem[appHandlerIcon="feed"] {
+ list-style-image: url("chrome://browser/skin/page-livemarks.png");
+}
+
+richlistitem[appHandlerIcon="plugin"],
+menuitem[appHandlerIcon="plugin"] {
+ list-style-image: url("chrome://mozapps/skin/plugins/pluginGeneric-16.png");
+}
+
+.actionsMenu .menulist-icon {
+ -moz-margin-end: 3px;
+}
+
+.actionsMenu > menupopup > menuitem > .menu-iconic-left {
+ -moz-padding-start: 0px;
+ -moz-padding-end: 2px;
+}
+
+.actionsMenu > menupopup > menuitem {
+ -moz-padding-start: 4px;
+}
diff --git a/themes/windows/preferences/mail.png b/themes/windows/preferences/mail.png
new file mode 100644
index 0000000..be1ed4d
--- /dev/null
+++ b/themes/windows/preferences/mail.png
Binary files differ
diff --git a/themes/windows/preferences/preferences.css b/themes/windows/preferences/preferences.css
new file mode 100644
index 0000000..40be343
--- /dev/null
+++ b/themes/windows/preferences/preferences.css
@@ -0,0 +1,146 @@
+/*
+# -*- 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/.
+*/
+
+/* Global Styles */
+#BrowserPreferences radio[pane] {
+ list-style-image: url("chrome://browser/skin/preferences/Options.png");
+ padding: 5px 3px 1px;
+}
+
+radio[pane=paneMain] {
+ -moz-image-region: rect(0, 32px, 32px, 0);
+}
+
+radio[pane=paneTabs] {
+ -moz-image-region: rect(0, 64px, 32px, 32px);
+}
+
+radio[pane=paneContent] {
+ -moz-image-region: rect(0, 96px, 32px, 64px);
+}
+
+radio[pane=paneApplications] {
+ -moz-image-region: rect(0, 128px, 32px, 96px);
+}
+
+radio[pane=panePrivacy] {
+ -moz-image-region: rect(0, 160px, 32px, 128px);
+}
+
+radio[pane=paneSecurity] {
+ -moz-image-region: rect(0, 192px, 32px, 160px);
+}
+
+radio[pane=paneAdvanced] {
+ -moz-image-region: rect(0, 224px, 32px, 192px);
+}
+
+%ifdef MOZ_SERVICES_SYNC
+radio[pane=paneSync] {
+ list-style-image: url("chrome://browser/skin/preferences/Options-sync.png") !important;
+}
+%endif
+
+/* Applications Pane */
+#BrowserPreferences[animated="true"] #handlersView {
+ height: 25em;
+}
+
+#BrowserPreferences[animated="false"] #handlersView {
+ -moz-box-flex: 1;
+}
+
+/* Privacy Pane */
+
+/* styles for the link elements copied from .text-link in global.css */
+.inline-link {
+ color: -moz-nativehyperlinktext;
+ text-decoration: none;
+}
+
+.inline-link:hover {
+ text-decoration: underline;
+}
+
+/* Modeless Window Dialogs */
+.windowDialog,
+.windowDialog prefpane {
+ padding: 0;
+}
+
+#browserHomePage:-moz-locale-dir(rtl) input {
+ unicode-bidi: plaintext;
+ direction: rtl;
+}
+
+.contentPane {
+ margin: 9px 8px 5px;
+}
+
+.actionButtons {
+ margin: 0 3px 6px !important;
+}
+
+/* Cookies Manager */
+#cookiesChildren::-moz-tree-image(domainCol) {
+ width: 16px;
+ height: 16px;
+ margin: 0 2px;
+ list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png") !important;
+}
+
+#cookiesChildren::-moz-tree-image(domainCol, container) {
+ list-style-image: url("chrome://global/skin/icons/folder-item.png") !important;
+ -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
+#cookiesChildren::-moz-tree-image(domainCol, container, open) {
+ -moz-image-region: rect(16px, 32px, 32px, 16px);
+}
+
+#cookieInfoBox {
+ border: 1px solid ThreeDShadow;
+ border-radius: 0;
+ margin: 4px;
+ padding: 0;
+}
+
+/* Advanced Pane */
+
+/* Adding padding-bottom prevents the bottom of the tabpanel from being cutoff
+ when browser.preferences.animateFadeIn = true */
+#advancedPrefs {
+ padding-bottom: 8px;
+}
+
+/* bottom-most box containing a groupbox in a prefpane. Prevents the bottom
+ of the groupbox from being cutoff */
+.bottomBox {
+ padding-bottom: 4px;
+}
+
+%ifdef MOZ_SERVICES_SYNC
+/* Sync Pane */
+
+#syncDesc {
+ padding: 0 8em;
+}
+
+.syncGroupBox {
+ padding: 10px;
+}
+
+#accountCaptionImage {
+ list-style-image: url("chrome://mozapps/skin/profile/profileicon.png");
+}
+
+#syncAddDeviceLabel {
+ margin-top: 1em;
+ margin-bottom: 1em;
+}
+
+%endif
diff --git a/themes/windows/preferences/saveFile.png b/themes/windows/preferences/saveFile.png
new file mode 100644
index 0000000..1248dd3
--- /dev/null
+++ b/themes/windows/preferences/saveFile.png
Binary files differ
diff --git a/themes/windows/privatebrowsing-dark.png b/themes/windows/privatebrowsing-dark.png
new file mode 100644
index 0000000..9eaf3ae
--- /dev/null
+++ b/themes/windows/privatebrowsing-dark.png
Binary files differ
diff --git a/themes/windows/privatebrowsing-light.png b/themes/windows/privatebrowsing-light.png
new file mode 100644
index 0000000..c12f507
--- /dev/null
+++ b/themes/windows/privatebrowsing-light.png
Binary files differ
diff --git a/themes/windows/reload-stop-go.png b/themes/windows/reload-stop-go.png
new file mode 100644
index 0000000..1017be9
--- /dev/null
+++ b/themes/windows/reload-stop-go.png
Binary files differ
diff --git a/themes/windows/sanitize.png b/themes/windows/sanitize.png
new file mode 100644
index 0000000..72eea2c
--- /dev/null
+++ b/themes/windows/sanitize.png
Binary files differ
diff --git a/themes/windows/sanitizeDialog.css b/themes/windows/sanitizeDialog.css
new file mode 100644
index 0000000..4312eb8
--- /dev/null
+++ b/themes/windows/sanitizeDialog.css
@@ -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/. */
+
+#sanitizeDurationChoice {
+ -moz-margin-end: 0;
+}
+
+/* Align the duration label with the warning box and item list */
+#sanitizeDurationLabel {
+ -moz-margin-start: 3px;
+}
+
+
+/* Hide the duration dropdown suffix label if it's empty. Otherwise it
+ takes up a little space, causing the end of the dropdown to not be aligned
+ with the warning box. */
+#sanitizeDurationSuffixLabel[value=""] {
+ display: none;
+}
+
+
+/* Places tree */
+#placesTreechildren::-moz-tree-row(selected),
+#placesTreechildren::-moz-tree-row(grippyRow) {
+ background: #999;
+}
+
+#placesTreechildren::-moz-tree-cell-text(selected) {
+ color: #111;
+}
+
+
+/* Sanitize everything warning box */
+#sanitizeEverythingWarningBox {
+ background-color: Window;
+ border: 1px solid ThreeDDarkShadow;
+ border-radius: 5px;
+ padding: 16px;
+}
+
+#sanitizeEverythingWarningIcon {
+ list-style-image: url("chrome://global/skin/icons/warning-large.png");
+ padding: 0;
+ margin: 0;
+}
+
+#sanitizeEverythingWarningDescBox {
+ padding: 0 16px;
+ margin: 0;
+}
+
+
+/* Progressive disclosure button */
+#detailsExpanderWrapper {
+ padding: 0;
+ margin: 6px 0;
+}
+
+.expander-up,
+.expander-down {
+ min-width: 0;
+ margin: 0;
+}
+
+.expander-up > .button-box,
+.expander-down > .button-box {
+ padding: 0;
+}
+
+.expander-up {
+ list-style-image: url("chrome://global/skin/icons/collapse.png");
+}
+
+.expander-down {
+ list-style-image: url("chrome://global/skin/icons/expand.png");
+}
+
+
+/* Make the item list the same width as the warning box */
+#itemList {
+ -moz-margin-start: 0;
+ -moz-margin-end: 0;
+}
+
+
+/* Align the last dialog button with the end of the warning box */
+.prefWindow-dlgbuttons {
+ -moz-margin-end: 0;
+}
+.dialog-button[dlgtype="cancel"] {
+ -moz-margin-end: 0;
+}
diff --git a/themes/windows/searchbar-dropdown-arrow.png b/themes/windows/searchbar-dropdown-arrow.png
new file mode 100644
index 0000000..79d8d61
--- /dev/null
+++ b/themes/windows/searchbar-dropdown-arrow.png
Binary files differ
diff --git a/themes/windows/searchbar.css b/themes/windows/searchbar.css
new file mode 100644
index 0000000..86a4855
--- /dev/null
+++ b/themes/windows/searchbar.css
@@ -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/. */
+
+.searchbar-textbox {
+ width: 6em;
+ min-width: 6em;
+}
+
+.autocomplete-textbox-container {
+ -moz-box-align: stretch;
+}
+
+.textbox-input-box {
+ margin: 0;
+}
+
+/* ::::: searchbar-engine-button ::::: */
+
+.searchbar-engine-image {
+ height: 16px;
+ width: 16px;
+ list-style-image: url("chrome://global/skin/icons/folder-item.png");
+ -moz-image-region: rect(0px, 16px, 16px, 0px);
+ -moz-margin-start: 2px;
+}
+
+.searchbar-engine-button {
+ -moz-appearance: none;
+ min-width: 0;
+ margin: 0;
+ padding: 0;
+ -moz-padding-end: 2px;
+ -moz-box-align: center;
+ background: none;
+ border: none;
+}
+
+.searchbar-engine-button > .button-box {
+ -moz-appearance: none;
+ padding: 0;
+ border: 0;
+}
+
+.searchbar-dropmarker-image {
+ list-style-image: url("chrome://browser/skin/searchbar-dropdown-arrow.png");
+ -moz-image-region: rect(0, 13px, 11px, 0);
+}
+
+.searchbar-engine-button[open="true"] > .searchbar-dropmarker-image {
+ -moz-image-region: rect(0, 26px, 11px, 13px);
+}
+
+
+/* ::::: search-go-button ::::: */
+
+.search-go-container {
+ -moz-box-align: center;
+}
+
+.search-go-button {
+ padding: 1px;
+ list-style-image: url("chrome://global/skin/icons/Search-glass.png");
+ -moz-image-region: rect(0px 16px 16px 0px);
+}
+
+.search-go-button:-moz-locale-dir(rtl) {
+ transform: scaleX(-1);
+}
+
+.search-go-button:hover {
+ -moz-image-region: rect(0px 32px 16px 16px);
+}
+
+.search-go-button:hover:active {
+ -moz-image-region: rect(0px, 48px, 16px, 32px);
+}
+
+.searchbar-engine-menuitem[selected="true"] > .menu-iconic-text {
+ font-weight: bold;
+}
diff --git a/themes/windows/setDesktopBackground.css b/themes/windows/setDesktopBackground.css
new file mode 100644
index 0000000..585284c
--- /dev/null
+++ b/themes/windows/setDesktopBackground.css
@@ -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/. */
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+@namespace html url("http://www.w3.org/1999/xhtml");
+
+html|canvas#screen {
+ margin: 12px 11px 32px;
+}
+
+#monitor {
+ list-style-image: url("chrome://browser/skin/monitor.png");
+}
+
+#monitor[aspectratio="16:10"] {
+ list-style-image: url("chrome://browser/skin/monitor_16-10.png");
+}
diff --git a/themes/windows/slowStartup-16.png b/themes/windows/slowStartup-16.png
new file mode 100644
index 0000000..5551ef0
--- /dev/null
+++ b/themes/windows/slowStartup-16.png
Binary files differ
diff --git a/themes/windows/statusbar/overlay.css b/themes/windows/statusbar/overlay.css
new file mode 100644
index 0000000..7f9a598
--- /dev/null
+++ b/themes/windows/statusbar/overlay.css
@@ -0,0 +1,104 @@
+/* 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");
+
+%include ../../shared/statusbar/overlay.css
+
+/*
+ * General
+ */
+
+#status4evar-status-text,
+#status4evar-progress-bar
+{
+ margin: 0px 3px;
+}
+
+/*
+ * Download status
+ */
+
+#status4evar-download-progress-bar
+{
+ height: 4px;
+}
+
+#status4evar-download-button #status4evar-download-icon
+{
+ min-width: 18px;
+ min-height: 18px;
+}
+
+#status4evar-download-button #status4evar-download-icon
+{
+ background: -moz-image-rect(var(--toolbarbutton-image), 0, 108, 18, 90) center no-repeat;
+}
+
+toolbar[brighttext] #status4evar-download-button #status4evar-download-icon
+{
+ background: -moz-image-rect(var(--toolbarbutton-inverted-image), 0, 108, 18, 90) center no-repeat;
+}
+
+@media (-moz-windows-compositor)
+{
+ :-moz-any(#toolbar-menubar, #nav-bar[tabsontop=false]) #status4evar-download-button #status4evar-download-icon:not(:-moz-lwtheme),
+ #TabsToolbar[tabsontop=true] #status4evar-download-button #status4evar-download-icon:not(:-moz-lwtheme),
+ #nav-bar + #customToolbars + #PersonalToolbar[collapsed=true] + #TabsToolbar[tabsontop=false]:last-child #status4evar-download-button #status4evar-download-icon:not(:-moz-lwtheme)
+ {
+ background: -moz-image-rect(var(--toolbarbutton-glass-image), 0, 108, 18, 90) center no-repeat;
+ }
+}
+
+#status4evar-download-button[attention] #status4evar-download-icon
+{
+ background: -moz-image-rect(var(--toolbarbutton-glass-image), 19, 108, 36, 90) center no-repeat;
+}
+
+toolbar[mode="icons"] #status4evar-download-button[forcelabel="true"] > label
+{
+ -moz-margin-start: 5px !important;
+}
+
+/*
+ * Splitter
+ */
+
+splitter.status4evar-status-splitter
+{
+ width: 6px;
+ margin: 0px -3px;
+}
+
+/*
+ * Location bar
+ */
+
+#urlbar-progress-alt
+{
+ -moz-margin-end: -2px;
+}
+
+/*
+ * Status bar
+ */
+
+#browser-bottombox[s4eboarder="true"] :-moz-any(#status4evar-status-bar, #addon-bar)
+{
+ -moz-appearance: none;
+}
+
+#browser-bottombox[s4eboarder="true"] > *:not([hidden="true"]):not([collapsed="true"])
+{
+ box-shadow: none !important;
+ border: none !important;
+ border-top: 2px solid !important;
+ -moz-border-top-colors: ThreeDShadow ThreeDHighlight !important;
+}
+
+#browser-bottombox[s4eboarder="true"] > *:not([hidden="true"]):not([collapsed="true"]) ~ *
+{
+ border: none !important;
+}
+
diff --git a/themes/windows/statusbar/prefs.css b/themes/windows/statusbar/prefs.css
new file mode 100644
index 0000000..005088b
--- /dev/null
+++ b/themes/windows/statusbar/prefs.css
@@ -0,0 +1,7 @@
+/* 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");
+
+%include ../../shared/statusbar/prefs.css
diff --git a/themes/windows/sync-128.png b/themes/windows/sync-128.png
new file mode 100644
index 0000000..1ea3481
--- /dev/null
+++ b/themes/windows/sync-128.png
Binary files differ
diff --git a/themes/windows/sync-16.png b/themes/windows/sync-16.png
new file mode 100644
index 0000000..0afb1c7
--- /dev/null
+++ b/themes/windows/sync-16.png
Binary files differ
diff --git a/themes/windows/sync-32.png b/themes/windows/sync-32.png
new file mode 100644
index 0000000..7a762cb
--- /dev/null
+++ b/themes/windows/sync-32.png
Binary files differ
diff --git a/themes/windows/sync-bg.png b/themes/windows/sync-bg.png
new file mode 100644
index 0000000..893a27d
--- /dev/null
+++ b/themes/windows/sync-bg.png
Binary files differ
diff --git a/themes/windows/sync-desktopIcon.png b/themes/windows/sync-desktopIcon.png
new file mode 100644
index 0000000..d3d1e27
--- /dev/null
+++ b/themes/windows/sync-desktopIcon.png
Binary files differ
diff --git a/themes/windows/sync-mobileIcon.png b/themes/windows/sync-mobileIcon.png
new file mode 100644
index 0000000..a3bda57
--- /dev/null
+++ b/themes/windows/sync-mobileIcon.png
Binary files differ
diff --git a/themes/windows/sync-throbber.png b/themes/windows/sync-throbber.png
new file mode 100644
index 0000000..d25490b
--- /dev/null
+++ b/themes/windows/sync-throbber.png
Binary files differ
diff --git a/themes/windows/syncCommon.css b/themes/windows/syncCommon.css
new file mode 100644
index 0000000..f0beae0
--- /dev/null
+++ b/themes/windows/syncCommon.css
@@ -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/. */
+
+/* The following are used by both sync/setup.xul and sync/genericChange.xul */
+.status {
+ color: -moz-dialogtext;
+}
+
+.statusIcon {
+ -moz-margin-start: 4px;
+ max-height: 16px;
+ max-width: 16px;
+}
+
+.statusIcon[status="active"] {
+ list-style-image: url("chrome://global/skin/icons/loading_16.png");
+}
+
+.statusIcon[status="error"] {
+ list-style-image: url("chrome://global/skin/icons/error-16.png");
+}
+
+.statusIcon[status="success"] {
+ list-style-image: url("chrome://global/skin/icons/information-16.png");
+}
+
+/* .data is only used by sync/genericChange.xul, but it seems unnecessary to have
+ a separate stylesheet for it. */
+.data {
+ font-size: 90%;
+ font-weight: bold;
+}
+
+dialog#change-dialog {
+ width: 40em;
+}
+
+image#syncIcon {
+ list-style-image: url("chrome://browser/skin/sync-32.png");
+}
+
+#introText {
+ margin-top: 2px;
+}
+
+#feedback {
+ height: 2em;
+}
diff --git a/themes/windows/syncProgress.css b/themes/windows/syncProgress.css
new file mode 100644
index 0000000..d7aa599
--- /dev/null
+++ b/themes/windows/syncProgress.css
@@ -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/. */
+@import url(chrome://global/skin/inContentUI.css);
+
+:root {
+ height: 100%;
+ width: 100%;
+ padding: 0;
+}
+
+body {
+ margin: 0;
+ padding: 0 2em;
+}
+
+#floatingBox {
+ margin: 4em auto;
+ max-width: 40em;
+ min-width: 23em;
+ padding: 1em 1.5em;
+ position: relative;
+ text-align: center;
+}
+
+#successLogo {
+ margin: 1em 2em;
+}
+
+#loadingText {
+ margin: 2em 6em;
+}
+
+#progressBar {
+ margin: 2em 10em;
+}
+
+#uploadProgressBar{
+ width: 100%;
+}
+
+#bottomRow {
+ margin-top: 2em;
+ padding: 0;
+ text-align: end;
+}
diff --git a/themes/windows/syncQuota.css b/themes/windows/syncQuota.css
new file mode 100644
index 0000000..1577de8
--- /dev/null
+++ b/themes/windows/syncQuota.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/. */
+
+#quotaDialog {
+ width: 33em;
+ height: 25em;
+}
+
+treechildren::-moz-tree-checkbox {
+ list-style-image: none;
+}
+treechildren::-moz-tree-checkbox(checked) {
+ list-style-image: url("chrome://global/skin/checkbox/cbox-check.gif");
+}
+treechildren::-moz-tree-checkbox(disabled) {
+ list-style-image: url("chrome://global/skin/checkbox/cbox-check-dis.gif");
+}
+
+#treeCaption {
+ height: 4em;
+}
+
+.captionWarning {
+ font-weight: bold;
+}
diff --git a/themes/windows/syncSetup.css b/themes/windows/syncSetup.css
new file mode 100644
index 0000000..fff65e9
--- /dev/null
+++ b/themes/windows/syncSetup.css
@@ -0,0 +1,132 @@
+/* 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/. */
+
+wizard {
+ -moz-appearance: none;
+ width: 55em;
+ height: 45em;
+ padding: 0;
+ background-color: Window;
+}
+
+.wizard-page-box {
+ -moz-appearance: none;
+ padding-left: 0;
+ padding-right: 0;
+ margin: 0;
+}
+
+wizardpage {
+ -moz-box-pack: center;
+ -moz-box-align: center;
+ margin: 0;
+ padding: 0 6em;
+ background-color: Window;
+}
+
+.wizard-header {
+ -moz-appearance: none;
+ border: none;
+ padding: 2em 0 1em 0;
+ text-align: center;
+}
+.wizard-header-label {
+ font-size: 24pt;
+ font-weight: normal;
+}
+
+.wizard-buttons {
+ background-color: rgba(0,0,0,0.1);
+ padding: 1em;
+}
+
+.wizard-buttons-separator {
+ visibility: collapse;
+}
+
+.wizard-header-icon {
+ visibility: collapse;
+}
+
+.accountChoiceButton {
+ font: menu;
+}
+
+.confirm {
+ border: 1px solid black;
+ padding: 1em;
+ border-radius: 5px;
+}
+
+/* Override the text-link style from global.css */
+description > .text-link,
+description > .text-link:focus {
+ margin: 0px;
+ padding: 0px;
+ border: 0px;
+}
+
+
+.success,
+.error {
+ padding: 2px;
+ border-radius: 2px;
+}
+
+.error {
+ background-color: #FF0000 !important;
+ color: #FFFFFF !important;
+}
+
+.success {
+ background-color: #00FF00 !important;
+}
+
+.warning {
+ font-weight: bold;
+ font-size: 100%;
+ color: red;
+}
+
+.mainDesc {
+ font-weight: bold;
+ font-size: 100%;
+}
+
+.normal {
+ font-size: 100%;
+}
+
+.inputColumn {
+ -moz-margin-end: 2px
+}
+
+.pin {
+ font-size: 18pt;
+ width: 4em;
+ text-align: center;
+}
+
+#passphraseHelpSpacer {
+ width: 0.5em;
+}
+
+#pairDeviceThrobber > image,
+#login-throbber > image {
+ list-style-image: url("chrome://global/skin/icons/loading_16.png");
+}
+
+#captchaFeedback {
+ visibility: hidden;
+}
+
+#successPageIcon {
+ /* TODO replace this with a 128px version (bug 591122) */
+ list-style-image: url("chrome://browser/skin/sync-32.png");
+}
+
+#tosDesc {
+ margin-left: -7px;
+ margin-bottom: 3px;
+} \ No newline at end of file
diff --git a/themes/windows/tabbrowser/alltabs-inverted.png b/themes/windows/tabbrowser/alltabs-inverted.png
new file mode 100644
index 0000000..002bdd4
--- /dev/null
+++ b/themes/windows/tabbrowser/alltabs-inverted.png
Binary files differ
diff --git a/themes/windows/tabbrowser/alltabs.png b/themes/windows/tabbrowser/alltabs.png
new file mode 100644
index 0000000..172d425
--- /dev/null
+++ b/themes/windows/tabbrowser/alltabs.png
Binary files differ
diff --git a/themes/windows/tabbrowser/connecting.png b/themes/windows/tabbrowser/connecting.png
new file mode 100644
index 0000000..e564fb5
--- /dev/null
+++ b/themes/windows/tabbrowser/connecting.png
Binary files differ
diff --git a/themes/windows/tabbrowser/loading.png b/themes/windows/tabbrowser/loading.png
new file mode 100644
index 0000000..ba54836
--- /dev/null
+++ b/themes/windows/tabbrowser/loading.png
Binary files differ
diff --git a/themes/windows/tabbrowser/newtab-glass.png b/themes/windows/tabbrowser/newtab-glass.png
new file mode 100644
index 0000000..15185be
--- /dev/null
+++ b/themes/windows/tabbrowser/newtab-glass.png
Binary files differ
diff --git a/themes/windows/tabbrowser/newtab-inverted.png b/themes/windows/tabbrowser/newtab-inverted.png
new file mode 100644
index 0000000..4ac1eba
--- /dev/null
+++ b/themes/windows/tabbrowser/newtab-inverted.png
Binary files differ
diff --git a/themes/windows/tabbrowser/newtab.png b/themes/windows/tabbrowser/newtab.png
new file mode 100644
index 0000000..7cea7bd
--- /dev/null
+++ b/themes/windows/tabbrowser/newtab.png
Binary files differ
diff --git a/themes/windows/tabbrowser/tab-arrow-left-glass.png b/themes/windows/tabbrowser/tab-arrow-left-glass.png
new file mode 100644
index 0000000..aac93a7
--- /dev/null
+++ b/themes/windows/tabbrowser/tab-arrow-left-glass.png
Binary files differ
diff --git a/themes/windows/tabbrowser/tab-arrow-left-inverted.png b/themes/windows/tabbrowser/tab-arrow-left-inverted.png
new file mode 100644
index 0000000..16cd7a2
--- /dev/null
+++ b/themes/windows/tabbrowser/tab-arrow-left-inverted.png
Binary files differ
diff --git a/themes/windows/tabbrowser/tab-arrow-left.png b/themes/windows/tabbrowser/tab-arrow-left.png
new file mode 100644
index 0000000..e0fb348
--- /dev/null
+++ b/themes/windows/tabbrowser/tab-arrow-left.png
Binary files differ
diff --git a/themes/windows/tabbrowser/tab-overflow-border.png b/themes/windows/tabbrowser/tab-overflow-border.png
new file mode 100644
index 0000000..77f2462
--- /dev/null
+++ b/themes/windows/tabbrowser/tab-overflow-border.png
Binary files differ
diff --git a/themes/windows/tabbrowser/tabDragIndicator.png b/themes/windows/tabbrowser/tabDragIndicator.png
new file mode 100644
index 0000000..c67c233
--- /dev/null
+++ b/themes/windows/tabbrowser/tabDragIndicator.png
Binary files differ
diff --git a/themes/windows/toolbarbutton-dropdown-arrow-inverted.png b/themes/windows/toolbarbutton-dropdown-arrow-inverted.png
new file mode 100644
index 0000000..f3261f1
--- /dev/null
+++ b/themes/windows/toolbarbutton-dropdown-arrow-inverted.png
Binary files differ
diff --git a/themes/windows/toolbarbutton-dropdown-arrow.png b/themes/windows/toolbarbutton-dropdown-arrow.png
new file mode 100644
index 0000000..a7abe73
--- /dev/null
+++ b/themes/windows/toolbarbutton-dropdown-arrow.png
Binary files differ
diff --git a/themes/windows/urlbar-arrow.png b/themes/windows/urlbar-arrow.png
new file mode 100644
index 0000000..fcab253
--- /dev/null
+++ b/themes/windows/urlbar-arrow.png
Binary files differ
diff --git a/themes/windows/urlbar-history-dropmarker.png b/themes/windows/urlbar-history-dropmarker.png
new file mode 100644
index 0000000..fc8b0be
--- /dev/null
+++ b/themes/windows/urlbar-history-dropmarker.png
Binary files differ
diff --git a/themes/windows/urlbar-popup-blocked.png b/themes/windows/urlbar-popup-blocked.png
new file mode 100644
index 0000000..e6fd29f
--- /dev/null
+++ b/themes/windows/urlbar-popup-blocked.png
Binary files differ
diff --git a/themes/windows/web-notifications-icon.svg b/themes/windows/web-notifications-icon.svg
new file mode 100644
index 0000000..f7186c7
--- /dev/null
+++ b/themes/windows/web-notifications-icon.svg
@@ -0,0 +1,15 @@
+<?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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid" width="64" height="64" viewBox="0 0 64 64">
+ <defs>
+ <style>
+ .icon {
+ fill: #a6a6a6;
+ fill-rule: evenodd;
+ }
+ </style>
+ </defs>
+ <path d="M57,48 L46,48 L46,60.016 L32.482,48 L7,48 C5.343,48 4,46.657 4,45 L4,11.031 C4,9.374 5.343,8.031 7,8.031 L57,8.031 C58.657,8.031 60,9.374 60,11.031 L60,45 C60,46.657 58.657,48 57,48 ZM36,16.031 C36,14.927 35.105,14.031 34,14.031 L30,14.031 C28.895,14.031 28,14.927 28,16.031 L28,30.031 C28,31.136 28.895,32.031 30,32.031 L34,32.031 C35.105,32.031 36,31.136 36,30.031 L36,16.031 ZM36,37.5 C36,36.672 35.328,36 34.5,36 L29.5,36 C28.672,36 28,36.672 28,37.5 L28,40.5 C28,41.328 28.672,42 29.5,42 L34.5,42 C35.328,42 36,41.328 36,40.5 L36,37.5 Z" class="icon"/>
+</svg>
diff --git a/themes/windows/web-notifications-tray.svg b/themes/windows/web-notifications-tray.svg
new file mode 100644
index 0000000..314026a
--- /dev/null
+++ b/themes/windows/web-notifications-tray.svg
@@ -0,0 +1,23 @@
+<?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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="16" viewBox="0 0 96 32">
+ <defs>
+ <style>
+ .style-icon-notification {
+ fill: #666666;
+ }
+ .style-icon-notification.hover {
+ fill: #808080;
+ }
+ .style-icon-notification.active {
+ fill: #4d4d4d;
+ }
+ </style>
+ <path id="shape-notifcations-push" d="M27,23.969 L24,23.969 L24,29.977 L17.241,23.969 L5,23.969 C3.343,23.969 2,22.626 2,20.969 L2,6.969 C2,5.312 3.343,3.969 5,3.969 L27,3.969 C28.657,3.969 30,5.312 30,6.969 L30,20.969 C30,22.626 28.657,23.969 27,23.969 ZM18,8.969 C18,7.864 17.105,6.969 16,6.969 C14.895,6.969 14,7.864 14,8.969 L14,13.969 C14,15.073 14.895,15.969 16,15.969 C17.105,15.969 18,15.073 18,13.969 L18,8.969 ZM16.5,17.969 L15.5,17.969 C14.672,17.969 14,18.640 14,19.469 C14,20.297 14.672,20.969 15.5,20.969 L16.5,20.969 C17.328,20.969 18,20.297 18,19.469 C18,18.640 17.328,17.969 16.5,17.969 Z"/>
+ </defs>
+ <use xlink:href="#shape-notifcations-push" class="style-icon-notification"/>
+ <use xlink:href="#shape-notifcations-push" transform="translate(32)" class="style-icon-notification hover"/>
+ <use xlink:href="#shape-notifcations-push" transform="translate(64)" class="style-icon-notification active"/>
+</svg>
diff --git a/themes/windows/webRTC-shareDevice-16.png b/themes/windows/webRTC-shareDevice-16.png
new file mode 100644
index 0000000..df01b33
--- /dev/null
+++ b/themes/windows/webRTC-shareDevice-16.png
Binary files differ
diff --git a/themes/windows/webRTC-shareDevice-64.png b/themes/windows/webRTC-shareDevice-64.png
new file mode 100644
index 0000000..d125789
--- /dev/null
+++ b/themes/windows/webRTC-shareDevice-64.png
Binary files differ
diff --git a/themes/windows/webRTC-sharingDevice-16.png b/themes/windows/webRTC-sharingDevice-16.png
new file mode 100644
index 0000000..a670676
--- /dev/null
+++ b/themes/windows/webRTC-sharingDevice-16.png
Binary files differ